blynx

Custom PHP code selector

Recommended Posts

Hej,

i am trying to build a custom PHP code selector to get all pages using a user template for a site using multiple templates for users.

Here are my two non-working approaches:

/**
 * A
 */
return function() {
$template_ids = implode("|", $config->userTemplateIDs);
return $pages->find("template=$template_ids, include=all");
};


/**
 * B
 */
return $pages->find("template=".implode("|", $config->userTemplateIDs).", include=all");

Maybe I am thinking too complicated and I miss something here - basically I need a Page Field which can select from all users with all the different user templates ...
uhm - has anyone an idea how this might work?

Also in general - has anyone an idea what is actually possible with this custom php code selector? I guess it is somewhat restrained?

Thanks!
Steffen

EDIT:

I guess that I can't access $config?

Share this post


Link to post
Share on other sites

I think that context can't access the $config variable. You could try wire('config')...

What else: I'm not fully 100% sure that "template="..." would work you could also try "template.id="...".

  • Like 1

Share this post


Link to post
Share on other sites

The custom php code is executes as part of InputfieldPage and therefore all api variables are accessable as $this->… . Only $pages as $page are available as local variables.

  • Like 2

Share this post


Link to post
Share on other sites

Yes!

Thanks @LostKobrakai, thats it - should've tried that one too ...
@Martijn Geerts, yep - I also tried wire('config') but that didn't work either – and I was not sure about `template=` vs. `template.id=`, too ... both work though ;)

PS:

Is there actually some kind of summarized documentation of where which variables are scoped and why? Like to get to know some underlying concepts of processwire? Or a forums thread where things like that are explained more thoroughly?

  • Like 1

Share this post


Link to post
Share on other sites

There are basically four different environments to talk about.

  1. TemplateFile
    TemplateFile is core class, which does include your template files, but does also power things like $page->render(), wireRenderFile(), wireIncludeFile() and others. This class will automatically make all API Variables available as local variables. Meaning all those included files can directly use $pages, $page, … . It won't work inside any functions defined in those files, because of the way PHP does scope variables.
  2. Classes extending Wire
    Every class, which is extending the Wire class will have access to API variables via $this->pages, $this->page, if the properties or __get() are not overwritten. Also there's $this->wire('pages'), which is less likely to be overwritten (even some core classes need to use $this->wire()).
  3. Anonymous functions
    There are some methods in the processwire code, which accept anonymous functions as parameters (e.g. WireCache). Those anonymous functions can retrieve api variables as parameters, even though I'm not sure how exactly this is working or if that's often used.
  4. Everywhere else
    Outside of wire classes, template files and special anonymous functions there's the wire() function, which is basically the same as $this->wire() in wire classes, but as a global function, which is accessable anywhere after pw is started.
    With PW 3.0 and multi-instance this is the option to avoid.

Places, which technically are number 2, but it may not be apparent to everybody:

Custom PHP code for fields like FieldtypePage or FieldtypeMarkup:
As those PHP code is evaluated/run inside those (input)field classes there's (besides manually available local api variables) always the option to use $this->pages or $this->wire('pages').

All those template files included by the TemplateFile class:
For the same reason as above there's not only $pages available in those template files, but also $this->pages, $this->wire('pages').

  • Like 10

Share this post


Link to post
Share on other sites
On 11. August 2016 at 8:47 PM, LostKobrakai said:

Everywhere else
[...]

With PW 3.0 and multi-instance this is the option to avoid.

Great overview, Benjamin!

Have you any reference on this or could you explain why we should avoid wire(). What's the best way to go, using $this?

Share this post


Link to post
Share on other sites

It's because all other options are aware of their instance (e.g. a page of instance1 will get the configs of instance1), whereas the global wire function does not have state and therefore does only ever return the "primary" instance, which is simply the first one ever instantiated.

Edit: Maybe 'avoid' it to hard, but one needs to be aware that the function is not aware of additional instances. Especially in hooks or alike, where people are currently often using wire() one should rather use $event->wire() so the hook is not prone to using a different instance than intended.

  • Like 4

Share this post


Link to post
Share on other sites
29 minutes ago, LostKobrakai said:

It's because all other options are aware of their instance (e.g. a page of instance1 will get the configs of instance1), whereas the global wire function does not have state and therefore does only ever return the "primary" instance, which is simply the first one ever instantiated.

