From 16a8232dfef74abcf20b5c2db6606eadfe5bb6e7 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 6 Oct 2016 10:13:14 -0400 Subject: [PATCH] Update the Page::matches() method to make it smarter and more flexible. This also accommodates issue #21. --- wire/core/PageComparison.php | 165 +++++++++++++++--- .../InputfieldPage/InputfieldPage.module | 11 +- 2 files changed, 146 insertions(+), 30 deletions(-) diff --git a/wire/core/PageComparison.php b/wire/core/PageComparison.php index d3cb73de..b1164bfd 100644 --- a/wire/core/PageComparison.php +++ b/wire/core/PageComparison.php @@ -48,14 +48,28 @@ class PageComparison { * */ public function matches(Page $page, $s) { + + $selectors = array(); if(is_string($s) || is_int($s)) { if(ctype_digit("$s")) $s = (int) $s; if(is_string($s)) { - // exit early for simple path comparison - if(substr($s, 0, 1) == '/' && $page->path() == (rtrim($s, '/') . '/')) return true; - if(!Selectors::stringHasOperator($s)) return false; - $selectors = $page->wire(new Selectors($s)); + if(!strlen($s)) { + // blank string matches nothing + return false; + } else if(substr($s, 0, 1) == '/' && $page->path() == (rtrim($s, '/') . '/')) { + // exit early for simple path comparison + return true; + } else if($page->name === $s) { + // early exit for simple name atch + return true; + } else if(Selectors::stringHasOperator($s)) { + // selectors string + $selectors = $page->wire(new Selectors($s)); + } else { + // some other type of string + return false; + } } else if(is_int($s)) { // exit early for simple ID comparison @@ -66,39 +80,27 @@ class PageComparison { $selectors = $s; } else { + // unknown data type to match return false; } $matches = false; + $ignores = array('limit', 'start', 'sort', 'include'); foreach($selectors as $selector) { - $name = $selector->field; - if(in_array($name, array('limit', 'start', 'sort', 'include'))) continue; - $matches = true; - $value = $page->getUnformatted($name); + $property = $selector->field; + $subproperty = ''; + if(strpos($property, '.')) list($property, $subproperty) = explode('.', $property, 2); + if(in_array($property, $ignores)) continue; + + $matches = true; + $value = $page->getUnformatted($property); + if(is_object($value)) { - // if the current page value resolves to an object - if($value instanceof Page) { - // if it's a Page, get both the ID and path as allowed comparison values - $value = array($value->id, $value->path); - } else if($value instanceof PageArray) { - // if it's a PageArray, then get the ID and path of all of them - // @todo add support for @ selectors - $_value = array(); - foreach($value as $v) { - $_value[] = $v->id; - $_value[] = $v->path; - } - $value = $_value; - } else if($value instanceof Template) { - $value = array($value->id, $value->name); - } else { - // otherwise just get the string value of the object - $value = "$value"; - } - + // convert object to array value(s) + $value = $this->getObjectValueArray($value, $subproperty); } else if(is_array($value)) { // ok: selector matches will accept an array } else { @@ -115,5 +117,112 @@ class PageComparison { return $matches; } + /** + * Given an object, return the value(s) it represents (optionally from a property in the object) + * + * This method is setup for the matches() method above this. It will go recursive when given a property + * that resolves to another object. + * + * @param Wire|object $object + * @param string $property Optional property to pull from object (may also be property.subproperty, and so on) + * @return array Always returns an array, which may be empty or populated + * + */ + protected function getObjectValueArray($object, $property = '') { + + $value = array(); + $_property = $property; // original + $subproperty = ''; + if(strpos($property, '.')) list($property, $subproperty) = explode('.', $property, 2); + + // if the current page value resolves to an object + if($object instanceof Page) { + // object is a Page + if($property) { + // pull specific property from page + $v = $object->getUnformatted($property); + if(is_object($v)) { + $value = $this->getObjectValueArray($v, $subproperty); + } else if(!is_null($v)) { + $value = array($v); + } + } else { + // if no property, get id, name and path as allowed comparison values + $value[] = $object->id; + $value[] = $object->path; + $value[] = $object->name; + } + + } else if($object instanceof WireArray) { + // it's a WireArray|PageArray + + if($property === 'count') { + // quick exit for count property + return array(count($object)); + } + + // iterate and get value of each item present + foreach($object as $v) { + if(is_object($v)) { + $v = $this->getObjectValueArray($v, $_property); // use original property.subproperty + if(count($v)) $value = array_merge($value, $v); + } else { + $value[] = $v; + } + } + + } else if($object instanceof Template) { + // Template object, compare to id and name + if($property) { + $v = $object->get($property); + if(!is_null($v)) $value[] = $v; + } else { + $value[] = $object->id; + $value[] = $object->name; + } + + } else if($object instanceof WireData) { + // some other type of WireData object + if($property) { + $v = $object->get($property); + if(is_object($v)) { + $value = $this->getObjectValueArray($v, $subproperty); + } else if(!is_null($v)) { + $value = array($v); + } + + } else { + // no property present, so we'll find some other way to identify the object + // get string value of object as a potential comparison + $v = (string) $object; + // string value that doesn't match class name identifies the object in some way + if($v !== wireClassName($object)) $value[] = $v; + // if the object uses the common 'id' or 'name' properties, consider those as well + foreach(array('id', 'name') as $key) { + $v = $object->get($key); + if(!is_null($v)) $value[] = $v; + } + } + + } else if($property && method_exists($object, '__get')) { + // some other object, property is present, object has a __get method that we can pull it from + $v = $object->__get($property); + if(is_object($v)) { + $value = $this->getObjectValueArray($v, $subproperty); + } else if(!is_null($v)) { + $value = array($v); + } + + } else if(!$property && method_exists($object, '__toString')) { + // items in WireArray are some type of Wire, use string value if not className + if(wireClassName($object) != (string) $object) $value[] = (string) $object; + + } else { + // property present with some kind of value that we don't know how to pull from + } + + return $value; + } + } diff --git a/wire/modules/Inputfield/InputfieldPage/InputfieldPage.module b/wire/modules/Inputfield/InputfieldPage/InputfieldPage.module index d6172381..cae8a24f 100644 --- a/wire/modules/Inputfield/InputfieldPage/InputfieldPage.module +++ b/wire/modules/Inputfield/InputfieldPage/InputfieldPage.module @@ -225,8 +225,15 @@ class InputfieldPage extends Inputfield implements ConfigurableModule { $selector = $findPagesSelector; if($editPage) $selector = self::getFindPagesSelector($editPage, $selector); if(!$page->matches($selector)) { - if($editPage) $editPage->set('_isValidPage', "Page $page does not match findPagesSelector: $selector"); - $valid = false; + // failed in-memory check, attempt $page->count() check... + $selector .= ", id=$page->id"; + if($page->wire('pages')->count($selector)) { + // looks like its okay + } else { + // also fails $pages->cont() check, so definitely not valid + if($editPage) $editPage->set('_isValidPage', "Page $page does not match findPagesSelector: $selector"); + $valid = false; + } } }