/**
  * Pre-process the given selector to perform any necessary replacements
  *
  * This is primarily used to handle sub-selections, i.e. "bar=foo, id=[this=that, foo=bar]"
  * 
  * @param Selector $selector
  * @return bool Returns false if selector should be skipped over by getQuery()
  *
  */
 protected function preProcessSelector(Selector $selector)
 {
     if ($selector->quote && !is_array($selector->value) && Selectors::stringHasSelector($selector->value)) {
         // selector contains an embedded quoted selector
         if ($selector->quote == '[') {
             // i.e. field=[id>0, name=something, this=that]
             // selector contains another embedded selector that we need to convert to page IDs
             $selectors = new Selectors($selector->value);
             $hasTemplate = false;
             $hasParent = false;
             foreach ($selectors as $s) {
                 if (is_array($s->field)) {
                     continue;
                 }
                 if ($s->field == 'template') {
                     $hasTemplate = true;
                 }
                 if ($s->field == 'parent' || $s->field == 'parent_id') {
                     $hasParent = true;
                 }
             }
             // special handling for page references, detect if parent or template is defined,
             // and add it to the selector if available. This makes it faster.
             if (!$hasTemplate || !$hasParent) {
                 $fields = is_array($selector->field) ? $selector->field : array($selector->field);
                 $templates = array();
                 $parents = array();
                 $findSelector = '';
                 foreach ($fields as $fieldName) {
                     if (strpos($fieldName, '.') !== false) {
                         list($unused, $fieldName) = explode('.', $fieldName);
                     }
                     $field = $this->wire('fields')->get($fieldName);
                     if (!$field) {
                         continue;
                     }
                     if (!$hasTemplate && $field->template_id) {
                         if (is_array($field->template_id)) {
                             $templates = array_merge($templates, $field->template_id);
                         } else {
                             $templates[] = (int) $field->template_id;
                         }
                     }
                     if (!$hasParent && $field->parent_id) {
                         $parents[] = (int) $field->parent_id;
                     }
                     if ($field->findPagesSelector && count($fields) == 1) {
                         $findSelector = $field->findPagesSelector;
                     }
                 }
                 if (count($templates)) {
                     $selectors->prepend(new SelectorEqual('template', $templates));
                 }
                 if (count($parents)) {
                     $selectors->prepend(new SelectorEqual('parent_id', $parents));
                 }
                 if ($findSelector) {
                     foreach (new Selectors($findSelector) as $s) {
                         $selectors->append($s);
                     }
                 }
             }
             $pageFinder = new PageFinder();
             $ids = $pageFinder->findIDs($selectors);
             // populate selector value with array of page IDs
             if (count($ids) == 0) {
                 // subselector resulted in 0 matches
                 // force non-match for this subselector by populating 'id' subfield to field name(s)
                 $fieldNames = array();
                 foreach ($selector->fields as $key => $fieldName) {
                     if (strpos($fieldName, '.') !== false) {
                         // reduce fieldName to just field name without subfield name
                         list($fieldName, $subname) = explode('.', $fieldName);
                         // subname intentionally unused
                     }
                     $fieldName .= '.id';
                     $fieldNames[$key] = $fieldName;
                 }
                 $selector->fields = $fieldNames;
                 $selector->value = 0;
             } else {
                 $selector->value = count($ids) > 1 ? $ids : reset($ids);
             }
             $selector->quote = '';
             /*
             } else {
             	$fieldName = $selector->field ? $selector->field : 'none';
             	if(!isset($this->extraSubSelectors[$fieldName])) $this->extraSubSelectors[$fieldName] = array();
             	$this->extraSubSelectors[$fieldName][] = new Selectors($selector->value);
             	return false;
             }
             */
         } else {
             if ($selector->quote == '(') {
                 // selector contains an quoted selector. At least one () quoted selector must match for each field specified in front of it
                 $fieldName = $selector->field ? $selector->field : 'none';
                 if (!isset($this->extraOrSelectors[$fieldName])) {
                     $this->extraOrSelectors[$fieldName] = array();
                 }
                 $this->extraOrSelectors[$fieldName][] = new Selectors($selector->value);
                 return false;
             }
         }
     }
     return true;
 }
