diff --git a/wire/core/PageFinder.php b/wire/core/PageFinder.php index 0bb90c7e..ce44dd60 100644 --- a/wire/core/PageFinder.php +++ b/wire/core/PageFinder.php @@ -159,6 +159,7 @@ class PageFinder extends Wire { protected $extraOrSelectors = array(); // one from each field must match protected $sortsAfter = array(); // apply these sorts after pages loaded protected $reverseAfter = false; // reverse order after load? + protected $pageArrayData = array(); // any additional data that should be populated back to any resulting PageArray objects // protected $extraSubSelectors = array(); // subselectors that are added in after getQuery() // protected $extraJoins = array(); @@ -377,6 +378,7 @@ class PageFinder extends Wire { $this->templates_id = null; $this->checkAccess = true; $this->getQueryNumChildren = 0; + $this->pageArrayData = array(); $this->setupStatusChecks($selectors, $options); // move getTotal option to a class property, after setupStatusChecks @@ -514,7 +516,7 @@ class PageFinder extends Wire { $eq = null; foreach($selectors as $selector) { - $field = $selector->field; + $field = $selector->field(); if($field === '_custom') { $selectors->remove($selector); @@ -548,8 +550,11 @@ class PageFinder extends Wire { } $selectors->remove($selector); - } else if(strpos($selector->field(), '.owner.') && !$this->wire('fields')->get('owner')) { + } else if(strpos($field, '.owner.') && !$this->wire('fields')->get('owner')) { $selector->field = str_replace('.owner.', '__owner.', $selector->field()); + + } else if(stripos($field, 'Fieldtype') === 0) { + $this->preProcessFieldtypeSelector($selectors, $selector); } } @@ -651,6 +656,117 @@ class PageFinder extends Wire { $this->start = $start; } + /** + * Pre-process a selector having field name that begins with "Fieldtype" + * + * @param Selectors $selectors + * @param Selector $selector + * + */ + protected function preProcessFieldtypeSelector(Selectors $selectors, Selector $selector) { + + $foundFields = null; + $foundTypes = null; + $replaceFields = array(); + $failFields = array(); + /** @var Languages|null $languages */ + $languages = $this->wire('languages'); + $selectorCopy = null; + + foreach($selector->fields() as $fieldName) { + + $subfield = ''; + $findPerField = false; + $findExtends = false; + + if(strpos($fieldName, '.')) { + $parts = explode('.', $fieldName); + $fieldName = array_shift($parts); + foreach($parts as $k => $part) { + if($part === 'fields') { + $findPerField = true; + unset($parts[$k]); + } else if($part === 'extends') { + $findExtends = true; + unset($parts[$k]); + } + } + if(count($parts)) $subfield = implode('.', $parts); + } + + $fieldtype = $this->wire('fieldtypes')->get($fieldName); + if(!$fieldtype) continue; + $fieldtypeLang = $languages ? $this->wire('fieldtypes')->get("{$fieldName}Language") : null; + + foreach($this->wire('fields') as $f) { + + if($findExtends) { + // allow any Fieldtype that is an instance of given one, or extends it + if(!wireInstanceOf($f->type, $fieldtype) + && ($fieldtypeLang === null || !wireInstanceOf($f->type, $fieldtypeLang))) continue; + + } else { + // only allow given Fieldtype + if($f->type !== $fieldtype && ($fieldtypeLang === null || $f->type !== $fieldtypeLang)) continue; + } + + $fName = $subfield ? "$f->name.$subfield" : $f->name; + + if($findPerField) { + if($selectorCopy === null) $selectorCopy = clone $selector; + $selectorCopy->field = $fName; + $selectors->replace($selector, $selectorCopy); + $count = $this->wire('pages')->count($selectors); + $selectors->replace($selectorCopy, $selector); + if($count) { + if($foundFields === null) { + $foundFields = isset($this->pageArrayData['fields']) ? $this->pageArrayData['fields'] : array(); + } + // include only fields that we know will match + $replaceFields[$fName] = $fName; + if(isset($foundFields[$fName])) { + $foundFields[$fName] += $count; + } else { + $foundFields[$fName] = $count; + } + } else { + $failFields[$fName] = $fName; + } + } else { + // include all fields (faster) + $replaceFields[$fName] = $fName; + } + + if($findExtends) { + if($foundTypes === null) { + $foundTypes = isset($this->pageArrayData['extends']) ? $this->pageDataArray['extends'] : array(); + } + $fType = $f->type->className(); + if(isset($foundTypes[$fType])) { + $foundTypes[$fType][] = $fName; + } else { + $foundTypes[$fType] = array($fName); + } + } + } + } + + if(count($replaceFields)) { + $selector->fields = array_values($replaceFields); + } else if(count($failFields)) { + // forced non-match and prevent field-not-found error after this method + $selector->field = reset($failFields); + } + + if(is_array($foundFields)) { + arsort($foundFields); + $this->pageArrayData['fields'] = $foundFields; + } + if(is_array($foundTypes)) { + $this->pageArrayData['extends'] = $foundTypes; + } + } + /** * Pre-process the given selector to perform any necessary replacements @@ -2456,5 +2572,19 @@ class PageFinder extends Wire { return true; } + + /** + * Get data that should be populated back to any resulting PageArray’s data() method + * + * @param PageArray|null $pageArray Optionally populate given PageArray + * @return array + * + */ + public function getPageArrayData(PageArray $pageArray = null) { + if($pageArray !== null && count($this->pageArrayData)) { + $pageArray->data($this->pageArrayData); + } + return $this->pageArrayData; + } } diff --git a/wire/core/PagesLoader.php b/wire/core/PagesLoader.php index 60581e55..73e58743 100644 --- a/wire/core/PagesLoader.php +++ b/wire/core/PagesLoader.php @@ -335,6 +335,7 @@ class PagesLoader extends Wire { $pages = $this->pages->newPageArray($loadOptions); } + $pageFinder->getPageArrayData($pages); $pages->setTotal($total); $pages->setLimit($limit); $pages->setStart($start); diff --git a/wire/core/WireArray.php b/wire/core/WireArray.php index 16877323..db2ae5bf 100644 --- a/wire/core/WireArray.php +++ b/wire/core/WireArray.php @@ -2101,19 +2101,55 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count * #pw-group-other-data-storage * #pw-link [Introduction of data method](https://processwire.com/talk/topic/5098-new-wirearray-api-additions-on-dev/) * - * @param string|null $key Name of data property you want to get or set. Omit to get all data properties. - * @param mixed|null $value Value of data property you want to set. Omit when getting properties. + * @param string|null|array|bool $key Name of data property you want to get or set, or: + * - Omit to get all data properties. + * - Specify associative array of [property => value] to set multiple properties. + * - Specify associative array and boolean TRUE for $value argument to replace all data with the new array given in $key. + * - Specify regular array of property names to return multiple properties. + * - Specify boolean FALSE to unset property name specified in $value argument. + * @param mixed|null|bool $value Value of data property you want to set. Omit when getting properties. + * - Specify boolean TRUE to replace all data with associative array of data given in $key argument. * @return WireArray|mixed|array|null Returns one of the following, depending on specified arguments: * - `mixed` when getting a single property: whatever you set is what you will get back. * - `null` if the property you are trying to get does not exist in the data. * - `$this` reference to this WireArray if you were setting a value. - * - `array` of all data if you specified no arguments. + * - `array` of all data if you specified no arguments or requested multiple keys. * */ public function data($key = null, $value = null) { - if(is_null($key) && is_null($value)) return $this->extraData; - if(is_null($value)) return isset($this->extraData[$key]) ? $this->extraData[$key] : null; - $this->extraData[$key] = $value; + if($key === null && $value === null) { + // get all properties + return $this->extraData; + } else if(is_array($key)) { + // get or set multiple properties + if($value === true) { + // replace all data with data in given $key array + $this->extraData = $key; + } else { + // test if array is associative + if(ctype_digit(implode('0', array_keys($key)))) { + // regular, non-associative array, GET only requested properties + $a = array(); + foreach($key as $k) { + $a[$k] = isset($this->extraData[$k]) ? $this->extraData[$k] : null; + } + return $a; + } else if(count($key)) { + // associative array, setting multiple values to extraData + $this->extraData = array_merge($this->extraData, $key); + } + } + } else if($key === false && is_string($value)) { + // unset a property + unset($this->extraData[$value]); + + } else if($value === null) { + // get a property + return isset($this->extraData[$key]) ? $this->extraData[$key] : null; + } else { + // set a property + $this->extraData[$key] = $value; + } return $this; }