Leaderboard
Popular Content
Showing content with the highest reputation on 01/30/2025 in Posts
-
I have to leave town later today for for my daughters gymnastics meet, so I'm getting the weekly post out early this week. I bumped the dev branch core version to 3.0.245 as well. While there isn't anything major in this particular core version, it does contain some updates to the ProcessPageList module that were needed to make in order to make it possible to build the new PageListFilter module (the one I mentioned last week). These updates focus on making some parts of the PageList more hookable. To go along with those core updates, I've released the PageListFilter module that I mentioned last week. This is an admin helper that enables you to filter pages in the page list with a click, such as first letter (A-Z, etc.). I've already found it quite handy on some of the ProcessWire installations I work with, I hope some of you find it useful too, please let me know what you think. Thanks and have a great rest of the week and weekend!8 points
-
Quick update- previously Plates for ProcessWire required that you manually install plates via Composer. The module now comes pre-packaged with Plates. If you have already installed Plates via Composer or prefer to do so, this module will prioritize the version that you have installed. If not, the module will use the version that comes with it. I prefer to manage packages via Composer, but that may not always be the case for everyone and I want this module to be easy to get started with, just like Plates itself, regardless of preference or experience. I have pushed some enhancements and improvements to the functions provided by the custom extensions. Going forward extensions will be focused on bugfixes and nonbreaking changes for stability.3 points
-
Have a nice weekend, Ryan. Any chance of getting a sneak peek at the redesign you teased at the end of last year?3 points
-
Hi @AndZyk - sorry about that - not really sure what changed to cause it, but I've implemented a fix. I also moved that show icon up and left just a bit because I always found it was so close to that bottom corner that my mouse pointer (on MacOS) would be triggered by the resize window event on the browser - this should alleviate that.2 points
-
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!2 points
-
View website: ID Studio Web Agency We have been working on the ID Studio website for quiet some time using ProcessWire extensively for ourseleves and 90% of all our clients. This post will highlight some features we have implmented and also show off some of the hidden functionality. A quick overview is as follows: Custom web design of course 🙂 Front-end uses Canvas and Three.JS The core objective for us is to get users engaged, reviewing the showcase and services, then getting in touch The showcase and blog have alot of content We hide the ID Lab and About section in the footer but there if folks want to dive in and have the time Development features include: We use the form builder system with some custom modifications 3D tools and management Linking 3D elements to HTML elements Repeater matrix for content panels and lots more, best way is to see it on the video overview below ID-Overview.mp41 point
-
I have no clue why this works under certain circumstances. I will take a look at the weekend. Otherwise I delete the hooking section if it does not work properly.1 point
-
Hello @adrian, today I setup a fresh PW install with ProcessWire 3.0.245 and of course Tracy Debugger 4.26.60. Now I don't like to have always the full Tracy bar expanded when I do simple stuff, so I hide the Tracy bar with the "Hide Tracy" button in the right corner. Today when I use the "Hide Tracy" button the Tracy bar gets collapsed as expected, but when I reload the page it is completely hidden. It seems that the #tracy-show-button has "display: none" as styling. It seems to be a console error in this line: document.getElementById("tracy-show-button").style.display = "block"; Uncaught TypeError: Cannot read properties of null (reading 'style') at hideDebugBar ((Index):204:240) Can somebody reproduce this? Regards, Andreas1 point
-
Hello @Frank Vèssia I have taken a look at this issue and I will not change the "untitled-xxxx" to something else, because this username is absolutely unique. If you want to prevent, such random usernames to be stored inside the database, then please go to the module configuration and go to the tab for the registration form. There please add the field "Username" to the registration form (and to the profile form as well if you want). This forces the user to enter a username during the registration process and you will not get the randomly created "untitled-xxxx". Best regards1 point
-
Hello @Frank Vèssia The untitled-xxxxx as name will be added by default if you choose login with email and password and not with username and password. BTW the random username can be overwritten by the user itself if he wants. So there is no need for the user to accept this random username. I know this is not a really nice random username, but using the email address instead is not a good idea, because if you want to output the username on the frontend, everyone can see the email address. I guess the "untitled" will be added by PW if nothing is entered in the name field and the number will be appended by a hook as I can remember, but I have to take a look (I do not remember exactly). The untitled-xxxx will be added by PW and makes the name absolutely unique. I will think over if there will be simple solution to change the name to a more nicer one 😉1 point
-
Hello @Andy Take a look at the custom rules. There are some ready-to-use validators which check against the database (fe. uniqueEmail to check if email is not registered inside the user table). List of Custom rules If you want to check against custom tables, you have to check it inside the isValid() method by your own. There is no possibility to create and add custom rules by yourself. if ($form->isValid()) { //make your custom check here (fe checking a form value against the database) if(customcheck is valid){ // go on with your code } else { // set an error message at the top of the form $alert = $form->getAlert(); $alert->setErrorMessage('Sorry, but there are errors inside the form'); // set an error message on the inputfield itself (fe. an email input) $emailInput = $form->getFormElementByName('email); $emailInput->setErrorMessage('There is an error'); } } I have not tested the code above, but you got the idea such an issue could be solved. The last option is to write an request on Github for a new validator and if it could be useful for others I will try to implement it 😉 Best regards1 point
-
I hope you all have had a great week. Last week was the blog post for our newest main/master version 3.0.244. This week I've been catching up with some other projects, so no new core updates to report. But one thing I've been working on (and am still working on) is a module that lets you provide filters in the admin page list. In my case, a client wants to be able to filter by the first letter of page titles, so they can quickly jump to all pages that start with the letter "C", for example. It figures out all the starting first-characters for page titles and builds a kind of pagination-style list for it, like seen in the screenshot below. Clicking any of the single character filters to just those pages by sending an Ajax request to the server, grabbing just the relevant pages and listing them. I think it's pretty useful in many cases. And I think there's potential for predefined filters to go beyond just letters. There's more to work out with this, but I hope to release it in the near future. Thanks for reading and have a great weekend!1 point
-
1 point
-
1 point
-
1 point
-
1 point
-
Is it possible to edit the rows attribute of the textarea? Can I add / remove input attributes? Sorry, I somehow missed the info about setAttribute() and removeAttribute().1 point
-
Thanks for your quick response @bernhard I compared your example to mine and I see that you are calling the method ($foo->hello()) inside your ready() method. In this case it works on my side too, but this is not the scenario that I want. I want to manipulate the output not inside the ready() but before page render, so the markup of the method should be changed before the page will be rendered. public function ___renderAsterisk(): string { return '<span class="asterisk">*</span>'; } This is a render function, which will be called during the render function of the form and the form will be rendered during the page will be rendered. So I want to change the output of the renderAsterisk() method inside the site/init.php and this does not work at all. wire()->addHookAfter('Label::renderAsterisk', function ($event) { $event->return = ' - hooked aha :)'; }); So this works fine if the renderAsterisk() method will be called inside the ready.php separately. $foo = new Label(); bd($foo->renderAsterisk()); But it does not work if the renderAsterisk() method is included inside another render function (in this case inside the render function of the form. This should be the issue of the problem. To simplify: It works if the renderAsterisk() method will be called directly, but not if this method is included within another method and will be called there.1 point
-
/site/init.php wire()->addHookAfter('Foo::hello', function ($event) { $event->return .= ' - hooked :)'; }); /site/modules/WhatEver/Test.module.php <?php namespace ProcessWire; use MyNamespace\Foo; class Test extends WireData implements Module { public static function getModuleInfo() { return [ 'title' => 'Test', 'version' => '0.0.1', 'summary' => 'Your module description', 'autoload' => true, 'singular' => true, 'icon' => 'smile-o', 'requires' => [], 'installs' => [], ]; } public function init() { wire()->classLoader->addNamespace('MyNamespace', __DIR__ . '/classes'); } public function ready() { $foo = new Foo(); bd($foo->hello()); } } /site/modules/WhatEver/classes/Foo.php <?php namespace MyNamespace; use ProcessWire\Wire; class Foo extends Wire { public function ___hello() { return "HELLO!"; } }1 point
-
This is a fun, simple project I built over the holidays and really goes back to what initially drew me to ProcessWire several years ago after being inspired by Ryan's Skyscrapers Demo. Backwards Compatible allows searching and browsing by various performance related tags and game pages display detailed data on each title. List of modules used: Import Pages by CSV - Essential for this kind of site, the vast majority of data was imported in one CSV upload Procache - Invaluable after an influx of 650k page views in 6 days(!) Form Builder - A simple implementation for monitored page updates and YT video submissions Soma's AjaxSearch Mike Rockett's Sitemap Additional styles/scripts: Bootstrap Grid only for CSS Fancybox.js Tippy.js Commento1 point
-
Hello @PWaddict I guess this is a namespace related problem, but I have not found a solution to this issue. Therefore I have written a blog post in the forum in the hope others could help. Best regards1 point
-
1 point
-
@zilli Some great questions! I spent many years writing vanilla PHP template markup. For Occam's Razor, have the leeway to apply as many or few features a tool offers. Your mentioning Markup Regions is a good example, I use many features of ProcessWire but have never used Markup Regions (I have no opinion, it just coincidentally never became part of my workflow). Both MR and templating engines aim to overcome challenges introduced when using PHP (like any language) alone for output. So it's up to the preference of the developer and needs of a project, a la "there are no wrong answers". My limited experience with MR make me a less than ideal candidate to draw comparisons or speak to compatibility. My first thought it to flip the question of complexity and see how it applies to tools you/me/we may already use. Page Classes are a layer of complexity to abstract logic out of presentation, ProcessWire itself is a layer of complexity to abstract database transactions/data management. Templating strategies are a layer of complexity that make your workflow less complex. Tools like MR and templating packages do that as well while sometimes affording some extra tools. My experience with templating solutions was born out of bumping my head on limitations and moments of thinking "there has to be a better way to do this". I ended up using creative tricks to make 'require' and 'require_once' carry the load but ended up making my code harder to manage, more files to make it work, and felt like I was breaking good practice rules. Consider this simple example: <!-- /site/templates/inc/header.inc.php --> <!DOCTYPE html> <html lang="en"> <head> <title><?=$page->title?></title> <?php if ($includeGoogleAnalytics ?? false): ?> <?php require_once '/path/to/google_analytics.inc.php' ?> <!-- GA code here --> <?php endif ?> </head> <body id="$pageId" class="<?=$pageClass?>"> <header> <img src="/path/to/logo.png"> <h1><?=$headline?></h1> </header> Then I have my home.php and to make this work, I have to start setting big scope variables to be made available to the 'require_once' code. This got worse as the site needed more features and the header and footer became more complex. Later I had to create a new headers for other parts of the site, so multiply the code above a few more times, and add some more 'require_once' lines to each of them. <?php namespace ProcessWire; // /site/templates/home.php $pageId = 'stuff-here'; $pageClass = 'stuff-there'; $includeGoogleAnalytics = true; require_once __DIR__ . 'partials/header.inc.php'; ?> <!-- We haven't even added one line of markup/code for the home page --> <?php $includeCallToAction = true; $includeTestimonials = false; $anotherOne = true; $iHearYouLikeOutOfScopeVariables = false; require_once __DIR__ . 'partials/footer.inc.php'; ?> Here's an actual snippet from a site I build years ago. If you look at a partial or component and it has a lot of if statements, sometimes but not always, it says "I'm a file that does too much" and it becomes increasingly difficult to manage. <!-- Real snippet of production code from /site/templates/partials/footer.inc.php --> <?php if ($mainNav): ?> <?php require_once __DIR__ . '/../inc/site_nav_overlay.inc.php' ?> <?php endif ?> <?php if ($salesCtaModal): ?> <?php require_once __DIR__ . '/../inc/cta_sales_quote_modal.inc.php' ?> <?php endif ?> <?php if ($solarCleaningCtaModal): ?> <?php require_once __DIR__ . '/../inc/cta_solar_cleaning_modal.inc.php' ?> <?php endif ?> <?php if ($solarRepairCtaModal): ?> <?php require_once __DIR__ . '/../inc/cta_solar_repair_modal.inc.php' ?> <?php endif ?> Is this how everyone does it? Maybe not, hopefully not, but every project runs the risk of becoming Frankenstein's monster. With a templating solution approach this would have been solved with 4 files. You can significantly reduce the number of if statements because each of these have a job to do, they know what it is, and when you're maintaining the site, or adding new features, there is zero confusion about where to go. Working on the blog parts? Go edit the blog templates and blog layout. The service/maintenance pages know they'll need the "cleaning" and "solar repair" modals so that whole if block doesn't exist anymore. Cake. <?php namespace ProcessWire; // This is the base layout. The layouts below declare this as *their* layout and pass data if needed $this->layout('layouts/base'); // For the majority of pages on the site $this->layout('layouts/main'); // For the pages dedicated to customer service and maintenance rather than sales $this->layout('layouts/service_mainenance'); // For all blog and blog related pages $this->layout('layouts/blog'); ?> Long story short, the complexity would have been immediately reduced by introducing a templating tool with a predictable set of features and functions to use. To contrast, as a longtime user of preprocessors myself, I can say that those have the potential to introduce a lot of things that fundamentally change how you work with the language itself. Sass/Less bring nesting, loops, variables (before custom properties were available), mixins, includes, file imports, modifier functions, if/else control flows, custom functions, (the list goes on) and an entire JavaScript toolset to handle it. You can solve a lot of problems you don't have! This isn't an argument against preprocessors, but there is a higher level of discipline you must have to keep this from going off the rails and being so clever you outsmart yourself. On the other hand, templating is so incredibly fundamental to producing apps and websites that the constrains start to tighten much sooner and it becomes more clear how many workarounds and "bending the rules" of good practice are present. The benefits of introducing templating tools bring a higher number of benefits that are more impactful faster. You're also adopting a common development practice, so it is leveling up your skillset. I have to mention that these benefits are not unique to Plates. Layouts, and enhanced insert/include features, are common to pretty much all templating tools. Pick the one that feels right be it Latte, Twig, Smarty, or Plates. My only argument in favor of Plates is that if templating is something that you're introducing into your projects for the first time you may be up to speed faster because: a) the core features set of Plates is very limited (intentionally), and b) it's the same PHP syntax you already use. The concepts and strategy you use with Plates will translate to other templating tools. In the case of this module, the potential amount of complexity is almost entirely due to the custom extensions I built which is why they are optional and disabled by default. You can safely ignore everything in my post above from "Plates for ProcessWIre Extensions" on down and still get simple yet powerful tools. If you have any Q's, post them here.1 point
-
@jploch - this is a feature that AOS has which I enable it on all sites - very handy indeed, as is of course BreadcrumbDropdowns.1 point
-
@MarkE There's definitely a lot of potential for this to evolve over time. Another example is that it can edit any pages this way, not just children. For that reason, I put in a hook: ___getChildren($page) so that anyone can override what are the "children" for the page. If there is hierarchy, such as there would be if the selector "parent_id=$page" were changed to "has_parent=$page", then I agree the way repeaters do hierarchy could work here too. But I figure the best bet is to keep it simple, focused and straightforward at first to get to a stable version 1, and then start evolving it to do more after that, if there's interest.1 point
-
@Jonathan Lahijani If you edit your child page template and temporarily enable $config->advanced = true; you can click on the "System" tab and check the box to have it show the "name" field on the content tab, which should do the trick.1 point
-
very easy now with newer versions! just put this in your /site/ready.php $wire->addHookAfter("Session::login", function(HookEvent $event) { $user = $event->return; // check if login was successful if($user) { // your condition here if(!$user->isSuperuser()) { // your destination page here wire('session')->redirect('/'); } } });1 point