Jump to content

FireWire

Members
  • Posts

    633
  • Joined

  • Last visited

  • Days Won

    43

Everything posted by FireWire

  1. ?I've ?been ?type ?hinting ?parameters ?with ?question ?marks ?for ?so ?long ?I ?don't ?even ?see ?them ?anymore. @ryan Seconding @adrian's many thanks for the PHP 8.4 attention 🙌 Will we see any (Disjunctive|Normal)|(Form&Types) type declarations? Maybe a contest for longest function signature¿ Wait, did I just break my key¿
  2. Hi all (again)! I'm continuing to create buttons and have created a Github repo for all of the images. I've since added another after updating the initial post above. The repo will always have the latest preview, and new buttons. As mentioned above, the latest push contains cleanup and consistency for existing buttons as well as several new ones. Download from or fork the Builder Buttons Github repository Enjoy!
  3. @modifiedcontent Virtualmin is open source and has a free community version. You can host multiple sites and have multiple users. Unless you're running a serious enterprise multi-server hosting business, I think Virtualmin would suit you just fine. What constitutes "use my websites professionally" more has to do with the features you expect to need. Virtualmin has an easy and clear breakdown of whether you're a person who actually needs a professional license at all, and "best" is very subjective. Personal preference, I've never liked cPanel but it's popular so some people think it's the best for them. I'm not an expert in server management software, but from what I've used I like Virtualmin the best. A downside for a more casual user is that the main admin (the one that manages users and has total control of the server) has a lot of features and may involve a little bit of a learning curve. It's likely not something you can't handle, but you have much more granular control over the server and users. I may have used Plesk in the past, probably through some managed hosting provider but it's not memorable enough to have an opinion. Given that the software you do choose is going to manage your server for the long term and there are open source/free versions, it's worth spending an afternoon trying them out. Spin up a cheap VPS, install the software, play around with it, then destroy the instance and review your notes. I've never been satisfied by just comparing specs or features "on paper" so to speak when it comes to server management software. It's something you have to feel out. Honestly- spending some time and getting a feel for the software is time well spent. You can be confident that the hours you spend up front now will ensure that you don't need to spend way, way, way more time later migrating all of your sites later because you find you can't live with it. Vultr has a marketplace that you can select an image to spin up a VPS with both cPanel and Plesk. It doesn't include Virtualmin or Vesta but those are essentially single line bash command installs. Just remember that if you're going to install Virtualmin, spin up a blank server with Linux and without any web hosting applications like Apache or MySQL- Virtualmin will take care of all of that.
  4. @bernhard <section class="rpb-quote page-section rpb-block" data-rpbblock=5827 alfred='{&quot;icons&quot;:[{&quot;icon&quot;:&quot;edit&quot;,&quot;tooltip&quot;:&quot;Edit Block #5827&quot;,&quot;href&quot;:&quot;\/admin\/page\/edit\/?id=5827&amp;language=1027&quot;,&quot;class&quot;:&quot;pw-modal alfred-edit&quot;,&quot;suffix&quot;:&quot;data-buttons=\&quot;button.ui-button[type=submit]\&quot; data-autoclose data-reload&quot;},{&quot;icon&quot;:&quot;clone&quot;,&quot;label&quot;:&quot;&quot;Five International Architecture Festivals Worth Building a Trip Around&quot;&quot;,&quot;tooltip&quot;:&quot;Clone Block #5827&quot;,&quot;href&quot;:&quot;\/admin\/rockpagebuilder\/clone\/?block=5827&quot;,&quot;confirm&quot;:&quot;Do you really want to clone this element?&quot;},{&quot;icon&quot;:&quot;moveh&quot;,&quot;label&quot;:&quot;&quot;Five International Architecture Festivals Worth Building a Trip Around&quot;&quot;,&quot;tooltip&quot;:&quot;Move Block #5827&quot;,&quot;class&quot;:&quot;pw-modal&quot;,&quot;href&quot;:&quot;\/admin\/page\/edit\/?id=1&amp;language=1027&amp;field=rockpagebuilder_blocks&amp;rpb-moveblock=5827&quot;,&quot;suffix&quot;:&quot;data-buttons=\&quot;button.ui-button[type=submit]\&quot; data-autoclose data-reload&quot;},{&quot;icon&quot;:&quot;trash-2&quot;,&quot;label&quot;:&quot;&quot;Five International Architecture Festivals Worth Building a Trip Around&quot;&quot;,&quot;tooltip&quot;:&quot;Trash Block #5827&quot;,&quot;href&quot;:&quot;\/admin\/rockpagebuilder\/trash\/?block=5827&quot;,&quot;confirm&quot;:&quot;Do you really want to delete this element?&quot;},{&quot;icon&quot;:&quot;code&quot;,&quot;label&quot;:&quot;\/home\/modernismweek\/public_html\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.latte&quot;,&quot;href&quot;:&quot;subl:\/\/open\/?url=file:\/\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.latte&amp;line=%line&quot;,&quot;tooltip&quot;:&quot;\/home\/modernismweek\/public_html\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.latte&quot;},{&quot;icon&quot;:&quot;php&quot;,&quot;label&quot;:&quot;\/home\/modernismweek\/public_html\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.php&quot;,&quot;href&quot;:&quot;subl:\/\/open\/?url=file:\/\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.php&amp;line=%line&quot;,&quot;tooltip&quot;:&quot;\/home\/modernismweek\/public_html\/site\/templates\/RockPageBuilder\/blocks\/Quote\/Quote.php&quot;}],&quot;addTop&quot;:&quot;\/admin\/rockpagebuilder\/add\/?block=5827&amp;above=1&quot;,&quot;addBottom&quot;:&quot;\/admin\/rockpagebuilder\/add\/?block=5827&quot;,&quot;addLeft&quot;:null,&quot;addRight&quot;:null,&quot;widgetStyle&quot;:false,&quot;type&quot;:&quot;Quote&quot;}'> <div class="pt-4 px-5 md:px-10 lg:px-16"> <blockquote class="relative w-fit max-w-[60rem] mx-auto"> <span class="block absolute top-0 left-0 w-20 h-20 text-neutral-100 transform -translate-x-8 -translate-y-8 child-svg:w-full child-svg:fill-neutral-200"> <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" aria-hidden="true" role="img" aria-labelledby="title-bc232395-adec-49ef-877a-a8c1d379b1d1"><title id="title-bc232395-adec-49ef-877a-a8c1d379b1d1">Open Quote</title><path d="M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z" /></svg> </span> <div class="relative z-10 prose prose-neutral max-w-none"> <em class="text-lg leading-relaxed tracking-wide sm:text-xl md:text-2xl"><div id=pw-edit-5 class='pw-edit pw-edit-InputfieldTextarea' data-name=rpbblock_textarea data-page=5827 data-lang='1027' style='position:relative'><div class=pw-edit-orig>One of the top five international architecture festivals in the world.</div><div class=pw-edit-copy id=pw-editor-rpbblock_textarea-5827 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>One of the top five international architecture festivals in the world.</div></div></em> </div> <footer class="mt-4 md:mt-6 pl-4 md:pl-10"> <div class="flex items-center justify-center md:justify-start space-x-4"> <div> <div class="text-base md:text-lg text-neutral-700"><span id=pw-edit-8 class='pw-edit pw-edit-InputfieldPageTitle' data-name=title data-page=5827 data-lang='1027' style='position:relative'><span class=pw-edit-orig>&quot;Five International Architecture Festivals Worth Building a Trip Around&quot;</span><span class=pw-edit-copy id=pw-editor-title-5827 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>"Five International Architecture Festivals Worth Building a Trip Around"</span></span></div> <div class="text-sm md:text-lg text-neutral-600 mt-2 pl-6"> <a href="https://robbreport.com/shelter/spaces/five-international-architecture-festivals-1235458827/https://robbreport.com/shelter/spaces/five-international-architecture-festivals-1235458827/" class="!no-underline" rel="noopener nofollow" target="_blank"> <span id=pw-edit-12 class='pw-edit pw-edit-InputfieldText' data-name=rpbblock_text data-page=5827 data-lang='1027' style='position:relative'><span class=pw-edit-orig>— Robb Report</span><span class=pw-edit-copy id=pw-editor-rpbblock_text-5827 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>— Robb Report</span></span> </a> </div> </div> </div> </footer> </blockquote> </div> </section>
  5. Edit a widget? Not today.
  6. When you try to edit a "widget" but something broke with the theme and the entire website goes down, front end, admin, everything. Rendered completely inaccessible. When I say "broke with the theme" I mean, best guess because it's impossible to tell. Then you, the person who was just asked to update the name of a person and an email address on the page has to explain why the client's site is down without looking like a complete idiot. Not sure if anyone had started following the WordPress ecosystem, which has further devolved into 3 dumpsters put into a pile and lit on fire, but it's been wild. https://www.theverge.com/2024/10/12/24268637/wordpress-org-matt-mullenweg-acf-fork-secure-custom-fields-wp-engine
  7. Pull request got merged into the dev branch, PW should be in an upcoming release of Devicons. https://github.com/devicons/devicon/pull/1905 Wait... what year is it?!
  8. I've used DigitalOcean for years and the experience has been great, but little less over time. Ever since they went public with their IPO they've raised their prices noticeably and it feels like there are a bunch of upcharges for things that should be included. As a word of warning to anyone stumbling across this post, don't even bother with their managed databases. Terrible performance with high latency that really slows down applications. Not surprised that @ErikMH tests of DO vs others showed lag. I'm going to take a look at Vultr for my next move, the pricing looks good, like DO used to be. @modifiedcontent The flavor of Linux you choose will have less of an impact than the hardware. I tend to go with Ubuntu on servers because everything is easily available through apt repositories and it has wide support. You could say the same for Debian, but I go with Ubuntu since they offer Expanded Security Maintenance and Livepatch services. Knowing that there's automatic updates to the server is nice. Combine that with configuring unattended-upgrades and it's a good way to keep things up to date. When you read the information on their site about these services, the language targets the enterprise, however when you create an account you can get these services for free for a limited number of machines. I've run ProcessWire on both Ubuntu and Alpine and didn't notice a difference other than Alpine was a little more bare bones. As long as the server can handle PHP and a database, ProcessWire won't know the difference. You're better off focusing on optimizing PHP-FPM workers and taking advantage of the strong PHP features like OPcache, adding the ModPageSpeed module, and JIT compiling (if it suits your use case). If you choose to explore using ModPageSpeed that will be an easy add and configuration on Ubuntu, Alpine will require a Docker container and extra work. As for a recommendation on hardware, NVMe drives will likely have the most impact on performance, followed by the processor. Depending on how much you expect your server to handle, they're worth the few extra bucks if it's in your budget. I've switched between the basic VPS and a VPS with an upgraded drive and CPU and the difference was noticeable in how fast the PW admin runs. ProCache content is also delivered faster via NVMe. I saw some benchmarks that were performed on a DO droplet and AMD beat out Intel easily on performance. Not sure if that translates to other cloud providers, but given what we've seen with Intel and their chip shenanigans in the past year, I'm all AMD these days. Not sure of how you prefer or plan to manage the server itself, but if you want a recommendation for a control panel, I've recently had a great experience with Virtualmin. It has a lot of UI control for some granular items that you would expect to manage from the CLI. I've managed VPSs via both, and have long preferred managing everything via bare SSH and bash but Virtualmin offers pretty much everything you need in a gui, and is great on performance. It also makes managing resources much easier than CLI, like how much RAM is dedicated to the database process (if you choose to run your DB on the same server), which makes it great for running lean on lower spec servers. I did just migrate to Virtualmin + bare Ubuntu from OpenLiteSpeed + CyberPanel within the last month. OLS has some attractive features out of the box, like native support for HTTP3 which provides excellent speed on TTFB, but it comes with tradeoffs and locks a lot of things behind an upgrade package that is hard to justify in cost. OLS locks you to one worker process unless you purchase a very expensive enterprise upgrade. I use ProCache to deliver content where possible but there are times when you can't, and the PW admin can run noticeably slow. The HTTP3 and extra layer of built-in caching are eclipsed by less than stellar performance everywhere else. I added some extras here that nobody asked for, but if anyone else has some experiences they can share I'm interested in hearing more from the community as well.
  9. That's the screenshot I forgot to include 🤦‍♂️ From what I can tell it's saying that the section tag itself is the cause? @bernhard I forgot to tag you 🤦‍♂️ (this emoji is getting a lot of use these days...)
  10. First time I've run into this. I have a block called "Quote" and I've used it on one template but started getting this error when using it on a different template. I removed all of my block markup but still getting the error. It looks like the {alfred($block)} call is inserting JSON into the element I feel like there's something I'm missing but can't figure out what would be causing it and not sure where to start looking.
  11. @bernhard Sorry, it's 2:40am here and I'm migrating servers. My brain is a little fried and I misread what you typed. I'll check it out when I can.
  12. @bernhard It was on the same page but addressed an overall issue where the page wasn't saving. I upped the max input variables to 2000 and the page started saving again. I haven't been using the front end editor due to the workflow and jumping around in the admin. Admittedly, I think the max input vars and this was a perfect storm or I probably would have seen it sooner, one covered up the other. I'll figure an alternative out. Using a custom field isn't a requirement. This post may help others in the meantime though. But this one is kinda on me... Ryan: Me: WELL I BETTER GO USE THIS IN PRODUCTION RIGHT AWAY!
  13. No worries at all, I always figure that if something gets posted in the forums it will end up helping someone!
  14. @gebeer Was a good idea to double check, in the example documentation in the module it shows compatibility. I do see AsmSelect in the list of approved fields on that blog post. <?php /** * Page reference fields * * All Page reference Inputfields need to have at least the following * properties defined: * * - `derefAsPage` (int): Whether it single or multiple mode: * * - `0`: Multiple page (PageArray). * - `1`: Single Page or NULL when empty. * - `2`: Single Page or NullPage when empty. * * - `inputfield` (string): Which input class to use, can be any of the following. * You can specify the Inputfield class name or optionally omit the `Inputfield` * prefix, like the options below: * * - `select`: Select one via standard select. * - `selectMultiple`: Select multiple via multiline select. * - `radios`: Select one via radio buttons. * - `checkboxes`: Select multiple via checkboxes. * - `asmSelect`: Select multiple/sortable via asmSelect. * - `pageAutocomplete`: Autocomplete for single or multiple selection. * - `pageListSelect`: Select one page from tree. * - `pageListSelectMultiple`: Select multiple pages from tree. * - `textTags`: Select one or multiple via text tags input. * */ I also tested it in a vanilla repeater field and wasn't able to replicate the issue there. Using the field in a repeater applies a unique suffix: event_activities_select_repeater5720_activities[]
  15. This may be an issue when using the ProFields Custom Field. I have a block that contains a Custom Field. That custom field contains a Page Reference field (single), Page Reference field (multiple), and a number input. When I create several blocks containing this field the values become unmanageable. They all appear in all blocks and can't be ordered in any of them. I have several of these blocks and each should have only 3 or 4, but they are all rendered with all of them. I did do some digging and found that the custom fields are not prefixed as a repeater field in the request data when saving the page, so each of these Page Reference field with multiple pages selected are all returned as a single variable array. I've attached a txt file with the POST request parameters and it shows the following variable repeated without a repeater suffix: event_activities_select_activities[] Which may need to be handled as something like: event_activities_select_activities_repeater5783[] This is just a guess... I could be off on that, but the only constant that I could see in the issues when rendering is that every block has the same event_activities_select_activities variable without a way for it to be processed individually by block. That's what I could pull together while experimenting. Something that's fixable? I have to determine if I need to rewrite the blocks to use different fields 😵‍💫 rpb_blocks_custom_fields_request.txt
  16. Just ran into this issue on a site and found this. Worked like a charm! If you have pages with a large amount of fields or a lot of data that may change, keep this tip in your back pocket. @elabx Many thanks 🙏
  17. WireCon 2025
  18. @bernhard I was experimenting with them and was having trouble getting them to work, it didn't seem like the changes were being detected, I figured it was a problem with my usage. Was the change a fix or an improvement?
  19. It doesn't look like this module is being maintained anymore and there's an issue that should be known by users. The sitemap generator is including: Pages that have 'include in sitemap' unchecked Hidden pages Children of unpublished pages Not sure if this is being experienced by others but this can be problematic for discovery of pages that shouldn't be readily known to the public. Correction If a parent page has indexing disabled, but a child page does not, then the child page will still be indexed, which kind of opens the parent page for indexing as well. Best to keep robots.txt up to date as a fallback. If you would like hidden pages to not show up in sitemap.xml, then modify L77 of SeoMaestro/src/SitemapManager.php <?php $selector = sprintf( 'template=%s,template!=admin,id!=%s,include=hidden', // Remove 'include=hidden' from this line implode('|', array_keys($templates)), $this->getExcludedPages() );
  20. @bernhard Outstanding module and perfect timing for a project! Found an odd bug that only occurs if the calendar field is added to a fieldset tab: If I resize the browser window it loads fine.
  21. First time for me as well haha. First time I've ever gone through and uninstalled modules to troubleshoot, turns out it wasn't necessary at all. So that's good news for the quality of everyone's work 😎 I assume RockMigrations would handle it? I guess the order of operations in my work kept that from happening.
  22. Should the module "clean up" after uninstall? I am working through some troubleshooting in my module dev environment and part of that is uninstalling all modules to troubleshoot "clean". I uninstalled RockSettings as part of that process and noticed that uninstalling the module doesn't remove the fields it creates.
  23. I didn't know where the right place to share this was on the forums, so I'll post it here since it may be helpful to those getting started with ProcessWire hooks, or some experienced ProcessWire developers who might find them useful. Either way, dear reader, If someone already wrote it, why write it again? If you're someone with experience, feedback is welcome! If there are better or more efficient ways to do something, I would love being a student. Some of these may either address challenges that others have experienced as well or were inspired by the awesome community sharing their solutions. Kudos to all of the people out there helping all of us. If someone sees something that was solved elsewhere please share it in the comments to give credit. I have to make a disclaimer- these have worked for me and while most of them are ready to copy/paste, a few of them are going to need customization or tweaking to make them work for your use case. I don't currently have the resources (time) to provide a lot of support. Some of these were slightly rewritten or adapted for the examples. If you run into issues, the best thing to do is research the solution so that you know exactly what is happening in your application. If you adapt something, fix a bug, or address an edge case, it would be great if you can come back and share that. Be smart, if you're going to run hooks that modify or create data, run a DB backup first. This is the part where I say "I'm not responsible if your site blows up". I don't think that's possible, but do the right thing. There are dozens of hooks in the project I am sharing these from, and to manage that I created a file structure to handle this because there were far too many to put in one file and keeping the init.php and ready.php files clean really makes a huge difference in maintainability. Being able to jump between files by filename is a supremely efficient way to work as well. The filenames don't matter, they're there to identify the files and make it easy to locate/switch between them. Here's my approach to directory organization: /site - hooks -- HookUtils -- init -- lazy_cron -- ready - init.php - ready.php The ready.php file contents: <?php namespace ProcessWire; if(!defined("PROCESSWIRE")) die(); /** @var ProcessWire $wire */ // Import all ready hooks foreach (glob(__DIR__ . '/hooks/ready/*.php') as $hook) { require_once $hook; } The init.php file contents: <?php namespace ProcessWire; if(!defined("PROCESSWIRE")) die(); /** @var ProcessWire $wire */ // Import all init hooks foreach (glob(__DIR__ . '/hooks/init/*.php') as $hook) { require_once $hook; } // Import all LazyCron hooks, import after init hooks as there may be dependencies foreach (glob(__DIR__ . '/hooks/lazy_cron/*.php') as $hook) { require_once $hook; } Operational Hooks Here are some favorites. Sort items in a Repeater matrix when a page is saved This one helped sort RM items by a date subfield to help the user experience when editing pages. This implementation is configured to only fire on a specific template but can be modified to fire everywhere if modified to check that a field exists on the page being saved first. This was adapted from an answer here in the PW forum but can't find the original post, so I'm going to include it. If you're having issues getting items to sort the way you want, check out this post about natural sorting, which also works elsewhere in ProcessWire. Github Gist Automatically add a new child page when a page with a specific template is created This automatically creates a new child page and saves it when a page having a specific template is created. This also has the ability to show a message to the user in the admin when new page(s) have been created by this hook. It is also error safe by catching any potential exceptions which will show an informative error to the admin user and log the exception message. The messaging/logging operation is abstracted to a separate object to allow reuse if creating multiple pages. Github Gist Conditionally show the user a message while editing a page This one shows a message on a page with a specific template under specific conditions. May be page status, field value, type of user, etc. Visual feedback when editing complex pages can be very helpful, especially when an operation may or may not take place depending on factors like the values of multiple fields. This can reduce the amount of explanations needed on each field or training required for users to use a ProcessWire application. In my case, a message is shown if the state of a page indicates that another operation that is triggered by other hooks will or will not run, which is something that the user doesn't directly trigger or may not be aware of. Github Gist Show the user a message when viewing the page tree This is intended to display a message, warning, or error when the page tree is viewed, such as on login, but in this case executes any time the main page tree is viewed to provide consistent communication and awareness. In my case it displays if there is an activity page located under an "Uncategorized" page for an event. This is something that may be buried in the page hierarchy and not noticeable, but if an activity isn't categorized, then is isn't visible on the website, and if it's not visible on the website, people aren't seeing it or buying tickets. So having a persistent message can bring visibility to important but otherwise potentially unnoticed issues. Or you can just say hi and something nice. Github Gist Hook Enhancement - Fast user switching Hooks can run on triggers that vary widely. Some can and should be identified as those that are triggered by the current user, others may be more autonomous like executing via cron. There may be other hooks that are executed by a user that isn't logged in. Depending on the type of action and your need to identify or track it, switching from the current user to another user created specifically to handle certain tasks can be very helpful. ProcessWire tracks a number of things that are attributed to users- log entries note the user, the user that creates pages is stored, the user that last updated the page is stored, etc. You may want to know who did what when, or only take action if the last user that touched something was X and not Y. I created a separate user that has been provided only the specific permissions it needs to complete jobs that are triggered by hooks or crons. Creating a user with less permissions may also help prevent accidental behaviors, or at least help you be very intentional in determining what actions are delegated. Creating custom permissions is also useful. With a dedicated user I can see explicitly that the last update on some pages were made by an autonomous script that syncs information between the ProcessWire application and a third party platform. Github Gist - Fast user switcher Github Gist - Example of switching users in a hook Fast, powerful, and very (very) easy custom admin buttons I needed a way to add custom interactive buttons that had some specific requirements. Needs to be a button that can be clicked by the user and does something Can be conditionally shown to the user with an alternate message if that action is not available Needs to do something on the server and interact with ProcessWire Here's what that looked like for my application. The green "Refresh Activity" button in the top right. That's a custom button and you don't have to author an Inputfield module to get it. When a user clicks that button, it sends a request to the server with GET variables that are recognized in a hook, actions are taken, then a nice message indicating success or failure is shown to the user. To do this you'll need to install FieldtypeRuntimeOnly and create a new field. Following the documentation for that field, create a button with a URL to the current page with GET variables appended. Then create a hook that watches for the specific GET variable that executes if it's present. Shoutout to @Robin S for helping make short work of a potentially complex task. Note that the field code contains JS that handles the URL on page load. Since the hook is looking for a GET variable in the URL, using the back button or refreshing the page will cause the action to run twice. The JS in that example removes the entry from the browser history and also removes the GET parameter after the page loads if it's present. Github Gist - An example gist for the hook that handles the action Github Gist - An example of the FieldtypeRuntimeOnly code that is displayed and interacted with by the user. Automatically convert logged object or array data to JSON If you're using the outstanding Logs JSON Viewer (yet another great one by @Robin S module, then this hook makes for a thoroughly enjoyable logging experience. Using array or stdClass data when logging your values helps store additional information in an organized way Github Gist <?php $log->save('log_name_here', 'Regular string message'); // Remains a string $log->save('log_name_here', ['gets' => 'converted', 'to' => 'json']); $log->save('log_name_here', (object) ['is' => 'stdClass', 'object' => 'friendly']); Use a separate field to store address data for a FieldtypeMapMarker field This one is really simple, more just sharing an implementation and idea, but proved valuable for reducing data redundancy. I have a FieldtypeMapMarker field but the way that I needed to store address data was much better suited to using multiple fields for things like street, city, state, and zip code. I wanted those fields to be the "controlling" fields for the map marker field to prevent needing to edit 2 fields to keep updated, or accidental content diversion between them. On page save the value from the address fields are pulled and converted into a single string that is added to the FieldtypeMapMarker field's "address" property. I used a Custom Field (ProFields) for my address fields but this can be modified to suit your use case very easily. Github Gist You might also consider hiding the address input on the FieldtypeMapMarker field itself to reduce confusion since the values will be updated automatically anyway. You'll need to have this in a file that is appended to the Admin styles /* You can find the appropriate class for the template you are applying this to in the <body> element when editing a page You can omit that if you want to apply this everywhere */ .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerAddress, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerToggle, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerLat, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerLng { display: none !important; } <?php // Add this to your ready.php file or ready-firing hook to insert the file containing that CSS to your admin. $config->styles->add("/path/to/your/custom/admin/css/file.css"); Not-A-Hook Bonus - Here's code for an interactive Google Map Renders a Google Map using a FieldtypeMapMarker field, a separate address field, Alpine.js, and Tailwind. You'll need a Google Maps API key, a styled map ID from your Google Developer account, and the aforementioned fields. I wrote it using the latest Google Maps API. Saved you some time. You'll probably need to tweak it. I adapted this so if you find a bug please let me know and I'll update the gist. Note- this makes use of the AlpineJS Intersect plugin to improve performance by only loading/initializing the map when a user scrolls close enough to it. If you don't want that, remove the x-intersect directive. If you want to see it in action, you can check it out here. Github Gist Hook Support Class - A static method class to translate a field into all languages automatically If you use the Fluency translation module, this is a class that will help out with translating a field into all languages programmatically. Sharing this here because the next hook uses this as a dependency. I keep this in the HookUtils directory noted in the file structure above. Usage is demonstrated in the next hook. Github Gist Translate all translatable fields using Fluency on page save whether from UI or API. This is useful for instances where you want a page translated automatically and especially helpful when you are creating pages programmatically. This requires the above hook support class, as well as Fluency connected to an API account. Here are things that must be kept in mind. Please read them, the code for the hook, and the code for the support class to ensure that it works to your needs. You should modify Fluency before using this, really. Change the value of CACHE_EXPIRY on line 19 in the TranslationCache file to WireCache::expireNever. Do this to prevent chewing through your API usage from month to month on repeat translations. This will become standard in the next release of Fluency. This is an expensive operation in terms of API usage, which is why you very much should modify the caching behavior. This hook does not make an effort to determine which fields have changed before translating because it doesn't really matter if the translation is already cached. First time translations of pages with a significant amount of fields/content may be slow, like noticeably slower first time page save because this operation is only as fast as the speed of the request/response loop between ProcessWire and the translation API. Later page saves will be much faster thanks to cached translations. This will not attempt to translate empty fields, so those won't cause any delays. This works with multi-language text/textarea/TinyMCE/CKEditor fields, RepeaterMatrix fields, and the newer Custom Fields (ProFields). Other fields haven't been tested, but it's definitely possible to adapt this to those needs. I prefer to target specific templates with hooks, you can add multiple but be mindful of your use case. Consider adding excluded fields to the array in the hook if it makes sense Consider adding a field to enable/disable translations from the UI, a checkbox field or something This hook is probably one of the uglier ones, sorry. If you run out of API usage on your account, you're going to see a big ugly exception error on screen. This is due to Fluency not handling an account overage error properly because the return type was not as expected. Will be fixed in the next version of the module This is one that may be tailored to my PW application, I think it's general enough to use as-is for your project, but testing is definitely required. Read all the code please. Github Gist ProcessWire Object Method & Property Hooks The following are custom methods that add functionality to native ProcessWire objects. Add a getMatrixChildren() method to RepeaterMatrixPage objects RepeaterMatrix fields represent nesting depth as an integer on each RepeaterMatrixPage item. So top level is 0, first nested level is 1, second 2, etc. When looping through RM items, determining nesting requires working with that integer. It works, but adding adding some functionality helps out. This is infinitely nestable, so accessing children, children of children, children of children of children, and so on works. Fun for the whole family. This was inspired by a forum post, another one I can't find... Github Gist <?php // Access nested RepeaterMatrix items as child PageArray objects $page->repeater_matrix_field->first()->getMatrixChildren(); // => PageArray ?> <!-- Assists with rendering nested RM items in templates Sponsors are nested under sponsorship levels in the RM field --> <div> <?php foreach ($page->sponsors as $sponsorshipLevel): ?> <h2><?=$sponsorshipLevel->title?></h2> <?php if ($sponsorshipLevel->getMatrixChildren()->count()): ?> <ul> <?php foreach ($sponsorshipLevel->getMatrixChildren() as $sponsor): ?> <li> <img src="<?=$sponsor->image->url?>" alt="<?=$sponsor->image->description?>"> <?=$sponsor->title?> </li> <?php endforeach ?> </ul> <?php endif ?> <?php endforeach ?> </div> Add a resizeAspectRatio() method to PageImage objects Adds a simple way to quickly resize an image to a specific aspect ratio. Use cases include sizing images for Google Structured Data and formatting images for consistency in image carousels. Could be improved by accepting second argument to specify an image width, but didn't fit my use case. Github Gist <?php $page->image_field->resizeAspectRatio('square')->url; // Alias for 1:1 $page->image_field->resizeAspectRatio('video')->url; // Alias for 16:9 $page->image_field->resizeAspectRatio('17:10')->url; // Arbitrary values accepted Add a responsiveAttributes() method to PageImage objects Adds a very helpful method to generate image variations and accompanying 'srcset' and 'sizes' attributes for any image. Designed to be very flexible and is Tailwind ready. Responsive sizing can be as simple or complex as your needs require. Includes an optional 'mobile' Tailwind breakpoint that matches a custom tailwind.config.js value: screens: { 'mobile': '320px'}. I added this breakpoint largely to further optimize images for small screens. The array of Tailwind breakpoints and size definitions can be edited to suit your specific setup if there are customizations When sizing for Tailwind, the last media query generated will automatically be switched to "min-width" rather than "max-width" to prevent problems arising from restricting widths. Example, you can specify values only for 'sm' and 'md' and the 'md' size will have the media query correctly adjusted so that it applies to all breakpoints above it. Github Gist <-- The responsiveAttributes() returns a renderable attribute string: srcset="{generated values}" sizes="{generated values}" --> <-- Create responsive images with arbitrary width and height at breakpoints --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ [240, 125, '(max-width: 300px)'], [225, 125, '(max-width: 600px)'], [280, 165, '(max-width: 900px)'], [210, 125, '(max-width: 1200px)'], [260, 155, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Heights can be selectively ommitted by setting the height value to null --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ [240, 125, '(max-width: 300px)'], [225, null, '(max-width: 600px)'], [280, 165, '(max-width: 900px)'], [210, null, '(max-width: 1200px)'], [260, null, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Create responsive images with only widths at breakpoints --> <img src="<?=$page->image->url?>" <?=page->image->responsiveAttributes([ [240, '(max-width: 300px)'], [225, '(max-width: 600px)'], [280, '(max-width: 900px)'], [210, '(max-width: 1200px)'], [260, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Create custom sizes matched to Tailwind breakpoints --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ 'mobile' => [240, 125], // Custom tailwind directive 'sm' => [225, 125], 'md' => [280, 165], 'lg' => [210, 125], 'xl' => [260, 155], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <!-- Resizes width of image to fit Tailwind breakpoints, useful for full width images such as hero images, doesn't change height. Also accepts 'tw' as an alias for 'tailwind' --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes('tailwind')?> width="240" height="125" alt="<?=$page->image->description?>" > Add PHP higher-order function methods to WireArray and WireArray derived objects WireArray objects are incredibly powerful and have tons of utility, but there are situations where I find myself needing to work with plain PHP arrays. I'm a very big fan of PHP's array functions that are efficient and make for clean readable code. I found myself often reaching for $wireArrayThing->getArray() to work with data then using functions like array_map, array_filter, and array_reduce. These return arrays, but could easily be modified to return WireArray objects if that is more helpful. Github Gist <?php // The EventPage page class has a method that determines sold out status from more than one source of data/page fields // which means that it isn't queryable using a ProcessWire selector. This returns a single integer calculated from ticket availability // of all events from non-queryable data. $totalEventsAvailable = $eventPages->reduce( fn ($total, $eventPage) => $count = $eventPage->isActive() ? $total++ : $total, 0 ); // Requires using a page class to determine status reliant on multiple data points not queryable via a selector. Knowing what the event // page is for an activity can't be determined using a selector for activity pages. $displayableActivities = $matches->filterToArray( fn ($activityPage) => $activityPage->eventPage()->isPublic() && $activityPage->isActive() ); // Iterating over each Page in a PageArray and processing data for sorting/ordering before rendering on a search results page // Executed within a page class $results = $searchResults->mapToArray(function($page) { return (object) [ 'page' => $page, 'summary' => $this->createResultSummary(page: $page, maxLength: 750), 'keywordMatchCount' => $this->getQueryMatchCount(page: $page), ]; }); Add an image orientation method/property to PageImage objects Get the portrait or landscape orientation of a PageImage. Github Gist <?php $page->image->orientation; $page->image->orientation(); Add the ability to get all related pages for Page objects at once Gets all of the related pages to a page at once by both page reference fields and links in fields. Transparently passes native arguments to Page methods for native behavior Github Gist <?php $page->allPageReferences(); $page->allPageReferences(true); // Optionally include all pages regardless of status $page->allPageReferences('your_selector=here', 'field_name'); // Use with native Page::references() and Page::links() arguments Add a saveWithoutHooks() convenience method to Page objects The number of hooks in my most recent project was... a lot. There were many that hooked into the page save event and a lot of operations that happen in the background where pages needed to be modified and saved quietly to prevent clearing ProCache files or excessive DB operations through chained hooks. Being able to use a method to do this rather than passing options felt more deliberate and clear when working across hundreds of files and in critical areas of very expensive operations. This method also accepts page save options, but in a way that hooks will always be disabled even if an option is accidentally passed enabling them. Furthermore, it also accepts a string as the first argument that, if debug mode is enabled, will dump a message to the bar via Tracy. Github Gist <?php // Adding a message can be very helpful during testing, especially when saving a page with/without hooks is conditionally based // where the result of another operation determines how a page is saved $page->saveWithoutHooks('updated event sync data hash, saved without hooks'); $page->saveWithoutHooks(['resetTrackChanges' => true]); $page->saveWithoutHooks('message and options', ['resetTrackChanges' => true]); These are a few that I've used to show some diversity in application. Hooking to ProcessWire events makes it possible to build beyond simple websites and implement truly custom behavior. Hope these may be useful to others. If you have any favorite hooks of your own, have corrections of those I've shared, or improvements, sharing them in the comments would be stellar. Cheers!
×
×
  • Create New...