Leaderboard
Popular Content
Showing content with the highest reputation on 06/09/2024 in all areas
-
This week I'm releasing the ProcessWire Fieldtype/Inputfield module "Functional Fields" as open source in the modules directory. This was originally built in 2017 for ProFields, but hasn't required much in terms of support resources, so I thought I should release this newest version (v4) publicly. While this module is completely different from other Fieldtypes, I think it's quite useful and fulfills some needs better than other Field options. Give it a try and please let me know what you think. For an introduction to Functional Fields, please revisit this blog post which covers all the details: https://processwire.com/blog/posts/functional-fields/ Functional Fields is available for download in the modules directory here: https://processwire.com/modules/fieldtype-functional/ Thanks and have a great weekend!3 points
-
The latest release fixes a bug that was caused by a recent chrome update where moving blocks led to selecting the underlying text: And after the fix: Happy dragging&dropping ?2 points
-
Very cool Ryan. Thank you for this, I never used functional fields before but they sound perfect for a use case I have right now.2 points
-
Here is a CSS-only solution to this exact problem from Kevin Powell:1 point
-
1 point
-
1 point
-
Welcome to the ProcessWire forums! This is a great question and I think ProcessWire is a great platform to begin transitioning into OOP because ProcessWire itself is object oriented and is built using OOP. It includes powerful tools and features that can help make your code cleaner, more efficient, and reusable. I recommend starting with custom Page classes. Custom page classes lets you use OOP principles to extend ProcessWire and add additional custom behaviors by thinking with objects. There are a couple of examples in that link, but I'll provide one here that specifically contrasts different methods of doing the same thing. This example is a real-world case that I use on many projects, and because of how it's written I can replicate this feature easily when I start new projects. This is just a simple example of code I use that hopefully opens the door to thinking in OOP when working with ProcessWire. On blog posts I like to add something that shows how long it will take to read it, a la "5 minute read" like articles on Medium do. I wrote code that implements Medium's own method of calculating read time and use it often. The code that calculates the reading time is real, but I threw this together for illustration so please excuse any errors. Lets assume that you have a template called blog-post.php where you calculate reading time and output that value to the page. Here is what that looks like using procedural code: <?php namespace ProcessWire; // site/templates/blog-post.php // Calculate the read time for this article by using the words contained in the title, summary, and // body fields $text = "{$page->title} {$page->summary} {$page->blog_body}"; $blogText = explode( ' ', $sanitizer->chars($text, '[alpha][digit] ')); $wordCount = count($blogText); $dom = new \DOMDocument; @$dom->loadHTML( "<?xml encoding=\"UTF-8\"><div>{$blogText}</div>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); // Account for images in text and add to read time // Starts at 12 seconds for first img, 11, 10, 9, etc. until floor of 3 secs $secondsPerImage = 12; $imageReadTimeInSeconds = 0; $imageCount = $dom->getElementsByTagName('img')->length; while ($imageCount > 0) { $imageReadTimeInSeconds += $secondsPerImage; $imageCount--; $secondsPerImage > 3 && $secondsPerImage--; } // Word count divided by average adult reading speed per minute with image read time added $readTime = (int) (ceil($wordCount / 275) + ceil($imageReadTimeInSeconds / 60)); ?> <!DOCTYPE html> <html lang="en"> <head> <title><?= $page->title; ?></title> </head> <body> <h1><?= $page->headline; ?></h1> <span class="read-time"><?= $readTime; ?> minute read</span> <div class="summary"> <?= $page->summary; ?> </div> <div class="blog-content"> <?= $page->blog_body; ?> </div> </body> </html> So, there's nothing wrong with that- gets the job done! But it could be better... It adds a lot of logic to our template and makes it harder to read, imagine if we had to add more logic for other features Mixing raw PHP and HTML works, but can be confusing when it comes to managing and maintaining our code We can't reuse the the code that calculates reading time, if we wrote this in another place then we have to make sure both are bug free and accurate Of course, we could create a function called readTime() that does the same thing and cleans up the template. But now we are writing functions that do a specific thing but exist without context and are harder to organize and maintain flexibility. Luckily, there's a better way. I'll let the notes by Ryan in that link I shared above explain how to start using custom Page classes, so I'll assume you have that set up. So now lets think in objects and use OOP to improve our code. Now we have our template, blog-post.php, and a custom Page class called BlogPostPage.php. Lets refactor. Here's our BlogPostPage.php file: <?php namespace ProcessWire; // site/classes/BlogPostPage.php class BlogPostPage extends Page { private const INITIAL_SECONDS_PER_IMAGE = 12; private const WORDS_READ_PER_MINUTE = 275; private const ALLOWED_CHARACTERS = '[alpha][digit] '; public function readTime(): int { $text = "{$this->title} {$this->summary} {$this->blog_body}"; $blogText = explode( ' ', wire('sanitizer')->chars($text, self::ALLOWED_CHARACTERS)); $wordCount = count($blogText); $dom = new \DOMDocument; @$dom->loadHTML( "<?xml encoding=\"UTF-8\"><div>{$blogText}</div>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); $secondsPerImage = self::INITIAL_SECONDS_PER_IMAGE; $imageReadTimeInSeconds = 0; $imageCount = $dom->getElementsByTagName('img')->length; while ($imageCount > 0) { $imageReadTimeInSeconds += $secondsPerImage; $imageCount--; $secondsPerImage > 3 && $secondsPerImage--; } $time = ceil($wordCount / self::WORDS_READ_PER_MINUTE) + ceil($imageReadTimeInSeconds / 60); return (int) $time; } } Now that our code is in the context of a class method, we've made a couple of extra changes: $page->{name of field} is now $this->{name of field} because we're now working within BlogPostPage which extends the Page class itself. We've added constants to define values that would otherwise be a little difficult to understand at first glance. Seeing self::WORDS_READ_PER_MINUTE is clear and self-documenting where only using the integer 275 in place doesn't really state what that number means We switched $sanitizer to wire('sanitizer') because the $sanitizer variable does not exist in the method scope and the wire() function makes the entire ProcessWire API available to us both inside and outside classes ProcessWire will use BlogPostPage class to create the $page object we use in our templates when it boots, executes our code, and renders content via our templates. Thanks to OOP inheritance, BlogPostPage has all of the methods and properties available in the Page class and can be used in our templates with the $page object. And now let's go back to our blog-post.php template: <?php namespace ProcessWire; // site/templates/blog-post.php ?> <!DOCTYPE html> <html lang="en"> <head> <title><?= $page->title; ?></title> </head> <body> <h1><?= $page->headline; ?></h1> <span class="read-time"><?= $page->readTime(); ?> minute read</span> <div class="summary"> <?= $page->summary; ?> </div> <div class="blog-content"> <?= $page->blog_body; ?> </div> </body> </html> Now we're talking. With a little extra code and some OOP we've created a method on the Page object. Some benefits: Our template is cleaner and easier to maintain We've made the BlogPostPage class extend Page, so it inherits all of the methods and properties you access via $page The $page object is used to output the reading time to the page, just like $page outputs our field content, so our custom behavior is predictable and and feels at home with the core ProcessWire API It's easier to find where your programming logic is and keep a separation of concerns What's even better is that because we have used OOP to extend the Page class and add new functionality, we can use this a lot more places in our templates (so now it's reusable too). Let's say that you want to add a blog feed to the home page that shows the latest 3 blog posts and displays them with their title, read time, summary, and a link to read the post. <?php namespace ProcessWire; // site/templates/home.php ?> <!DOCTYPE html> <html lang="en"> <head> <title><?= $page->title; ?></title> </head> <body> <header> <h1><?= $page->headline; ?></h1> </header> <section class="blog-feed"> <!-- Create an <article> preview card for each blog post --> <?php foreach ($pages->get('template=blog-post')->slice(0, 3) as $blogPost): ?> <article> <h2><?= $blogPost->title; ?></h2> <span class="read-time"> <?= $blogPost->readTime(); ?> minute read </span> <div class="post-summary"> <?= $blogPost->summary; ?> </div> <a href="<?= $blogPost->url; ?>">Read More</a> </article> <?php endforeach ?> </section> </body> </html> That would be a lot harder to do if you had to write more procedural code to calculate the read time for each blog post. Thanks to that method in BlogPostPage, we can use it anywhere we reference a blog post. Think we can make this better? Let's improve it using PHP traits. Using a Trait will allow us to reuse our code that calculates reading time in many places thanks to OOP. We'll create another file called CalculatesReadingTime.php and put it in a new folder at /site/classes/traits. Time to refactor, here's our new trait file: <?php namespace ProcessWire; // site/classes/traits/CalculatesReadingTime.php trait CalculatesReadingTime { private const INITIAL_SECONDS_PER_IMAGE = 12; private const WORDS_READ_PER_MINUTE = 275; private const ALLOWED_CHARACTERS = '[alpha][digit] '; /** * Takes an arbitrary number of field values and calculates the total reading time * @param string $fieldValues Contents of fields to calculate reading time for * @return int Total read time, in minutes */ public function calculateReadingTime(string ...$fieldValues): int { $text = array_reduce( $fieldValues, fn ($content, $fieldValue) => $content = trim("{$content} {$fieldValue}"), '' ); $text = explode( ' ', wire('sanitizer')->chars($text, self::ALLOWED_CHARACTERS)); $wordCount = count($text); $dom = new \DOMDocument; @$dom->loadHTML( "<?xml encoding=\"UTF-8\"><div>{$text}</div>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); $secondsPerImage = self::INITIAL_SECONDS_PER_IMAGE; $imageReadTimeInSeconds = 0; $imageCount = $dom->getElementsByTagName('img')->length; while ($imageCount > 0) { $imageReadTimeInSeconds += $secondsPerImage; $imageCount--; $secondsPerImage > 3 && $secondsPerImage--; } $time = ceil($wordCount / self::WORDS_READ_PER_MINUTE) + ceil($imageReadTimeInSeconds / 60); return (int) $time; } } Let's take a look at that before we go back to our other files. Our new trait has a name that is an "action" because now, as we'll see, we can add this ability to other classes so they can calculate reading times We've abstracted our code. Now instead of referring to content as $blogText, we are calling it $text because it can be used in many places and many contexts but provide the same behavior The readTime() method is now called calculateReadingTime() and has been converted to a variadic function so you can pass as many field values as needed We've taken an extra step of type hinting our parameters as strings to make sure the method is always getting the proper type of data to work with. This will help a lot as this method becomes used more in different places Our docblock is more robust to help understand what this method does, what parameters it takes, and what it will return, another extra step to help us as we use this method in more places Now back to our BlogPostPage.php file <?php namespace ProcessWire; // site/classes/BlogPostPage.php require_once __DIR__ . '/traits/CalculatesReadingTime.php'; class BlogPostPage extends Page { use CalculatesReadingTime; public function readTime(): int { return $this->calculateReadingTime($this->title, $this->summary, $this->blog_body); } } Now we're giving our BlogPostPage class the ability to calculate reading time and all of the functionality has been kept the same. We were able to abstract the logic for calculating reading time to a reusable trait that can be included in any Page class Our readTime() method still exists and is available to all of the templates where we were already using $page->readTime() Now our readTime() method calls the calculateReadingTime() method and passes the fields we know we need as they exist in our blog-post template This code is clean, concise, and easy to maintain. Right now it looks like OOP has made things look nice but caused some extra work... but then the phone rings. Your client wants to add Press Releases to the blog section of the site and it's going to need a new layout and different fields, but they love your reading time calculator so much that they want it on the new Press Release pages too. So we create our new files- a press-release.php template, and a PressReleasePage.php file. We'll skip writing out the press-release.php HTML, but while creating the new template in ProcessWire you've created new fields. The new fields are 'pr_abstract', 'pr_body', 'company_information', and 'pr_contact_info'. Here's our new PressReleasePage.php file: <?php namespace ProcessWire; // site/classes/PressReleasePage.php require_once __DIR__ . '/traits/CalculatesReadingTime.php'; class PressReleasePage extends Page { use CalculatesReadingTime; public function readTime(): int { return $this->calculateReadingTime( $this->pr_abstract, $this->pr_body, $this->company_information, $this->pr_contact_info ); } } Since we've created this simple class that uses the CalculatesReadingTime trait, we can use $page->readTime() in all of our Press Release pages, and anywhere that a Press Release $page object is present. Very nice. Now OOP has really shown how useful it is and we can appreciate how ProcessWire uses objects and provides tools for us extend that power with our own code. There's also some other OOP things happening here: Our BlogPostPage and PressReleasePage classes do one thing and one thing only: handle logic and features for their respective pages. So, they have a single responsibility The calculateReadingTime() and readTime() methods do one thing and one thing only, calculate reading time based on content. They only care about one thing and have no side effects. Our BlogPostPage and PressReleasePage clases can both calculate reading time, but other hypothetical page classes, like "HomePage" and "AboutUsPage" aren't required to have a readTime() method that isn't used, thanks to making use of traits to share behavior only where it's needed. So, that's composition over inheritance Our readTime() method does not expose how it calculates reading time and it provides an interface to only expose information we want our object to make available. So, readTime() is read-only and can safely be used knowing that the value will never be overwritten or modified except when content is changed by editing the page. This is a great tool that shows the difference between setting a value to $page->title and getting a value from readTime(), each have their purposes and roles. Our code is modular and easy to maintain. If we had to adjust how reading time was calculated- we could for example adjust the value of WORDS_READ_PER_MINUTE in our CalculatesReadingTime trait. Then all of our Press Releases and Blog Posts would have their reading time correctly calculated with one change. We can also add CalculatesReadingTime to any future page classes that need it. ProcessWire's strong OOP foundation and the way that it uses objects that are created from classes for everything (like $page, $config, $input, etc.) is the reason that the API is easy to work with, enjoyable, and powerful. If you get the hang of working with OOP in ProcessWire you can build even more powerful websites and applications, better understand the ProcessWire core code, and write your own modules (which is actually pretty fun). Wasn't sure of your overall exposure to OOP but hopefully this helps and inspires!1 point
-
This week the core dev branch version remains at 3.0.239 but relative to last week, it contains several optimizations and improvements. Details can be found in the dev branch commit log. I've also moved two Textformatter modules out of ProFields and into GitHub and the modules directory. These include: Auto Links This Textformatter module automatically links your specified phrases/words to your specified URLs. This is a potential SEO and accessibility tool for creating automatic contextual links with little effort. If there are pages that you commonly link to in your site from your textarea/rich text fields, then this Textformatter can save you some effort, automatically linking to those URLs. Text Blocks This Textformatter module enables you to assign a name to any block/region of text in a Textarea field. They are defined by typing start_name where you want the block to start, and stop_name where you want the block to stop. The block(s) of text can then be shown in any other Textarea field at runtime (site-wide) simply by typing the block name on its own line in the format show_name. Note that the word "name" in all of these examples would be whatever you've decided to name the block. Both modules have been updated just for public release with a new version. Both are considered Stable, as they have both been running without incident for several years. These modules were moved from ProFields to the public modules directory for three reasons. First is that they don't consume hardly any support resources, so they don't need to be commercially supported modules anymore. Second is that I'd like to keep ProFields focused more on field related modules (Fieldtype, Inputfield, and related) rather than Textformatter modules. Though admittedly the TextBlocks module does blur the line a bit, as it promotes potential greater reusability with existing Textarea/TinyMCE/CKEditor fields. Third is that there's already a lot in ProFields and I wanted to make room for new modules, such as the recently added PageEditChildren, which is part of ProFields at least in the short term. The FunctionalFields module may come out of ProFields well, as it hasn't required much in terms of support resources recently, though it is a useful Fieldtype/Inputfield module that is very much in keeping with the theme of ProFields, so I'm still debating on that one. Thanks for reading and have a great weekend!1 point
-
? This is outstanding and I love the implementation. Being able to choose where blocks are available/rendered using a function is really powerful, even better than I hoped for. As usual, RPB continues to deliver so many great features that I wouldn't be surprised at all if this module even made coffee. My wife is really not going to like this.1 point
-
Here's a copy of my blog with some reflections on building my first site with ProcessWire as someone coming from Drupal: peopleandplanet.org ProcessWire is an open source CMS Content Management System or Framework (CMS / CMF respectively) using PHP and MariaDB/MySQL. It's been around a while, humbly gathering users and sites. I spent a good while reviewing lots of open source CMSes recently as I have previously relied on Drupal 7 (excellent) and didn't like Drupal 8+ nor BackDrop (a fork of Drupal 7). WordPress? not with Gutenberg/React and all those plugin ads, thanks. Turns out I'm quite picky about CMSes! This is because my role is developer, trainer, implementer, themer, discusser of problems and solutions and dreams. I have personal relationships with my clients and am here to help. So I need a system that makes it simple for them to do as much as possible without me, while also being flexible enough for me to rapidly develop new features. So I was shopping for me and my clients, not just one of those parties. ProcessWire seemed a good balance, and once I started developing with it I was hooked. I've now launched my first site with it: peopleandplanet.org and my clients are pretty pleased with it, and I have another job in the pipeline. Templates and pages In ProcessWire, everything (even users!) is a Page, and every Page has a Template. So in Drupal-speak, it's kinda like Page = Content/Entity and Template = Content/Entity Type. A Template is configured with lots of options and describes what fields apply. Templates don't all need to be renderable, but typically are, so generally have an accompanying Template File. Key implementation decisions I made There are many ways to generate and theme page HTML in ProcessWire and I went through them all! Here's what I settled on: Use Page classes: these are PHP classes which add/bend functionality for a particular page/template. Doing pre-processing of data this way seemed the cleanest way to organise things, using inheritance to share code between similar templates. I used the excellent Latte templating engine instead of plain PHP or other options like Blade/Smarty/... Latte is context-aware which makes templates cleaner and clearer to look at and safer because it knows to escape content in one way as HTML text and another as an attribute, for example. The final clincher is that it uses the same syntax as PHP itself, so as a developer hopping between PHP and Latte, there's much less brain strain. Use PageTableNext. This is similar to Drupal's Paragraphs or Gutenberg's Blocks (ish). It allows a page to be built from slices/sections of various different templates, e.g. I have things like "text" and "text-over-image" and "animated stats" etc. These let authors make interesting pages, applying different preset styles to things, giving a good mix of creative control and theme adherence. What I liked Beyond the above features, I liked these things: Fairly unopinionated: the core is quite small and everything is a module, so when you write your own module, you have similar level of access. e.g. I was able to make a core module behave differently by using hooks rather than having to maintain a patch of a core code file. The selector API is a domain-specific query language for fetching page data that makes a lot of common things you need to do very easy and clear to read. I like readable code a lot. A lot of basic CMS features are implemented really nicely; thought has gone into them. e.g. Drupal has a redirect module that can add redirects from old URLs when you update the 'path alias' for a page - great - but ProcessWire's implementation (a) prevents you making circular redirects which is a quick way to kill a Drupal site by accident that's happened more than once, and (b) has some useful rules like let's only do this if the page has been in existence for a little while - because typically while first composing a page you may change the title quite a few times before you publish. e.g. when you save a page that has links to other pages in it, it stores the page IDs of those other pages too, and when the page is fetched it will check that the URLs exist and still match the ID, fixing them if needed. Images - have 'focus' built in as well as resizing etc. so if a crop is needed you can ensure the important content of the image is still present. Booting is flexible; it enabled me to boot ProcessWire from within Drupal7 and thereby write a migration script for a lot of content. There's a good community on the forum. A forum feels a bit old fashioned, but it's good because you can have long form discussions; it sort of doubles as a blog, and a lot of new features are announced in forum posts by Ryan and others. The Tracy debugger is mind-blowingly good, just wow. Modules - third party or core - are typically very focussed and often tiny. This is a testament to what can be achieved throught the flexible and well-designed APIs. Weekly updates on core development from both the lead developer on the blog and the community, both with RSS feeds so it's easy to keep updated. What I don't like Logging is weird and non-standard. I prefer one chronological log of well categorised events, but instead we have lots of separate files. This is a bit weird. One thing it is opinionated on is that there should be a strict hierarchy between pages and URLs. I like that level of order, but in real life, it's not what I needed. e.g. People & Planet has three main campaigns and it wanted those at /campaign-name not /campaigns/campaign-name. And we wanted news at /news/2024-03-25/title-of-news-article, but we don't want a page at /news/2024-03-25/ and we want the news index to be under an Info page in the menu. This meant I had to write a custom menu implementation to implement a different hierarchy to display what we wanted it to look (3 campaigns under a Campaigns menu, the news index under an Info menu). There was a page hook for having news articles describe their URLs with the publish date, but this took quite a bit of figuring out. Ryan Cramer leads/owns the project and other contributors are sparse. He seems like a lovely person, and I'm super grateful for his work, but there's only one of him, so this is a bit of a risk. Also, the code has no phpunit tests. Gulp. There have been various initiatives, but this sort of thing needs to come from a core team, and it's not a priority for Ryan. I love tests, they help avoid regressions, help document designed behaviour, etc. Likewise, there's a styleguide, but it's not adhered to, so... Right decision? I'm very happy with it, and it seems a great fit for my clients' needs, and they're very happy. All software has risks and I got burned twice with Drupal 8/9 - once backing out after months of development; another project that went to completion I regret and dislike administering/maintaining now. But so far, so good, and I intend to use ProcessWire for other projects (including replacing this website) very soon. Contributions I have two ProcessWire modules that are there for anyone who needs them. TagHerder provides a page at Setup » Tag Herder that lists every tag and every page tagged with that tag, and provides three options: Rename the tag; Trash the tag; Replace the tag with another. Useful for cleaning up tags that have gotten out of control! EditablePublishedDate lets you edit the published date of a page. Useful for entering historical page information or such.1 point
-
@elabx First thing to check is that you've got an index on your `data` column for that field. If not then execute this query: ALTER TABLE field_event_recurring_dates ADD INDEX data (data) I'm also wondering about the other indexes on your Fieldtype. You've got an "id" as the primary key, which is unusual for a Fieldtype. Usually the primary key is this for a FieldtypeMulti: $schema['keys']['primary'] = 'PRIMARY KEY (pages_id, sort)'; So I'm guessing you may not have the index on pages_id and sort, which would definitely slow it down, potentially a lot. If you don't need the "id" then I would probably drop it, since you don't really need it on a FieldtypeMulti. The only Fieldtype I've built that keeps an id is FieldtypeTable, and it uses the 'data' column for that id and then uses this in its getDatabaseSchema: $schema['data'] = 'INT UNSIGNED NOT NULL AUTO_INCREMENT'; $schema['keys']['primary'] = 'PRIMARY KEY (data)'; $schema['keys']['pages_id'] = 'UNIQUE (pages_id, sort)'; unset($schema['keys']['data']); Most likely the above is the primary reason for the bottleneck. But another more general reason that your query may be slow is because the $pages->find() selector you are using doesn't filter by anything other than your event_recurring_dates field. In a real use case, usually you'd at least have template(s) or a parent in that selector, or you'd be using children(), etc. Any should improve the performance. Without any filter, you are asking it to query all pages on the site, and if it's a big site, that's going to be potentially slow. The reason for the LEFT JOIN is because without it you can only match rows that that exist in the database. You have an operator "<=" that can match empty or NULL values. Since you also have an operator ">=" that can't match empty values, then the left join of course isn't necessary, but each part of the selector is calculated on its own (independent calls to getMatchQuery). You are getting the fallback Fieldtype and PageFinder logic since your Fieldtype doesn't provide its own getMatchQuery(). This is a case where you may find it beneficial to have your own getMatchQuery() method. The getMatchQuery in FieldtypeDatetime might also be a good one to look at. In your case, since your Fieldtype is just matching dates, it may be that you don't need a left join situation at all, since you might never need to match null (non-existing) rows. So you could probably get by with a pretty simple getMatchQuery(). Maybe something like this (this is assuming 'data' is the only column you use, otherwise replace 'data' with $subfield): public function getMatchQuery($query, $table, $subfield, $operator, $value) { if($subfield === 'count') { return parent::getMatchQuery($query, $table, $subfield, $value); } // limit to operators: =, !=, >, >=, <, <= (exclude things like %=, *=, etc.) if(!$this->wire()->database->isOperator($operator)) { throw new WireException('You can only use DB-native operators here'); } if(empty($value)) { // empty value, which we'll let FieldtypeMulti handle if(in_array($operator, [ '=', '<', '<=' ])) { // match non-presence of rows return parent::getMatchQuery($query, $table, 'count', '=', 0); } else { // match presence of rows return parent::getMatchQuery($query, $table, 'count', '>', 0); } } // convert value to ISO-8601 and create the WHERE condition if(!ctype_digit("$value")) $value = strtotime($value); $value = date('Y-m-d H:i:s', (int) $value); $query->where("$table.data{$operator}?", $value); reuturn $query; }1 point