Jump to content


  • Posts

  • Joined

  • Last visited

Everything posted by ZGD

  1. I need to return many image descriptions—one for each language—simultaneously. I'm currently using the following: <?php $descriptions = []; foreach(\Processwire\wire('languages') as $language) { // Returns only primary language formatted $desc = $page->imagefield->description($language); $descriptions[$language->language_code] = $description; } echo json_encode($descriptions); This kind of works, but when using a textformatter for my imagefield field the formatter seems to apply only to the default language. I'm using a parsedown formatter, so for example two descriptions for English and German: [A Hyperlink](https://www.google.com/) [Ein Hyperlink](https://www.google.com/) Will return... <a href="https://www.google.com/">A Hyperlink</a> [Ein Hyperlink](https://www.google.com/) Does anyone know a way around this?
  2. Thanks @abdus, I should have mentioned I'm searching pages not templates here. So I don't think using $result->sort('label') on the results PageArray would work for this situation?
  3. I usually stick to a convention for my template names so each is a single word. However the template label may be more descriptive, for example I have a template called post with the label Blog Post. I am implementing a search function and want to group results into types, so all Blog Posts appear together. Using the selector sort=template works for grouping in order by template name, but I would like the groups to appear alphabetically by label. TL;DR: Is there a way I can use selectors to sort by template label, something like sort=template.label?
  4. Replying to myself here in case anyone else was looking for a similar migration. This seems to work for setting up repeater fields via a single migration file. Not sure if the method I've used for generating the repeater template is sensible but it will do for now: <?php class Migration_2017_10_01_10_10_10 extends FieldMigration { public static $description = "<b>+FIELD</b> Custom Matrix (custom_matrix)"; protected function getFieldName(){ return 'custom_matrix'; } protected function getFieldType(){ return 'FieldtypeRepeaterMatrix'; } protected function getMatrixTemplateName() { return FieldtypeRepeater::templateNamePrefix . $this->getFieldName(); } protected function getMatrixTypes() { return [ [ 'name' => 'foo', 'label' => 'Foo', 'head' => '{matrix_label}', 'fields' => [ 'title' => [ 'label' => 'Title (Optional)', 'required' => 0, ], 'markdown', ], ], [ 'name' => 'bar', 'label' => 'Bar', 'head' => '{matrix_label}', 'fields' => [ 'title' => [ 'label' => 'Title (Required)', 'required' => 0, ], 'image', ], ], ]; } protected function addFieldToRepeaterList($matrixField, $field) { $repeaterFields = $matrixField->repeaterFields; if (!is_array($repeaterFields)) $repeaterFields = []; if (!in_array($field->id, $repeaterFields)) { $repeaterFields[] = $field->id; } $matrixField->repeaterFields = $repeaterFields; } protected function addMatrixType($matrixField, $index, $config) { $templateName = $this->getMatrixTemplateName(); $prefix = 'matrix' . ($index + 1) . '_'; $matrixField[$prefix.'name'] = $config['name']; $matrixField[$prefix.'label'] = $config['label']; $matrixField[$prefix.'head'] = $config['head']; $matrixField[$prefix.'sort'] = $index; $fields = []; foreach ($config['fields'] as $key => $property) { $field = (!is_numeric($key)) ? $this->fields->get($key) : $this->fields->get($property); $fields[] = $field->id; $this->addFieldToRepeaterList($matrixField, $field); $this->insertIntoTemplate($templateName, $field); if (!is_numeric($key)) { $this->editInTemplateContext($templateName, $field, function(Field $f) use ($property) { foreach ($property as $prop => $val) { $f->$prop = $val; } }); } } $matrixField[$prefix.'fields'] = $fields; } protected function fieldSetup(Field $f){ $f->label = 'Custom Matrix'; $f->tags = 'matrix'; $f->required = 1; $f->icon = 'fa-bars'; // Do this to generate a template file for the repeater // @TODO Find a better way to do this $fieldtype = $f->type; $numOldReady = $fieldtype->countOldReadyPages($f); // Setup matrix stuff $matrixTypes = $this->getMatrixTypes(); foreach ($matrixTypes as $i => $matrixData) { $this->addMatrixType($f, $i, $matrixData); } // Insert into template $template = 'my-template'; $this->insertIntoTemplate($template, $f); } }
  5. I'm making extensive use of migrations for a project and had a question regarding writing migrations for matrix fields. When creating a new FieldtypeRepeaterMatrix field, I usually need to create two migrations. The first actually creates the blank matrix field, and then the second populates it with types/fields. This is because the template associated with the matrix is not actually created when the initial save() method is called when creating the field. Does anyone know if it is possible to force PW to create this template via the API? Wanted to also say how useful this module is. Would love to see migrations built into the core one day as it's totally changed how we use PW. I've been thinking about submitting a pull request with some new migration classes I've been building, would be great to have some reusable templates for complex situations like this. EDIT: I just realized I could probably just create the template as I would any other template on the initial field setup. But if there is an alternative save() method that would let PW handle the template creation that would still seem easier!
  6. Just noticed that FieldtypeFieldsetPage doesn't seem to be compatible with RepeaterMatrix, has anyone tried using either of the new FieldtypeFieldset modules within a RepeaterMatrix yet? EDIT I'd created the FieldtypeFieldsetPage fields using the API in a migration file, seems that re-saving them via the admin GUI solved the problem.
  7. I'm currently working on quite a large project and want to take advantage of some of the new features that have been introduced to the dev branch over the last couple of weeks, namely FieldsetPage and FieldsetGroup. We're using a large number of matrix repeaters with slight variations, and taking advantage of these new features would help us a great deal. The project is about a month away from launch, and unfortunately I know that the client will take issue with using the development build when we move to production. So my question is, at what point is the development branch generally considered mature enough to merge with the master? Is there a release schedule as part of the roadmap or is this more up to Ryan's discretion? Totally understand if it is the latter, just wanted to get some more information so we can weigh up our options over the next few weeks.
  8. Not sure if this has been discussed before, but is it possible to use a different method for storing image variations with PW? For example, would it be possible that instead of storing variations like this: /image.jpg /image.2000x0.jpg /image.0x900.jpg Variations were instead stored like this: /image.jpg /2000x0/image.jpg /0x900/image.jpg Normally the default method is fine, but we are working on a project where the image filenames may need to remain unchanged, but variations still produced. If anyone has any information about this it would be much appreciated! EDIT: Having looked at the size method in the Pageimage class, it looks like this isn't something that could be changed easily.
  9. Figured out a slight workaround today using various hooks, it's probably not ideal but seems to work OK. It adds two new fields to every template, the first gives the option to hide view links from pages using that template, the second adds the ability to edit the URL. The following is in my ready.php file: <?php // Helper function to rewrite URLs with dynamic properties // For example {name} will be replaced with the page name property function rewriteURL($page, $url) { $appUrl = wire('pages')->get(1)->link; // Base URL set in home template $regex = '/(?:{([^}]+?)})|(.+?(?={[^}]+?}|$))/'; $out = ''; preg_match_all($regex, $url, $matches, PREG_SET_ORDER, 0); foreach($matches as $match) { if ($match[1]) $out .= $page[$match[1]]; else if ($match[2]) $out .= $match[2]; } return $appUrl.$out; // e.g. http://www.example.com/some-path } // Rewrite page list view URLs $wire->addHook('ProcessPageListActions::getActions', function($event) { $page = $event->arguments(0); $template = $page->template; if ($template->hideViewLink) { $actions = $event->return; unset($actions['view']); $event->return = $actions; } elseif ($template->rewriteViewURL) { $actions = $event->return; if (array_key_exists('view', $actions)) { $actions['view'] = array( 'cn' => $actions['view']['cn'], 'name' => $actions['view']['name'], 'url' => rewriteURL($page, $template->rewriteViewURL), ); $event->return = $actions; } } }); // Rewrite page edit view URLs $wire->addHookBefore('ProcessPageEdit::buildFormView', function($event) { $page = $event->object->getPage(); $template = $page->template; if ($template->rewriteViewURL) { $updatedURL = rewriteURL($page, $template->rewriteViewURL); $event->arguments = [ $updatedURL ]; } }); // Edits the edit page 'View' tab if required // Removes dropdown items $wire->addHookAfter('ProcessPageEdit::getTabs', function($event) { $template = $event->object->getPage()->template; $tabs = $event->return; $editTab = array_key_exists('ProcessPageEditView', $tabs); if ($editTab) { if ($template->hideViewLink) { $tabs['ProcessPageEditView'] = false; } else { $tabs['ProcessPageEditView'] = preg_replace('/<span.*(?=<\/a>)/', '', $tabs['ProcessPageEditView']); } $event->return = $tabs; } }); // Adds extra fields to templates $wire->addHookAfter("ProcessTemplate::buildEditForm", function(HookEvent $event) { $template = $event->arguments[0]; $form = $event->return; $f = $this->modules->get('InputfieldText'); $f->attr('id+name', 'rewriteViewURL'); $f->value = $template->rewriteViewURL; $f->label = $this->_('Rewrite View URL'); $f->description = $this->_('You can access page properties by wrapping them in curly braces.'); $f->notes = $this->_('e.g. {name} will be replaced with the page name'); $form->insertAfter($f, $form->templateLabel); $f = $this->modules->get('InputfieldRadios'); $f->attr('id+name', 'hideViewLink'); $f->addOption(1, 'Yes'); $f->addOption(NULL, 'No'); $f->value = $template->hideViewLink; $f->label = $this->_('Hide View Link'); $f->description = $this->_('If this template should hide view links.'); $form->insertAfter($f, $form->templateLabel); $event->return = $form; }); // Save the extra template fields $wire->addHookBefore("ProcessTemplate::executeSave", function() { $template = $this->templates->get($this->input->post->id); $template->set('hideViewLink', $this->input->post->hideViewLink); $template->set('rewriteViewURL', $this->input->post->rewriteViewURL); }); Also need some custom CSS rules to hide the view link in the dropdown Save button. .pw-button-dropdown .ui-menu-item { display: block } .pw-button-dropdown .ui-menu-item a[data-pw-dropdown-value="view"] { display: none; } Would still be interested in a cleaner solution though!
  10. Bumping this to see if anyone else found a viable solution. We need to be able to redirect users to an app served by a different server as the PW instance just outputs JSON data. Seems like there is no hook available to simply alter the view URL. Would be super useful.
  11. Basically yes, I'm trying to access an unpublished page via an asynchronous request from another server, so it's effectively the same as trying to access it from a browser. My JS app running on the node.js server makes an AJAX request to the ProcessWire specific URL (an unpublished page), but as that session is not authenticated, PW responds with 404.
  12. This is similar to what I'm looking for but I don't think it would work in this case. I would essentially do... $page = $pages->get($page); ... in my template. But if I understand correctly ProcessWire will redirect to the 404 page so I can't actually run this code in my template. I guess I would need to somehow hook into the page render method and prevent this 404 redirect?
  13. TL;DR: Is it possible to render unpublished pages even if the request comes from an unauthenticated user? I'm currently building a website that consists of a two parts: A front-end 'app' built using Vue.js served by a node.js/express server (necessary for server-side rendering). A ProcessWire back-end that serves up a JSON API, this is running on an Apache server. I've added hooks so the "View" page links/tabs in ProcessWire direct to the front-end app's URLs, rather than just showing a page of the raw JSON data exposed by ProcessWire. So for example if my PW page lives at https://api.example.com/pages/foo, clicking View directs to a URL pointing to the front-end app like https://example.com/foo. The problem I now have is that I need to be able to view unpublished pages. As sessions are not shared between ProcessWire and the front-end as they run on different servers, this means if I redirect to the app from PW as above, the app then makes a request to PW and obviously returns a 404. PW rightly sees the request from the app as coming from an unauthenticated user. My proposed solution is that I edit the page preview hook to include a query in the URL like so https://example.com/foo?session=a1b2c3d4e5. The front-end server then passes this query string to any requests it makes to the PW API (e.g. https://api.example.com/pages/foo?session=a1b2c3d4e5), which in turn checks it against a list of valid session variables (even better if I could somehow check this against a session_id of logged in users). If the query was valid, then PW should render the page even if it is unpublished. Is this possible?
  14. Managed to resolve this, code below for any that's interested. My translations class: <?php namespace Translations; class Translation { private function __construct() {} protected static function translations() { return []; } private static function initialize() { $languages = \ProcessWire\wire('languages'); $user = \ProcessWire\wire('user'); $translated = []; foreach($languages as $lang) { $user->language = $lang; foreach(static::translations() as $k => $t) { $translated[$k][$lang->language_code] = $t; } } return $translated; } public static function translate() { return static::initialize(); } } include_once __DIR__ . '/greetings.php'; include_once __DIR__ . '/questions.php'; Now I can create as many different translation files as I like, for example greetings.php <?php namespace Translations; use function ProcessWire\__ as __; class Greetings extends Translation { protected static function translations() { return [ 'hello' => __('Hello'), // A casual greeting message 'goodbye' => __('GoodBye'), // A casual departing message ]; } } and questions.php <?php namespace Translations; use function ProcessWire\__ as __; class Questions extends Translation { protected static function translations() { return [ 'howareyou' => __('How are You?'), 'howmuch' => __('How Much?'), ]; } } So in my template I can now write: <?php use Translations\Greetings; use Translations\Questions; $greetings = Greetings::translate(); $questions = Questions::translate(); // ... output here
  15. I'm using PW to build an API which returns data in multiple languages at once. For example I might have the words "Hello" and "Goodbye" that need to be translated into several languages. The API (which returns data as JSON) needs to return something like the following... { "hello": { "de": "Hallo", "en": "Hello", "fr": "Bonjour", "it": "Ciao", "tc": "你好" }, "goodbye": { "de": "Auf Wiedersehen", "en": "Goodbye", "fr": "Au Revoir", "it": "Addio", "tc": "再見" } } Ideally I'd like to be able to use PW's template string translating feature, as there will be potentially hundreds of different translations and creating many fields seems the wrong way to go. Each translation will also need to have a note or description alongside it. I was originally thinking something like the example below may be possible, but it seems unlikely. <?php function _t($translation) { // logic to return all translations here... return $translation; } $translations = [ 'hello' => _t(__('Hello')), // A casual greeting message 'goodbye' => _t(__('Goodbye')) // A casual departing message ]; echo json_encode($translations); Can anyone suggest a good method for returning all translations of a translatable string? Is it even possible to access multiple translations of static strings within a template? Alternatively, if anyone has solved a similar problem before, I'm open to suggestions. Thanks!
  16. I'm also seeing this, did you find a workaround? EDIT: Prepended the ProcessWire namespace to the wirePopulateStringTags method which seems to have fixed the problem for now.
  17. @kongondo Do you know if this is something that is being built already? I'd like to contribute if possible.
  18. Thanks @LostKobrakai. That all makes sense. I'd estimate less than 5% of images will have alternatives, so the manual uploading for those would be manageable. Being able to drag and drop multiple images at once is really the priority, but I had a better look through all the core modules earlier and decided the development time to create something custom probably wasn't worth it for this project. I looks like we will have to go with a repeater or pagetable approach as you've suggested.
  19. Nope not quite, sorry I probably wasn't very clear there. Drag-and-drop does indeed work inside repeaters, my problem is that if I need to separate each image and its associated crop in the image grid into a separate repeater with an image field then I can't drag-and-drop say 20 images at once. I have to manually drag each image to the associated field in the repeater array. An ideal situation would be the ability to store a secondary image with each image. For example each image has an input for alt text, I would somehow like there to be another input for another image. Essentially nested images. CroppableImage3 is great but it also won't work for this as the images are prepared outside of PW as they have various other applications. The crops also aren't systematic and we also need to support the option for the crop being a completely different image entirely.
  20. I'm currently developing a site where some image fields need to be able to have two images associated with them, a thumbnail and a full sized image (which is often a different crop) which will be shown in a lightbox view. This is fine for most templates, I'm just using normal image fields that return an array and support up to two images. If a second image is present in the field then it will be used as the alternative image for the lightbox. If only one image is present, it will be used for both thumbnail and full sized (the template resizes for the thumbnail using the API). However I have templates with flexible layouts using a repeater matrix, and one of the matrix types is an 'image grid'. This is an image field that supports an unlimited number of images, so obviously this method is not suitable here. I've tried creating a repeater in this matrix type which contains an image field (each supporting two images, as above), but this means that I can no longer drag and drop multiple images at once. Each image in the grid has to be uploaded individually which is a big problem as there are many instances of these grids with a large number of images. I've tried the ImageExtra module but it seems you can't add an image field as a secondary field. Any suggestions as to how I might solve this problem?
  21. I've looked through the forum and various issues on Github and can't seem to find a definitive answer for this. It seems some Lazycron hooks have stopped working reliably on one of my PW installations. The function which adds the hooks is triggered successfully every minute and logs a message (I've tested the cronjob and it is working correctly), however it seems the hook itself is being very temperamental. This line in the module's init method should add a hook for every minute: $this->addHook("LazyCron::everyMinute", $this, 'myFunction'); The first line of myFunction logs a string whenever it is executed, before doing anything else. I've been watching the module log all day and this function seems to execute each minute up to around 5 times, and then stops executing. I've been monitoring the site's cache folder, and sometimes deleting the LazyCron.cache file resolves the problem temporarily, other times it does not. Sometimes if I leave everything alone for a few hours, myFunction starts executing again. Unlike other people who have reported similar issues, I don't have have a LazyCronLock.cache file that is causing problems. I can't work this one out, has anyone encountered something similar?
  22. Ah I understand now, I hadn't realized single quotes were used for literal strings. All makes sense and the above code seems to do the trick, thanks once again!
  23. No luck with that unfortunately, still lots of fragmented lines. Do the instances of '\r\n' need to be wrapped in double quotes? I can't work out the logic here! I should also note that these strings can include \r\n but are also sometimes littered with instances of \r or \n individually.
  24. I think I may have found a small problem with WireQueueTextfile, although it could be due to this PHP bug? The array is being serialized in the addItem() function, but in my case an element's value is sometimes a string containing newline characters "\r\n". I believe WireQueueTextfile saves each array to one line, but these characters are causing the serialized string to be split onto multiple lines, which I'm assuming causes problems for WireQueue. Would it make sense to somehow sanitize these values (outside of WireQueue) before adding them to the queue?
  25. Thanks LostKobrakai, works perfectly. I'll think about creating a new/modified field for pre-generating variants for future use.
  • Create New...