Macrura

Settings Factory

Recommended Posts

Hi @Macrura

Do you have plans to support ML in your module?

I meen support of 

'useLanguages' => true,

in field config.

Eugene. 

Share this post


Link to post
Share on other sites

@Zeka - as far as i know this module shouldn't prevent you from using languages, since you create the inputfields, and the module does not limit what you put in the config settings for any/each inputfield. Did  you test it already and find it not working (?), if so i can look and see if the issue is with the module, otherwise it should work the same as if you were creating module config settings..

Share this post


Link to post
Share on other sites

@Macrura

Yes, I have tested it and only values for default language are saved. 

As far as I can see the issue is in the processForm method.

Share this post


Link to post
Share on other sites

I'm not experienced with the multilanguage stuff; I'd have to research to see if there is a way to support it...

Share this post


Link to post
Share on other sites

@Macrura
Just made pull request with some modifications to support ML and useLanguages. 
It's quite dirty, but works for me. Maybe you will find it useful.

  • Like 2

Share this post


Link to post
Share on other sites

Hey @Macrura,

I was surprised that the Process module didn't automatically create its own page, so I was a little lost for a minute or two, but otherwise looks great and looking forward to using it - thanks for the hard work!

Share this post


Link to post
Share on other sites

Hi @adrian - thanks for testing this - hoping to submit to modules directory soon. In terms of the process page, i think it is better to not install any, because it may cause more confusion, since the name of the process page is important in the setup.. LMK if you have any ideas about that, or do you think it should install a starter settings page, but as an option?

Share this post


Link to post
Share on other sites

I don't think it needs to be automatic, but perhaps the instructions could be expanded. Currently all they say is:

"Create your processes then enter a path to the json or php definition of the fields in the path field."

and that didn't initially make sense to me. I think spell it out:

1. Create a page under Admin (at the top level or under Setup if you prefer)

2. Save the page and then choose the ProcessSettingsFactory process from the dropdown.

3. Enter the path to youre settings file. Not sure using a placeholder is good the way you did - I actually thought it was already filled out with: settings/site-settings.php

I also think you should say included "samples", not examples, because of the folder they are stored in.

Hope that helps.

  • Like 3
  • Thanks 1

Share this post


Link to post
Share on other sites

@Macrura

I have several hooks to before/after TemplateFile render method and typically I can exclude PW backend like

$templateFile = $event->object;

        if ($templateFile->page->template == 'admin') {
            return;
        }

or

if ($this->wire('page')->template->name == 'admin') {
	return;
}

But these checks fail for rendering settings pages and hooks are executed also for those template files in the admin panel and I get "PHP Notice: Trying to get property of non-object" as $this->wire('page') = null and there is no $event->object->page->template. 

I'm not sure that it is directly relative to SettingsFactory, so I would ask you to check this behavior.

 

 

Share this post


Link to post
Share on other sites

i'm not following 100%; the module assumes you will want the settings everywhere (front/back).

I don't see $this->wire('page') anywhere in the module, can you provide more details?

By the way, the flowchart for the (non-process) module is like this:

(1) find all admin pages using ProcessSettingsFactory
(2) process each settings page and store the settings

(these 2 procedures run in the buildFactory() method which runs on init(); and this module is autoload).

This procedure might be able to be more optimized, but does ensure that any changes you make to your files (like adding inputfields) are reflected. Maybe checking the last mod date; or maybe a settings lock field on the process that prevents the file from being further processed and wirecache the array from the file;

 

Share this post


Link to post
Share on other sites

@Macrura

For example, I have this hook in module

public function init()
{
  $this->addHookAfter('TemplateFile::render', $this, 'hookAfterTemplateRender');
}

public function hookAfterTemplateRender($event)
{
  if ($this->wire('page')->template->name == 'admin') {
  return;
  }

  // some logic which I don't want in admin pages
}

You use WireRenderFIle which is the shortcut of TemplateFile class. In that way, my hook executes also for calls of WireRenderFile method in the module and I get mentioned notices as $thiss->wire('page') is null.

So the issue is that I can't find the way how to check and prevent the execution of hook for calls of wireRenderFile. Hope it explains what I mean. 

Share this post


Link to post
Share on other sites

@Zeka, maybe you can use the filename, prependFilename or appendFilename properties of the TemplateFile to limit where your hook applies. E.g.

$wire->addHookAfter('TemplateFile::render', function(HookEvent $event) {
    $template_file = $event->object;
    $proceed = false;
    foreach($template_file->appendFilename as $filename) {
        if(substr($filename, -9) === '_main.php') $proceed = true;
    }
    if(!$proceed) return;
    // Your code...
});

I'm curious - what's your reason for hooking after TemplateFile::render? If you are targeting non-admin template files inside your /site/ why wouldn't you include whatever logic you need inside the template files rather than via a hook?

  • Like 1

Share this post


Link to post
Share on other sites

Hadn't predicted that need, but i can look into it...

  • Like 1

Share this post


Link to post
Share on other sites

@Macrura - Great looking module!  Couple of questions for you:

Can you perform any validations when using this module?  Example: Say you have a settings field for integers.  Is there a way to verify that the integer is greater than 10?

Can each settings page be restricted by some kind of role or permission?  Ex. Site editors could access/edit a "Theme settings" page, but couldn't access/edit the "API Settings" page.

Share this post


Link to post
Share on other sites
33 minutes ago, gmclelland said:

Can you perform any validations when using this module?  Example: Say you have a settings field for integers.  Is there a way to verify that the integer is greater than 10?