Thanks for the explanations. Is there an instance-aware way to get API variables in function scope?

Share this post


Link to post
Share on other sites

There isn't one, other than passing the instance into the function as parameter. But really a class is way better in handling this kind of state. 

<?php namespace ProcessWire;

class Helpers extends Wire{
	
	public function getGlobalSeo()
	{
		return $this->wire('pages')->get('/')->seo;
	}
}

In the init.php

$helpers = $this->wire(new Helpers); // Wire the instance into the helpers class
$this->wire('helpers', $helpers); // Add as api variable

In your templates

$seo = $helpers->getGlobalSeo();

 

  • Like 4

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 Alex CA
      In my search.php template, I am using 
      $selector = "title|body%=$search_term, limit=50"; to find the top 50 search results for the page.
      I implemented this following this link https://processwire.com/api/selectors/
      My question is how does the selector sort its results? Does it look at the number of time the $search_term has occurred in the page or does it start searching from the root page to all the children.
      Also, will this change if I do not put any limits to the search results?
       
    • By mattcohen
      Hi guys, I'd really appreciate some help from you. I am wondering how I would about using the Selector field type within a repeater.
      It seems kinda bad design to have multiple selectors (e.g. Grid 1, Grid 2, Grid 3) when I could use a repeater for it.
      Using Ryan's example, I have managed to get my repeater to work
      foreach($page->grid_repeater as $grids) { echo "<h2>{$grids->grid_type}</h2><p>"; echo " }  
    • By Barry
      I'm trying to figure out how to create a selector that will find pages where a certain text field is either empty of unique. I have about 3500 records where the majority of have a specific field which is simply empty, there some records however that will have a certain value filled in this same field and some of these values are the same. I want to only select the first page which is found with this value and exclude the rest of these pages with the same value.
      Any ideas?
    • By sirhc
      Hi all,
      Im looking for a selector that gets the pages added a specific time frame (eg. from yesterday 9:00pm to today 9:00pm).
      Is there a existing selector im missing or does someone now a good solution for this?
      Thanks in advance .
    • By dscONE
      Found myself needing to filter repeater items based upon start and end date fields.  These fields aren't required, so repeater items with no dates should be included and those with dates (or a just one date -- start or end) should be evaluated for inclusion.
      I slowly figured out that I was dealing with in-memory filtering (of a RepeaterPageArray) and not a database-driven operation.  So, that eliminated the possibility of using or-groups in my selector.  I still thought I could use the 'filter' and 'not' functions to get the job done.  Turns out the way selectors are handled for in-memory filtering is quite different from database-driven selectors.
      Here is what I want(ed) to do to filter the dates:
      // Start date not specified or in the past $repeater->filter('start_time<='.$now); // Not when end date is specified and has passed $repeater->not('end_time>0, end_time<'.$now); The first one worked exactly as expected.  The second it where I ran into problems.  The 'filter' function (which calls the 'filterData' function) takes a $selector argument.  From everything I read about selectors, I assumed that the entire selector would have to match.  In other words, each comma represented an 'and' scenario.  Turns out that each separate comma-separated-selector in the $selector as a whole gets evaluated independently.  In the case of the 'filterData' function, the elements are removed if any one of those selectors matches.  Essentially, we have an 'or' scenario instead of an 'and' scenario.
      In my case above, there was no way for me to filter for a non-empty date AND one that occurs before a given date...at least that I could think of.
      So, my main question is if the filter (and related) function should operate on the entire selector passed to the function instead of its individual parts in some sequence.  That is what I would have assumed a selector to do.
      In my project, altering the segment of the WireArray::filterData function solved my problem for now.  ...at least till I forget about it and overwrite it when I upgrade next.  Here is the code I changed within the function
      // now filter the data according to the selectors that remain foreach($this->data as $key => $item) { $filter_item = true; foreach($selectors as $selector) { if(is_array($selector->field)) { $value = array(); foreach($selector->field as $field) $value[] = (string) $this->getItemPropertyValue($item, $field); } else { $value = (string) $this->getItemPropertyValue($item, $selector->field); } if($not === $selector->matches($value)) continue; $filter_item = false; break; } if($filter_item && isset($this->data[$key])) { $this->trackRemove($this->data[$key], $key); unset($this->data[$key]); } } I would love to hear what you all think.  If I have misunderstood something, then I would welcome a different solution.
      Thanks!