Leaderboard
Popular Content
Showing content with the highest reputation on 05/05/2018 in all areas
-
In v1.9.3 I switched to the $datetime api var in pagelist markup and introduced %modifiedUser% and %createdUser% tokens. As a result it is possible to display relative date like this: I never thought about this but it came from a request and seems like a nice addition. In fact I only knew that a relative date function exists in PW but not that it's also an api var. I was even happier when I learned that I need only slightly change the code to support both relative and the existing date formats with $datetime->date(). Full changelog: pagelist markup: switch to $datetime api var (allows relative datetime strings too) pagelist markup: add %createdUser% and %modifiedUser% tokens pagelistUnselect: fix Uikit theme spacing issue (reported by Robin S) CKEditor plugin Link Files Menu: apply fix from Robin S from his original module (ajax response not working for non-superusers)3 points
-
Page Query Boss Build complex nested queries containing multiple fields and pages and return an array or JSON. This is useful to fetch data for SPA and PWA. You can use the Module to transform a ProcessWire Page or PageArray – even RepeaterMatrixPageArrays – into an array or JSON. Queries can be nested and contain closures as callback functions. Some field-types are transformed automatically, like Pageimages or MapMarker. Installation Via ProcessWire Backend It is recommended to install the Module via the ProcessWire admin "Modules" > "Site" > "Add New" > "Add Module from Directory" using the PageQueryBoss class name. Manually Download the files from Github or the ProcessWire repository: https://modules.processwire.com/modules/page-query-builder/ Copy all of the files for this module into /site/modules/PageQueryBoss/ Go to “Modules > Refresh” in your admin, and then click “install” for the this module. Module Methods There are two main methods: Return query as JSON $page->pageQueryJson($query); Return query as Array $page->pageQueryArray($query); Building the query The query can contain key and value pairs, or only keys. It can be nested and contain closures for dynamic values. To illustrate a short example: // simple query: $query = [ 'height', 'floors', ]; $pages->find('template=skyscraper')->pageQueryJson($query); Queries can be nested, contain page names, template names or contain functions and ProcessWire selectors: // simple query: $query = [ 'height', 'floors', 'images', // < some fileds contain default sub-queries to return data 'files' => [ // but you can also overrdide these defaults: 'filename' 'ext', 'url', ], // Assuming there are child pages with the architec template, or a // field name with a page relation to architects 'architect' => [ // sub-query 'name', 'email' ], // queries can contain closure functions that return dynamic content 'querytime' => function($parent){ return "Query for $parent->title was built ".time(); } ]; $pages->find('template=skyscraper')->pageQueryJson($query); Keys: A single fieldname; height or floors or architects The Module can handle the following fields: Strings, Dates, Integer… any default one-dimensional value Page references Pageimages Pagefiles PageArray MapMarker FieldtypeFunctional A template name; skyscraper or city Name of a child page (page.child.name=pagename); my-page-name A ProcessWire selector; template=building, floors>=25 A new name for the returned index passed by a # delimiter: // the field skyscraper will be renamed to "building": $query = ["skyscraper`#building`"] Key value pars: Any of the keys above (1-5) with an new nested sub-query array: $query = [ 'skyscraper' => [ 'height', 'floors' ], 'architect' => [ 'title', 'email' ], ] A named key and a closure function to process and return a query. The closure gets the parent object as argument: $query = [ 'architecs' => function($parent) { $architects = $parent->find('template=architect'); return $architects->arrayQuery(['name', 'email']); // or return $architects->explode('name, email'); } ] Real life example: $query = [ 'title', 'subtitle', // naming the key invitation 'template=Invitation, limit=1#invitation' => [ 'title', 'subtitle', 'body', ], // returns global speakers and local ones... 'speakers' => function($page){ $speakers = $page->speaker_relation; $speakers = $speakers->prepend(wire('pages')->find('template=Speaker, global=1, sort=-id')); // build a query of the speakers with return $speakers->arrayQuery([ 'title#name', // rename title field to name 'subtitle#ministry', // rename subtitle field to ministry 'links' => [ 'linklabel#label', // rename linklabel field to minlabelistry 'link' ], ]); }, 'Program' => [ // Child Pages with template=Program 'title', 'summary', 'start' => function($parent){ // calculate the startdate from timetables return $parent->children->first->date; }, 'end' => function($parent){ // calculate the endate from timetables return $parent->children->last->date; }, 'Timetable' => [ 'date', // date 'timetable#entry'=> [ 'time#start', // time 'time_until#end', // time 'subtitle#description', // entry title ], ], ], // ProcessWire selector, selecting children > name result "location" 'template=Location, limit=1#location' => [ 'title#city', // summary title field to city 'body', 'country', 'venue', 'summary#address', // rename summary field to address 'link#tickets', // rename ticket link 'map', // Mapmarker field, automatically transformed 'images', 'infos#categories' => [ // repeater matrix! > rename to categories 'title#name', // rename title field to name 'entries' => [ // nested repeater matrix! 'title', 'body' ] ], ], ]; if ($input->urlSegment1 === 'json') { header('Content-type: application/json'); echo $page->pageQueryJson($query); exit(); } Module default settings The modules settings are public. They can be directly modified, for example: $modules->get('PageQueryBoss')->debug = true; $modules->get('PageQueryBoss')->defaults = []; // reset all defaults Default queries for fields: Some field-types or templates come with default selectors, like Pageimages etc. These are the default queries: // Access and modify default queries: $modules->get('PageQueryBoss')->defaults['queries'] … public $defaults = [ 'queries' => [ 'Pageimages' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus', ], 'Pagefiles' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'filesize', 'filesizeStr', 'hash', ], 'MapMarker' => [ 'lat', 'lng', 'zoom', 'address', ], 'User' => [ 'name', 'email', ], ], ]; These defaults will only be used if there is no nested sub-query for the respective type. If you query a field with complex data and do not provide a sub-query, it will be transformed accordingly: $page->pageQueryArry(['images']); // returns something like this 'images' => [ 'basename', 'url', 'httpUrl', 'description', 'ext', 'focus'=> [ 'top', 'left', 'zoom', 'default', 'str', ] ]; You can always provide your own sub-query, so the defaults will not be used: $page->pageQueryArry([ 'images' => [ 'filename', 'description' ], ]); Overriding default queries: You can also override the defaults, for example $modules->get('PageQueryBoss')->defaults['queries']['Pageimages'] = [ 'basename', 'url', 'description', ]; Index of nested elements The index for nested elements can be adjusted. This is also done with defaults. There are 3 possibilities: Nested by name (default) Nested by ID Nested by numerical index Named index (default): This is the default setting. If you have a field that contains sub-items, the name will be the key in the results: // example $pagesByName = [ 'page-1-name' => [ 'title' => "Page one title", 'name' => 'page-1-name', ], 'page-2-name' => [ 'title' => "Page two title", 'name' => 'page-2-name', ] ] ID based index: If an object is listed in $defaults['index-id'] the id will be the key in the results. Currently, no items are listed as defaults for id-based index: // Set pages to get ID based index: $modules->get('PageQueryBoss')->defaults['index-id']['Page']; // Example return array: $pagesById = [ 123 => [ 'title' => "Page one title", 'name' => 123, ], 124 => [ 'title' => "Page two title", 'name' => 124, ] ] Number based index By default, a couple of fields are transformed automatically to contain numbered indexes: // objects or template names that should use numerical indexes for children instead of names $defaults['index-n'] => [ 'Pageimage', 'Pagefile', 'RepeaterMatrixPage', ]; // example $images = [ 0 => [ 'filename' => "image1.jpg", ], 1 => [ 'filename' => "image2.jpg", ] ] Tipp: When you remove the key 'Pageimage' from $defaults['index-n'], the index will again be name-based. Help-fill closures & tipps: These are few helpfill closure functions you might want to use or could help as a starting point for your own (let me know if you have your own): Get an overview of languages: $query = ['languages' => function($page){ $ar = []; $l=0; foreach (wire('languages') as $language) { // build the json url with segment 1 $ar[$l]['url']= $page->localHttpUrl($language).wire('input')->urlSegment1; $ar[$l]['name'] = $language->name == 'default' ? 'en' : $language->name; $ar[$l]['title'] = $language->getLanguageValue($language, 'title'); $ar[$l]['active'] = $language->id == wire('user')->language->id; $l++; } return $ar; }]; Get county info from ContinentsAndCountries Module Using the [ContinentsAndCountries Module](https://modules.processwire.com/modules/continents-and-countries/) you can extract iso code and names for countries: $query = ['country' => function($page){ $c = wire('modules')->get('ContinentsAndCountries')->findBy('countries', array('name', 'iso', 'code'),['code' =>$page->country]); return count($c) ? (array) $c[count($c)-1] : null; }]; Custom strings from a RepeaterTable for interface Using a RepeaterMatrix you can create template string for your frontend. This is usefull for buttons, labels etc. The following code uses a repeater with the name `strings` has a `key` and a `body` field, the returned array contains the `key` field as, you guess, keys and the `body` field as values: // build custom translations $query = ['strings' => function($page){ return array_column($page->get('strings')->each(['key', 'body']), 'body', 'key'); }]; Multilanguage with default language fallback Using the following setup you can handle multilanguage and return your default language if the requested language does not exist. The url is composed like so: `page/path/{language}/{content-type}` for example: `api/icf/zurich/conference/2019/de/json` // get contenttype and language (or default language if not exists) $lang = wire('languages')->get($input->urlSegment1); if(!$lang instanceof Nullpage){ $user->language = $lang; } else { $lang = $user->language; } // contenttype segment 2 or 1 if language not present $contenttype = $input->urlSegment2 ? $input->urlSegment2 : $input->urlSegment1; if ($contenttype === 'json') { header('Content-type: application/json'); echo $page->pageQueryJson($query); exit(); } Debug The module respects wire('config')->debug. It integrates with TracyDebug. You can override it like so: // turns on debug output no mather what: $modules->get('PageQueryBoss')->debug = true; Todos Make defaults configurable via Backend. How could that be done in style with the default queries? Module in alpha Stage: Subject to change This module is in alpha stage … Query behaviour (especially selecting child-templates, renaming, naming etc) could change2 points
-
I thought I'd start this topic because I seem to be recommending certain courses to people repeatedly, so why not share it with the PW community. The topic can be a review or overview of any IT or non IT-related courses you're doing or have done. I'll start off with a course on teaching you how to learn. https://www.coursera.org/learn/learning-how-to-learn I'm halfway through this course and I can already see huge improvements in the way I'm learning and also my approach to learning. I wished I've taken this course when I was a student! The course can be taken free, and all it takes is a couple of hours of your time. If you have kids that are in school, I think this should be compulsory viewing for all children in academia. If you're into self-improvement or learning, then I can't recommend this course highly enough.2 points
-
This week's version of ProcessWire on the dev branch continues resolution of GitHub issue reports, and it also adds a new text truncation function to our $sanitizer API, something requested from our requests repository: https://processwire.com/blog/posts/processwire-3.0.101-core-updates/ We didn't have a blog post for last week's version 3.0.100—see the core updates section in ProcessWire Weekly #207 for more details on that version.2 points
-
There is a typo in your post @ryan. You write: The correct sentence should be: But okay... I'm fine with your sentence, too.2 points
-
Hey Robin - thanks for thoughts - makes sense. I am heading on vacation this morning for a couple of weeks, but I'll add this to my list for when I'm back.1 point
-
1 point
-
https://transfer.sh/brOON/site-RockFinderTest.zip (will be deleted in 14 days)1 point
-
It just happens to be that the first argument to the function hooked in that part of the documentation is a page object. So it's always about the functions arguments. There are a few things wrong with passing around the hook event object: The hook event object is used only in combination with a hook. So every code touching that object automatically binds itself to a usage only through a hook, which is a unneccesary coupling. This violates the open/closed principle (O if SOLID), because if anything related to hooks does change you need to modify each method receiving the hook event and not only the one method, which is triggered by the hook. On the other hand if any of your business logic does change you should not need to modify code, which is only there to make sure your hook is handled. When you're not complying to the above it's also way easier to get lost in where things do actually change in regards to handling a hook. You wouldn't want to be in a situation, where you know a hook does cause issues, but you cannot find the place where that hook is manipulated incorrectly, because the objects is passed around in so many places. Metaprogramming (hooks are kind of that) is manipulating code at runtime, which is a responsibility to keep up to. This is best done by keeping the surface of code actually manipulating other code as small as possible. On the other hand if your hook callback function is only dealing with "making the hook work" and passing all other responsibility of to other functions – just passing data gathered from the $event object, but not the event itself – you automatically have functions, which you can easily use from places other than that hook as well. Say you later notice that you need to use that tierprice() method from another hook with different arguments or in some other part of your business logic, which is not triggered by a hook at all. You wouldn't want to duplicate the function, just to be able to use it from different places. You also don't want to fabricate a fake $event object just to call it. [SOLID Principles] https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)1 point
-
Thanks robin! I will definitely try this out when I am not on mobile. Unfortunately, the system was set up in a bit of a haste and grew beyond what it was originally intended for. I should have had the foresight and set it up differently. I really appreciate all the help and thank you for always commenting on helpful suggestions/fixes. Hopefully I can get this all working together without a complete overhaul, but it might be needed at this point.1 point
-
What is causing you to draw this conclusion? The value of a (multiple) Page Reference field is a PageArray. The sort order of pages in the field will be the sort order of the pages in the PageArray at the time the field (or the whole page containing the field) is saved. So setting the sort order of the field consists of putting the pages in the PageArray into the order you want and then saving the field (or the whole page containing the field). So if I have a Page Reference field "colours" which is currently empty on page $p and I add some pages to the field then the order is the order in which I add them (to the PageArray)... $p->of(false); $yellow = $pages->get('name=yellow'); $p->colours->add($yellow); $red = $pages->get('name=red'); $p->colours->add($red); $blue = $pages->get('name=blue'); $p->colours->add($blue); $p->save('colours'); // Alternatively you could save the whole page // Sort order of $p->colours is yellow, red, blue Suppose I now want to change this sort order to some other custom order. Let's say I want to sort them by the length of the page name. $p->of(false); foreach($p->colours as $colour) { $colour->custom_sort = strlen($colour->name); } $p->colours->sort('custom_sort'); $p->save('colours'); // Alternatively you could save the whole page // Sort order of $p->colours is red, blue, yellow1 point
-
As a general observation, I think using the page title or page name as a way to connect different pages to a single person is not very robust. It would be better to have a separate section in your page tree for "Persons", with John Doe, Jane Doe, etc, stored under there. Those pages don't necessarily have to appear on the front-end or have a template file. Then other pages are connected to a person by a Page Reference field. But on to your question... If it is acceptable to sort the persons listing by the name of the person then you could do this... $services = $pages->find("template=service-a|service-b, sort=parent.name"); $name = ''; foreach($services as $service) { if($service->parent->name !== $name) { // This service is from a different person, so output a heading echo "<h3>{$service->parent->title}</h3>"; $name = $service->parent->name; } echo "<p>$service->title</p>"; } Note that if you were using a Page Reference field to link services to a person then you would have more sorting options available to you - i.e. you could sort by any field that exists in the person template.1 point
-
Wow, that truncate function will truncate my search for the ultimate truncate function! ? I wonder if the default for the "visible" option should be true rather than false. If my string included entities and markup tags I think in nearly all cases I wouldn't want those invisible items affecting the visible string length.1 point
-
I love this idea. My 2 cents on this: https://www.udemy.com/ - always offers several courses for free (or super discounted) from all kinds of topics. (No affiliate link!) ? YouTube is another great option for courses and knowledge. Just a few examples from my subscription list: Code Course*: https://www.youtube.com/channel/UCpOIUW62tnJTtpWFABxWZ8g Dansky*: https://www.youtube.com/channel/UCAbq1eKey19tt-FfaIO1RMA DevTips**: https://www.youtube.com/channel/UCyIe-61Y8C4_o-zZCtO4ETQ FunFunFunction*: https://www.youtube.com/channel/UCO1cgjhGzsSYb1rsB4bFe4Q Layout Land*: https://www.youtube.com/channel/UC7TizprGknbDalbHplROtag Travery Media: https://www.youtube.com/channel/UC29ju8bIPH5as8OGnQzwJyA Wes Bos: https://www.youtube.com/channel/UCoebwHSTvwalADTJhps0emA Free Code Camp: https://www.youtube.com/channel/UC8butISFwT-Wl7EV0hUK0BQ Pick your topic. Watch the videos. My favourites highlighted with an *. Super awesome channels marked with an **.1 point
-
I would like to take a look at all this, but I don't have a real site with thousands of pages. I know about all the various ways I could create + populate PW pages with some dummy data. But do you think you could provide us some sort of data set (site-profile?) that actually has so many entries it's even worth playing around with it? I can imagine having such a quick setup would greatly facilitate further improvements / tests.1 point
-
Thanks for reporting this issue. It should be fixed in v0.1.5 of CKEditor Link Files. If you check the changes in this commit you can apply something similar in your module. @tpr, this issue will affect the version included in AdminOnSteroids too. I thought a hook before module edit would trigger before the access control kicks in but it seems not. Sort of obvious in hindsight. I really didn't want to have to add a page just for the AJAX response so went looking for some other process to hook into. I settled on ProcessLogin::executeLogout as this is one process module/method that should be accessible for any user. You might want to update AOS with a similar fix.1 point
-
Looking at the line where that addBodyClass error is coming from: $this->wire('adminTheme')->addBodyClass('LanguageTabsJqueryUI'); we can see it's related to the theme issues you are having. At this point I would copy across all the files from the wire folder again with a fresh set from Github. If that doesn't work on it's own, I'd be calling: http://mysite.com/processwire/module/?reset=1 If that doesn't work, empt the assets/cache folder. Then try the cache db table.1 point