Jump to content

Robin S

Members
  • Posts

    4,770
  • Joined

  • Last visited

  • Days Won

    302

Robin S last won the day on March 7

Robin S had the most liked content!

Profile Information

  • Gender
    Male
  • Location
    New Zealand

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

Robin S's Achievements

Hero Member

Hero Member (6/6)

9.3k

Reputation

17

Community Answers

  1. Similar to my suggestion to your earlier question, I think you should give your roles the necessary permissions to edit/create/add children for all the templates they will be working with. If you don't do this you're going to be fighting against what PW thinks that users with those roles are allowed to do and it will be more difficult to achieve what you want. Once those roles have the generic permissions to do what they need to do then focus on selectively taking away those permissions in your custom hooks according to your test on $user, $page, $parent, etc. Also, I've never understood the advantage to not explicitly setting access to every template. "Inheriting" access just seems like a recipe for confusion and accidental oversight. It only takes a short while to set the template access you want roles to have, and even less time if you use the Template Access module. Below is some code with demo logic for determining the allowed templates. You would replace that with your own logic which I expect would use some combination of $parent and $user. $wire->addHookAfter('ProcessPageAdd::getAllowedTemplates', function(HookEvent $event) { $allowed = $event->return; $parent_id = (int) $event->wire()->input->get('parent_id'); if(!$parent_id) return; $parent = $event->wire()->pages->get($parent_id); // Your custom logic here to remove any disallowed templates if($parent->template == 'basic_page' && $event->wire()->user->name === 'test-editor') { $allowed_template_ids = [29, 78]; foreach($allowed as $template_id => $template) { if(!in_array($template_id, $allowed_template_ids)) { unset($allowed[$template_id]); } } $event->return = $allowed; } });
  2. The "1 to 10 of 20" part is something you get from PaginatedArray::getPaginationString() rather than from MarkupPagerNav. So if your paginated results are in a variable named $results you would do something like this: <p>Showing <?= $results->getPaginationString() ?> results</p> And the Previous/Next navigation: unfortunately MarkupPagerNav doesn't provide a direct solution for this. There's an open request for it: https://github.com/processwire/processwire-requests/issues/248 So for now you have to hide the numbered links with CSS (the easy option) or code your own Previous/Next links.
  3. @sz-ligatur, the dependent selects feature only works if both inputfields are one of the following: Select, Select Multiple or AsmSelect @ryan, it would be great to have this feature documented somewhere.
  4. Verify Links Periodically verifies that external links are working and not leading to an error page. How it works The module identifies links on a page when the page is saved and stores the URLs in a database table. For the purposes of this module a "link" is an external URL in any of the following... FieldtypeURL fields, and fields whose Fieldtype extends it (e.g. ProFields Verified URL) URL columns in a ProFields Table field URL subfields in a ProFields Combo field URL subfields in a ProFields Multiplier field ...and external href attributes from <a> tags in any of the following... Textarea fields where the "Content Type" is "Markup/HTML" (e.g. CKEditor and TinyMCE fields) CKEditor and TinyMCE columns in a ProFields Table field CKEditor and TinyMCE subfields in a ProFields Combo field The link URLs stored in the database table are then checked in batches via LazyCron and the response code for each URL is recorded. Configuration On the module config screen you can define settings that determine the link verification rate. You can choose the frequency that the LazyCron task will execute and the number of links that are verified with each LazyCron execution. The description line in this section informs you approximately how often all links in the site will be verified based on the number of links currently detected and the settings you have chosen. The module verifies links using curl_multi_exec which is pretty fast in most cases so if your site has a lot of links you can experiment with increasing the number of links to verify during each LazyCron execution. You can also set the timeout for each link verification and customise the list of user agents if needed. Usage Visit Setup > Verify Links to view a paginated table showing the status of the links that have been identified in your site. The table rows are colour-coded according to the response code: Potentially problematic response = red background Redirect response = orange background OK response = green background Link has not yet been checked = white background Where you see a 403 response code it's recommended to manually verify the link by clicking the URL to see if the page loads or not before treating it as a broken link. That's because some servers have anti-scraping firewalls that issue a 403 Forbidden response to requests from IP ranges that correspond to datacentres rather than to individual ISP customers and this will cause a "false positive" as a broken link. For each link the "Page" column contains a link to edit the page and the "View" column contains a link to view the page on the front-end. You can use the "Column visibility" dropdown to include a "Redirect" column in the table, which shows the redirect URL where this is available. For those who can't wait The module identifies links as pages are saved and verifies links on a LazyCron schedule. If you've installed the module on an existing site and you don't want to wait for this process to happen organically you can use the ProcessWire API to save pages and verify links en masse. // Save all non-admin, non-trashed pages in the site // If your site has a very large number of pages you may need to split this into batches $items = $pages->find("has_parent!=2|7, template!=admin, include=all"); foreach($items as $item) { $item->of(false); $item->save(); } // Verify the given number of links from those that VerifyLinks has identified // Execute this repeatedly until there are no more white rows in the Verify Links table // You can try increasing $number_of_links if you like $vl = $modules->get('VerifyLinks'); $number_of_links = 20; $vl->verifyLinks($number_of_links); Advanced There are hookable methods but most users won't need to bother with these: VerifyLinks::allowForField($field, $page) - Allow link URLs to be extracted from this field on this page? VerifyLinks::isValidLink($url) - Is this a valid link URL to be saved by this module? VerifyLinks::extractHtmlLinks($html) - Extract an array of external link URLs from the supplied HTML string https://github.com/Toutouwai/VerifyLinks https://processwire.com/modules/verify-links/
  5. When searching a datetime field the PW PageFinder can use any string that the PHP strtotime() function understands. So the problem you're describing only relates to limitations on the input where you are putting your search value. InputfieldSelector uses the jQuery UI datepicker for the value input, and this has a constrainInput setting that's true by default. So if you set constrainInput to false you can enter free text into the input and it should just work. You have to set this option after the datepicker is initialised because PW doesn't provide any way of customising the datepicker options before initialisation (I've opened a request for that here: https://github.com/processwire/processwire-requests/issues/523). Here's how you might do it with some custom admin JS: $(document).on('focus', '.InputfieldSelector .InputfieldDatetimeDatepicker', function() { const $el = $(this); // Set constrainInput to false after a brief delay so that the datepicker has had time to initialise setTimeout(function() { if($el.datepicker('option', 'constrainInput')) { $el.datepicker('option', 'constrainInput', false); // Updating the option seems to cause the datepicker to hide so show it again $el.datepicker('show'); } }, 100); }); The result in Pages > Find as an example: Edit: you may know this already but you can add a custom row to InputfieldSelector where you can use any selector clause you like. So if you can trust your editors to get the field name right you could have a row like this: This is the better option if you want to save the value in a Lister bookmark for example, as it seems that InputfieldDatetime will convert a time string to the equivalent timestamp when it reloads so it then loses the "dynamic" aspect.
  6. @Flashmaster82, ProcessWire tells you the requirements of a module when you retrieve the information from the modules directory: Also, the module readme is a good place to look for information about requirements.
  7. Change the form method to "GET" and on the home page create links to URLs like "/case-studies/?selected=telecom"
  8. I was going to suggest using inputfield dependencies as this would be the simplest option but it turns out there's a problem when using these with custom image fields: https://github.com/processwire/processwire-issues/issues/1889 So here's a hook instead. In this demo I want to show an error message if text_2 is left empty when text_1 is populated. You can adapt the names/labels/logic to your case. $wire->addHookAfter('InputfieldText::processInput', function(HookEvent $event) { $inputfield = $event->object; $field = $inputfield->hasField; $page = $inputfield->hasPage; // Only for an inputfield associated with field "text_2" // Adjust the field name to suit if($field && $field->name === 'text_2') { // Only for an inputfield that is associated with a page using the custom image fields template // Adjust the template name to suit if($page && $page->template == 'field-images_custom') { /** @var InputfieldWrapper $wrapper */ $wrapper = $inputfield->parent; // Get sibling inputfield by label because the name will include a random string // Adjust inputfield label to suit $text_1 = $wrapper->children->get('label="Text 1"'); // Show an error message if text_2 is left empty when text_1 is populated if($text_1 && $text_1->value && !$inputfield->value) { $inputfield->error('A value for "Text 2" is required when "Text 1" is populated.'); } } } });
  9. Try "has_parent!=2, include=hidden". If you only need a selector and not more advanced PHP logic then it's simpler to use the "Selector string" option rather than the "Custom PHP code" option.
  10. A module for generating and serving AVIF files:
  11. This module won't suit everyone because... It requires what is currently the latest dev version of ProcessWire It requires your server environment have AVIF support Generating AVIF files is slow It offers fewer features than the core provides for WebP It is likely incompatible with the core WebP features so is an either/or prospect ...but it allows for the basic generation and serving of AVIF files until such time as the core provides AVIF features. Auto AVIF Automatically generates AVIF files when image variations are created. The AVIF image format usually provides better compression efficiency than JPG or WebP formats, in many cases producing image files that are significantly smaller in size while also having fewer visible compression artifacts. Requires ProcessWire v3.0.236 or newer. In order to generate AVIF files your environment must have a version of GD or Imagick that supports the AVIF format. If you are using ImageSizerEngineGD (the ProcessWire default) then this means you need PHP 8.1 or newer and an OS that has AVIF support. If you want to use Imagick to generate AVIF files then you must have the core ImageSizerEngineIMagick module installed. The module attempts to detect if your environment supports AVIF and warns you on the module config screen if it finds a problem. Delayed Image Variations Generating AVIF files can be very slow - much slower than creating an equivalent JPG or WebP file. If you want to use this module it's highly recommended that you also install the Delayed Image Variations module so that image variations are created one by one on request rather than all at once before a page renders. Otherwise it's likely that pages with more than a few images will timeout before the AVIF files can be generated. Configuration On the module configuration screen are settings for "Quality (1 – 100)" and "Speed (0 – 9)". These are parameters for the underlying GD and Imagick AVIF generation methods. There is also an option to create AVIF files for existing image variations instead of only new image variations. If you enable this option then all image variations on your site will be recreated the next time they are requested. As per the earlier note, the process of recreating the image variations and the AVIF files is likely to be slow. Usage Just install the module, choose the configuration settings you want, and make the additions to the .htaccess file in the site root described in the next section. How the AVIF files are served The module doesn't have all the features that the ProcessWire core provides for WebP files. It's much simpler and uses .htaccess to serve an AVIF file instead of the original variation file when the visitor's browser supports AVIF and an AVIF file named the same as the variation exists. This may not be compatible with the various approaches the core takes to serving WebP files so you'll want to choose to serve either AVIF files via this module or WebP files via the core but not both. Two additions to the .htaccess file in the site root are needed. 1. Immediately after the RewriteEngine On line: # AutoAvif RewriteCond %{HTTP_ACCEPT} image/avif RewriteCond %{QUERY_STRING} !original=1 RewriteCond %{DOCUMENT_ROOT}/$1.avif -f RewriteRule (.+)\.(jpe?g|png|gif)$ $1.avif [T=image/avif,E=REQUEST_image,L] 2. After the last line: # AutoAvif <IfModule mod_headers.c> Header append Vary Accept env=REQUEST_image </IfModule> <IfModule mod_mime.c> AddType image/avif .avif </IfModule> Opting out of AVIF generation for specific images If you want to prevent an AVIF file being generated and served for a particular image you can hook AutoAvif::allowAvif and set the event return to false. AutoAvif generates an AVIF file when an image variation is being created so the hookable method receives some arguments relating to the resizing of the requested variation. Example: $wire->addHookAfter('AutoAvif::allowAvif', function(HookEvent $event) { $pageimage = $event->arguments(0); // The Pageimage that is being resized $width = $event->arguments(1); // The requested width of the variation $height = $event->arguments(2); // The requested height of the variation $options = $event->arguments(3); // The array of ImageSizer options supplied // You can check things like $pageimage->field, $pageimage->page and $pageimage->ext here... // Don't create an AVIF file if the file extension is PNG if($pageimage->ext === 'png') $event->return = false; }); Deleting an AVIF file If you delete a variation via the "Variations > Delete Checked" option for an image in an Images field then any corresponding AVIF file is also deleted. And if you delete an image then any AVIF files for that image are also deleted. Deleting all AVIF files If needed you can execute this code snippet to delete all AVIF files sitewide. $iterator = new \DirectoryIterator($config->paths->files); foreach($iterator as $dir) { if($dir->isDot() || !$dir->isDir()) continue; $sub_iterator = new \DirectoryIterator($dir->getPathname()); foreach($sub_iterator as $file) { if($file->isDot() || !$file->isFile()) continue; if($file->getExtension() === 'avif') { unlink($file->getPathname()); echo 'Deleted: ' . $file->getFilename() . '<br>'; } } } Saving an original variation file Because requests to images are being rewritten to matching AVIF files where they exist, if you try to save example.500x500.jpg from your browser you will actually save example.500x500.avif. You can prevent the rewrite and load/save the original variation file by adding "original=1" to the query string in the image URL, e.g. example.500x500.jpg?original=1. https://github.com/Toutouwai/AutoAvif https://processwire.com/modules/auto-avif/
  12. It's working reliably on a few sites I've tested on but what you're saying makes sense. So you could add the button with PHP in a hook and not in the JS as demonstrated below. I also had a thought that Page Edit might be opened in a modal in some other circumstances (I think ListerPro has an option for this) where the "Save + Close" button wouldn't work or wouldn't be wanted. So I've updated the code in the first post and in the code below so that a "quick_edit" URL parameter is added for identification purposes. admin-custom.js $(document).ready(function() { const $body = $('body'); const is_modal = $body.hasClass('modal') || $body.hasClass('pw-iframe'); if($body.hasClass('ProcessPageList') && !is_modal) { $(document).on('ajaxComplete', function() { $('.PageListActionEdit a:not([data-autoclose])').each(function() { $(this).attr('data-autoclose', '#save-and-close').attr('href', $(this).attr('href') + '&quick_edit=1'); }); }); } }); Additional hook for /site/ready.php $wire->addHookAfter('ProcessPageEdit::buildForm', function(HookEvent $event) { /* @var InputfieldWrapper $form */ $form = $event->return; // Return if Page Edit is not in a modal window if(!$event->wire()->config->modal) return; // Return if quick_edit GET parameter is not present if(!$this->wire()->input->get('quick_edit')) return; $save_button = $form->getChildByName('submit_save'); if(!$save_button) return; /** @var InputfieldSubmit $f */ $f = $event->wire()->modules->get('InputfieldSubmit'); $f->id = 'save-and-close'; $f->name = 'submit_save'; $f->value = 'Save'; $f->text = 'Save + Close'; $f->addClass('uk-hidden', 'wrapClass'); $form->insertAfter($f, $save_button); });
  13. Yes, it does show, but only in the modal and not in Page Edit itself, which is the intention. The way that modal buttons work is that the modal script looks for buttons that are actually within the page that is being displayed in the modal iframe and then it constructs new buttons for the bottom of the modal. The newly constructed buttons don't carry over things like the uk-hidden class so they are not hidden. But because the button I'm adding is actually within the Page Edit interface (right next to the normal Save button) I never want that button to be shown in normal page editing because "close" wouldn't make sense there. The check for is_modal should also prevent the Save + Close button being displayed when it isn't wanted so the uk-hidden is really just optional but it's a sort of "belt and suspenders" approach just to be on the safe side. 🙂
  14. I was browsing the requests repo and saw a request from @adrian that reminded me of a PW feature I had forgotten existed: from Page List you can open a page for editing in a modal window by long-clicking on the Edit button. This is quite handy for when you want to make a quick edit to a page. But as the request notes, it would speed things up if there was a "Save + Close" button in the modal. Until the request is actioned in the core I thought I'd try implementing it in some custom code, and it's also an opportunity to show an easy way you can add custom JS and CSS to the ProcessWire admin. The first step is to create the following files at /site/templates/admin-assets/admin-custom.js and /site/templates/admin-assets/admin-custom.css. The CSS is optional. admin-custom.js $(document).ready(function() { const $body = $('body'); const is_modal = $body.hasClass('modal') || $body.hasClass('pw-iframe'); if($body.hasClass('ProcessPageEdit') && is_modal) { const params = new URLSearchParams(window.location.search); const $save_button = $('#submit_save'); if(params.has('quick_edit') && $save_button.length) { $save_button.parent().append('<button type="submit" name="submit_save" value="Save" class="ui-button uk-hidden" id="save-and-close">Save + Close</button>'); } } if($body.hasClass('ProcessPageList') && !is_modal) { $(document).on('ajaxComplete', function() { $('.PageListActionEdit a:not([data-autoclose])').each(function() { $(this).attr('data-autoclose', '#save-and-close').attr('href', $(this).attr('href') + '&quick_edit=1'); }); }); } }); admin-custom.css /* Avoid Tracy debugbar appearing on top of modal */ .ui-widget-overlay.ui-front { z-index:40000; } .ui-dialog { z-index:40001; } /* Place modal buttons on the left rather than the right */ .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float:none; } The second step is to add the following hook to /site/ready.php One good thing about using a hook to AdminTheme::getExtraMarkup to add custom CSS is that the file gets loaded after any CSS files that are loaded by modules or the PW core, so you can override any CSS rules without needing to add extra specificity. // Add custom JS and CSS to admin $wire->addHookAfter('AdminTheme::getExtraMarkup', function(HookEvent $event) { $parts = $event->return; $config = $event->wire()->config; // JS file should exist at /site/templates/admin-assets/admin-custom.js $js_url = $config->urls->templates . 'admin-assets/admin-custom.js'; $modified = filemtime(rtrim($config->paths->root, '/') . $js_url); $parts['head'] .= "<script type='text/javascript' src='$js_url?m=$modified'></script>"; // CSS file should exist at /site/templates/admin-assets/admin-custom.css $css_url = $config->urls->templates . 'admin-assets/admin-custom.css'; $modified = filemtime(rtrim($config->paths->root, '/') . $css_url); $parts['head'] .= "<link rel='stylesheet' href='$css_url?m=$modified'>"; $event->return = $parts; }); The end result:
×
×
  • Create New...