Пример #2
0
 /**
  * Hook a function/method to a hookable method call in this object
  *
  * Hookable method calls are methods preceded by three underscores. 
  * You may also specify a method that doesn't exist already in the class
  * The hook method that you define may be part of a class or a globally scoped function. 
  * 
  * If you are hooking a procedural function, you may omit the $toObject and instead just call via:
  * $this->addHook($method, 'function_name'); or $this->addHook($method, 'function_name', $options); 
  *
  * @param string $method Method name to hook into, NOT including the three preceding underscores. 
  * 	May also be Class::Method for same result as using the fromClass option.
  * @param object|null|callable $toObject Object to call $toMethod from,
  * 	Or null if $toMethod is a function outside of an object,
  * 	Or function|callable if $toObject is not applicable or function is provided as a closure.
  * @param string $toMethod Method from $toObject, or function name to call on a hook event. Optional.
  * @param array $options See self::$defaultHookOptions at the beginning of this class. Optional.
  * @return string A special Hook ID that should be retained if you need to remove the hook later
  * @throws WireException
  *
  */
 public function addHook($method, $toObject, $toMethod = null, $options = array())
 {
     if (is_array($toMethod)) {
         // $options array specified as 3rd argument
         if (count($options)) {
             // combine $options from addHookBefore/After and user specified options
             $options = array_merge($toMethod, $options);
         } else {
             $options = $toMethod;
         }
         $toMethod = null;
     }
     if (is_null($toMethod)) {
         // $toObject has been ommitted and a procedural function specified instead
         // $toObject may also be a closure
         $toMethod = $toObject;
         $toObject = null;
     }
     if (is_null($toMethod)) {
         throw new WireException("Method to call is required and was not specified (toMethod)");
     }
     if (substr($method, 0, 3) == '___') {
         throw new WireException("You must specify hookable methods without the 3 preceding underscores");
     }
     if (method_exists($this, $method)) {
         throw new WireException("Method " . $this->className() . "::{$method} is not hookable");
     }
     $options = array_merge(self::$defaultHookOptions, $options);
     if (strpos($method, '::')) {
         list($fromClass, $method) = explode('::', $method, 2);
         if (strpos($fromClass, '(') !== false) {
             // extract object selector match string
             list($fromClass, $objMatch) = explode('(', $fromClass, 2);
             $objMatch = trim($objMatch, ') ');
             if (Selectors::stringHasSelector($objMatch)) {
                 $objMatch = new Selectors($objMatch);
             }
             if ($objMatch) {
                 $options['objMatch'] = $objMatch;
             }
         }
         $options['fromClass'] = $fromClass;
     }
     $argOpen = strpos($method, '(');
     if ($argOpen && strpos($method, ')') > $argOpen + 1) {
         // extract argument selector match string(s), arg 0: Something::something(selector_string)
         // or: Something::something(1:selector_string, 3:selector_string) matches arg 1 and 3.
         list($method, $argMatch) = explode('(', $method, 2);
         $argMatch = trim($argMatch, ') ');
         if (strpos($argMatch, ':') !== false) {
             // zero-based argument indexes specified, i.e. 0:template=product, 1:order_status
             $args = preg_split('/\\b([0-9]):/', trim($argMatch), -1, PREG_SPLIT_DELIM_CAPTURE);
             if (count($args)) {
                 $argMatch = array();
                 array_shift($args);
                 // blank
                 while (count($args)) {
                     $argKey = (int) trim(array_shift($args));
                     $argVal = trim(array_shift($args), ', ');
                     $argMatch[$argKey] = $argVal;
                 }
             }
         } else {
             // just single argument specified, so argument 0 is assumed
         }
         if (is_string($argMatch)) {
             $argMatch = array(0 => $argMatch);
         }
         foreach ($argMatch as $argKey => $argVal) {
             if (Selectors::stringHasSelector($argVal)) {
                 $argMatch[$argKey] = new Selectors($argVal);
             }
         }
         if (count($argMatch)) {
             $options['argMatch'] = $argMatch;
         }
     }
     if ($options['allInstances'] || $options['fromClass']) {
         // hook all instances of this class
         $hookClass = $options['fromClass'] ? $options['fromClass'] : $this->className();
         if (!isset(self::$staticHooks[$hookClass])) {
             self::$staticHooks[$hookClass] = array();
         }
         $hooks =& self::$staticHooks[$hookClass];
         $options['allInstances'] = true;
         $local = 0;
     } else {
         // hook only this instance
         $hookClass = '';
         $hooks =& $this->localHooks;
         $local = 1;
     }
     $priority = (string) $options['priority'];
     if (!isset($hooks[$method])) {
         if (ctype_digit($priority)) {
             $priority = "{$priority}.0";
         }
         $hooks[$method] = array();
     } else {
         if (strpos($priority, '.')) {
             // priority already specifies a sub value: extract it
             list($priority, $n) = explode('.', $priority);
             $options['priority'] = $priority;
             // without $n
             $priority .= ".{$n}";
         } else {
             $n = 0;
             $priority .= ".0";
         }
         // come up with a priority that is unique for this class/method across both local and static hooks
         while ($hookClass && isset(self::$staticHooks[$hookClass][$method][$priority]) || isset($this->localHooks[$method][$priority])) {
             $n++;
             $priority = "{$options['priority']}.{$n}";
         }
     }
     // Note hookClass is always blank when this is a local hook
     $id = "{$hookClass}:{$priority}:{$method}";
     $options['priority'] = $priority;
     $hooks[$method][$priority] = array('id' => $id, 'method' => $method, 'toObject' => $toObject, 'toMethod' => $toMethod, 'options' => $options);
     // cacheValue is just the method() or property, cacheKey includes optional fromClass::
     $cacheValue = $options['type'] == 'method' ? "{$method}()" : "{$method}";
     $cacheKey = ($options['fromClass'] ? $options['fromClass'] . '::' : '') . $cacheValue;
     self::$hookMethodCache[$cacheKey] = $cacheValue;
     // keep track of all local hooks combined when debug mode is on
     if ($this->wire('config')->debug && $hooks === $this->localHooks) {
         $debugClass = $this->className();
         $debugID = ($local ? $debugClass : '') . $id;
         while (isset(self::$allLocalHooks[$debugID])) {
             $debugID .= "_";
         }
         $debugHook = $hooks[$method][$priority];
         $debugHook['method'] = $debugClass . "->" . $debugHook['method'];
         self::$allLocalHooks[$debugID] = $debugHook;
     }
     // sort by priority, if more than one hook for the method
     if (count($hooks[$method]) > 1) {
         defined("SORT_NATURAL") ? ksort($hooks[$method], SORT_NATURAL) : uksort($hooks[$method], "strnatcmp");
     }
     return $id;
 }
