Jump to content

Robin S

Members
  • Posts

    5,039
  • Joined

  • Days Won

    340

Everything posted by Robin S

  1. Really great tutorial, thanks! Might be worth noting that a minimum of PHP 7.1 is required for the code syntax used.
  2. It's working for me here... <?php namespace ProcessWire; class TestModule extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => "Test Module", 'version' => 1, 'autoload' => true, ); } public function init() { $this->addHookAfter('Pagefile::url', function(HookEvent $event) { if(!$event->object instanceof Pagefile) { return; } $file = $event->object; $fileBasename = $file->basename; $event->return = $fileBasename . '?lorem=ipsum'; }); $this->pages->addHookAfter('save', function(HookEvent $event) { $page = $event->arguments(0); if($page->hasField('image')) { // Output formatting will probably be off, but get unformatted value to be sure $pageimages = $page->getUnformatted('image'); // Get first image URL $url = $pageimages->first()->url; // Dump URL with Tracy Debugger bd($url, 'url'); } }); } } You shouldn't use $this as the second argument to addHookAfter() if your hook function is a closure, but I'm guessing that is just an error in the demo code you posted and not in your actual module. Your issue might be due to output formatting being off in the Pages::save() hook which means an Images field will return a Pageimages object which doesn't have a URL property. So if the field holds only a single Pageimage you would get that with first().
  3. FYI, you can set the age the temp directory lives for with the maxAge option: $files->tempDir('RockPdf', ['maxAge' => 600]); // or $files->tempDir('RockPdf', 600);
  4. Yes, that's what the code in my previous post is for. There's no foreach loop involved. Did you try it? That code provides a way of working out the position of any page relative to a set of pages it is a part of. When you put the code in the template file of a restaurant location page then the page is represented by the API variable $page. If you want to use it in some other template file (e.g. your account template) then you would substitute $page with $p where $p is a restaurant location page.
  5. I understand now. There are a couple of ways you could do this - a simpler way and a more performant way. In the demo code below my selector is for sorting news items by title - you would adjust the selector as needed to find your restaurants. This code would go in the template file for your restaurant location template. 1. Simpler way: probably fine if there is not a huge number of matched pages // Function to get number plus ordinal suffix function ordinal($number) { $suffixes = ['th','st','nd','rd','th','th','th','th','th','th']; if(($number % 100) >= 11 && ($number % 100) <= 13) { return $number. 'th'; } else { return $number. $suffixes[$number % 10]; } } // Define the selector that finds your pages $selector = "template=news_item, sort=title"; // Get all the matched pages $items = $pages->find($selector); $position = $items->getItemKey($page) + 1; // add 1 to adjust for zero index $position_str = ordinal($position); // the position with ordinal suffix echo "This is the $position_str item."; 2. More performant way: if there are a large number of matched pages you can use an SQL query which avoids the overhead of loading all the pages First work out the SQL query for matching your pages: $query = $pages->getPageFinder()->find(new Selectors($selector), ['returnQuery' => true])->getQuery(); bdb($query); // Use Tracy barDumpBig() to dump the SQL query Now tweak the query a bit because you only need the IDs of the matched pages. The finished example for my sorted news items is below: $sql = "SELECT pages.id FROM `pages` LEFT JOIN field_title AS _sort_title ON _sort_title.pages_id=pages.id WHERE (pages.templates_id=45) AND (pages.status<1024) ORDER BY _sort_title.data"; $query = $database->query($sql); // Get the IDs in an array, flipped so the ID is the key and the position is the value $results = array_flip($query->fetchAll(\PDO::FETCH_COLUMN)); $position = $results[$page->id] + 1; // add 1 to adjust for zero index $position_str = ordinal($position); // the position with ordinal suffix echo "This is the $position_str item."; Credits: 1, 2, 3
  6. Everyone has their own strategies for this sort of thing. Some people like to use modules for site settings and just deal with the limitations around inputfield types. There are a couple of existing settings modules you could study: https://modules.processwire.com/modules/process-general-settings/ https://modules.processwire.com/modules/settings-factory/ Personally I have a custom site profile (generated via Site Profile Exporter) that I use as the starting point for every new project, that has all the fields/templates/pages/modules that I tend to use in every project.
  7. I don't understand the question - perhaps you can clarify more... How are you measuring and storing the popularity score? Some sort of "likes" system? If the popularity is per location rather that per restaurant brand then you'll need some kind of entry (page most likely, but technically it could be something like a Profields Table row also) for each restaurant location. Let's say it's a page, and it uses template "restaurant_location". Each restaurant location page is a child of the restaurant brand (or the restaurant locations could be under a separate parent and use a Page Reference field to link them with a restaurant brand - with PW there is always lots of flexibility to set things up in a way that suits you). Template restaurant_location has a Page Reference field for location that selects from the City pages, and a "popularity_score" integer field that gets incremented every time someone "likes" it. The listing page for each city gets all restaurant locations relating to that city sorted by popularity_score.
  8. Module config settings are stored as a JSON string in a single field in the database. Module config fields don't store data in separate tables as per the fieldtypes you might be used to from using fields in a template. So certain core inputfields that need special support from a fieldtype to store their data can't be used in module configs: Files, Images, Repeater, PageTable, maybe a few more. BTW, personally I would use a standard page/template to store site settings rather than a module config for just this reason.
  9. Those are some sweet updates Ryan! Seeing as you've been working on transferring the blog posts over to the new processwire.com site, do you think you could add a feature so that blog posts automatically get anchors on every <h3>? There could be an automatic table of contents from those anchors if you think that would be good, but having anchors is the main thing. Because there is so much useful documentation of features within the blog I often want to save references to specific sections within a blog post, or provide a link to a section in a forum reply. Having heading anchors would make it possible to do this.
  10. The page tree can actually be zero clicks or hovers away... there's an option in AdminThemeUikit to show the page tree in the sidebar. https://processwire.com/blog/posts/uikit-3-admin-theme-updates/
  11. Excited about the new processwire.com! I agree that the search could be better - especially if processwire.com is to be advertised as powered by ProcessWire. I get why the forum search might be poor because that is not powered by PW, but it has long puzzled me why the Modules Directory search is so poor seeing as that is PW-powered. An example: right now in the "Latest Additions" section we can see a module from netcarver titled "Street Address". So how come when I enter the exact words "Street Address" in the search box that module doesn't appear as the first result, and even doesn't appear on the first page? https://modules.processwire.com/search/?q=Street+Address
  12. Correct.
  13. You could use a selector to match the repeater pages (according to their template) and then get their owner pages (the events) with getForPage(). // You need check_access=0 to match repeater pages for non-superusers $repeater_items = $pages->find("template=repeater_event_time, event_time_start>=06:00, event_time_start<=12:00, sort=event_time_start, check_access=0"); foreach($repeater_items as $repeater_item) { $owner_page = $repeater_item->getForPage(); // Use $repeater_item and $owner_page as needed to build the event/time string // ... }
  14. OR-groups are a feature of PageFinder selectors - selectors that query the database, e.g. $pages->find(), $page->find(), $page->children(). WireArray::find() is a completely different kettle of fish and doesn't support OR-groups. Related: @ryan, it would be good if the distinction between WireArray/PageFinder selectors were covered somewhere in the official documentation.
  15. Note that if you have an SQL query that you are finding difficult to turn into an equivalent selector then you do have the option of getting the page IDs via SQL and then loading those page IDs. An example (not that this is difficult to do as a selector)... $sql = "SELECT pages.id FROM `pages` JOIN field_title AS field_title ON field_title.pages_id=pages.id AND (field_title.data LIKE '%a%') WHERE (pages.templates_id=59) AND (pages.status<1024)"; $query = $database->query($sql); $ids = $query->fetchAll(\PDO::FETCH_COLUMN); $items = $pages->getById($ids);
  16. There isn't anything inefficient about this (or at least there isn't really a more efficient way of doing it). The value of a Repeater field is a PageArray. When you do anything with a Repeater field value then the whole PageArray is loaded to memory. It doesn't matter if you only want a single page from it - you can't avoid loading all the pages if you get the Repeater field value.
  17. This might be relevant:
  18. I'd say this is the way to go for sure. There aren't that many fieldtypes that require special treatment, and this way you get exactly the markup you want. You can get the inputfield widths used in Page Edit too so the layout is similar. A starting point... $out = ''; $p = $pages(1234); // get the page however you like $fieldgroup = $p->fields; foreach($fieldgroup as $field) { /* @var Field $field */ $field = $fieldgroup->getFieldContext($field); $inputfield = $field->getInputfield($p); $width = $inputfield->columnWidth ?: 100; // getFieldValue() is a function/method you create that returns a string (could be markup) value for $field on $p, depending on the type of $field $value = getFieldValue($p, $field); $out .= "<div class='field-value' style='width: {$width}%'>$value</div>"; } I think it's unlikely that the admin markup is going to render nicely when you convert it to PDF. In my experience the HTML-to-PDF conversion tools don't handle complex layouts well. Plus there is going to be all kinds of markup in Page Edit that won't be relevant to what you want to show in your PDF (tabs, buttons, "Add New" links for Repeaters, etc). If you do want to try HTML-to-PDF conversion for PW's default markup value for fields, or using the Page Edit markup, then here are some avenues to explore. Your mileage may vary as I only tested these quickly. You'll need to include the admin CSS. // Use PW's default markup value for different field types $out = ''; $p = $pages(1234); // get the page however you like foreach($p->fields as $field) { $out .= $p->getMarkup($field->name); } // Get markup for the whole Page Edit form $input->post->id = 1234; // the ID of the page you want $ppe = $modules->ProcessPageEdit; $out = $ppe->execute(); // Get markup for just the Content tab (you can't easily show more than one tab in the PDF anyway) $input->post->id = 1234; // the ID of the page you want $ppe = $modules->ProcessPageEdit; $form = $ppe->buildFormContent(); $out = $form->render();
  19. Thanks for the updates Ryan! For the children counts in Page List, would you please consider adding hookable methods that return $numChildren and $numTotal? That would allow the option for devs to add a hook to correct those counts in case some descendant pages are not listable by some roles. Thanks. Also, not sure if this is deliberate but I notice the new child count options don't take effect in the Pages > Tree flyout menu.
  20. I believe this is because $page->next() is for traversing pages as they appear in the page tree. So when you use it on a repeater page it will refer to the next sibling page in the page tree, in the Repeaters section under the Admin branch. Those pages use a system repeater template that is accessed controlled. Non-superusers don't have access to those pages themselves, they only have access to repeater items via a repeater field value (a RepeaterPageArray). Whether this is a good thing or not has been under debate here: https://github.com/processwire/processwire-issues/issues/183 A better option might be to use WireArray::getNext() on the repeater field value, which will hopefully avoid the access issues for non-superusers. // $value is repeater field value if($value->getNext($item) && $value->getNext($item)->size->value != 'half')
  21. Interesting. It's based on FieldtypeFieldsetOpen, which likewise creates a database table (as does FieldtypeFieldsetClose). Not sure why these fieldtypes do that considering they don't store data, but it doesn't do any harm that I can see.
  22. @bernhard, you can actually remove a few more methods from such a fieldtype - FieldtypeFieldsetOpen is a good one to refer to for a fieldtype that doesn't store anything in the database. A while ago I made a simple runtime-only fieldtype that generates inputfield markup from PHP files in a subfolder within /site/templates/ - sort of like a stripped-back version of RuntimeMarkup without any config fields in admin. It was just intended for use in my own projects and I didn't think it was worth announcing publicly seeing as we already have kongondo's module, but I've moved it to a public repo in case it is interesting for you to take a look at: https://github.com/Toutouwai/FieldtypeRuntimeOnly
  23. Exactly that. It's great when you have a big messy spreadsheet and you just want to grab selections of data to add to the Table.
  24. Just popping in to say thanks for this awesome module! The "Paste in CSV Data" field saved me a heap of time today.
  25. You have two accounts here at the forum? The first post in this topic is from a different user. Usually this kind of page list error is due to some error message (e.g. PHP notice) being returned in the AJAX response used to build the page list instead of the expected JSON data. You can debug by checking the AJAX response in your browser dev tools. It should look like this when it's working normally:
×
×
  • Create New...