Jump to content

Robin S

Members
  • Posts

    4,928
  • Joined

  • Days Won

    321

Everything posted by Robin S

  1. Unfortunately I don't think this module can be easily adapted to support multi-language menus, because to get the repeater-like interface for variable numbers of child items working I needed to come up with a custom way to store that data in JSON format rather than use the core way of storing individual inputfield data. So multi-language support would mean creating a new way of storing multi-language data that's separate from PW's way and I don't want to try to reinvent that wheel. But you could hook some of the same methods that the module does to add your own custom menus to the admin. Example: $wire->addHookBefore('ProcessController::execute', function(HookEvent $event) { // Prevent admin menus from being cached $this->wire()->session->removeFor('AdminThemeUikit', 'prnav'); $this->wire()->session->removeFor('AdminThemeUikit', 'sidenav'); }); $wire->addHookAfter('AdminThemeFramework::getPrimaryNavArray', function(HookEvent $event) { $items = $event->return; $user = $event->wire()->user; $data = [ [ 'label' => [ 'default' => 'Shirts', 'french' => 'Chemises', ], 'url' => [ 'default' => '/shirts/', 'french' => '/chemises/', ], 'children' => [ [ 'label' => [ 'default' => 'Small', 'french' => 'Petit', ], 'url' => [ 'default' => '/shirts/small/', 'french' => '/chemises/petit/', ], 'icon' => 'smile-o', ], [ 'label' => [ 'default' => 'Medium', 'french' => 'Moyen', ], 'url' => [ 'default' => '/shirts/medium/', 'french' => '/chemises/moyen/', ], 'icon' => 'thumbs-o-up', ], ], ], ]; foreach($data as $item) { $menu = [ 'id' => 0, 'parent_id' => 0, 'name' => '', 'title' => $item['label'][$user->language->name], 'url' => $item['url'][$user->language->name], 'icon' => '', 'children' => [], 'navJSON' => '', ]; foreach($item['children'] as $child) { $menu['children'][] = [ 'id' => 0, 'parent_id' => 0, 'name' => '', 'title' => $child['label'][$user->language->name], 'url' => $child['url'][$user->language->name], 'icon' => $child['icon'], 'children' => [], 'navJSON' => '', ]; } $items[] = $menu; } $event->return = $items; });
  2. @ryan, is there an update on this? It would be good to have PW use the current version of TinyMCE because v6 is now past the official End Of Life.
  3. @adrian, wonderful, thanks!
  4. Hi @adrian, Is it possible for an action to return markup apart from the successMessage and failureMessage? I'd like to use MarkupAdminDataTable to show a table of results of the action. Putting the markup in successMessage sort of works, except it has the green background so I figure it's intended for a simple sentence rather than longer markup like this. If the module doesn't already have a solution for this that I've missed, what do you think about adding a new method similar to successMessage(), or maybe if the return value of executeAction() is a string rather than boolean the module could render that under the success/failure message? Thanks for considering.
  5. To do this I think you'll have to hook the rendering of the repeater item, which is a fieldset: $wire->addHookBefore('InputfieldFieldset(name^=repeater_item)::render', function(HookEvent $event) { $inputfield = $event->object; $inputfield->label = $event->wire()->sanitizer->unentities($inputfield->label); });
  6. @mel47, I didn't follow everything in your post, but Dynamic Options doesn't rename your images or create any variations. Looking at your code, you want to avoid doing this: Because $par is a Pageimages object and every Pageimages object is bound to a particular page (Page A), so when you add images to it from a different page (Page B) then the image files will be copied from from Page B's folder in /site/assets/files/ to Page A's folder, and I doubt that's what you intend. I suggest you rewrite your code so that you only add the image information to the $options array. Something like: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { $page = $event->arguments(0); $field = $event->arguments(1); $pages = $event->wire()->pages; if($field->name === 'image_selection') { $options = []; foreach($pages(1065)->images as $image) { $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } foreach($pages(1042)->content_blocks->find("repeater_matrix_type=2") as $p) { $image = $p->images->last(); if(!$image) continue; $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } $event->return = $options; } });
  7. @Macrura, yeah, that's not wanted so in v0.3.6 I've excluded the inputfield mods when the process is ProcessPageListerPro.
  8. TinyMCE itself supports this via the style_formats setting, where you would define a "class" value rather than a "classes" value, e.g. style_formats: [ { title: 'Table row 1', selector: 'tr', class: 'tablerow1' } ] But PW's implementation of TinyMCE doesn't provide a way to directly control this setting and instead parses the contents of the "Custom style formats CSS" config textarea into the style_formats setting. Situations like this are why I think PW should provide a hookable method allowing the array of data that becomes the TinyMCE settings to be modified at runtime, as mentioned in this issue: https://github.com/processwire/processwire-issues/issues/1981
  9. You might be experiencing this issue: https://github.com/processwire/processwire-issues/issues/1952 https://github.com/processwire/processwire-issues/issues/1974 To fix you can update to the latest PW dev version so as to get this commit, or change this line of .htaccess to: RewriteRule ^(.*)$ index.php?it=$1 [L,QSA,UnsafeAllow3F]
  10. I needed to do this and thought the code might be useful for others too. In my case the organisation has a main site (Site A) and a related but separate site (Site B). The objective is for the users at Site B to be automatically kept in sync with the users of Site A via PW multi-instance. Users are only manually created or deleted in Site A. Both sites have the same roles configured. // InputfieldPassword::processInput $wire->addHookAfter('InputfieldPassword::processInput', function(HookEvent $event) { /** @var InputfieldPassword $inputfield */ $inputfield = $event->object; $input = $event->arguments(0); /** @var UserPage $page */ $page = $inputfield->hasPage; if($page->template != 'user') return; // Return early if there are any password errors if($inputfield->getErrors()) return; // Get the new password as cleartext from $input $pass = $input->get($inputfield->name); if(!$pass) return; // Set the password as a custom property on the Page object $page->newPass = $pass; }); // Pages::saved $pages->addHookAfter('saved', function(HookEvent $event) { /** @var UserPage $page */ $page = $event->arguments(0); if($page->template != 'user') return; if($page->isUnpublished()) return; // Update or create user in Site B $site_b = new ProcessWire('/home/siteb/siteb.domain.nz/', 'https://siteb.domain.nz/'); /** @var UserPage $u */ $u = $site_b->users->get($page->name); // Create a new user if none exists with this name if(!$u->id) $u = $site_b->users->add($page->name); // Set the password if the custom property was set in the InputfieldPassword::processInput hook if($page->newPass) $u->pass = $page->newPass; // Set email address $u->email = $page->email; // Set roles $u->roles->removeAll(); foreach($page->roles as $role) { $u->addRole($role->name); } $u->save(); }); // Pages::deleteReady $pages->addHookAfter('deleteReady', function(HookEvent $event) { /** @var Page $page */ $page = $event->arguments(0); if($page->template != 'user') return; // Delete user in Site B $site_b = new ProcessWire('/home/siteb/siteb.domain.nz/', 'https://siteb.domain.nz/'); $u = $site_b->users->get($page->name); if(!$u->id) return; $site_b->users->delete($u); }); This assumes the use of the default "user" template and not an alternative template. In my case the user template only has the default fields, but the code could be adapted if you have additional fields in your user template. This doesn't handle renaming of users as that's not something I have a need for. But there would be ways to achieve this too, e.g. store the user ID for Site B in a field on the user template in Site A, and then get the Site B user by ID rather than name.
  11. You can do something like this: $wire->addHookBefore('Inputfield::render', function(HookEvent $event) { /** @var Inputfield $inputfield */ $inputfield = $event->object; $process = $this->wire()->process; // Return early if this is not ProcessPageEdit if(!$process instanceof ProcessPageEdit) return; // The page being edited $page = $process->getPage(); // The field associated with the inputfield, if any // Useful for when the inputfield is in a repeater, as the inputfield name will have a varying suffix $field = $inputfield->hasField; // The page that the inputfield belongs to // Useful for identifying if the inputfield is in a repeater $inputfield_page = $inputfield->hasPage; // Return early if this is not a page we are targeting if($page->template != 'test_combo') return; // Do some check to identify the inputfield by name or field name if($field && $field->name === 'text_1' && $inputfield_page->template == 'repeater_test_repeater') { // Check some other field value if the message depends on it if($page->test_combo->my_date === '2024-10-18 00:00:00') { // Show an error message $inputfield->error('This is a test error message'); } } // Do some check to identify the inputfield by name or field name if($inputfield->name === 'test_combo_my_date') { // Check some other field value if the message depends on it if($page->test_repeater->first()->text_1 === 'hello') { // Show an error message $inputfield->error('Another test error message'); } } });
  12. @nurkka, when I test here DelayedImageVariations is sort of working with webp(), although I'm surprised it works at all because I thought it would be a case like the one mentioned in the readme: I thought the webp() method would need an actual resized variation to be available at the time it is called. But I'm testing like this... foreach($page->images as $image) { $image_url = $image->size(250,250)->webp()->url; bd($image_url, "image_url"); echo "<img src='$image_url' alt=''>"; } ...and what I experience is that on the first page load the webp()->url results in a JPG file... ...and on subsequent page loads it results in a WebP file. I don't get any 404s or broken images, and the only effect is slightly larger file sizes on the first page load, which isn't that big of a deal. But if you experience something different then I don't have a solution within DelayedImageVariations, sorry. My suggestion then would be to pursue a different strategy to pre-generate your image variations rather than use DelayedImageVariations. You could generate the variations on page save. See this post for how you could do it without slowing down page saves in the admin:
  13. Since PW 3.0.238 you can do this: $wire->addHookAfter('ProcessPageSearch::findReady', function(HookEvent $event) { /** @var ProcessPageSearch $pps */ $pps = $event->object; $selector = $event->return; $for_selector_name = $event->wire()->input->get('for_selector_name'); if($for_selector_name) { $data = $pps->getForSelector($for_selector_name, true); // $data includes the PageAutocomplete field name and the ID of the edited page // Do something with $selector... } });
  14. I think the key thing will be to make sure the selector string you've defined in the field settings does not exclude any pages that you might want to include in the modified selector coming from your findReady hook. In other words, any selector in the settings should be broader than the selector from the hook. This is because any pages set to the inputfield will be validated against the field settings on save. I would make the field settings very broad (e.g. just a template) and then narrow this in your hook.
  15. Not sure what you mean here. The ProcessWire admin for non-superusers is the most minimal CMS back-end I've seen. It's just the page tree, which is easily understood even by beginners because it corresponds to the front-end structure. And from the page tree editors can only edit the pages that have the templates you allow for them. If the page tree is still too overwhelming you customise it with hooks. If the editors only need to deal with pages in a particular branch of the tree you can conditionally set the top-level parent of the tree. $wire->addHookBefore('ProcessPageList::execute', function(HookEvent $event) { /** @var ProcessPageList $ppl */ $ppl = $event->object; if($event->wire()->page->process !== 'ProcessPageList') return; if($event->wire()->config->ajax) return; // If the user has the "editor" role... if($event->wire()->user->hasRole('editor')) { // Limit Page List to only show a particular page and its descendants $ppl->id = 1085; } }); Or for more complex situations you can control which pages are "listable" individually:
  16. My pleasure. The hooks and inputfields APIs in ProcessWire make module development an absolute dream. It's very satisfying seeing the results you can get with just a little code, and working on modules provides a motivation to dig into the core code so I learn a lot in the process. Also, another shout out to @adrian's amazing Tracy Debugger, which has been transformative for my coding. I have several more modules in progress so watch this space. 🙂
  17. Sorry, I made a typo in that version. Please upgrade to v0.3.4 and it should be working again.
  18. Thanks. That's an unknown. It's not worth the time of making a PR if Ryan doesn't want it as a PR. When it's a module I and everyone else can use it right away. When it's a PR then it's something that maybe becomes usable someday, maybe not...
  19. Inputfield Dependency Helper Adds "Insert field name" and "Insert value" dropdown menus to help with constructing show-if/required-if conditions, aka inputfield dependencies. The "Insert field name" menu helps you remember the field names that exist in your site (or exist in the current template) and avoids typos. The "Insert value" menu lets you select values for Page Reference or Select Options fields via the human-friendly label whereas the show-if/required-if conditions require those values to be inserted as numerical IDs. Insert field name When you click the button a dropdown menu appears listing field names, with the field labels in parentheses. When editing a field in template context only the fields that exist in the template are listed, and the field labels are in template context. When editing a field outside of template context all the non-system fields are listed. When you click an item in the list the field name is inserted into the settings field. Insert value When using a Page Reference or Select Options field value in a show-if/required-if condition you have to enter the numerical ID of the page/option, and this is not so user-friendly – often you have to switch to another tab and go and look up the relevant ID. The "Insert value" button is intended to make this process easier. When you click the button a dropdown menu appears listing any Page Reference and Select Options fields that exist in the current template (when editing in template context) or in the site. When you click one of the fields the selectable options for the field are AJAX-loaded into a flyout menu. Clicking one of the selectable options will insert the numerical ID of the option into the settings field. Configuration In the module config you can set a limit to the number of selectable options shown in the menu, so the menu doesn't get excessively long. https://github.com/Toutouwai/InputfieldDependencyHelper https://processwire.com/modules/inputfield-dependency-helper/
  20. You probably want to exclude by template for the "Link to URL" autocomplete field too. // ProcessPageEditLink > Link to URL: exclude pages by template $wire->addHookBefore('InputfieldPageAutocomplete(name=link_page_url)::render', function(HookEvent $event) { $inputfield = $event->object; $exclude_templates = ['basic_page', 'movies']; $inputfield->findPagesSelector .= ', template!=' . implode('|', $exclude_templates); }); // ProcessPageEditLink > Select Page / Select Child Page: exclude pages by template $wire->addHookAfter('ProcessPageEditLink::execute', function(HookEvent $event) { $exclude_templates = ['basic_page', 'movies']; $css = ''; foreach($exclude_templates as $template_name) { // Hide the page list item (and its children) $css .= ".PageListTemplate_$template_name, "; // Or just hide the "Choose page" button, if the child pages should remain selectable // $css .= ".PageListTemplate_$template_name .PageListActionSelect, "; } $css = rtrim($css, ', '); $css .= ' { display:none !important; }'; $event->return .= "<style>$css</style>"; });
  21. @mel47, my fault, there are two bundled modules and I forgot to update the version number of one of them. Plus there was a problem where GitHub didn't pick up the casing change of a subdirectory. If you upgrade to the newly released v0.3.2 it should be fixed.
  22. Please upgrade the module to the latest version and let me know if there is still a problem.
  23. @szabesz, this is the support thread for Logs JSON Viewer but I think you are talking about the Custom Logs module. Custom Logs is working for me here with a log named "my-log", so it would be good to find out why it's not working for you. Do you have a custom log named "my-log" defined in the module config? What do you see if you use Tracy to do this... bd($name, "name"); bd($custom_logs, "custom_logs"); ...just before this line.
  24. @joe_g, you might be interested in this recently released module:
  25. Search Corrections Suggests alternative words for a given input word. This can be useful in a website search feature where the given search term produces no results, but an alternative spelling or stem of the term may produce results. The module has two methods intended for public use: findSimilarWords(): this method suggests corrected spellings or similar alternatives for the given word based on words that exist in the website. stem(): this method returns the stem of the given word, which may give a full or partial match for a word within the website. The module doesn't dictate any particular way of using it in a website search feature, but one possible approach is as follows. If a search produces no matching pages you can take the search term (or if multiple terms, split and then loop over each term) and use the module methods to find alternative words and/or the stem word. Then automatically perform a new search using the alternative word(s), and show a notice to the user, e.g. Your search for "begining" produced no matches. Including results for "beginning" and "begin". findSimilarWords() This method creates a list of unique words (the "word list") that exist on the pages and fields that you define, and compares those words to a target word that you give it. The method returns an array of words that are sufficiently similar to the target word. For multi-language sites, the $user language determines which language populates the word list. Similarity The method ranks similar words by calculating the Levenshtein distance from the target word. Where several results have the same Levenshtein distance from the target word these are ordered so that results which have more letters in common with the target word at the start of the result word are higher in the order. Method arguments $target (string) The input word. $selector (string) A selector string to find the pages that the word list will be derived from. $fields (array) An array of field names that the word list will be derived from. $options (array) Optional: an array of options as described below. minWordLength (int) Words below this length will not be included in the word list. Default: 4 lengthRange (int) Words that are longer or shorter than the target word by more than this number will not be included in the word list. Default: 2 expire (int) The word list is cached for this number of seconds, to improve performance. Default: 3600 maxChangePercent (int) When the Levenshtein distance between a word and the target word is calculated, the distance is then converted into a percentage of changed letters relative to the target word. Words that have a higher percentage change than this value are not included in the results. Default: 50 insertionCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 replacementCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 deletionCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 Example of use // The input word that may need correcting $target = 'dispraxia'; // Get the Search Corrections module $sc = $modules->get('SearchCorrections'); // Define a selector string to find the pages that the word list will be derived from $selector = "template=basic-page"; // Define an array of field names that the word list will be derived from $flds = ['title', 'body']; // Optional: override any of the default options $options = ['maxChangePercent' => 55]; // Get an array of similar words that exist in the pages/fields you defined // The return value is in the format $word => $levenshtein_distance $results = $sc->findSimilarWords($target, $selector, $flds, $options); Example result: stem() This method uses php-stemmer to return the stem of the given word. As an example, "fish" is the stem of "fishing", "fished", and "fisher". The returned stem may be the original given word in some cases. The stem is not necessarily a complete word, e.g. the stem of "argued" is "argu". If using the stem in a search you will probably want to use a selector operator that can match partial words. Method arguments $word (string) The input word. $language (string) Optional: the language name in English. The valid options are shown below. Default: english catalan danish dutch english finnish french german italian norwegian portuguese romanian russian spanish swedish Alternatively, you can use the ISO 639 language code for any of the above languages. Example of use // The input word $word = 'fishing'; // Get the Search Corrections module $sc = $modules->get('SearchCorrections'); // Get the stem of the word $stem = $sc->stem($word); https://github.com/Toutouwai/SearchCorrections https://processwire.com/modules/search-corrections/
×
×
  • Create New...