yes, since you create the inputfields, they support all built in attributes, so you'd just need to set those attributes in your PHP or JSON array.

33 minutes ago, gmclelland said:

Can each settings page be restricted by some kind of role or permission?  Ex. Site editors could access/edit a "Theme settings" page, but couldn't access/edit the "API Settings" page. 

Yes, each settings page is full access controllable.

  • Thanks 1

Share this post


Link to post
Share on other sites

there a video or tutorial with screens for this module? I feel like I'd love to use it but cant visualise how it works in my head.

Share this post


Link to post
Share on other sites
21 hours ago, benbyf said:

there a video or tutorial with screens for this module? I feel like I'd love to use it but cant visualise how it works in my head.

ok yeah, I'll see if i can improve the readme and i'll post a walkthrough here with some screenshots shortly...

In the meantime a lot of the info in this post is still relevant:

 

  • Like 1

Share this post


Link to post
Share on other sites

The process of setting up a settings page is very simple; You need to start with a JSON or PHP fields definition file; There are samples of these in the module. Once you have your file in valid JSON, or PHP that returns an array of inputfields, you would create a page somewhere under your Admin branch.

You can name this whatever you want, but bear in mind that the name of the process page is how you will access your settings. Once you create the page (using Admin template), and set the process as "ProcessSettingsFactory", you should see a blank text field; In this field you enter the path to your definition file, which should be located somewhere in templates.

At this point you can now access your settings page and see if it works (you should see your fields). You can populate the fields, and save, and your data will save in the module config.

You can now access your settings on the front end by doing this:

$factory = $modules->get("SettingsFactory");
$newsSettings = $factory->getSettings('news-settings');// where news-settings is the name of the process page holding the settings you want to get,

You can view the contents of what is returned Tracy (e.g. bd($newsSettings);)

So far this has been rock solid on some very big sites, both in terms of users accessing and modifying settings, as well as dealing with permissions to the settings, and outputting the settings in the front end; Haven't noticed any performance issues, and some sites have 5-6 different settings panels.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Noel Boss
      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: 

       
    • By Noel Boss
      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
    • By Eunico Cornelius
      Hi, I am trying to create a PHP page in my website that is hidden and produce a plain JSON format. However, when I implement json_encode on my string, the result is not only a plain JSON but also a <!DOCTYPE HTML>.... I have created a new page and a new template that has it's cache disabled, but it still doesn't work.
      this is what my code looks like 
      <?PHP $useMain=false; $data = "[{'title':'AAAAA','url':'https://google.com'}]"; header('Content-Type: application/json'); echo json_encode($data); ?> This is what I currently get
      "[{'title':'AAAAA','url':'https:\/\/google.com'}]"<!DOCTYPE html><html>....</html> This is what I am trying to achieve
      "[{'title':'AAAAA','url':'https:\/\/google.com'}]" What am I missing here? I am also using ProCache.(dont know if it has any influence since I created and disable cache in a new template)
    • By h365
      Hi Guys,
      So i have a big piece of Json data for a Travel - Tour 
      all kinds of rates, dates, services and so on.. 
      The Structure of the Json slightly changes here and there depending on the Tour. 
      What would be the best way to import that data in processwire to make it easily searchable etc trough the API.. ? 
      I tried Multi Level Repeater Matrixes which are good and would do the job, but then i have no big options to search trough that data.. 
      Would really appreciate all input!
      Thanks so much
    • By dragan
      I just noticed that PW will output frontend-editing markup, even if I output JSON.
      Is there a way I can disable that, from inside my template?
      I use classic switch/case statements and URL-segments to output different content, HTML + JSON, in the same template. I know that my JS modules will not see all that extra markup, and guest users neither.
      [{"title":"Joe Doe","firstName":"<span id=pw-edit-1 class='pw-edit pw-edit-InputfieldText' data-name=firstName data-page=1161 style='position:relative'><span class=pw-edit-orig>Joe<\/span><span class=pw-edit-copy id=pw-editor-firstName-1161 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>Joe<\/span><\/span>","lastName":"<span id=pw-edit-2 class='pw-edit pw-edit-InputfieldText' data-name=lastName data-page=1161 style='position:relative'><span class=pw-edit-orig>Doe<\/span><span class=pw-edit-copy id=pw-editor-lastName-1161 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>Doe<\/span><\/span>","teamEmail":"<span id=pw-edit-3 class='pw-edit pw-edit-InputfieldEmail' data-name=teamEmail data-page=1161 style='position:relative'><span class=pw-edit-orig>akbari@inspire.ethz.ch<\/span><span class=pw-edit-copy id=pw-editor-teamEmail-1161 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>joe.doe@foo.ch<\/span><\/span>","office":"<span id=pw-edit-4 class='pw-edit pw-edit-InputfieldText' data-name=office data-page=1161 style='position:relative'><span class=pw-edit-orig>PFA E 91<\/span><span class=pw-edit-copy id=pw-editor-office-1161 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>PFA E 91<\/span><\/span>","phone":"<span id=pw-edit-5 class='pw-edit pw-edit-InputfieldText' data-name=phone data-page=1161 style='position:relative'><span class=pw-edit-orig>+41 44 123 45 67<\/span><span class=pw-edit-copy id=pw-editor-phone-1161 style='display:none;-webkit-user-select:text;user-select:text;' contenteditable>+41 44 123 45 67<\/span><\/span>"}] But when working on a PW site, it would be cool to somehow not see this.
      Is there a setting for this? Do I have to add something to my selector?