Пример #3
0
 /**
  * Given a $expire seconds, date, page, or template convert it to an ISO-8601 date
  * 
  * Returns an array of expires info requires multiple parts, like with self::expireSelector.
  * In this case it returns array with array('expires' => date, 'selector' => selector);
  * 
  * @param $expire
  * @return string|array
  * 
  */
 protected function getExpires($expire)
 {
     if (is_object($expire) && $expire->id) {
         if ($expire instanceof Page) {
             // page object
             $expire = $expire->template->id;
         } else {
             if ($expire instanceof Template) {
                 // template object
                 $expire = $expire->id;
             } else {
                 // unknown object, substitute default
                 $expire = time() + self::expireDaily;
             }
         }
     } else {
         if (is_array($expire)) {
             // expire value already prepared by a previous call, just return it
             if (isset($expire['selector']) && isset($expire['expire'])) {
                 return $expire;
             }
         } else {
             if (in_array($expire, array(self::expireNever, self::expireSave))) {
                 // good, we'll take it as-is
                 return $expire;
             } else {
                 if (is_string($expire) && Selectors::stringHasSelector($expire)) {
                     // expire when page matches selector
                     return array('expire' => self::expireSelector, 'selector' => $expire);
                 } else {
                     // account for date format as string
                     if (is_string($expire) && !ctype_digit("{$expire}")) {
                         $expire = strtotime($expire);
                         $isDate = true;
                     } else {
                         $isDate = false;
                     }
                     if ($expire === 0 || $expire === "0") {
                         // zero is allowed if that's what was specified
                         $expire = (int) $expire;
                     } else {
                         // zero is not allowed because it indicates failed type conversion
                         $expire = (int) $expire;
                         if (!$expire) {
                             $expire = self::expireDaily;
                         }
                     }
                     if ($expire > time()) {
                         // a future date has been specified, so we'll keep it
                     } else {
                         if (!$isDate) {
                             // a quantity of seconds has been specified, add it to current time
                             $expire = time() + $expire;
                         }
                     }
                 }
             }
         }
     }
     $expire = date(self::dateFormat, $expire);
     return $expire;
 }