1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-08 15:57:01 +02:00

Various updates including optimizations to WireHooks, support for a hookable renderReadyHook method in Inputfield, cache prevention measures for Pages::findMany() per @apeisa, and some work in progress on InputfieldSelector support for improved "custom (field=value)" searches.

This commit is contained in:
Ryan Cramer
2016-12-13 14:24:01 -05:00
parent e2e8c35c2c
commit 953ca72014
14 changed files with 393 additions and 100 deletions

View File

@@ -43,17 +43,23 @@ class HookEvent extends WireData {
/**
* Construct the HookEvent and establish default values
*
* @param array $eventData Optional event data to start with
*
*/
public function __construct() {
$this->set('object', null);
$this->set('method', '');
$this->set('arguments', array());
$this->set('return', null);
$this->set('replace', false);
$this->set('options', array());
$this->set('id', '');
$this->set('cancelHooks', false);
public function __construct(array $eventData = array()) {
$data = array(
'object' => null,
'method' => '',
'arguments' => array(),
'return' => null,
'replace' => false,
'options' => array(),
'id' => '',
'cancelHooks' => false
);
if(!empty($eventData)) $data = array_merge($data, $eventData);
$this->data = $data;
}
/**
@@ -202,7 +208,7 @@ class HookEvent extends WireData {
* ~~~~~
*
* @param string|null $hookId
* @return $this
* @return HookEvent|WireData $this
*
*/
public function removeHook($hookId) {
@@ -227,6 +233,5 @@ class HookEvent extends WireData {
return $s;
}
}

View File

@@ -69,7 +69,7 @@
* @property InputfieldWrapper|null $parent The parent InputfieldWrapper for this Inputfield or null if not set. #pw-internal
* @property null|bool|Fieldtype $hasFieldtype The Fieldtype using this Inputfield, or boolean false when known not to have a Fieldtype, or null when not known. #pw-group-other
* @property bool|null $useLanguages When multi-language support active, can be set to true to make it provide inputs for each language, where supported (default=false). #pw-group-behavior
* @property null|bool|int $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label, or set to a Inputfield::textFormat constant. (default=true). #pw-group-output
* @property null|bool|int $entityEncodeLabel Set to boolean false to specifically disable entity encoding of field header/label (default=true). #pw-group-output
* @property null|bool $entityEncodeText Set to boolean false to specifically disable entity encoding for other text: description, notes, etc. (default=true). #pw-group-output
* @property int $renderValueFlags Options that can be applied to renderValue mode, see "renderValue" constants (default=0). #pw-group-output
* @property string $wrapClass Optional class name (CSS) to apply to the HTML element wrapping the Inputfield. #pw-group-other
@@ -80,6 +80,7 @@
* ================
* @method string render()
* @method string renderValue()
* @method void renderReadyHook(Inputfield $parent, $renderValueMode)
* @method Inputfield processInput(WireInputData $input)
* @method InputfieldWrapper getConfigInputfields()
* @method array getConfigArray()
@@ -385,7 +386,7 @@ abstract class Inputfield extends WireData implements Module {
*
* @param string $key Name of property to set
* @param mixed $value Value of property
* @return $this
* @return Inputfield|WireData
*
*/
public function set($key, $value) {
@@ -642,7 +643,7 @@ abstract class Inputfield extends WireData implements Module {
* - String with attributes split by "+" or "|" to set them all to have the same value.
* - Specify boolean true to get all attributes in an associative array.
* @param string|int|null $value Value to set (if setting), omit otherwise.
* @return mixed|$this If setting an attribute, it returns this instance. If getting an attribute, the attribute is returned.
* @return Inputfield|array|string|int|object|float If setting an attribute, it returns this instance. If getting an attribute, the attribute is returned.
* @see Inputfield::removeAttr(), Inputfield::addClass(), Inputfield::removeClass()
*
*/
@@ -702,7 +703,7 @@ abstract class Inputfield extends WireData implements Module {
* - Omit if getting an attribute.
* - Value to set for $key of setting.
* - Boolean false to remove the attribute specified for $key.
* @return string|array|$this Returns one of the following:
* @return Inputfield|string|array|null Returns one of the following:
* - If getting, returns attribute value of NULL if not present.
* - If setting, returns $this.
* @see Inputfield::attr(), Inputfield::addClass()
@@ -1030,9 +1031,24 @@ abstract class Inputfield extends WireData implements Module {
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
if($parent) {}
if($renderValueMode) {}
return $this->wire('modules')->loadModuleFileAssets($this) > 0;
$result = $this->wire('modules')->loadModuleFileAssets($this) > 0;
if($this->wire('hooks')->isMethodHooked($this, 'renderReadyHook')) {
$this->renderReadyHook($parent, $renderValueMode);
}
return $result;
}
/**
* Hookable version of renderReady(), not called unless 'renderReadyHook' is hooked
*
* Hook this method instead if you want to hook renderReady().
*
* @param Inputfield $parent
* @param bool $renderValueMode
*
*/
public function ___renderReadyHook(Inputfield $parent = null, $renderValueMode) { }
/**
* This hook was replaced by renderReady
*
@@ -1502,7 +1518,7 @@ abstract class Inputfield extends WireData implements Module {
* @param string $what Name of property that changed
* @param mixed $old Previous value before change
* @param mixed $new New value
* @return $this
* @return Inputfield|WireData $this
*
*/
public function trackChange($what, $old = null, $new = null) {

View File

@@ -123,6 +123,12 @@ class PageFinder extends Wire {
*
*/
'reverseSort' => false,
/**
* Allow use of _custom="another selector" in Selectors?
*
*/
'allowCustom' => false,
);
@@ -293,7 +299,6 @@ class PageFinder extends Wire {
$sort = $parent->template->sortfield;
if(!$sort) $sort = $parent->sortfield;
if($sort) $selectors->add(new SelectorEqual('sort', $sort));
$hasSort = true;
}
}
@@ -312,23 +317,31 @@ class PageFinder extends Wire {
* @param array $options
* - `findOne` (bool): Specify that you only want to find 1 page and don't need info for pagination (default=false).
* - `findHidden` (bool): Specify that it's okay for hidden pages to be included in the results (default=false).
* - `findUnpublished` (bool): Specify that it's okay for hidden AND unpublished pages to be included in the results (default=false).
* - `findTrash` (bool): Specify that it's okay for hidden AND unpublished AND trashed pages to be included in the results (default=false).
* - `findAll` (bool): Specify that no page should be excluded - results can include unpublished, trash, system, no-access pages, etc. (default=false)
* - `getTotal` (bool|null): Whether the total quantity of matches should be determined and accessible from getTotal() method call.
* - `findUnpublished` (bool): Specify that it's okay for hidden AND unpublished pages to be included in the
* results (default=false).
* - `findTrash` (bool): Specify that it's okay for hidden AND unpublished AND trashed pages to be included in the
* results (default=false).
* - `findAll` (bool): Specify that no page should be excluded - results can include unpublished, trash, system,
* no-access pages, etc. (default=false)
* - `getTotal` (bool|null): Whether the total quantity of matches should be determined and accessible from
* getTotal() method call.
* - null: determine automatically (default is disabled when limit=1, enabled in all other cases).
* - true: always calculate total.
* - false: never calculate total.
* - `getTotalType` (string): Method to use to get total, specify 'count' or 'calc' (default='calc').
* - `returnQuery` (bool): When true, only the DatabaseQuery object is returned by find(), for internal use. (default=false)
* - `loadPages` (bool): This is an optimization used by the Pages::find() method, but we observe it here as we may be able to apply
* some additional optimizations in certain cases. For instance, if loadPages=false, then we can skip retrieval of IDs and omit
* sort fields. (default=true)
* - `stopBeforeID` (int): Stop loading pages once a page matching this ID is found. Page having this ID will be excluded as well (default=0).
* - `startAfterID` (int): Start loading pages once a page matching this ID is found. Page having this ID will be excluded as well (default=0).
* - `loadPages` (bool): This is an optimization used by the Pages::find() method, but we observe it here as we
* may be able to apply some additional optimizations in certain cases. For instance, if loadPages=false, then
* we can skip retrieval of IDs and omit sort fields. (default=true)
* - `stopBeforeID` (int): Stop loading pages once a page matching this ID is found. Page having this ID will be
* excluded as well (default=0).
* - `startAfterID` (int): Start loading pages once a page matching this ID is found. Page having this ID will be
* excluded as well (default=0).
* - `reverseSort` (bool): Reverse whatever sort is specified.
* - `returnVerbose` (bool): When true, this function returns array of arrays containing page ID, parent ID, template ID and score.
* When false, returns only an array of page IDs. True is required by most usage from Pages class. False is only for specific cases.
* - `returnVerbose` (bool): When true, this function returns array of arrays containing page ID, parent ID,
* template ID and score. When false, returns only an array of page IDs. True is required by most usage from
* Pages class. False is only for specific cases.
* - `allowCustom` (bool): Whether or not to allow _custom='selector string' type values (default=false).
* @return array|DatabaseQuerySelect
* @throws PageFinderException
*
@@ -468,7 +481,28 @@ class PageFinder extends Wire {
$options['returnVerbose'] = false;
return $this->find($selectors, $options);
}
/**
* Pre-process given Selectors object
*
* @param Selectors $selectors
* @param array $options
*
*/
protected function preProcessSelectors(Selectors $selectors, $options = array()) {
if(!empty($options['allowCustom'])) {
foreach($selectors as $selector) {
$field = $selector->field;
if(!is_string($field) || $field !== '_custom') continue;
$selectors->remove($selector);
$_selectors = $this->wire(new Selectors($selector->value()));
/** @var Selectors $_selectors */
foreach($_selectors as $s) $selectors->add($s);
}
}
}
/**
* Pre-process the given selector to perform any necessary replacements
*
@@ -742,6 +776,7 @@ class PageFinder extends Wire {
// $this->extraJoins = array();
$startLimit = false; // true when the start/limit part of the query generation is done
$database = $this->wire('database');
$this->preProcessSelectors($selectors, $options);
/** @var DatabaseQuerySelect $query */
$query = $this->wire(new DatabaseQuerySelect());
@@ -1070,7 +1105,7 @@ class PageFinder extends Wire {
static $tableCnt = 0;
$table = $database->escapeTable($field->table);
$tableAlias = $table . "__blank" . (++$tableCnt);
$blankValue = $field->type->getBlankValue(new NullPage(), $field, $value);
$blankValue = $field->type->getBlankValue(new NullPage(), $field);
$blankIsObject = is_object($blankValue);
if($blankIsObject) $blankValue = '';
$blankValue = $database->escapeStr($blankValue);

View File

@@ -199,20 +199,22 @@ class Pages extends Wire {
*
* @param string|int|array|Selectors $selector Specify selector (standard usage), but can also accept page ID or array of page IDs.
* @param array|string $options One or more options that can modify certain behaviors. May be associative array or "key=value" selector string.
* - `findOne` (boolean): Apply optimizations for finding a single page.
* - `findAll` (boolean): Find all pages with no exculsions (same as include=all option).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default: true except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default: true).
* - `findOne` (boolean): Apply optimizations for finding a single page (default=false).
* - `findAll` (boolean): Find all pages with no exclusions, same as "include=all" option (default=false).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default=true).
* The only reason why you'd want to change this to false would be if you only needed the count details from
* the PageArray: getTotal(), getStart(), getLimit, etc. This is intended as an optimization for $pages->count().
* Does not apply if $selector argument is an array.
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. pages.count
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. Default=none. Typically you would specify this
* - `cache` (boolean): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
* - `allowCustom` (boolean): Allow use of _custom="another selector" in given $selector? For specific uses. (default=false)
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. "pages.count" (default=blank).
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. (default=none). Typically you would specify this
* directly in the selector string, so the option is mainly useful if your first argument is not a string.
* - `stopBeforeID` (int): Stop loading pages once page matching this ID is found (default=0).
* - `startAfterID` (int): Start loading pages once page matching this ID is found (default=0).
* - `lazy` (bool): Specify true to force lazy loading. This is the same as using the Pages::findMany() method (default=false).
* - `loadOptions` (array): Optional assoc array of options to pass to getById() load options.
* - `loadOptions` (array): Optional associative array of options to pass to getById() load options.
* @return PageArray Pages that matched the given selector.
*
* Non-visible pages are excluded unless an "include=x" mode is specified in the selector
@@ -289,6 +291,7 @@ class Pages extends Wire {
$debug = $this->debug;
if($debug) $this->debug(false);
$options['lazy'] = true;
if(!isset($options['cache'])) $options['cache'] = false;
$matches = $this->loader->find($selector, $options);
if($debug) $this->debug($debug);
return $matches;

View File

@@ -154,6 +154,8 @@ class PagesLoader extends Wire {
* - findOne: boolean - apply optimizations for finding a single page
* - findAll: boolean - find all pages with no exculsions (same as include=all option)
* - getTotal: boolean - whether to set returning PageArray's "total" property (default: true except when findOne=true)
* - cache: boolean - Allow caching of selectors and pages loaded (default=true). Also sets loadOptions[cache].
* - allowCustom: boolean - Whether to allow use of "_custom=new selector" in selectors (default=false).
* - lazy: boolean - makes find() return Page objects that don't have any data populated to them (other than id and template).
* - loadPages: boolean - whether to populate the returned PageArray with found pages (default: true).
* The only reason why you'd want to change this to false would be if you only needed the count details from
@@ -177,6 +179,8 @@ class PagesLoader extends Wire {
$caller = isset($options['caller']) ? $options['caller'] : 'pages.find';
$lazy = empty($options['lazy']) ? false : true;
$debug = $this->debug && !$lazy;
$cachePages = isset($options['cache']) ? (bool) $options['cache'] : true;
if(!$cachePages && !isset($loadOptions['cache'])) $loadOptions['cache'] = false;
$pages = $this->findShortcut($selector, $options, $loadOptions);
if($pages) return $pages;
@@ -293,7 +297,9 @@ class PagesLoader extends Wire {
$pages->setSelectors($selectors);
$pages->setTrackChanges(true);
if($loadPages) $this->pages->cacher()->selectorCache($selectorString, $options, $pages);
if($loadPages && $cachePages) {
$this->pages->cacher()->selectorCache($selectorString, $options, $pages);
}
if($debug) {
$this->pages->debugLog('find', $selectorString, $pages);

View File

@@ -29,6 +29,7 @@ require_once(PROCESSWIRE_CORE_PATH . "Selector.php");
* #pw-body
*
* @link https://processwire.com/api/selectors/ Official Selectors Documentation
* @method Selector[] getIterator()
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
@@ -365,10 +366,9 @@ class Selectors extends WireArray {
/**
* Given a selector string, return an array of (field, value, operator) for each selector in the strong.
* Given a selector string, populate to Selector objects in this Selectors instance
*
* @param string $str The string containing a selector (or multiple selectors, separated by commas)
* @return array
*
*/
protected function extractString($str) {
@@ -741,6 +741,47 @@ class Selectors extends WireArray {
return true;
}
/**
* Return array of all field names referenced in all of the Selector objects here
*
* @param bool $subfields Default is to allow "field.subfield" fields, or specify false to convert them to just "field".
* @return array Returned array has both keys and values as field names (same)
*
*/
public function getAllFields($subfields = true) {
$fields = array();
foreach($this as $selector) {
$field = $selector->field;
if(!is_array($field)) $field = array($field);
foreach($field as $f) {
if(!$subfields && strpos($f, '.')) {
list($f, $subfield) = explode('.', $f, 2);
if($subfield) {} // ignore
}
$fields[$f] = $f;
}
}
return $fields;
}
/**
* Return array of all values referenced in all Selector objects here
*
* @return array Returned array has both keys and values as field values (same)
*
*/
public function getAllValues() {
$values = array();
foreach($this as $selector) {
$value = $selector->value;
if(!is_array($value)) $value = array($value);
foreach($value as $v) {
$values[$v] = $v;
}
}
return $values;
}
/**
* Does the given Wire match these Selectors?
*

View File

@@ -50,7 +50,7 @@
* @property WireMailTools $mail #pw-internal
* @property WireFileTools $files #pw-internal
*
* @method changed(string $what) See Wire::___changed()
* @method changed(string $what, $old = null, $new = null) See Wire::___changed()
* @method log($str = '', array $options = array()) See Wire::___log()
* @method callUnknown($method, $arguments) See Wire::___callUnknown()
* @method Wire trackException(\Exception $e, $severe = true, $text = null)
@@ -367,11 +367,14 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable {
* @param $method
* @param $arguments
* @return mixed
* @internal
*
*/
public function _callMethod($method, $arguments) {
return call_user_func_array(array($this, $method), $arguments);
if(empty($arguments)) {
return $this->$method();
} else {
return call_user_func_array(array($this, $method), $arguments);
}
}
/**
@@ -953,7 +956,13 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable {
}
if(is_null($old) || is_null($new) || $lastValue !== $new) {
$this->changed($what, $old, $new); // triggers ___changed hook
/** @var WireHooks $hooks */
$hooks = $this->wire('hooks');
if(($hooks && $hooks->isHooked('changed')) || !$hooks) {
$this->changed($what, $old, $new); // triggers ___changed hook
} else {
$this->___changed($what, $old, $new);
}
}
if($this->trackChanges & self::trackChangesValues) {
@@ -1490,8 +1499,6 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable {
/**
* ProcessWire instance
*
* This will replace static fuel in PW 3.0
*
* @var ProcessWire|null
*
*/
@@ -1505,7 +1512,6 @@ abstract class Wire implements WireTranslatable, WireFuelable, WireTrackable {
* #pw-internal
*
* @param ProcessWire $wire
* @return $this
*
*/
public function setWire(ProcessWire $wire) {

View File

@@ -71,23 +71,38 @@ class WireHooks {
protected $staticHooks = array();
/**
* A static cache of all hook method/property names for an optimization.
* A cache of all hook method/property names for an optimization.
*
* Hooked methods end with '()' while hooked properties don't.
*
* This does not distinguish which instance it was added to or whether it was removed.
* But will use keys in the form 'fromClass::method' (with value 'method') in cases where a fromClass was specified.
* This cache exists primarily to gain some speed in our __get and __call methods.
*
*/
protected $hookMethodCache = array();
/**
* Same as hook method cache but for "Class::method"
*
* @var array
*
*/
protected $hookClassMethodCache = array();
/**
* Cache of all local hooks combined, for debugging purposes
*
*/
protected $allLocalHooks = array();
/**
* Cached parent classes and interfaces
*
* @var array of class|interface => [ 'parentClass', 'parentClass', 'interface', 'interface', 'etc.' ]
*
*/
protected $parentClasses = array();
/**
* @var Config
*
@@ -135,6 +150,9 @@ class WireHooks {
$hooks = array();
// see if we can do a quick exit
if($method && $method !== '*' && !$this->isHookedOrParents($object, $method)) return $hooks;
// first determine which local hooks when should include
if($type !== self::getHooksStatic) {
$localHooks = $object->getLocalHooks();
@@ -205,7 +223,7 @@ class WireHooks {
if($needSort && count($hooks) > 1) {
defined("SORT_NATURAL") ? ksort($hooks, SORT_NATURAL) : uksort($hooks, "strnatcmp");
}
return $hooks;
}
@@ -217,8 +235,11 @@ class WireHooks {
* As a result, a true return value indicates something "might" be hooked, as opposed to be
* being definitely hooked.
*
* If checking for a hooked method, it should be in the form "Class::method()" or "method()".
* If checking for a hooked property, it should be in the form "Class::property" or "property".
* If checking for a hooked method, it should be in the form `Class::method()` or `method()` (with parenthesis).
* If checking for a hooked property, it should be in the form `Class::property` or `property`.
*
* If you need to check if a method/property is hooked, including any of its parent classes, use
* the `WireHooks::isMethodHooked()`, `WireHooks::isPropertyHooked()`, or `WireHooks::hasHook()` methods instead.
*
* @param string $method Method or property name in one of the following formats:
* Class::method()
@@ -228,23 +249,114 @@ class WireHooks {
* @param Wire|null $instance Optional instance to check against (see hasHook method for details)
* Note that if specifying an $instance, you may not use the Class::method() or Class::property options for $method argument.
* @return bool
* @see WireHooks::isMethodHooked(), WireHooks::isPropertyHooked(), WireHooks::hasHook()
*
*/
public function isHooked($method, Wire $instance = null) {
if($instance) return $this->hasHook($instance, $method);
$hooked = false;
if(strpos($method, ':') !== false) {
if(array_key_exists($method, $this->hookMethodCache)) $hooked = true; // fromClass::method() or fromClass::property
$hooked = isset($this->hookClassMethodCache[$method]); // fromClass::method() or fromClass::property
} else {
if(in_array($method, $this->hookMethodCache)) $hooked = true; // method() or property
$hooked = isset($this->hookMethodCache[$method]); // method() or property
}
return $hooked;
}
/**
* Similar to isHooked() method but also checks parent classes for the hooked method as well
*
* This method is designed for fast determinations of whether something is hooked
*
* @param string|Wire $class
* @param string $method Name of method or property
* @param string $type May be either 'method', 'property' or 'either'
* @return bool
*
*/
protected function isHookedOrParents($class, $method, $type = 'either') {
$property = '';
$className = is_object($class) ? wireClassName($class) : $class;
if($type == 'method' || $type == 'either') {
if(strpos($method, '(') === false) $method .= '()';
if($type == 'either') $property = rtrim($method, '()');
}
if($type == 'method') {
if(!isset($this->hookMethodCache[$method])) return false; // not hooked for any class
$hooked = isset($this->hookClassMethodCache["$className::$method"]);
} else if($type == 'property') {
if(!isset($this->hookMethodCache[$property])) return false; // not hooked for any class
$hooked = isset($this->hookClassMethodCache["$className::$property"]);
} else {
if(!isset($this->hookMethodCache[$method])
&& !isset($this->hookMethodCache[$property])) return false;
$hooked = isset($this->hookClassMethodCache["$className::$property"]) ||
isset($this->hookClassMethodCache["$className::$method"]);
}
if(!$hooked) {
foreach($this->getClassParents($class) as $parentClass) {
if($type == 'method') {
if(isset($this->hookClassMethodCache["$parentClass::$method"])) {
$hooked = true;
$this->hookClassMethodCache["$class::$method"] = true;
}
} else if($type == 'property') {
if(isset($this->hookClassMethodCache["$parentClass::$property"])) {
$hooked = true;
$this->hookClassMethodCache["$class::$property"] = true;
}
} else {
if(isset($this->hookClassMethodCache["$parentClass::$method"])) {
$hooked = true;
$this->hookClassMethodCache["$class::$method"] = true;
}
if(!$hooked && isset($this->hookClassMethodCache["$parentClass::$property"])) {
$hooked = true;
$this->hookClassMethodCache["$class::$property"] = true;
}
}
if($hooked) break;
}
}
return $hooked;
}
/**
* Similar to isHooked() method but also checks parent classes for the hooked method as well
*
* This method is designed for fast determinations of whether something is hooked
*
* @param string|Wire $class
* @param string $method Name of method
* @return bool
*
*/
public function isMethodHooked($class, $method) {
return $this->isHookedOrParents($class, $method, 'method');
}
/**
* Similar to isHooked() method but also checks parent classes for the hooked property as well
*
* This method is designed for fast determinations of whether something is hooked
*
* @param string|Wire $class
* @param string $property Name of property
* @return bool
*
*/
public function isPropertyHooked($class, $property) {
return $this->isHookedOrParents($class, $property, 'property');
}
/**
* Similar to isHooked(), returns true if the method or property hooked, false if it isn't.
*
* Accomplishes the same thing as the isHooked() method, but this is more accruate,
* Accomplishes the same thing as the isHooked() method, but this is more accurate,
* and potentially slower than isHooked(). Less for optimization use, more for accuracy use.
*
* It checks for both static hooks and local hooks, but only accepts a method() or property
@@ -270,9 +382,7 @@ class WireHooks {
}
// quick exit when possible
if(!in_array($method, $this->hookMethodCache)) {
return false;
}
if(!isset($this->hookMethodCache[$method])) return false;
$_method = rtrim($method, '()');
$localHooks = $object->getLocalHooks();
@@ -285,12 +395,10 @@ class WireHooks {
$hooked = true;
} else {
// check parent classes and interfaces
$classes = wireClassParents($object, false);
$interfaces = wireClassImplements($object);
if(is_array($interfaces)) $classes = array_merge($interfaces, $classes);
foreach($classes as $class) {
foreach($this->getClassParents($object) as $class) {
if(!empty($this->staticHooks[$class][$_method])) {
$hooked = true;
$this->hookClassMethodCache["$class::$method"] = true;
break;
}
}
@@ -299,6 +407,31 @@ class WireHooks {
return $hooked;
}
/**
* Get an array of parent classes and interfaces for the given object
*
* @param Wire|string $object Maybe either object instance or class name
* @param bool $cache Allow use of cache for getting or storing? (default=true)
* @return array
*
*/
public function getClassParents($object, $cache = true) {
if(is_string($object)) {
$className = $object;
} else {
$className = $object->className();
}
if($cache && isset($this->parentClasses[$className])) {
$classes = $this->parentClasses[$className];
} else {
$classes = wireClassParents($object, false);
$interfaces = wireClassImplements($object);
if(is_array($interfaces)) $classes = array_merge($interfaces, $classes);
if($cache) $this->parentClasses[$className] = $classes;
}
return $classes;
}
/**
* Hook a function/method to a hookable method call in this object
@@ -364,7 +497,7 @@ class WireHooks {
}
unset($ref);
}
if(strpos($method, '::')) {
list($fromClass, $method) = explode('::', $method, 2);
if(strpos($fromClass, '(') !== false) {
@@ -380,7 +513,6 @@ class WireHooks {
}
$options['fromClass'] = $fromClass;
}
$argOpen = strpos($method, '(');
if($argOpen && strpos($method, ')') > $argOpen+1) {
@@ -453,7 +585,7 @@ class WireHooks {
$priority = "$options[priority].$n";
}
}
// Note hookClass is always blank when this is a local hook
$id = "$hookClass:$priority:$method";
$options['priority'] = $priority;
@@ -468,10 +600,10 @@ class WireHooks {
);
$hooks[$method][$priority] = $hook;
// cacheValue is just the method() or property, cacheKey includes optional fromClass::
// cache record known hooks so they can be detected quickly
$cacheValue = $options['type'] == 'method' ? "$method()" : "$method";
$cacheKey = ($options['fromClass'] ? $options['fromClass'] . '::' : '') . $cacheValue;
$this->hookMethodCache[$cacheKey] = $cacheValue;
if($options['fromClass']) $this->hookClassMethodCache["$options[fromClass]::$cacheValue"] = true;
$this->hookMethodCache[$cacheValue] = true;
// keep track of all local hooks combined when debug mode is on
if($local && $this->config->debug) {
@@ -534,14 +666,15 @@ class WireHooks {
$realMethod = "___$method";
if($type == 'method') $result['methodExists'] = method_exists($object, $realMethod);
if(!$result['methodExists'] && !$this->hasHook($object, $method . ($type == 'method' ? '()' : ''))) {
// if(!$result['methodExists'] && !$this->hasHook($object, $method . ($type == 'method' ? '()' : ''))) {
if(!$result['methodExists'] && !$this->isHookedOrParents($object, $method, $type)) {
return $result; // exit quickly when we can
}
$hooks = $this->getHooks($object, $method);
$cancelHooks = false;
$profiler = $this->wire->wire('profiler');
foreach(array('before', 'after') as $when) {
if($type === 'method' && $when === 'after' && $result['replace'] !== true) {
@@ -557,6 +690,7 @@ class WireHooks {
if(!$hook['options'][$when]) continue;
if(!empty($hook['options']['objMatch'])) {
/** @var Selectors $objMatch */
$objMatch = $hook['options']['objMatch'];
// object match comparison to determine at runtime whether to execute the hook
if(is_object($objMatch)) {
@@ -571,6 +705,7 @@ class WireHooks {
$argMatches = $hook['options']['argMatch'];
$matches = true;
foreach($argMatches as $argKey => $argMatch) {
/** @var Selectors $argMatch */
$argVal = isset($arguments[$argKey]) ? $arguments[$argKey] : null;
if(is_object($argMatch)) {
// Selectors object
@@ -594,15 +729,16 @@ class WireHooks {
if(!$matches) continue; // don't run hook
}
$event = new HookEvent();
$event = new HookEvent(array(
'object' => $object,
'method' => $method,
'arguments' => $arguments,
'when' => $when,
'return' => $result['return'],
'id' => $hook['id'],
'options' => $hook['options']
));
$this->wire->wire($event);
$event->object = $object;
$event->method = $method;
$event->arguments = $arguments;
$event->when = $when;
$event->return = $result['return'];
$event->id = $hook['id'];
$event->options = $hook['options'];
$toObject = $hook['toObject'];
$toMethod = $hook['toMethod'];
@@ -624,6 +760,7 @@ class WireHooks {
}
$toMethod($event);
} else {
/** @var Wire $toObject */
if($hook['toPublic']) {
// public
$returnValue = $toObject->$toMethod($event);
@@ -632,6 +769,7 @@ class WireHooks {
$returnValue = $toObject->_callMethod($toMethod, array($event));
}
// @todo allow for use of $returnValue as alternative to $event->return
if($returnValue) {}
}
if($profilerEvent) $profiler->stop($profilerEvent);
@@ -660,9 +798,9 @@ class WireHooks {
/**
* Start timing a hook and return the timer name
*
* @param $object
* @param $method
* @param $arguments
* @param Wire $object
* @param String $method
* @param array $arguments
* @return string
*
*/
@@ -694,8 +832,8 @@ class WireHooks {
* }
*
* @param Wire $object
* @param string|null $hookId
* @return $this
* @param string|null $hookID
* @return Wire
*
*/
public function removeHook(Wire $object, $hookID) {
@@ -707,6 +845,9 @@ class WireHooks {
$object->setLocalHooks($localHooks);
} else {
unset($this->staticHooks[$hookClass][$method][$priority]);
if(empty($this->staticHooks[$hookClass][$method])) {
unset($this->hookClassMethodCache["$hookClass::$method"]);
}
}
}
return $object;

View File

@@ -390,7 +390,7 @@ class FieldtypePageTable extends FieldtypeMulti implements Module {
if(!is_array($value) || !count($value) || empty($field->template_id)) return $this->getBlankValue($page, $field);
$template_id = $field->template_id;
$template_id = $field->get('template_id');
if(!is_array($template_id)) {
$template_id = $template_id ? array($template_id) : array();
@@ -402,16 +402,26 @@ class FieldtypePageTable extends FieldtypeMulti implements Module {
$template = null;
}
if($field->sortfields) {
$loadOptions = array('cache' => false);
if($template) $loadOptions['template'] = $template;
$sortfields = $field->get('sortfields');
if($sortfields) {
$selector = $template ? "template=$template, " : "";
$selector .= "include=unpublished, id=" . implode('|', $value);
foreach(explode(',', $field->sortfields) as $sortfield) {
foreach(explode(',', $sortfields) as $sortfield) {
$selector .= ", sort=" . $this->wire('sanitizer')->name(trim($sortfield));
}
$items = $this->wire('pages')->find($selector);
$options = array(
'cache' => false,
'caller' => $this->className() . '::wakeupValue',
'loadOptions' => $loadOptions
);
$items = $this->wire('pages')->find($selector, $options);
} else {
$items = $this->wire('pages')->getById($value, $template);
$items = $this->wire('pages')->getById($value, $loadOptions);
}
return $items;

View File

@@ -640,7 +640,17 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
*/
// load the repeater pages
$a = $this->wire('pages')->find($selector);
$options = array(
'cache' => false,
'caller' => $this->className() . '::wakeupValue',
'loadOptions' => array(
'cache' => false,
'parent_id' => $parent_id,
'template' => $this->wire('templates')->get($template_id)
)
);
$a = $this->wire('pages')->find($selector, $options);
$class = $this->getPageArrayClass();
$pageArray = $this->wire(new $class($page, $field));
$pageArray->import($a);

View File

@@ -588,9 +588,10 @@ var InputfieldSelector = {
if(s.field == '_custom') {
if(s.isOrGroup) {
s.value = s.value.replace('(', '').replace(')', '');
selector += s.field + '=' + '(' + s.value + ')';
selector += s.field + '=' + '(' + $.trim(s.value) + ')';
} else {
selector += s.value;
//selector += s.value;
selector += s.field + '="' + $.trim(s.value) + '"';
}
} else {
selector += s.field + s.operator + $.trim(s.value);

File diff suppressed because one or more lines are too long

View File

@@ -273,7 +273,10 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
} else if($action == 'subfield' && ($fieldName = $input->get->field)) {
$fieldName = $sanitizer->name($fieldName);
if(strpos($fieldName, '.')) list($fieldName, $subfieldName) = explode('.', $fieldName);
if(strpos($fieldName, '.')) {
list($fieldName, $subfieldName) = explode('.', $fieldName);
if($subfieldName) {} // ignore
}
$out = $this->renderSelectSubfield($fieldName);
} else if($action == 'opval' && ($fieldName = $input->get->field)) {
@@ -617,6 +620,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
// consider multi-language
if(strpos($name, 'data') === 0 && $this->wire('languages')) {
list($unused, $languageID) = explode('data', "x$name");
if($unused) {}
if(ctype_digit($languageID)) {
$language = $this->wire('languages')->get((int) $languageID);
if($language && $language->id) {
@@ -630,7 +634,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
} else if(!empty($subfield['label'])) {
$label = $subfield['label'];
} else if(strpos($name, 'data') === 0 && ctype_digit(substr($name, 4)) && $this->wire('languages')) {
$label = $this->wire('languages')->get((int) substr($name, 4))->get('title|name');
} else {
$f = $this->wire('fields')->get($name);
$label = $f ? $f->getLabel() : $name;
@@ -646,6 +650,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
$_subfields = array();
foreach($subfields as $key => $subfield) {
list($label, $name) = explode("\t", $key);
if($label) {}
$_subfields[$name] = $subfield;
}
$subfields = $_subfields;
@@ -839,6 +844,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
if($settings['showFieldLabels']) {
$customFields = array();
foreach($settings['customFields'] as $field) {
/** @var Field $field */
$label = $field->getLabel();
while(isset($customFields[$label])) $label .= ' ';
$customFields[$label] = $field;
@@ -1183,8 +1189,18 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
&& ($field->get('findPagesCode') || $field->get('findPagesSelector'))) {
// see if we can locate options purely with the parent or template
$findSelector = array("include=unpublished, limit=500, sort=title, sort=name, ");
if($field->get('parent_id')) $findSelector[] = "parent_id=" . (int) $field->get('parent_id');
if($field->get('template_id')) $findSelector[] = "templates_id=" . (int) $field->get('template_id');
$parent_ids = $field->get('parent_ids');
$template_ids = $field->get('template_ids');
if($parent_ids && count($parent_ids)) {
$findSelector[] = "parent_id=" . implode('|', $parent_ids);
} else if($field->get('parent_id')) {
$findSelector[] = "parent_id=" . (int) $field->get('parent_id');
}
if($template_ids && count($template_ids)) {
$findSelector[] = "templates_id=" . implode('|', $template_ids);
} else if($field->get('template_id')) {
$findSelector[] = "templates_id=" . (int) $field->get('template_id');
}
foreach($this->wire('pages')->find(implode(', ', $findSelector)) as $item) {
$options[$item->id] = $inputfield->getPageLabel($item); // $item->get('title|name');
}
@@ -1386,6 +1402,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
if(strpos($limitField, '.') === false) continue;
if(strpos($limitField, $fieldName) !== 0) continue;
list($limitField, $limitSubfield) = explode('.', $limitField);
if($limitField) {} // ignore
if($limitSubfield) $limitSubfields[$limitSubfield] = $limitSubfield;
}
// render all the subfield options
@@ -1625,7 +1642,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
*
* @param array|string $key
* @param int|string $value
* @return $this
* @return InputfieldSelector|WireData
*
*/
public function setAttribute($key, $value) {
@@ -1913,6 +1930,7 @@ class InputfieldSelector extends Inputfield implements ConfigurableModule {
*
*/
public function getModuleConfigInputfields(array $data) {
if($data) {} // ignore
$form = $this->wire(new InputfieldWrapper());
$f = $this->wire('modules')->get('InputfieldSelector');
$f->name = 'test';

View File

@@ -1504,7 +1504,8 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$this->finalSelector = $selector;
try {
$results = $selector ? $this->wire('pages')->find($selector) : $this->wire('pages')->newPageArray();
$options = array('allowCustom' => true);
$results = $selector ? $this->wire('pages')->find($selector, $options) : $this->wire('pages')->newPageArray();
} catch(\Exception $e) {
$this->error($e->getMessage());