Jump to content

Noel Boss

Members
  • Posts

    170
  • Joined

  • Last visited

  • Days Won

    6

Everything posted by Noel Boss

  1. Thank you for the detailed response @kongondo ! The above code does not work, the plugin extends $page so you can directly call ‚pageQueryJson‘ on a Page or a PageArray you call it - altough you need to specify what results you‘d like to receive; $json = $pages->find('template=skyscraper')->pageQueryJson(['title']); from what you describe, it probably does not need to autoload I’ll change that with the next release. Thanks again!
  2. Thank you @kongondo No, no reason – I'm new to PW and Module development and had issues with Modules without autoload before – and the module I was taking clues from had autoload true as well. I probably have to look into it a bit more. Any good resources about that? Should I not use it?
  3. 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
  4. Hi, i run into problems trying to dump stuff inside a hook attached by a module. Any idea what I am doing wrong? public function init() { // act on invite request wire('forms')->addHookBefore('FormBuilderProcessor::processInput', $this, 'processRequestIcfAccess'); } public function processRequestIcfAccess(HookEvent $event) { // grab the data $church = $form->get('church_relation')->value; \TD::dump($church); } Fatal error: Uncaught Error: Class 'TD' not found in /home/ubuntu/workspace/www/site/modules/IcfModules/IcfProcessInviteRequest.module:115 Stack
  5. Shouldn't it work like so; $amenities = implode(', amenities.title=', $yourArray); // starts with a comma // should result in ", amenities.title=$array[0], amenities.title=$array[1], amenities.title=$array[2]" … $pages->find("parent=1031, start=1, limit=6, sort=title, category=1111 {$amenities}, status<1024"); I'm looking at the debug output where you have the array with the two titles… Or am I missing the point?
  6. You could probably implode the array with , amenities.title= as glue.
  7. I ? markup regions. I found Processwire arround a year ago when they where introduced and used MUR exclusively since. I use it like a template engine with layouts like this: //1. Append file app/index.php > Append file, init, loads Component() & Snippet() Methods app/theme/componen.php /snippet.php // 2. ProcessWire Template, extends layout Template.php > extends markup regions, can define layout or false // Template uses Components and Snipptes: src/components/component/component.php /component-varian.php /component.js > compiled by gulp /component.scss > compiled by gulp src/snippets/snippet/snippet.php src/snippets/snippet/snippet-varian.php /snippet.js > compiled by gulp /snippet.scss > compiled by gulp // 3.1 Layout app/layouts.php > Prependfile Handles include of layout: // 3.2 Load markup region layout src/layouts/default.php > Markup Regions /Template.php > Markup Regions Example Template, can assign a $page->layout variable or set it to false. I use custom Component and Snippet methods to render Parts that I use multiple times: <div id='pw-author' pw-remove></div> <div id='pw-header' pw-replace> <?= Component('header', 'profile') ?> // loads src/components/header/profile.php </div> <!-- Adding class to main --> <div id='pw-main' class="uk-padding-remove-bottom" pw-append></div> <div id='pw-body' pw-append> <?= Component('profile', 'page'); ?> // loads src/components/profile/profile-page.php <?= Snippet('buttons', 'logout'); ?> // loads src/snippets/buttons/profile-logout.php </div> <div id="pw-more" pw-remove></div> <div id="pw-aside" pw-remove></div> Prependfile: app/layout.php > this file loads a layout, if there is a layout file with the same name as the Template or a set $pages->layout variable: $layout = isset($page->layout) ? Wire('config')->paths->templates."src/layouts/$page->layout.php" : Wire('config')->paths->templates."src/layouts/{$page->template->name}.php"; $default = Wire('config')->paths->templates.'src/layouts/default.php'; switch (true) { // layout like Page-Template exists case is_file($layout): include $layout; break; // default template case is_file($default): include $default; break; default: return "<div class='uk-alert uk-alert-danger'>could not find layout. Searched for $layout / $default.</div>"; break; } Component Function: app/template/component.php – components should never be nested, can contain snippets (css & js namespaced) namespace ProcessWire; function Component($component, $variation = '', $vars = []) { if (is_array($variation)) { $vars = $variation; $filename = $component; } else { $filename = $variation ? $component.'-'.$variation : $component; } $file = Wire('config')->paths->templates."src/components/$component/$filename.php"; if (is_file($file)) { $vars['skin'] = isset($vars['skin']) ? $vars['skin'] : ''; $vars['class'] = isset($vars['class']) ? $vars['class'] : ''; $out = wireRenderFile($file, $vars); return $out; } wire('log')->error('Could not find '.$file); return null; } Snippet function: app/theme/snippet.php – can be used inside components (css & js namespaced) namespace ProcessWire; function Snippet($name, $file = null, $vars = []) { if (is_array($file)) { $vars = $file; $file = $name; } else { $file = $file ? $name.'-'.$file : $name; } $file = Wire('config')->paths->templates."src/snippets/$name/$file.php"; if (is_file($file)) { return wireRenderFile($file, $vars); } return "<div class='uk-alert uk-alert-danger'>could not find $file.</div>"; }
  8. Nope, this WORKS! Thank you!!!
  9. Hi, I'm stuck since hours and don't know what to do. Here is my Problem: I try to generate Previews of PDF using imagick. I have 4 PDF, I generate a preview of the first page of the pdf, save it to a temporary file and want to import it using the api into an image field. It works for the later 3 pdf but not the first. I add it to the image field and save it. Inside the function that saves it, the image is stored in 'data' as well as in 'itemsAdded' but as soon as i leave the function, its nowhere to be found. Process: 1. create previewimage using imagick and create Pageimage > works 2. add image to filed 'filepreviews', returns Pageimages array with image added > okay 3. save page > returns true 4. Outside renderPreview method, image is not anymore in 'filepreviews' // mymodule // … // foreach($files as $file){ $preview = wire('page')->filepreviews->get('name*='.$file->basename(false)); // if there is no preview image… if (!$preview instanceof Pageimage) { $this->renderPreview($file) // we create one using $this->renderPreview > should return true on successful save dump(wire('page')->filepreviews); // my Image is nowhere to be found $preview = wire('page')->filepreviews->get('name*='.$file->basename(false)); } // end for each // render a preview of an otherwhise not supported file format // return true if sucessfull save private function renderPreview($file) { $page = wire('page'); // get path to temporary image $tempFile = $path.$file->basename(false).'-preview.jpg'; // … some imagick code // … and save it: $imagick->writeImage($tempFile); $img = new Pageimage($page->filepreviews, $tempFile); $img->description = $file->basename(false); // destroy temp image unlink($tempFile); // this is my Pageimage, all good… dump($img); // save image, my Pageimage can be found in data and itemsAdded – all good dump($page->filepreviews->add($img)); $page->of(false); $success = $page->save(); // sucess = true dump($success); return $success; } Second question: Would there a generally better approach? Like using pageFiles somehow. Goal is to be able to use the image api like scale etc – I don't generally need the images to be stored in an image field.
  10. Hi Felix, again thanks for your awesome Plugin! It's really handy! I think i found a bug tough; This link is not correctly converted: `https://www.youtube.com/watch?v=Vbw3dGbWsEM&amp;t=3s` This link works: `https://www.youtube.com/watch?v=Vbw3dGbWsEM&amp;` Added an issue on github: https://github.com/felixwahner/TextformatterOEmbed/issues/9 Keep up the great work!
  11. We have the same issue with files larger than 250MB, our limits are 1Gigabytes on upload_max_filesize and post_max_size only memory_limit is 256M: Could something like this solve the problem for the future? https://stackoverflow.com/questions/5249279/file-get-contents-php-fatal-error-allowed-memory-exhausted?answertab=votes#tab-top
  12. Thanks @bernhard - but i want it to be directly on the page without any iframe or lightbox. I know these solutions but i think there are many scenarios where you would like to CRUD things in the frontend and not have a separate ui or overriding to much. Kind of surprises me this is not a question or need more often. @Macrura thanks! I will try it. Did you use it lately?
  13. Okay, I could probably use: http://modules.processwire.com/modules/form-helper < long time not updated… http://modules.processwire.com/modules/form-template-processor/ < even longer… http://processwire.com/api/modules/form-builder/#about-the-form-builder-project-and-author < using formbuilder with page cration… could be the solution Is there a native way?
  14. @adrian How / Is it possible to easily receive a Template with all it's fields and settings to create a new form in the frontend? Basically the backend form for the frontend. I am also looking to make editing and creation of new Objects possible in the frontend and know how to build a form manually but is there an easy way to more or less get the whole template? Something like: // just made that code up… $form = $templates->get('MyTemplate')->form; $form->get('MyField')->attr('value', "New Value"); $form->render();
  15. Thank you, I did not know this, need to check… Thank you for your heads up!
  16. Sounds very interesting… I wonder if it could remove the need for a (Rest/Graphql) API and directly contact PW from a SPA…
  17. Thanks for all the inputs! Whit it's help, I was able to move the content of the Children Tab to the content Tab and remove the tab, as well as moving the name field below the title or subtitle field. Maybe someone else can use this as well: // Reorder Fields wire()->addHookAfter('ProcessPageEdit::buildForm', function ($event) { // make sure we're editing a page and not a user if ($event->process != 'ProcessPageEdit') { return; } $page = $event->object->getPage(); $form = $event->return; $settingsTab = $form->children->getChildByName('id=ProcessPageEditSettings'); $contentTab = $form->children->getChildByName('id=ProcessPageEditContent'); // move name below title or subtitle foreach ([$settingsTab->getChildByName('_pw_page_name')] as $child) { if ($child) { $child->getParent()->remove($child->name); $child->collapsed = Inputfield::collapsedPopulated; $contentTab->insertAfter($child, $contentTab->get('subtitle|title')); } } // continue only if on certain template if ($page->template->name !== 'ResourceEntry') { return; } // children tab $childrenTab = $form->children->getChildByName('id=ProcessPageEditChildren'); if (!$childrenTab) { return; } // move all content foreach ($childrenTab->children as $child) { if ($child) { // relable "add" buttton if ($child->get('AddPageBtn') instanceof InputfieldButton) { $child->get('AddPageBtn')->attr('value', _('Add New')); } $contentTab->append($child); } } // remove Tab and tab content: $form->remove($childrenTab); $event->object->removeTab('ProcessPageEditChildren'); });
  18. Thank you Theo! What are the main purposes of the module?
  19. I tried to find something in the forum and using google but I got stuck somewhere between imageSizerOptions and https://github.com/processwire/processwire/blob/master/wire/modules/ImageSizerEngineIMagick.module > and??? How exactly can I use actions from the API side? I'd like to convert Images to greyscale… Thanks for your help
  20. I think its time to reconsider the decision to split the repos in light of the hurdles and dissadvanatges separated repos bring for community support. If github cant provide the features required (which are not listed) - gitlab has private repos, protected branches etc etc... great points @StanLindsey thanks for sharing your toughts!
  21. Okay, sorry, didn't know - then its probably the update module that does format the files this way. Never the less, I still think having the date in the filename would be the best default in the majority of cases, since if you want to restore a backup, you‘d probably want to know the date anyway. But no problem for me, I figured it out, its just that I tought maybe that would help some to get started faster. Still great Module without this change.
  22. Yes, it uses # as default name resulting in dbname-1.sql, dbname-2.sql etc – so one can not easily tell how old the date is... Since PW itself already uses a name with date appended when backing up to the same folder on upgrades, why not use the same? The format PW uses: dbname-Version-YYYY-MM-DD_HH-ii-ss.sql processwire-3.0.86-2018-01-09_13-36-54.sql
  23. Thanks for the great Module! How about providing a default name if no name is present? #-%Y-m-d-H-i-s% Or a better description with an example, I had to go into code before I realised that I had to surround "all" of the date format with %. before I tried to surround every parameter (like %Y%-%m% etc)...
×
×
  • Create New...