Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 04/28/2018 in all areas

  1. Happy Friday! Just a short update this week. This latest version of the core on the dev branch contains a whole lot of commits, mostly related to GitHub issue resolutions. See April 23–27 in the dev branch commit log. Thanks for all the continued testing, reports and discussion. I don't have anything new to report other than that, but am working to have another master version here shortly. Thanks and have a great weekend!
    10 points
  2. 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
    3 points
  3. Use either InputfieldPageAutocomplete or InputfieldPageListSelectMultiple as input for the page reference field. These will only load selectable pages on request.
    2 points
  4. https://processwire.com/blog/posts/processwire-3.0.73-and-new-fieldset-types/ https://processwire.com/blog/posts/processwire-3.0.74-adds-new-fieldsetpage-field-type/ https://processwire.com/blog/posts/fieldsetgroup-module-released/
    2 points
  5. PageTableExtraActions A module for ProcessWire CMS/CMF. Allows PageTable items to be published/unpublished and hidden/unhidden directly from the inputfield. Usage Install the PageTableExtraActions module. Use the icons in the "Actions" column of a PageTable field to publish/unpublish or hide/unhide an item. https://modules.processwire.com/modules/page-table-extra-actions/ https://github.com/Toutouwai/PageTableExtraActions/
    1 point
  6. Yeah, I think so. Honestly I am not too up on the current state of this autocomplete stuff, but I think my suggestions will work. Let me know how it goes when you test on Tuesday.
    1 point
  7. Hi @szabesz - it can probably be solved quite easily with some autocomplete attributes. Can you please try adding: autocomplete="pw-phone-country" autocomplete="pw-phone-area-code" autocomplete="pw-phone-number" autocomplete="pw-phone-extension" to the respective subfields. Here is the chunk of code that these need inserting into: https://github.com/adrianbj/FieldtypePhone/blob/ab02ad1a6aafc1b1abce60317cd97ce5a2c35374/InputfieldPhone.module#L98-L116 Let me know if that works as expected and feel free to submit a PR.
    1 point
  8. Hi Adrian, I have not yet installed the 3.0 branch because I though before I spend time on it I ask you about an issue we have. So we still have version 2.0.15 and the field is added to users (dunno if it matter as at all...) When the inputbox of the "core" number (not country, area nor extension) is empty then Chrome automatically fills it in with the username used for logging into ProcessWire on the standard login screen and letting Chrome save the credentials. In other words, if we have Chrome save the login info, the current user's name gets injected into the inputbox when editing a user with an empty number, and this is an annoying issue because the inputbox must be manually cleared if we want to save the user. Have you any idea how to stop this behavior?
    1 point
  9. Hi, and welcome, You can get any image from any page with the $pages variable. Basic use is like this: $image = $pages->get('/path/to/page/')->imagefield-> Examples: $mypage = $pages->get('/path/to/mypage/'); $myimage = $mypage->images->get("name=myimage.jpg"); You can also use shorthand rules or hanna code to write more compact code More here: https://processwire.com/api/variables/pages/
    1 point
  10. Depends on your use case, but.. that's not entirely true. When you embed an image into a CKEditor field, for an example, you get to choose from which page you want to pick the image from. Similarly you can request an image from any given page via the API in your template files. So no, an image is not strictly tied to one page: you can embed images anywhere, and you can indeed use them in multiple places on your site. There are also some modules that make reusing media easier, such as MediaLibrary (free) and Media Manager (commercial). It is true, though, that behind the scenes an image is always connected to a single page, and because of that ... A) you can't have a single image showing up in image fields of multiple pages, B) only one copy of each image file is stored in a page-specific directory under /site/assets/files/, and C) if you have restricted access to a given page and enabled $config->pagefileSecure, access to files (including images) on that page will also be restricted.
    1 point
  11. Thank you, Ryan! Keep the excellent work up! Cheers!
    1 point
  12. ahem... not sure if serious? OTOH, I just recently found out about the :empty pseudo-selector, which would have helped me to avoid some stupid workarounds or hacks in some situations... https://developer.mozilla.org/en-US/docs/Web/CSS/:empty
    1 point
  13. Hello, i'm currently building an access control system where people can physically authenticate at a door with a dongle. If the user has access to a specific door, the door can be opened. Every access to a door should be logged with information about time, user and dongle. Normally, i would just create a mysql table and put the data in there. Of course the table can get quite large over time. Are there any best-cases for handling this type of data in processwire? Should i just create a template with fields like user, dongle, time and create pages for each authentication? And then search / select with proccesswire selectors? Thanks! Best regards, Kevin
    1 point
  14. Only just noticed the release_date key is either not there at all, or present, but empty for some movies e.g.: [97] => Array ( [character] => Fred Rogers [credit_id] => 5a709346c3a36847e4012aaa [poster_path] => [id] => 501907 [video] => [vote_count] => 0 [adult] => [backdrop_path] => [genre_ids] => Array ( [0] => 18 ) [original_language] => en [original_title] => You Are My Friend [popularity] => 1 [title] => You Are My Friend [vote_average] => 0 [overview] => The story of Fred Rogers, the honored host and creator of the popular children's television program, Mister Rogers' Neighborhood (1968). [release_date] => ) Had to modify the functions as checking for key existence was not enough: // Movies with release date function hasReleaseDate($movie) { // Has key with value if (array_key_exists('release_date', $movie) && !empty($movie['release_date'])) { return true; } return false; } // Movies without release date function noReleaseDate($movie) { // No key if (!array_key_exists('release_date', $movie)) { return true; } // Has key but no value if (array_key_exists('release_date', $movie) && empty($movie['release_date'])) { return true; } return false; } Seems to work now.
    1 point
  15. In incognito mode, the websites load correctly ? Also you can try to clear the DNS cache, in a terminal type : 1) ipconfig /flushdns 2) ipconfig /registerdns 3) ipconfig /release 4) ipconfig /renew 5) netsh winsock reset If it doesn't work, you should open a ticket on your ISP. PS: I had problem accessing Github and ProcessWire this last 23 April.
    1 point
  16. Thanks Nik, that did the trick! Here is the current version, which seems to work nicely. It duplicates the renderList() and executeList() methods until Ryan makes those two improvements to the renderList() method. This allows to search users (username requires direct hit, other fields use ^= operator). It searches those fields that are chosen from module settings (the same ones that are visible). You can also combine text search and role filters. To try this out, just install the module and then edit your /processwire/access/users/ page and change the process to ProcessUserExtended (page is locked by default, so open the lock). You cannot create new page with different name (like users-extended) and try it there, since ProcessPageType excepts to have pageName which is also fuel (like users, roles etc). Grab the code: <?php class ProcessUserExtended extends ProcessUser { static public function getModuleInfo() { return array( 'title' => __('Users extended', __FILE__), // getModuleInfo title 'version' => 100, 'summary' => __('Extended view for user management', __FILE__), // getModuleInfo summary 'permanent' => false, 'permission' => 'user-admin', ); } public function ___execute() { $out = ''; $wrapper = new InputfieldWrapper; $fields = $this->modules->get("InputfieldFieldset"); $form = $this->modules->get("InputfieldForm"); $form->attr('method', 'get'); $form->attr('id', 'userSearch'); $form->label = $this->_("Filter users"); if (!$this->input->get->roles && !$this->input->get->search) { $form->collapsed = Inputfield::collapsedYes; } $field = $this->modules->get("InputfieldText"); $field->attr('name', 'search'); $field->label = $this->_("Search"); if ($this->input->get->search) { $this->input->whitelist('search', $this->input->get->search); $field->attr('value', htmlentities($this->input->get->search, ENT_QUOTES, 'UTF-8')); } $form->add($field); $field = $this->modules->get("InputfieldCheckboxes"); $field->attr('name', 'roles'); $field->optionColumns = 5; foreach(wire('roles') as $role) { if ($role->name == "guest") continue; $attrs = array(); $this->input->whitelist("roles", $this->input->get->roles); if (is_array($this->input->get->roles)) { if(in_array($role->name, $this->input->get->roles)) { $attrs['selected'] = 'selected'; } } $field->addOption($role->name, $role->get('title|name'), $attrs); } $field->label = $this->_("Filter by role"); $form->add($field); $field = $this->modules->get("InputfieldSubmit"); $field->attr('value', $this->_("Search")); $form->add($field); $fields->add($form); $out .= $fields->render(); $selector = "limit=10, status<" . Page::statusMax; if ($this->input->get->roles) { $roles = new PageArray; foreach($this->input->get->roles as $roleName) { $roles->add($this->roles->get($roleName)); } if ($role->id) $selector .= ", roles=$roles"; } if ($this->input->get->search) { $fieldNames = implode("|", array_diff($this->showFields, array('name', 'created', 'modified', 'roles'))); $search = $this->sanitizer->selectorValue($this->input->get->search); $directHit = $this->pages->get("name=$search"); if ($directHit->id) $out .= "<h3>". $this->_('Found by username') .": <a href='./edit/?id=$directHit->id'>$directHit->name</a></h3>"; $selector .= ", $fieldNames^=$search"; } return $out . $this->renderList($selector, array("arrayToCSV" => false)); } public function ___executeList() { return $this->renderList("limit=25, status<" . Page::statusMax); } protected function renderList($selector, $pagerOptions = null) { $out = ''; if(!$this->pages->getTemplate()) { $form = $this->getTemplateFilterForm(); $out = $form->render(); } $table = $this->modules->get("MarkupAdminDataTable"); $table->setEncodeEntities(false); $fieldNames = $this->showFields; $fieldLabels = $fieldNames; foreach($fieldLabels as $key => $name) { if($name == 'name') { $fieldLabels[$key] = $this->_('Name'); // Label for 'name' field continue; } $field = wire('fields')->get($name); $languageID = wire('user')->language ? wire('user')->language->id : ''; $label = $field->get('label' . $languageID); if(!$label) $label = $field->label; if(!$label) $label = $name; $fieldLabels[$key] = htmlentities($label, ENT_QUOTES, "UTF-8"); } $table->headerRow($fieldLabels); $pages = $this->pages->find($selector); foreach($pages as $page) { if(!$page->editable()) continue; $n = 0; $row = array(); foreach($fieldNames as $name) { if(!$n) $row[$page->get($name) . ' '] = "edit/?id={$page->id}"; else $row[] = $this->renderListFieldValue($name, $page->get($name)); $n++; } $table->row($row); } if($this->page->addable()) $table->action(array($this->_('Add New') => 'add/')); if($pages->getTotal() > count($pages)) { $pager = $this->modules->get("MarkupPagerNav"); $out .= $pager->render($pages, $pagerOptions); } $out .= $table->render(); return $out; } }
    1 point
×
×
  • Create New...