Search the Community
Showing results for 'find'.
-
Hi @PWaddict - I have fixed that in 1.4.0 but I have also created a new 2.0 branch if you'd like to test it. It contains a lot of bug fixes along with removing the need for DOMDocument and eval() Please let me know if you find any issues in 2.0
-
Hello @ all I want to share a new module with you, which makes the creation and validation of forms easy. Take a look at the following example of a simple contact form: // A very simple example of a contactform for demonstration purposes $form = new Form('contactform'); $gender = new Select('gender'); $gender->setLabel('Gender'); $gender->addOption('Mister', '0'); $gender->addOption('Miss', '1'); $form->add($gender); $surname = new InputText('surname'); $surname->setLabel('Surname'); $surname->setRule('required'); $form->add($surname); $name = new InputText('name'); $name->setLabel('Name'); $name->setRule('required'); $form->add($name); $email = new InputText('email'); $email->setLabel('E-Mail'); $email->setRule('required'); $form->add($email); $subject = new InputText('subject'); $subject->setLabel('Subject'); $subject->setRule('required'); $form->add($subject); $message = new Textarea('message'); $message->setLabel('Message'); $message->setRule('required'); $form->add($message); $privacy = new InputCheckbox('privacy'); $privacy->setLabel('I accept the privacy policy'); $privacy->setRule('required')->setCustomMessage('You have to accept our privacy policy'); $form->add($privacy); $button = new Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if($form->isValid()){ print_r($form->getValues()); // do what you want } // render the form echo $form->render(); This piece of code creates a simple contact form and validates it according to the validation rules set. Inside the isValid() method you can run your code (fe sending an email) Highlights: 30+ validation types Support for UiKit 3 and Bootstrap 5 CSS framework SPAM protection Highly customizable Hookable methods for further customization Multi-language You can download and find really extensive information on how to use at https://github.com/juergenweb/FrontendForms. Please report errors or suggestions directly in GitHub. Best regards and happy testing ? If you have downloaded the module in the past I recommend you to uninstall the module completely and install the newest version 2.1.14. There are a lot of changes in the new version, so please test carefully.
- 441 replies
-
- 26
-
-
-
This is caused by the TracyDebugger - I have forgotten to remove this call after Debugging. Please go to the Form.php and find the following line and remove it: bd($this->formErrors); Then this error will be gone. I have removed the TracyCall on Github too. This is strange, because it has nothing to do with the PHP code. Please take a look at your CSS classes. On my site the color is white. Yes, this is a browser security behavior and independent which browser you will use - file upload fields will always be empty after submission (whether successfully or not). I will test it with file upload fields and see if I can reproduce the issues. For now, please remove the bd() call first.
-
I never use AI to generate code, my case was more about understanding an issue and how the framework works. The kind of issue that is hard to find a solution with a Google search. The fun fact is that this issue never existed, that was a misinterpretation by myself, but all chatbot (ChatGPT, Claude and the framework dedicated chatbot) said this was a "very common issue", and gave me very bad solutions, all bullshit. 😂
-
Yes, that is the purpose of this variable: To add additional values to the validator. You can add several values as an array to the validator. To get a specific value from this array, you only have call it by its key (fe $params[2] will return the array value on the position with the key 2). You will find a lot of examples on how to write custom rules inside the CustomRules.php file. There you can study how to use the $params variable to test conditions with it.
-
It is obvious that AI/LLMs/agents, etc..., should be used for development with frameworks the developer already has at least a basic understanding of, and the agent should not be allowed to generate code the developer cannot understand, otherwise the whole project will fail in the end. These are new tools, they are constantly changing, and we need to learn how to utilize them best. I am trying to find the optimal balance between tinkering, learning, and doing actual work. This has always been the case whenever I started using a new piece of software or system for the first time. There is no change in that regard.
-
A module helping you to manage SEO related tasks like a boss! Automatically generates and maintains a XML sitemap from your pages. Includes a Fieldtype and Inputfield to manage sitemap settings and meta data for pages (Title, Description, Canonical URL, Opengraph, Twitter, Structured Data etc.) Multi language support for the sitemap and meta data. Configure default values for meta data on template level and let pages inherit or overwrite them individually. Map existing fields to meta data, reducing the need to duplicate content. Live preview for content editors how the entered meta data appears on Google. Live preview for content editors how the entered Opengraph data looks like when sharing a page with Facebook. Check out the README on GitHub for more details, including usage instructions. The module is currently released as beta and needs testing! Please report any issues on GitHub or in this forum thread, if you find time to give it a try ? Examples Here is an example of rendered meta data you will get from a single SeoMaestro field: <title>Sed dictum eros quis massa semper rutrum. | acme.com</title> <meta name="description" content="Si lobortis singularis genitus ibidem saluto. Dolore ad nunc, mos accumsan paratus duis suscipit luptatum facilisis macto uxor iaceo quadrum. Demoveo, appellatio elit neque ad commodo ea. Wisi, iaceo, tincidunt at commoveo rusticus et, ludus."> <meta name="keywords" content="Foo,Bar"> <link rel="canonical" href="https://acme.com/en/about/"> <meta property="og:title" content="Sed dictum eros quis massa semper rutrum."> <meta property="og:description" content="Si lobortis singularis genitus ibidem saluto. Dolore ad nunc, mos accumsan paratus duis suscipit luptatum facilisis macto uxor iaceo quadrum. Demoveo, appellatio elit neque ad commodo ea. Wisi, iaceo, tincidunt at commoveo rusticus et, ludus."> <meta property="og:image" content="https://acme.com/site/assets/files/1001/og-image.jpg"> <meta property="og:image:type" content="image/jpg"> <meta property="og:image:width" content="1600"> <meta property="og:image:height" content="1200"> <meta property="og:image:alt" content="Lorem Ipsum"> <meta property="og:type" content="website"> <meta property="og:url" content="https://acme.com/en/about/"> <meta property="og:locale" content="en_EN"> <meta name="twitter:card" content="summary"> <meta name="twitter:creator" content="@schtifu"> <meta name="twitter:site" content="@schtifu"> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "About", "item": "https://acme.com/en/about/" } ] } </script> <meta name="generator" content="ProcessWire"> <link rel="alternate" href="https://acme.com/en/about/" hreflang="en"> <link rel="alternate" href="https://acme.com/en/about/" hreflang="x-default"> <link rel="alternate" href="https://acme.com/de/ueber/" hreflang="de"> <link rel="alternate" href="https://acme.com/fi/tietoja/" hreflang="fi"> And some screenshots of the UI:
- 275 replies
-
- 37
-
-
-
Last update: June 2024 Current stable version: 1.5.4 Dev version: github dev-branch You can find the module on Github and in the modules directory : https://modules.processwire.com/modules/duplicator/ https://github.com/flydev-fr/Duplicator/archive/master.zip Screenshots / Features Dir and files exclusion CRON job Advanced settings Local and Cloud storage duplicator.mp4
- 548 replies
-
- 23
-
-
-
Hi @adrian When uploading and inserting an image into a field (e.g., a TinyMCE "body" field) with this module active, any UTF-8 characters (like Greek letters) in other Textarea fields on the same page (e.g., a plain text "seo_description" field) are unexpectedly converted into HTML entities (e.g., "α" becomes "α" ) and saved to the database. I asked Gemini to provide a solution. Steps to Reproduce: Create a page with a TinyMCE field (body) and a standard Textarea field (seo_description). Enter UTF-8 characters (e.g., Greek text) into the "seo_description" field and save. Upload and insert an image into the "body" field. Save the page. Check the "seo_description" field or the database; the UTF-8 text has been converted to HTML entities. Root Cause Analysis: The issue stems from the "replaceRteLinks" method, which is triggered to update image URLs after a rename. Scope Logic: The method fetches all fields of type FieldtypeTextarea and FieldtypeTextareaLanguage and processes them using DOMDocument, regardless of whether the field actually contains the image URL. Encoding Handling: DOMDocument::saveHTML() natively converts extended characters into HTML entities. Because it processes unrelated fields, it permanently alters their raw text in the database. Proposed Solution: Updating the "replaceRteLinks" method to strictly check if the field contains the target URL before processing it, and utilizing ProcessWire's native sanitizer to restore entities converted by DOMDocument. Here is the patched method: private function replaceRteLinks($newFilename, $oldFilename) { $textareaFields = $this->wire('fields')->find("type=FieldtypeTextarea|FieldtypeTextareaLanguage"); $fieldsStr = $textareaFields->implode('|', 'name'); $oldRelativeUrl = str_replace($this->wire('config')->paths->root, '', $oldFilename); $oldRelativeUrlSansExt = str_replace(pathinfo($oldFilename, PATHINFO_EXTENSION), '', $oldRelativeUrl); foreach($this->wire('pages')->find("$fieldsStr%=$oldRelativeUrlSansExt, include=all") as $p) { foreach($textareaFields as $taf) { // FIX 1: Only parse the field if it actually contains the image URL. if($p->$taf != '' && strpos((string)$p->$taf, $oldRelativeUrlSansExt) !== false) { // FIX 2: Explicitly declare UTF-8 for the DOMDocument $pagedom = new DOMDocument('1.0', 'UTF-8'); libxml_use_internal_errors(true); // add <cun> as fake root element $pagedom->loadHTML('<?xml encoding="utf-8" ?><cun>' . $p->$taf . '</cun>', LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED | LIBXML_SCHEMA_CREATE); $pagedom = $this->replaceRteLink($pagedom, $newFilename, $oldFilename, 'a', 'href'); $pagedom = $this->replaceRteLink($pagedom, $newFilename, $oldFilename, 'img', 'src'); // remove fake root element $html = str_replace(['<cun>', '</cun>', '<?xml encoding="utf-8" ?>'], '', $pagedom->saveHtml()); // FIX 3: Restore UTF-8 characters converted to entities by DOMDocument $html = $this->wire('sanitizer')->unentities($html); $p->of(false); $p->$taf = $html; libxml_clear_errors(); $p->save($taf); } } } }
-
I wanted a way to chat with my Processwire site and built a Module (PW MCP) and an MCP server to connect into it. It's a private repo at the moment but it can be public if anyone finds it useful. It's basically a way to use the Cursor Chat Ui to query my site, fields, templates and content. Here's part of the readme which explains it better. What Is It? ProcessWire MCP is a bridge between ProcessWire and Cursor IDE (the AI-powered code editor). It lets you query your ProcessWire site's structure and content directly from Cursor's chat interface using natural language. Instead of writing selectors or browsing the admin, you can just ask: "What templates does this site have?" "Show me the fields on the blog-post template" "Search for pages containing 'summer'" "Find all images with 'lake' in the filename" Why I Built It Cursor can see your template files and code in the local directory, but it can't see what's actually in your ProcessWire database — which templates and fields are registered, what pages exist, or what content they contain. With ProcessWire MCP, the AI can: Query the actual database schema (not just parse template files) Look up page content by ID, path, or selector Understand field configurations (types, settings, which templates use them) Search across all text content and find files/images Get RepeaterMatrix content with type labels See file metadata (dimensions, descriptions, URLs) It's the difference between seeing $page->body in code vs. knowing what that page's body actually contains. Architecture Cursor Chat → MCP Server (Node.js) → PHP CLI → ProcessWire API The module consists of: PwMcp — A ProcessWire module with a CLI interface mcp-server — A Node.js server that speaks the Model Context Protocol The CLI can also be used standalone for quick queries from terminal. Available Commands Command Description health Check connection and get site info list-templates List all templates with field counts get-template [name] Get template details and fields list-fields List all fields with types get-field [name] Get field details and usage get-page [id\|path] Get page by ID or path with all field values query-pages [selector] Query pages using PW selectors search [query] Search content across all text fields search-files [query] Search files by name/extension export-schema Export complete site schema Example: Health Check php site/modules/PwMcp/bin/pw-mcp.php health --pretty { "status": "ok", "pwVersion": "3.0.241", "siteName": "www.example.com", "moduleLoaded": true, "counts": { "templates": 45, "fields": 72, "pages": 960 } } Example: Content Search Ask Cursor: "Search for pages containing 'summer'" { "query": "summer", "count": 5, "results": [ { "id": 1764, "title": "Lake District walks in summer", "path": "/guides/lake-district-summer/", "template": "page-guide", "matchedField": "Body", "snippet": "The Lake District offers some of the best walking trails in summer. From gentle lakeside strolls to challenging fell walks..." } ] } Example: File Search Ask Cursor: "Find images with 'lake' in the filename" { "query": "lake", "count": 5, "results": [ { "filename": "lake-windermere-sunset.jpg", "url": "/site/assets/files/1070/lake-windermere-sunset.jpg", "size": 31207, "sizeStr": "30.5 kB", "description": "Sunset over Lake Windermere", "field": "Images", "page": { "id": 1070, "title": "Lake District walks in summer", "path": "/guides/lake-district-summer/" }, "width": 500, "height": 626 } ] } Example: Get Page with All Fields Ask Cursor: "Get the page at /about/" { "id": 1050, "name": "about", "path": "/about/", "url": "/about/", "template": "basic-page", "status": 1, "statusName": "published", "parent": { "id": 1, "path": "/", "title": "Home" }, "numChildren": 5, "created": "2023-05-15T10:30:00+00:00", "modified": "2024-11-20T14:22:00+00:00", "fields": { "title": "About Us", "body": "<p>We are a team of dedicated professionals...</p>", "Images": { "_count": 2, "_files": ["team-photo.jpg", "office.jpg"] } } } Example: RepeaterMatrix Support The module fully supports RepeaterMatrix fields, returning the actual content with type labels: { "matrix": { "_count": 3, "_items": [ { "_typeId": 1, "_typeLabel": "Body", "Body": "<h2>Welcome to our guide</h2><p>This guide covers...</p>", "Images": null }, { "_typeId": 2, "_typeLabel": "FAQs", "faq_question": "What is the best time to visit?", "faq_answer": "The summer months offer the best weather for walking..." }, { "_typeId": 3, "_typeLabel": "Call to Action", "cta_title": "Plan Your Visit", "cta_link": "/contact/" } ] } } So thats the first part done and working. My next plan is to be able to 1. PULL / convert a databse page into a local text file which lists all page properties, fields, template etc 2. edit the file as a local text file 3 PUSH the text file back into PW so that the original content picks up the changes Just having fun and building something useful. Very likely there are similar solutions or better ways to handle this but this suits my workflow ATM. Cheers P
-
This week on the dev branch we've got several commits with various core improvements and fixes. @adrian has been using Claude Code to suggest core optimizations (focused mostly on the PageFinder) and so he sent the suggestions to me. (PageFinder is the brains behind the $pages->find() method, and many others). I took the suggestions and coded them into our PageFinder, but didn't want to mess with what was already working well, so put them in a new class named PageFinder2, at least temporarily. If running the latest dev branch, you can enable PageFinder2 by adding the following to your /site/config.php: $config->PageFinder('version', 2); The most significant changes are: using subqueries for subselectors rather than separate independent queries; Reusing PageFinder instances (keeping a pool of typically 1-3 PageFinders rather than creating a new one for each $pages->find() operation); and lots of in_array() calls have been converted to isset() lookups, which should technically be faster (still the case in PHP8?, I'm not sure). There were some other changes as well. Theoretically these changes should make PageFinder even faster than it already is. I did quite a bit of testing and found that for the most part it performs the same as PageFinder v1. But then I came across a rather complex selector that translated to a much faster PageFinder operation, nearly twice as fast, and that convinced me it was worthwhile. While PageFinder v2 is not consistently faster than v1, there are some situations where it can be a lot faster. I'm not totally clear on what those situations are just yet, but I'll be doing more testing. In other situations it also can use a lot fewer queries, though that doesn't necessarily translate to a performance difference. But on the whole, all of Claude's suggestions were quite good, regardless of performance improvements. I was pretty impressed with what Claude Code had suggested, so decided to install it on my computer too. I've found it's particularly good at finding bugs. I'll ask it to do a code review on a core file, and it always has good suggestions. It uses ProcessWire terminology too. For instance it pointed me to an object that wasn't properly "wired to the ProcessWire instance", and that's something you'd only ever hear in ProcessWire land. Claude code also helped with improvements to our DatabaseQuery* classes, PagesVersions module, Wire base class, NullPage class, and minor updates to the PagesLoader* classes. I'm not having it write any code just yet, but am having it suggest where improvements can be made. I like to code. I asked it how it knew so much about ProcessWire, and it said that it stays up-to-date with the forums, the website, API docs, and GitHub repo. Thanks to @adrian and @Jan V. for recommending it to me (Jan V. uses it to manage this webserver), I can see how it's going to be a big help to ProcessWire with its suggestions and ideas, I'm already learning a lot from it. And if you get a chance to try the updated PageFinder, please let me know how it works for you. Thanks for reading and have a great weekend!
- 18 replies
-
- 26
-
-
-
-
ProcessWire Email Obfuscation (EMO) Download | GitHub Email Obfuscation module for email addresses with 64 base crypting. This module finds all plaintext emails and email links from the document and replaces them with span elements including configurable replace text. All the addresses are encoded to 64 base strings and stored in spans data attributes. Then on client side we decode these strings back to their original state. Install Create new 'EmailObfuscation' folder into /site/modules/ and place the content of this repository into the directory. Login to processwire and go to Modules page and click 'Check for new modules'. Find 'EmailObfuscation' and click install. You can make optional configuration changes in the module admin page. Thanks This is a ProcessWire module fork from MODX Evolution plugin emo E-Mail Obfuscation. http://modx.com/extras/package/emoemailobfuscation EDITED ON: 2013-03-03 - Added description. 2020-04-16 - Fixed GitHub links and updated description. Hello all. Just found PW few days ago and it's already looking awesome! Here comes first contribute and some questions for developing it further. There was one existing email obfuscator on reposity that didn't use any crypting for addresses so I decided to do a little test run and port the one that we currenly use with MODX Evo to ProcessWire module. I'd like to make PageAutocomplete like gonfigure option to admin so that one could select and set templates to exclude module action. It looks like autocomplete is tied to pages and since templates are not set to system as pages this is option is no go, am I right?
-
I am about to put my first ProcessWire-powered website onto a live server, and I was searching the website and forum on how to do this. Maybe I used the wrong search-terms, but I did not find anything on how this should be done. I can obviously just throw all the files on the server through FTP, and put the database in place using PhpMyAdmin. Then change the DB-credentials in config.php and I think it should work, but I don't know what the recommended way is to push a website from your local/dev-environment to a live webserver.
-
The client hosting company has a cap on emails per hour that is fine for now but we might exceed it eventually. Can anyone recommend a paid SMTP service that scales in tems of volume and works well with the stock ProcessWire SMTP module? I just want something that will plug and play but inputting the SMTP credentials rather than having to authorise an app, etc. I have used Mailgun before so that is an option and found the module in the PW directory works. I just find some SMTP services don't always work right away so I wondered if there are any you can recommend. The server is based in the UK so something that has a server nearby would be a bonus for lower latency. Thanks. 🙂
-
Recommended way to move installation from local to production server
evan replied to _Roy_'s topic in Getting Started
Something I've adopted for my PW sites is putting environment-specific DB credentials and configuration in a separate file, /site/config.local.php: /** * Installer: Database Configuration * */ $config->dbHost = '127.0.0.1'; $config->dbName = 'db'; $config->dbUser = 'default'; $config->dbPass = 'default'; $config->dbPort = '3306'; /** * Installer: HTTP Hosts Whitelist * */ $config->httpHosts = array( 'my-local-domain.test'); And then including that file into /site/config.php: /*** INSTALLER CONFIG ********************************************************************/ // DB Config, Hosts Whitelist include dirname(__FILE__) . '/config.local.php'; I find this makes it easier to push changes when using version control (i.e. git) across different environments, and keep the credentials and other sensitive information excluded from the repo. On some managed hosts like Cloudways, you can pull and push DB changes from staging to/from production applications, as well as branch changes from git. Obviously this doesn't help when you make DB changes locally, though. Otherwise, I will either export/import the entire DB via the Database Backups module, or Pages Import/Export (under Admin > Modules, Core modules) depending on the kind of changes. After using Vercel and Netlify for static-based websites, it does make me wish there was a quicker way to push/pull DB changes, but perhaps that extra bit of friction is good so you don't inadvertently disrupt your production data. Definitely something I can improve in my own workflow! Managing local/remote image assets Occasionally I've added this code snippet to /site/ready.php, that allows for your local installation to pull down the asset from prod, which can save you some time. I'm also generally curious to know more about other folks' deployment strategies – could be a potentially useful thing to collect and make more visible in the PW docs, too. -
The toughest challenge so far? Being our own client. Actually, the website we launched back then was just a placeholder, and the plan was to quickly replace it with a proper portfolio website. But as is often the case, it ended up taking a little longer than expected. A year later, we’ve finally done it: Our new website is live! https://konkat.studio/ The goal of the new website is to showcase our work and better communicate our services. The site is bilingual and was built using ProcessWire and PageGrid. More on that later. In addition to the website, we’ve also evolved our visual identity and logo. KONKAT (from concatenation) stands for linking individual elements into a functional whole. Our new branding makes this connection visible. In our case, we combine strategy, design, and technology into a unified process. The logo mark communicates this as well; as most of you probably know, the += operator in JavaScript joins elements and assigns the result. It took us some iterations to get the design right, but once the design was done, development was pretty straightforward. Most of the time was spent preparing the content for the projects, and that is also where PageGrid was super useful since it allowed us to design the layout and content of each project individually. Backend view: Managing project content and layouts with PageGrid. PageGrid also significantly sped up development, as we built all other pages using only its core blocks. For the projects overview, for instance, we used the datalist block to automatically generate the listing from our project pages, working perfectly out of the box without any custom logic. We also added some custom code where it made sense, e.g. the scroll animation on the homepage was just a bit easier to achieve with custom code (it uses native CSS sticky). Backend view: Using Pagegrid's inline editing to update some text on the english version auf our services page. Another great thing is that PageGrid takes care of lazy loading images and videos (using the famous lazysizes js plugin) and is caching its content automatically. As a result, we got a 100 on the Google Lighthouse test on desktop and 99 on mobile without any extra optimizations (we are not using Markup Cache or ProCache for this site). Backend view: Editing a thumbnail on the homepage If you have any further questions regarding our workflow or process, feel free to ask. I will do my best to answer them. Also, please let us know if you find any bugs, since the website is brand new, there are probably some we haven't caught yet! We also welcome any feedback you may have. Best, Jan & Diogo (KONKAT)
-
Hi, I am trying to collect the page titles of a parent and its children, but this code won;t work: <?php // collect titles for parent + children $titles = [$page->title]; // parent title foreach($page->children as $child) { $titles[] = $child->title; // child titles } // build a selector value like: title="A|B|C" $titleSelector = implode('|', array_unique($titles)); // now find all Championship_results whose winner_name page // has a title matching ANY of those titles, sorted by year descending $champwinx = $pages->find( "template=Championship_results, winner_name.title=$titleSelector, sort=-year" ); ?> It produces this error: any suggestions re a fix appreciated Error: Exception: Unknown Selector operator: '[empty]' -- was your selector value properly escaped? field='G', value='Miss|Temple-Dobell', selector: 'template=Championship_results, winner_name.title=Ravenscroft, G (Miss)|Temple-Dobell, G (Mrs), sort=-year' (in wire/core/Selectors.php line 222)
-
On a side note, because of the ProcessWire 'way' of handling images, it helps to think about the purpose of the images your page will have. As teppo flagged. So I usually have 1 x carousel images or a hero image field 1 x general images field i might display in the content 1 x something else The reason for doing this is that you can keep all your images and image field settings appropriate for the image purpose etc. Especially if you have clients adding or removing images. I find having just one image field can be an issue if they accidentally remove something or add an image which should not be part of the hero banner etc. Later on you might want to also look at tagging images and calling them onto your front-end pages https://processwire.com/modules/textformatter-image-tags/ But I'd recommend getting used to the basics first. An example of how I use image tagging might be. I have a product field with a lot of general images one of them in particular is used as a preview thumb on a product overview page so i'll tag that image 'preview' or whatever you want really. Lets us know how it goes
-
I recommend not subscribing to a Plaid plan at kimi.com. I wanted to cancel my subscription, but there was no such option, so I had to delete the account, as that was the only option I could find. I wrote to support, but they never replied. I removed my saved credit card on Stripe's interface, but I still receive a message every other day: "...payment to MOONSHOT AI PTE. LTD. was unsuccessful We attempted to charge your card for your MOONSHOT AI PTE. LTD. subscription again, but were unsuccessful. Please update your billing information to continue your subscription." For a theoretically deleted account...
-
@mattgs Like Adrian, I also consider myself very environmentally conscious. I've not spent much time learning AI in part because I thought it was problematic for a lot of reasons. But I don't think we're likely to stop these AI companies so that's why I thought I should try things out with a company that seems to have more ethics than the others. Anthropic seems to have a mission for AI safety and sustainability. I hope it's legit. And as far as I can tell, the other companies don't, which I find concerning. But I'm also not as up-to-speed as you are on the on the issues you brought up, so I'll have to look closer as well as check out the video you mentioned (do you have a link?). I'm also aware that a project like ProcessWire gets executed millions upon millions of times every month (or day?) throughout the world, and every execution consumes energy. So I've always been very interested in optimization and making ProcessWire use as little time and energy as possible to do its work. The updates that we've been focusing on here are aimed directly at that. So perhaps AI is using a little energy to find optimizations and bugs in PW, but that single brief code review session reduces the energy usage of every ProcessWire execution going forward. This is a case where AI is likely saving a lot more energy than it consumes, indirectly by making ProcessWire use less energy. Some of the optimizations and bugs its found have been there since the beginning, and likely would have never been identified otherwise.
- 18 replies
-
- 12
-
-
There may be a time where you need to create a page reference field using the Select inputfield and it's selecting repeater pages. Let's say I have a repeater field called "order_line_items" and I want to create a page reference field called "order_line_item" that allows me to select a repeater item (which is a page) of the "order_line_items" repeater field. Repeater pages are a bit different from regular pages in that their "parent" is a container admin page associated with the page in which it exists (dig into /admin/repeaters/order_line_items/ in your page tree to see what I mean). So when you are configuring your page reference field, you can't really choose a Parent. However when configuring your field, your instinct would be to choose the Template of "repeater_order_line_items". Then because you need extra precision in what pages are actually available for selection (rather than all of them across all pages), your instinct will be to implement custom PHP code: $wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) { if($event->object->hasField == 'order_line_item') { $event->return = $event->pages->find('your selector here'); } }); The problem with that approach is that even though you have defined the custom PHP code and the select field correctly shows the selectable repeater pages in the select field, behind-the-scenes, ProcessWire has still loaded EVERY SINGLE REPEATER that has the "repeater_order_line_items" template (you can see this is TracyDebugger's pages loaded list)! Your site will definitely be slower as a result, dramatically so if you have thousands or tens of thousands of repeater pages of that template. I hit this issue years ago (2018) and I thought it was a bug. I discussed it with Ryan and it's technically not a bug, but kind of the way ProcessWire works, which is beyond this tutorial. While you can circumvent this using the PageAutocomplete field, I don't like the ergonomics of that field in certain situations. I want the good-old select field. The solution to this is to NOT select anything for the "Template" when configuring your field. So in my example, I chose "repeater_order_line_items", but instead, it should be left blank. Now the field will just rely on the code portion and all the unnecessary page loads will be eliminated.
- 2 replies
-
- 10
-
-
The more you use it, the more you’ll be blown away. At least that’s how it has gone for me and most devs I know. Since Opus 4.x, Claude has completely transformed my workflow 🙂 I enjoy “manual” coding too, but AI coding has plus sides. Going from rough idea to usable feature is blazing fast, testing and prototyping has been so much fun, and AI tends to find issue (and opportunities!) humans would never spot. Also with Claude taking some of the load off my shoulders I often find myself working on multiple things at the same time… though I guess not everyone will see that as a good thing 🫣 Oh, and docs and tests! Claude is great for that stuff. It needs human guidance for both, though, as it tends to get confused about what actually matters. AI docs are often easy to spot: many words, little (or no) meaning. Admittedly sometimes this way of doing things tends to feel less like coding and more like managing a team of skilful but a little naive devs that often need help with ideation, architecture, testing, and just plain common sense 😅
-
New blog: All about custom page classes in ProcessWire
ryan replied to ryan's topic in News & Announcements
@Ivan Gretsky I'm always a little reluctant to make a blanket statement like "avoid markup in page classes", but I'm referring to what I think works best with the projects I work on. The files in /site/templates/ are the view layer, as nearly all code in there is aimed at generating markup/output. Even if something isn't directly generating markup, it's still finding and preparing things for output. Most markup comes from "partials", which are files that I put in /site/templates/parts/, or if exclusive to a particular template, then /site/templates/[template]/. And then I either include() them, or files()->render() them from the site's template files. I primarily use Markup Regions. The _main.php establishes the base markup: <?php namespace ProcessWire; /** @var Page $page **/ ?><!DOCTYPE html> <html> <head id="html-head"> <?php include('./parts/html-head.php');?> </head> <body id="html-body"> <header id="header"> <?php include('./parts/header.php');?> </header> <h1 id="headline"><?=$page->title?></h1> <main id="content"> <?=$page->body?> </main> <aside id="sidebar" pw-optional> </aside> <footer id="footer"> <?php include('./parts/footer.php');?> </footer> </body> </html> Below is a template file for the /products/ page which lists product pages, supports pagination, and uses URL segments for sorting: <?php namespace ProcessWire; // products.php /** @var ProductsPage|CategoryPage $page */ $products = findProducts($page); $body = input()->pageNum === 1 ? $page->body : ''; $headline = $page->get('headline|title'); ?> <h1 id="headline"><?=$headline?></h1> <main id="content"> <?=$body?> <?php include('./parts/sorts.php'); // no expects ?> <?php include('./parts/products-list.php'); // expects $products ?> </main> <aside id="sidebar"> <?php include('./parts/categories-list.php'); // no expects ?> </aside> The category template file works exactly the same way, except that it lists products for the category rather than listing all products. The same code works either way, so "category.php" just includes "products.php": <?php namespace ProcessWire; // category.php include('./products.php'); There's that findProducts() function above in the products.php template file -- I usually have helper functions in a /site/templates/_func.php, /site/templates/_products.php, or /site/templates/products/func.php (assuming exclusive for "products"). Another place would be for the ProductsPage and CategoryPage to have findProducts() methods, but usually I don't want the page classes getting involved with detecting stuff about the current request (sort, pageNum, etc.) so like these in a simple function library file: <?php namespace ProcessWire; // file site/templates/_func.php included by _init.php function getSorts(): array { return [ 'name' => 'A-Z', '-name' => 'Z-A', 'price' => 'Price (low-high)', '-price' => 'Price (high-low)', 'created' => 'Date added (oldest)', '-created' => 'Date added (newest)' ]; } function getSort(): string { $sorts = getSorts(); $sort = input()->urlSegment('sort-(*)'); if(empty($sort)) $sort = 'name'; if(!isset($sorts[$sort])) wire404('Invalid sort'); return $sort; } function findProducts($page, $limit = 20): PageArray { $sort = getSort(); $find = "template=product, sort=$sort, limit=$limit"; if($page instanceof CategoryPage) $find .= ", categories=$page"; return pages()->find($find); } Here's an example of a ./parts/products-list.php file: <?php namespace ProcessWire; // file: parts/products-list.php /** @var PageArray|ProductPage[] $products */ $subhead = $products->getPaginationStr('Products'); $pagination = files()->render('parts/pagination.php', [ 'items' => $products ]); ?> <h3><?=$subhead?></h3> <?=$pagination?> <ul class="products-list"> <?php foreach($products as $product): ? <?php include('./parts/products-item.php'); // expects $product ?> <?php endforeach; ?> </ul> And the parts/products-item.php, though in reality there would likely be more to it: <?php namespace ProcessWire; // file: parts/products-item.php /** @var ProductPage $product */ ?> <li class="products-item"> <h3><?=$product->title?></h3> <p><?=$product->summary?></p> <p><a href="<?=$product->url?>">View Details</a></p> </li> To complete it, here's the parts/sorts.php file: <?php namespace ProcessWire; // file: parts/sorts.php $currentSort = getSort(); $url = page()->url; $sorts = []; foreach(getSorts() as $sort => $label) { if($sort != $currentSort) $label = "<a href='{$url}sort-$sort/'>$label</a>"; $sorts[] = $label; } echo "<p class='sorts'>Sort by: " . implode(' / ', $sorts) . "</p>"; If I start needing to output products in more places in the site, then I'll usually do fewer include()'s and move the rendering to dedicated functions. That way these things render in their own variable namespace and don't bleed variables or overwrite variables in the main rendering. So this would also go in that _func.php (or _products.php or ./products/func.php) mentioned above, and the include() calls in template fiels would be replaced with render...() calls: function renderProducts(PageArray $products): string { return files()->render('parts/products-list.php', [ 'products' => $products ]); } function renderCategories(): string { return files()->render('parts/categories-list.php'); } function renderPagination(PageArray $items) { return files()->render('parts/pagination.php', [ 'items' => $items ]); } So if using render() functions then the <main> with the include('./parts/products-list.php'); would get replaced with this: <main id="content"> <?=$body?> <?=renderProducts($products)?> </main> Ah yes, I hadn't thought about that in a long time. I can't remember if that was implemented yet or not. I'll find out. If not yet implemented I'll have to implement it, it should be fairly simple.