Search the Community
Showing results for tags 'query'.
-
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 change
- 24 replies
-
- 29
-
-
-
Hi guys, I would like to realize a search function on my site that follows some clear rules: 1 – Search is performed in the two fields 'tite' and 'body'. 2 – If a user searches for 'foo' and 'bar' he can select whether both terms have to be in at least one of the fields (AND) or at least one of the terms has to be in at least one of the fields (OR). 3 – 'foo' should match words like 'food' or 'foolish' as well as 'foo' (LIKE). 4 – Search results where 'foo' or 'bar' are found in the title should be displayed first, followed by the results found in 'body' only. This is what I have: <?php if($input->get->q) { $q = $sanitizer->text($input->get->q); if($q) { $input->whitelist('q', $q); $qs = explode(" ", $q); foreach($qs as $key => $q) : $qs[$key] = $sanitizer->selectorValue($q); endforeach; $selector1 = "title%=".implode("|", $qs).", template=entry, limit=50"; $selector2 = "body%=".implode("|", $qs).", template=entry, limit=50"; // Trying to separate 'important' title matches from 'less important' body matches $matches = $pages->find($selector1); if ($matches->count) < $limit)) $matches->import($pages->find($selector2)); if ($matches->count) { foreach($matches as $m) : // Output matches title & body excerpt endforeach; } else { // Output no matches } } } else { // Output no search term(s) } ?> First problem is regarding rule 2: I don’t know how to do an AND search. As far as I can see, it’s always an OR. Second problem is the order of the search results (rule 4): I split the queries to separate them nicely, but they appear mixed up. Output starts with a few title matches as I would expect, followed by some body matches, then some further title matches appears. I don’t understand how this happens: $matches->import adds array 2 to array 1 without mixing them, isn’t that true? And just in case I will get the job done someday: how could I avoid doubled matches? Matches in body aren’t that interesting anymore, when the terms was already found in the title field. I will appreciate any helping hand – thanks. Ralf
-
ProcessWire & Vue.js — a Lovestory Introducing the all new ICF Conference Website The new ICF Conference Page — Fearless » What would happen if we were equipped to fearlessly face the daily challenges and live a life without fear? « This question is at the core of our next ICF Conference in 2019 in Zurich. Its also the question we set out to answer in terms of developing the new website; the all new ICF Conference website is our most advanced website in terms of technology, designed to take advantage of the latest web-technologies. Its a brand new design powered by a lean setup, using ProcessWire for easy content management and a slick frontend based on Vue.js, Quasar and a heavily customized Uikit theme. Technology-stack — From backend to frontend, technologies that are fun, easy and fast to develop with We built on the ICF Ladieslounge website as a solid foundation and took our learnings from building our last Conference Booklet PWA (Progressive Web App) and applied it to the new website. Some highlights of the new ICF Conference website: Completely decoupled backend and frontend Custom design based on Uikit frontend framework Changing of languages happens instantly, no page-reload required Easy content updates thanks to ProcessWire All data is transferred using a single request returning custom JSON » Continue reading on Medium And please don't forget to clap and share:
-
Hello, How would I get the DB query that is used to gather the data for something like wire('pages')->find("template=log, id_gc={$serverId}, timestamp>={$dateStart}, timestamp<={$dateEnd}, sort=timestamp"); Tried using the Debug Mode Tools in the backend on a lister with similar selector. But I couldn't make out the right query in there. I'd like to use that query directly to dump data from the DB to a csv via SELECT INTO. Processing 10.000s of pages in chunks of 100 via the API into a csv. This is quite time consuming (several minutes for ~20.000 pages that result in a ~13MB csv file). Any help would be much appreciated.
-
I'm displaying a list of products which are found by their templates, but the pages are taking a very long time to load. At first, I blamed it on my image rendering (using PIM2), but even with all those images now stored in the file tree, the page is taking abysmally long to load. ProCache seems to help but I don't feel as though what I'm trying to do should be gnawing the bones of my resources quite so long. The variable for the selector is defined in my header include: $productCatList="prod_series|prod_series_ethernet|prod_series_access|prod_series_accessories|prod_series_fiber|prod_series_pwr_supplies|prod_series_pwr_systems|prod_series_wireless"; $getCurrentProdOptions="template=$productCatList, prod_status_pages!=1554|1559|1560|4242"; Then in the template for the page upon which the directory loads: $products = $pages->find("$getCurrentProdOptions"); include_once("./prod-list-row.inc"); echo $out; And the prod-list-row.inc foreach (which is on every page that's exhibiting the slowdown): <?php $sum = 0; $out =""; $out .= "<div class='span_12_of_12'>\n"; foreach($products as $p){ $sum += 1; if ($sum % 2 == 0) { $bgcolor = '#fff'; } else { $bgcolor = '#e4e4e4';} $par = $p->parent; $out .="<div class='section group' style='background: $bgcolor ; min-height: 110px'>\n"; $img = $p->prod_image; $thumb = $img->pim2Load('squarethumb100')->canvas(100,100,array(0,0,0,0),'north',0)->pimSave()->url; $out .="<div data-match-height='{$p->title}' class='col span_2_of_12 hide'>"; $out .="<a href='{$p->url}'><span class='product-image-box'><img src='{$thumb}' alt='{$p->title}' title='{$p->title}'></span></a>"; $out .= "</div>"; $out .= "<div data-match-height='{$p->title}' class='col span_6_of_12'>"; $out .= "<div class='prod-list-name-label'><a href='{$p->url}'>{$p->title}</a></div>"; if($page!=$par) { $out .= "<div class='prod-list-category-label' style='font-size: .7em;'>Category: <a href='{$par->url}'>{$par->title}</a></div>"; } $out .= "<div class='list-headline' style='font-size: .8em;'>{$p->headline}</div>"; $out .="<div class='learn-more-buttons-sm'>"; $out .="<a href='{$p->url}' title='Product Specs and Documentation'><span class='find-out-more-button' style='font-size: .8em;'><i style='font-size: .8em;' class='fa fa-lightbulb-o' ></i> Learn More</span></a>"; $out .="</div>"; $out .="</div> \n"; $out .= " <div data-match-height='{$p->title}' class='col span_4_of_12'>"; if(count($p->prod_feat_imgs) >0 ){ $out .= "<div class='featured-icons-list' margin: 2em .5em;'>"; foreach($p->prod_feat_imgs as $feat){ $icon = $pages->get("$feat->prod_featicon_pages"); if($icon->image) { if($feat->prod_feat_textlang) { $icontitle = $feat->prod_feat_textlang;} else {$icontitle = $icon->title;} $out .= "<img src='".$icon->image->size(35,35,$imgOptions)->url . "' alt='" . $icontitle . "' title='" . $icontitle . "' class='listing-feat-icon' style='margin-right: .5em;' />"; } } $out .= "</div>"; if($p->prod_product_line){ foreach($p->prod_product_line as $pline) if($pline->image) { $out .= "<div style='height: 35px;'>\n"; $out .= "<img src='{$pline->image->size(75,35,$imgOptions)->url}' alt='{$pline->title}' />"; $out .= "</div>"; } } } $out .= "</div>"; $out .="</div>"; } $out .= "</div>"; Is there a clear culprit here of what I'm doing that's so stressing the system? I turned off TracyDebugger because I saw another thread about that causing slowdown (even though I'm using the latest), but that had no effect. Every time I thought I found the culprit and commented it out, nothing changed. Would appreciate some more eyes on this. Thank you! ETA: prod_feat_imgs is a repeater field which contains a Page reference field (from which I pull the image and title) and a multilanguage textfield (to override the page reference title if it exists). Could that be the problem?
-
I have a website with a slow page load mainly due to a slow query on a listing. I think there might be a better way to query the data / arrange the data back end which is what's causing the query to be slow. So the data is like this Area -> Level -> Path, the path then links to a pool of units included within that path. The units then have study locations listed as child pages for that unit with contact information etc. I have a page where i list all the study locations, but because i'm going through every unit and then every child study location page it takes quite a while. I have over 200 units with around 5 locations as sub pages. Any way i can reorganise the data to make this listing faster to load?
-
Hey guys, I thought I had selectors under control until I ran into this situation. This is my structure of four templates: Articles - Article Authors - Author Article contains a pagefield for author. If I wanted to get all articles written by an author, I can run a selector such as "template=article, author=$authorName". So far so good. Here is where I am stuck: I want to display a list of top 10 authors with the most number of articles written. In other words, I want to write a selector that - finds items with template=author - sorts descending by the count of articles written by author <-- I am unable to formulate this part - limits items to 10 One way to solve this is: Get a count of articles by all authors in a php array. Then sort the resultset by article count and get author names for the top ten in the array. This method can be heavy on both database and php, depending on the number of authors and articles in the system. Is there a selector-only trick to solve this problem? thanks
-
Is there an easy way to get back the SQL query generated by a specific processwire selector? I have what appears to be a cartesian product and/or groupwise maximum bug in a selector, but I'm not sure how to confirm it. I'm querying for a set of pages with a specific template which are related to the current page. The returned pages include a URL field and a file field. The selector looks like this: $grades = $pages->find('template=grade,include=all,classification.title="'.$page->title.'",sort=series,sort=title'); "classification" is a page field. Series and title are text fields. I'm iterating through the results and outputting three text fields, a URL field, and a file (->url) field. On one of the nine pages using this query, it should only return one row, but it returns two. The text fields are identical for both rows. The URL and file fields are empty in the first row and populated in the second row. The other eight pages all work as expected with no duplication. Thanks, Jason
-
I have been working on this logic off and on lately and have made some progress. The original question has been answered/fixed. Now I am on to making my search results make more sense. See from post #11 for new content. ---------------------------------------------------------------------- I fear I am a bit out of my depth on this one. I have been trying to get a site search up on an art gallery site that I am developing. I have created a template that has all of the fields in all of the templates on my site that I want to search against. From this list I am wanting to generate my search query. When I try and run a fulltext query based on these templates I get a big error. Error: Exception: SQLSTATE[HY000]: General error: 1191 Can't find FULLTEXT index matching the column list //use field template to create list of fields foreach ($pages->get("/search-engine-fields/")->fields as $field){ $allFields .= $field.'|'; } $matches = $pages->find("{$allFields}body~=$q,limit=50,template!=admin,template!=permission,template!=user"); I have attempted using %= but then I am unable to locate items which have segments that contain the terms. Is there another method that I should employ?
-
All: If you saw some of my recent postings where I was trying to solve the problem of running a query as a field, I have solved it. I started with Hanna text, but that didn't quite get me all the way. Then I tried just a Concat field, and that didn't get me all the way. I modified the Concat fieldtype for my solution. I had a need to dynamically pull Pages that were cross-referenced back: Product as a Page Photo Pages with a multi-Page Select field that referenced Product (A photo could represent 1+ Products) I wanted a ->photos field from Product that was updated dynamically according to what Photo entries were currently in place, and I didn't want copy/pasted code, and I wanted the selectors to be easily modifiable from the admin screens. Usage is faily simple: 1: Install as a normal module 2: Create a field as a PagesSelectorQuery type 3: On the field details, enter your selector string, ie: template=my_template,select_page_field=$page 4: Add your field to whichever templates. 5: Access the PageArray results like you would any other $page->field I hope you find it useful, at the very least as an exercise in my madness. Thanks again for all the help this community has provided. EDIT: Added to GitHub: https://github.com/alevinetx/processwire-modules/blob/master/FieldtypePagesSelectorQuery.module And Module's page: http://modules.processwire.com/modules/fieldtype-pages-selector-query/ FieldtypePagesSelectorQuery.module
-
I feel like I'm getting super lost with all of my questions today I have a Checkbox field, "premium". This field is added to a template, myContent I have 2 pages created with the myContent template. One has premium checked, the other does not. My query is: Show me all Pages using 'myContent' as a template, and whose premium field is not selected (0): selector: template=myContent,premium=0 This query is returning 2 Pages, which is should not. When I iterate through the returned list, I can see ->premium as set 1 for one, 0 for the other. If I change "premium" in the selector to some random value that doesn't exist as a field name, the query runs and still returns 2 Pages. selector: template=myContent,feujfhejhejejhejhefje=0 I would think it should error out, but instead seems to be doing a short-circuit evaluation. Are comma-delimited selectors not "AND"ed together? If I place the premium field at the start, or even by itself selector: premium=0 I get this error: Notice: Uninitialized string offset: 0 in C:\dev\xampp\htdocs\pwire\wire\core\Selector.php on line 49 Fatal error: Exception: Field does not exist: (in C:\dev\xampp\htdocs\pwire\wire\core\PageFinder.php line 248) #0 [internal function]: PageFinder->___getQuery(Object(Selectors)) #1 C:\dev\xampp\htdocs\pwire\wire\core\Wire.php(271): call_user_func_array(Array, Array) #2 C:\dev\xampp\htdocs\pwire\wire\core\Wire.php(229): Wire->runHooks('getQuery', Array) #3 C:\dev\xampp\htdocs\pwire\wire\core\PageFinder.php(145): Wire->__call('getQuery', Array) #4 C:\dev\xampp\htdocs\pwire\wire\core\PageFinder.php(145): PageFinder->getQuery(Object(Selectors)) #5 C:\dev\xampp\htdocs\pwire\wire\core\Pages.php(144): PageFinder->find(Object(Selectors), Array) #6 [internal function]: Pages->___find('=0') #7 C:\dev\xampp\htdocs\pwire\wire\core\Wire.php(271): call_user_func_array(Array, Array) #8 C:\dev\xampp\htdocs\pwire\wire\core\Wire.php(229): Wire->runHooks('find', Array) #9 C:\dev\xampp\htdocs\pwire\site\assets\cache\HannaCode\pages_query.php(3): Wire->__call('find', Array) #10 C:\dev\xampp\htdocs\pwire\site\assets\cache\HannaCode\pages_que in C:\dev\xampp\htdocs\pwire\index.php on line 214 Please point out what I'm doing wrong! Thank you. Again!