Jump to content

Robin S

  • Posts

  • Joined

  • Last visited

  • Days Won


Robin S last won the day on July 26

Robin S had the most liked content!

Profile Information

  • Gender
  • 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)




Community Answers

  1. @hollyvalero, after these posts were written a powerful truncate feature was added to the core as a sanitizer method. https://processwire.com/api/ref/sanitizer/truncate/ https://processwire.com/blog/posts/processwire-3.0.101-core-updates/
  2. FieldtypeDecimal uses InputfieldFloat as its inputfield, and it's the renderValue() method that determines what is shown when a field is not editable. So you could do this: $wire->addHookAfter('InputfieldFloat::renderValue', function(HookEvent $event) { /** @var InputfieldFloat $inputfield */ $inputfield = $event->object; $field = $inputfield->hasField; if($field && $field->name === 'your_field_name') { $event->return = '$' . number_format((float) $inputfield->value, 2); } });
  3. Welcome to the PW forums @virgomania! I don't believe that's possible. The PW core philosophy is to have images and files connected to the pages they are uploaded to and not in one central repository. In the past Ryan (lead developer of PW) has discussed why he favours this approach. Here's one old topic... ...and maybe others will chime in with links to more background on this subject. So a few suggestions: 1. Consider if you absolutely must have a central image/file repository. Maybe you could try the PW-way and you might find it works pretty well for 99% of cases. I think a lot of people new to PW first thought they needed a media library and then gradually learned that they didn't. 🙂 2. If your main use case is linking/inserting files or images into rich text (CKEditor) fields then be aware that you can link/insert images from any page, not only the current page. Some people even create one or more dedicated "library" pages which hold a lot of files/images that they use on other pages. The Media Library module can help with this. 3. If your main use case is dedicated fields which will hold files/images then have a look at the commercial Media Manager module. I think this module also allows you to link/insert the media in CKEditor fields. 4. You mentioned the Select Images inputfield (used together with Dynamic Options I assume). Because you supply the selectable images via a hook you can make images from multiple different pages selectable. Here's an example hook that would make images selectable from any image field on any page using the basic_page or home template: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // For each page that uses the basic_page or home template foreach($event->wire()->pages->find("template=basic_page|home") as $p) { // Get the FieldtypeImage fields that are in the page's template $image_fields = $p->fields->find("type=FieldtypeImage"); // For each image field foreach($image_fields as $field) { // Get the images stored on the page in that field $images = $p->getUnformatted($field->name); // For each image foreach($images as $image) { // Add a selectable option $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } } } $event->return = $options; } }); But be aware that this won't scale infinitely. At some point the number of selectable images might be unmanageable and you may strike performance issues in the page editor. I think Media Manager might be more scalable in this respect.
  4. @rsi you can use $pages->sort() to set the sort value of the new page to zero after it is added, and that will make it appear at the top of its siblings but still be manually sortable. In /site/ready.php: $pages->addHookAfter('added', function(HookEvent $event) { $pages = $event->object; // The page that is being added $page = $event->arguments(0); // If the page passes some kind of test if($page->template == 'news_item') { // Set the sort value for the new page to zero (sibling sort will be automatically adjusted) $pages->sort($page, 0); } });
  5. If you just want to do this from time-to-time you can add a filter row like this: Or if you want the default limit to always be 50 (or whatever limit you want) then you can add a hook in /site/ready.php: $wire->addHookBefore('ProcessPageLister::execute', function(HookEvent $event) { /** @var ProcessPageLister $lister */ $lister = $event->object; // Only for the "Find" lister if($event->wire()->page->name === 'lister') { $lister->defaultLimit = 50; } });
  6. In most cases I expect people to use the Select Image inputfield in conjunction with the Dynamic Options fieldtype. And regarding the value of a Dynamic Options field: The Select Images input type is a bit of a special case because it can be a "multiple" or a "single" input type: And beyond that there is a config option for Dynamic Options that can be used if the values that are saved are paths or URLs to Pagefiles or Pageimages, and this could be a good option when you're using Select Images as the inputfield: When that option is enabled you could use the Pageimage sizing methods on the field value. Note that for multiple images the value is an array of Pageimage objects and not a Pageimages object. That's because these modules are intended to be flexible enough to allow selection of images from more than one PW page but a Pageimages object only supports Pageimage objects associated with a single page (see the class constructor). The readme for Select Images says: So you can have the value for each option (thumbnail) be any string you like and output that in a template file or use it in a conditional to affect the page markup in some way. But I think my most common use case will be as an "image reference" field to select URLs to Pageimages and have the formatted value be a Pageimage or array of Pageimage objects. People have suggested different use cases for an image reference field in the GitHub request linked to at the start of the readme: https://github.com/processwire/processwire-requests/issues/207 Personally I have used image references in these scenarios: To allow an editor to select a social media "share" image from among all the uploads to Images fields on a page. In cases where an editor may only select from a collection of existing images and is not allowed to upload new images. Similar to the previous example, but where the editor needs to choose images that have been prepared in advance to fit in spaces with particular aspect ratios. So for a landscape space they can only select landscape images, square space can only select square images, etc. The allowed options for a Dynamic Options field are determined at runtime according to the FieldtypeDynamicOptions::getSelectableOptions hook you are using. These allowed options are used to validate any stored value when it is accessed. So if an image that was referenced is deleted then it will no longer be in the value you get from $page->your_dynamic_options_field
  7. v0.1.3 released. This version adds options to set a limit to the number of items that may be selected. When the limit is reached the supported core "multiple" inputfields become disabled. It adds an option for the formatted value to be Pagefile/Pageimage object(s) where that would be relevant. And it integrates with the newly released Select Images module to create a kind of "image reference" field. See the updated readme for more detail.
  8. Select Images An inputfield that allows the visual selection and sorting of images, intended for use with the FieldtypeDynamicOptions module. Together these modules can be used to create a kind of "image reference" field. Integration with FieldtypeDynamicOptions InputfieldSelectImages was developed to be used together with FieldtypeDynamicOptions (v0.1.3 or newer): Create a Dynamic Options field. Choose "Select Images" as the "Inputfield type". Select Images appears in the "Multiple item selection" category but you can set "Maximum number of items" to 1 if you want to use Select Images for single image selections. Define selectable options for the field via a FieldtypeDynamicOptions::getSelectableOptions hook. See some examples below. FieldtypeDynamicOptions is recommended but is not a strict requirement for installing InputfieldSelectImages in case you want to use an alternative way to store the field data. Selection of Pageimages In this example the field allows selection of Pageimages that are in the "images" field of the home page. The field will store URLs to the Pageimages so it works as a kind of "image reference" field. You can use the "Format as Pagefile/Pageimage object(s)" option for the Dynamic Options field to have the formatted value of the field be automatically converted from the stored Pageimage URLs to Pageimage objects. $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get Pageimages within the "images" field on the home page foreach($event->wire()->pages(1)->images as $image) { // Add an option for each Pageimage // When the key is a Pageimage URL the inputfield will automatically create a thumbnail // In this example the label includes the basename and the filesize /** @var Pageimage $image */ $options[$image->url] = "{$image->basename}<br>{$image->filesizeStr}"; } $event->return = $options; } }); Selection of image files not associated with a Page When not working with Pageimages you must add a "data-thumb" attribute for each selectable option which contains a URL to a thumbnail/image. In this example the field allows selection of image files in a "/pics/" folder which is in the site root. $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "select_images" if($field->name === 'select_images') { $options = []; // Get files that are in the /pics/ folder $root = $event->wire()->config->paths->root; $path = $root . 'pics/'; $files = $event->wire()->files->find($path); // Add an option for each file foreach($files as $file) { $basename = str_replace($path, '', $file); $url = str_replace($root, '/', $file); // The value must be an array with the following structure... $options[$url] = [ // The label for the image 'label' => $basename, 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => $url, ], ]; } $event->return = $options; } }); The field values don't have to be image URLs The values stored by the Dynamic Options field don't have to be image URLs. For example, you could use the images to represent different layout options for a page, or to represent widgets that will be inserted on the page. Also, you can use external URLs for the thumbnails. In the example below the options "calm" and "crazy" are represented by thumbnails from placecage.com. $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // For a field named "calm_or_crazy" if($field->name === 'calm_or_crazy') { $options = []; // Add options that are illustrated with thumbnails from placecage.com $options['calm'] = [ // The label for the option 'label' => 'Nicolas Cage is a calm man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/260/260', ] ]; $options['crazy'] = [ // The label for the option 'label' => 'Nicolas Cage is a crazy man', 'attributes' => [ // An image URL in the "data-thumb" attribute 'data-thumb' => 'https://www.placecage.com/c/260/260', ] ]; $event->return = $options; } }); Field configuration You can define labels for the button, notices, etc, that are used within the inputfield if the defaults don't suit. https://github.com/Toutouwai/InputfieldSelectImages https://processwire.com/modules/inputfield-select-images/
  9. This isn't caused by ProcessWire; it's just the default behaviour of browsers. The same as if you followed a link to a JPG file the content would be shown in the browser rather than downloaded. The simplest way to have any link trigger a download is to add the "download" attribute to the <a> tag: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-download If you were constructing the link markup in PHP by looping over the files in a Files field it would be easy to add that download attribute to the link. But it sounds like you are creating the links in CKEditor and it's a bit more difficult there because both ACF and HTML Purifier will strip out the download attribute by default. Rather than messing around trying to configure ACF and HTML Purifier to allow the download attribute I would simply add the attribute on the front-end with a bit of JavaScript. Here's how you could automatically add the download attribute to any MP3 link using jQuery: $(document).ready(function() { // Add "download" attribute to MP3 links $('a[href$=".mp3"]').attr('download', ''); });
  10. It's working for me. I'm using PHP 7.3 so if you are using a newer version then it could be that the included Parsedown library needs an update. If you trace it back to being related to PHP version then please open a GitHub issue because TextformatterMarkdownExtra is a core module.
  11. I can't reproduce that here in v3.0.181 My guess would be it relates to custom hooks or some third-party module but if you can reproduce it on a clean PW installation then it would be good to create a GitHub issue for it.
  12. @Cybermano, I don't understand what you are doing with this line inside the foreach: $boat->barca_results->sort('id', $race); This doesn't seem to correspond to the correct use of either $wirearray->sort() or $pages->sort() so maybe you are getting mixed up with the methods? To go back to brass tacks (you may already know a lot of this but it might be useful for others)... The first meaning of "sort" as it relates to pages is the sort value. This is a column in the "pages" database table. Every page (including Repeater pages) has a sort value and this determines its sort position relative to its sibling pages (assuming no automatic sort has been set on the parent page or template). The sort value starts at zero, and the lower the number is the closer to the top of the sort order the page will be relative to its siblings. So in this page structure... The sort value of "Red" will be 0, the sort value of "Green" will be 1, and so on. In some circumstances you can end up with gaps in the sort values as pages are deleted or moved and that is where the $pages->sort() method can be used to "rebuild" those sort values under a parent. But in 99% of cases you don't need to worry about sort values because PW just takes care of it and you don't have to use $pages->sort() to sort your Repeater items so we can ignore that. You can set the sort value of a page the same as you would an integer field in the page's template: $page->of(false); $page->sort = 123; $page->save(); The second meaning of "sort" is the WireArray::sort() method. When it comes to pages, a PageArray is a kind of WireArray, and a RepeaterPageArray is a kind of PageArray, so this means we can use WireArray::sort() to sort a RepeaterPageArray. The method works by sorting the WireArray by one or more properties of its items. In the case of a RepeaterPageArray this could be something like the "modified" timestamp of the items or it could be a field such as "title". So you could do something like this to sort a field named "test_repeater" alphabetically by title (assuming that the Title field was used in the Repeater): $page->test_repeater->sort('title'); And you can get more advanced by adding a temporary custom property to the items in a WireArray/PageArray/RepeaterPageArray and then using WireArray::sort() on that temporary property. I'll show an example of that in a moment. If you have a page $p that contains a Repeater field "test_repeater" then you can sort the RepeaterPageArray as shown above in your template file and when you output the items in $p->test_repeater they'll be in the sort order you want. But if you wanted to save $p after you sorted test_repeater then it's not enough to just have the items in test_repeater in the right order and then save $p. And that's because PW uses the sort value of each Repeater page to determine the sorting of the Repeater field when it loads it from the database. So to make the sort order stick we need to save the sort value of each Repeater page in the RepeaterPageArray, and to do that we can use an incrementing counter that starts at zero. A couple of code examples... Sort test_repeater by title and save: // Get the page containing the Repeater items $p = $pages(1066); // Turn off output formatting for the page because we are going to save it later $p->of(false); // Sort the Repeater items alphabetically by title $p->test_repeater->sort('title'); // $i is a counter we will use to set the sort value of each Repeater item/page $i = 0; foreach($p->test_repeater as $repeater_item) { $repeater_item->of(false); $repeater_item->sort = $i; $repeater_item->save(); $i++; // Increment the counter } // Save the test_repeater field for $p $p->save('test_repeater'); More advanced sorting using a temporary property: // Get the page containing the Repeater items $p = $pages(1066); // Turn off output formatting for the page because we are going to save it later $p->of(false); // More advanced sorting: sorting by title length // We add a temporary "custom_sort" property to each Repeater item // And then sort the RepeaterPageArray by that custom_sort property foreach($p->test_repeater as $repeater_item) { $repeater_item->custom_sort = strlen($repeater_item->title); } $p->test_repeater->sort('custom_sort'); // $i is a counter we will use to set the sort value of each Repeater item/page $i = 0; foreach($p->test_repeater as $repeater_item) { $repeater_item->of(false); $repeater_item->sort = $i; $repeater_item->save(); $i++; // Increment the counter } // Save the test_repeater field for $p $p->save('test_repeater'); Hope this helps.
  13. 10 templates is definitely not going to be a problem. There's no particular number of templates that you should never go above, but I've asked Ryan about this topic in a Pro module forum and he said: ...and in reply to my proposal of a strategy to reduce the number of templates needed in a project... On that particular project I currently have 51 templates and there are no noticeable performance issues.
  14. You can achieve this with hooks to Page::addable and Page::editable. 1. Define access controls for the news-region and news-article templates so that users with the news-editor role can add children under news-region pages and they can edit and create news-article pages. 2. Create a news_regions Page Reference field which allows pages with template news-region. Best to make it a "multiple" AsmSelect field so it covers the potential case that a user needs to add/edit within multiple regions. Add this field to the user template. 3. Edit the news-editor users and set the regions they are allowed to add/edit within. 4. In /site/ready.php: $wire->addHookAfter('Page::addable', function(HookEvent $event) { /** @var Page $page */ $page = $event->object; $user = $event->wire()->user; // Return early if PW has already determined that the user is not allowed to add if(!$event->return) return; if($user->hasRole('news-editor') && $page->template == 'news-region') { // Addability depends on if the news_regions Page Reference field has the page $event->return = $user->news_regions->has($page); } }); $wire->addHookAfter('Page::editable', function(HookEvent $event) { /** @var Page $page */ $page = $event->object; $user = $event->wire()->user; // Return early if PW has already determined that the user is not allowed to edit if(!$event->return) return; if($user->hasRole('news-editor') && $page->template == 'news-article') { // Editability depends on if the news_regions Page Reference field has the parent page $event->return = $user->news_regions->has($page->parent); } }); The result for user "ringo": You could add all the regions to that user's page, but personally I would create a different news-supereditor role and just not make them subject to the restrictions in the hooks.
  15. No, it does prevent a bunch of downstream hooks from firing, but specifically it doesn't include Pages::save and Pages::saveField (and I think it should include those methods). And to avoid confusion it would be good if the documentation explained exactly which hookable methods are affected by the noHooks option when it used in any of Page:save(), Pages::save() and Pages::saveField(). I've opened a GitHub issue so that Ryan is more likely to notice the problem and our discussion of it: https://github.com/processwire/processwire-issues/issues/1405
  • Create New...