/** * Pre-process the selectors to add Page status checks * */ protected function setupStatusChecks(Selectors $selectors, $options = array()) { $maxStatus = null; foreach ($selectors as $key => $selector) { if ($selector->field == 'status') { $value = $selector->value; if (!ctype_digit("{$value}")) { // allow use of some predefined labels for Page statuses if ($value == 'hidden') { $selector->value = Page::statusHidden; } else { if ($value == 'unpublished') { $selector->value = Page::statusUnpublished; } else { if ($value == 'locked') { $selector->value = Page::statusLocked; } else { $selector->value = 1; } } } if ($selector->operator == '=') { // there is no point in an equals operator here, so we make it a bitwise AND, for simplification $selectors[$key] = new SelectorBitwiseAnd('status', $selector->value); } } if (is_null($maxStatus) || $value > $maxStatus) { $maxStatus = (int) $selector->value; } } } if (!is_null($maxStatus)) { // if a status was already present in the selector, then just make sure the page isn't unpublished if ($maxStatus < Page::statusUnpublished) { $selectors->add(new SelectorLessThan('status', Page::statusUnpublished)); } } else { if ($options['findOne']) { // findOne option, apply optimizations enabling hidden pages to be loaded $selectors->add(new SelectorLessThan('status', Page::statusUnpublished)); } else { // no status is present, so exclude everything hidden and above $selectors->add(new SelectorLessThan('status', Page::statusHidden)); } } if ($options['findOne']) { $selectors->add(new SelectorEqual('start', 0)); $selectors->add(new SelectorEqual('limit', 1)); } }
/** * Add all individual selector types to the runtime Selectors * */ public static function loadSelectorTypes() { Selectors::addType(SelectorEqual::getOperator(), 'SelectorEqual'); Selectors::addType(SelectorNotEqual::getOperator(), 'SelectorNotEqual'); Selectors::addType(SelectorGreaterThan::getOperator(), 'SelectorGreaterThan'); Selectors::addType(SelectorLessThan::getOperator(), 'SelectorLessThan'); Selectors::addType(SelectorGreaterThanEqual::getOperator(), 'SelectorGreaterThanEqual'); Selectors::addType(SelectorLessThanEqual::getOperator(), 'SelectorLessThanEqual'); Selectors::addType(SelectorContains::getOperator(), 'SelectorContains'); Selectors::addType(SelectorContainsLike::getOperator(), 'SelectorContainsLike'); Selectors::addType(SelectorContainsWords::getOperator(), 'SelectorContainsWords'); Selectors::addType(SelectorStarts::getOperator(), 'SelectorStarts'); Selectors::addType(SelectorStartsLike::getOperator(), 'SelectorStartsLike'); Selectors::addType(SelectorEnds::getOperator(), 'SelectorEnds'); Selectors::addType(SelectorEndsLike::getOperator(), 'SelectorEndsLike'); Selectors::addType(SelectorBitwiseAnd::getOperator(), 'SelectorBitwiseAnd'); }
/** * Pre-process the selectors to add Page status checks * */ protected function setupStatusChecks(Selectors $selectors) { $maxStatus = null; $options = $this->options; foreach ($selectors as $key => $selector) { if ($selector->field == 'status') { $value = $selector->value; if (!ctype_digit("{$value}")) { // allow use of some predefined labels for Page statuses if ($value == 'hidden') { $selector->value = Page::statusHidden; } else { if ($value == 'unpublished') { $selector->value = Page::statusUnpublished; } else { if ($value == 'locked') { $selector->value = Page::statusLocked; } else { if ($value == 'max') { $selector->value = Page::statusMax; } else { $selector->value = 1; } } } } if ($selector->operator == '=') { // there is no point in an equals operator here, so we make it a bitwise AND, for simplification $selectors[$key] = new SelectorBitwiseAnd('status', $selector->value); } } if (is_null($maxStatus) || $value > $maxStatus) { $maxStatus = (int) $selector->value; } } else { if ($selector->field == 'include' && $selector->operator == '=' && in_array($selector->value, array('hidden', 'all'))) { if ($selector->value == 'hidden') { $options['findHidden'] = true; } else { if ($selector->value == 'all') { $options['findAll'] = true; } } $selectors->remove($key); } else { if ($selector->field == 'check_access' || $selector->field == 'checkAccess') { $this->checkAccess = (int) $selector->value > 0 ? true : false; $selectors->remove($key); } } } } if (!is_null($maxStatus)) { // if a status was already present in the selector, then just make sure the page isn't unpublished if ($maxStatus < Page::statusUnpublished) { $selectors->add(new SelectorLessThan('status', Page::statusUnpublished)); } } else { if ($options['findAll']) { // findAll option means that unpublished, hidden, trash, system may be included $selectors->add(new SelectorLessThan('status', Page::statusMax)); $this->checkAccess = false; } else { if ($options['findOne'] || $options['findHidden']) { // findHidden|findOne option, apply optimizations enabling hidden pages to be loaded $selectors->add(new SelectorLessThan('status', Page::statusUnpublished)); } else { // no status is present, so exclude everything hidden and above $selectors->add(new SelectorLessThan('status', Page::statusHidden)); } } } if ($options['findOne']) { $selectors->add(new SelectorEqual('start', 0)); $selectors->add(new SelectorEqual('limit', 1)); } }
/** * Get the value of a requested Page property * * @param string $key * @return mixed * @see __get * */ public function get($key) { if (is_array($key)) { $key = implode('|', $key); } $value = null; switch ($key) { case 'parent_id': case 'parentID': $value = $this->parent ? $this->parent->id : 0; break; case 'child': $value = $this->child(); break; case 'children': case 'subpages': // PW1 $value = $this->children(); break; case 'has_parent': case 'hasParent': $value = $this->parents(); break; case 'parent': case 'parents': case 'rootParent': case 'siblings': case 'next': case 'prev': case 'url': case 'path': case 'outputFormatting': case 'isTrash': $value = $this->{$key}(); break; case 'httpUrl': case 'httpURL': $value = $this->httpUrl(); break; case 'fieldgroup': case 'fields': $value = $this->template->fieldgroup; break; case 'template_id': case 'templates_id': case 'templateID': case 'templatesID': $value = $this->template ? $this->template->id : 0; break; case 'template': case 'templatePrevious': case 'parentPrevious': case 'namePrevious': case 'statusPrevious': case 'isLoaded': case 'isNew': case 'pageNum': case 'instanceID': $value = $this->{$key}; break; case 'out': case 'output': $value = $this->output(); break; case 'filesManager': $value = $this->filesManager(); break; case 'name': $value = $this->settings['name']; break; case 'modified_users_id': case 'modifiedUsersID': case 'modifiedUserID': $value = (int) $this->settings['modified_users_id']; break; case 'created_users_id': case 'createdUsersID': case 'createdUserID': $value = (int) $this->settings['created_users_id']; break; case 'modifiedUser': if (!$this->modifiedUser) { if ($this->settings['modified_users_id'] == $this->wire('user')->id) { $this->modifiedUser = $this->wire('user'); } else { $this->modifiedUser = $this->wire('users')->get((int) $this->settings['modified_users_id']); } } $this->modifiedUser->of($this->of()); $value = $this->modifiedUser; break; case 'createdUser': if (!$this->createdUser) { if ($this->settings['created_users_id'] == $this->wire('user')->id) { $this->createdUser = $this->wire('user'); } else { $this->createdUser = $this->wire('users')->get((int) $this->settings['created_users_id']); } } $this->createdUser->of($this->of()); $value = $this->createdUser; break; case 'urlSegment': $value = $this->wire('input')->urlSegment1; // deprecated, but kept for backwards compatibility break; case 'accessTemplate': $value = $this->getAccessTemplate(); break; case 'num_children': case 'numChildren': $value = $this->settings['numChildren']; break; case 'numChildrenVisible': case 'numVisibleChildren': case 'hasChildren': $value = $this->numChildren(true); break; case 'editUrl': case 'editURL': $value = $this->wire('config')->urls->admin . "page/edit/?id={$this->id}"; break; default: if ($key && isset($this->settings[(string) $key])) { return $this->settings[$key]; } if (($value = $this->getFieldFirstValue($key)) !== null) { return $value; } if (($value = $this->getFieldValue($key)) !== null) { return $value; } // if there is a selector, we'll assume they are using the get() method to get a child if (Selectors::stringHasOperator($key)) { return $this->child($key); } // check if it's a field.subfield property, but only if output formatting is off if (!$this->outputFormatting() && strpos($key, '.') !== false && ($value = $this->getDot($key)) !== null) { return $value; } // optionally let a hook look at it if (self::isHooked('Page::getUnknown()')) { return $this->getUnknown($key); } } return $value; }
/** * 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; }
/** * Does this WireArray have the given index or match the given selector? * * If the WireArray uses numeric keys, then this will also match a wire's "name" field. * * @param int|string $key Key of item to check or selector. * @return bool True if the item exists, false if not. */ public function has($key) { if (is_object($key)) { $key = $this->getItemKey($key); } if (array_key_exists($key, $this->data)) { return true; } if (is_string($key)) { if (Selectors::stringHasOperator($key)) { $match = $this->findOne($key); } else { if ($this->usesNumericKeys()) { $match = $this->getItemThatMatches('name', $key); } } } else { $match = null; } return $match ? true : false; }
/** * Given a Selectors object or a selector string, return whether this Page matches it * * @param Page $page * @param string|Selectors $s * @return bool * */ public function matches(Page $page, $s) { 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 = new Selectors($s); } else { if (is_int($s)) { // exit early for simple ID comparison return $page->id == $s; } } } else { if ($s instanceof Selectors) { $selectors = $s; } else { return false; } } $matches = false; foreach ($selectors as $selector) { $name = $selector->field; if (in_array($name, array('limit', 'start', 'sort', 'include'))) { continue; } $matches = true; $value = $page->getUnformatted($name); 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}"; } } } } else { if (is_array($value)) { // ok: selector matches will accept an array } else { // convert to a string value, whatever it may be $value = "{$value}"; } } if (!$selector->matches($value)) { $matches = false; break; } } return $matches; }
/** * 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; }
/** * Clone an entire page, it's assets and children and return it. * * @param Page $page Page that you want to clone * @param Page $parent New parent, if different (default=same parent) * @param bool $recursive Clone the children too? (default=true) * @param array|string $options Optional options that can be passed to clone or save * - forceID (int): force a specific ID * - set (array): Array of properties to set to the clone (you can also do this later) * - recursionLevel (int): recursion level, for internal use only. * @return Page the newly cloned page or a NullPage() with id=0 if unsuccessful. * @throws WireException|Exception on fatal error * */ public function ___clone(Page $page, Page $parent = null, $recursive = true, $options = array()) { if (is_string($options)) { $options = Selectors::keyValueStringToArray($options); } if (!isset($options['recursionLevel'])) { $options['recursionLevel'] = 0; } // recursion level // if parent is not changing, we have to modify name now if (is_null($parent)) { $parent = $page->parent; $n = 1; $name = $page->name . '-' . $n; } else { $name = $page->name; $n = 0; } // make sure that we have a unique name while (count($parent->children("name={$name}, include=all"))) { $name = $page->name; $nStr = "-" . ++$n; if (strlen($name) + strlen($nStr) > self::nameMaxLength) { $name = substr($name, 0, self::nameMaxLength - strlen($nStr)); } $name .= $nStr; } // Ensure all data is loaded for the page foreach ($page->template->fieldgroup as $field) { $page->get($field->name); } // clone in memory $copy = clone $page; $copy->id = isset($options['forceID']) ? (int) $options['forceID'] : 0; $copy->setIsNew(true); $copy->name = $name; $copy->parent = $parent; // set any properties indicated in options if (isset($options['set']) && is_array($options['set'])) { foreach ($options['set'] as $key => $value) { $copy->set($key, $value); } if (isset($options['set']['modified'])) { $options['quiet'] = true; // allow for modified date to be set if (!isset($options['set']['modified_users_id'])) { // since 'quiet' also allows modified user to be set, make sure that it // is still updated, if not specifically set. $copy->modified_users_id = $this->wire('user')->id; } } if (isset($options['set']['modified_users_id'])) { $options['quiet'] = true; // allow for modified user to be set if (!isset($options['set']['modified'])) { // since 'quiet' also allows modified tie to be set, make sure that it // is still updated, if not specifically set. $copy->modified = time(); } } } // tell PW that all the data needs to be saved foreach ($copy->template->fieldgroup as $field) { $copy->trackChange($field->name); } $o = $copy->outputFormatting; $copy->setOutputFormatting(false); $this->cloneReady($page, $copy); try { $this->cloning = true; $options['ignoreFamily'] = true; // skip family checks during clone $this->save($copy, $options); } catch (Exception $e) { $this->cloning = false; throw $e; } $this->cloning = false; $copy->setOutputFormatting($o); // check to make sure the clone has worked so far if (!$copy->id || $copy->id == $page->id) { return new NullPage(); } // copy $page's files over to new page if (PagefilesManager::hasFiles($page)) { $copy->filesManager->init($copy); $page->filesManager->copyFiles($copy->filesManager->path()); } // if there are children, then recurisvely clone them too if ($page->numChildren && $recursive) { $start = 0; $limit = 200; do { $children = $page->children("include=all, start={$start}, limit={$limit}"); $numChildren = $children->count(); foreach ($children as $child) { /** @var Page $child */ $this->clone($child, $copy, true, array('recursionLevel' => $options['recursionLevel'] + 1)); } $start += $limit; $this->uncacheAll(); } while ($numChildren); } $copy->parentPrevious = null; // update pages_parents table, only when at recursionLevel 0 since pagesParents is already recursive if ($recursive && $options['recursionLevel'] === 0) { $this->saveParents($copy->id, $copy->numChildren); } $copy->resetTrackChanges(); $this->cloned($page, $copy); $this->debugLog('clone', "page={$page}, parent={$parent}", $copy); return $copy; }
/** * Given a Selectors object or a selector string, return whether this Page matches it * * @param string|Selectors $s * @return bool * */ public function matches($s) { if (is_string($s)) { if (!Selectors::stringHasOperator($s)) { return false; } $selectors = new Selectors($s); } else { if ($s instanceof Selectors) { $selectors = $s; } else { return false; } } $matches = false; foreach ($selectors as $selector) { $name = $selector->field; if (in_array($name, array('limit', 'start', 'sort', 'include'))) { continue; } $matches = true; $value = $this->get($name); if (!$selector->matches("{$value}")) { $matches = false; break; } } return $matches; }
/** * 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; }
/** * Get the value of a requested Page property * * @param string $key * @return mixed * @see __get * */ public function get($key) { if (is_array($key)) { $key = implode('|', $key); } $value = null; switch ($key) { case 'parent_id': case 'parentID': $value = $this->parent ? $this->parent->id : 0; break; case 'child': $value = $this->child(); break; case 'children': case 'subpages': // PW1 $value = $this->children(); break; case 'has_parent': case 'hasParent': $value = $this->parents(); break; case 'parent': case 'parents': case 'rootParent': case 'siblings': case 'next': case 'prev': case 'url': case 'path': case 'outputFormatting': case 'isTrash': $value = $this->{$key}(); break; case 'httpUrl': case 'httpURL': $value = $this->httpUrl(); break; case 'fieldgroup': case 'fields': $value = $this->template->fieldgroup; break; case 'template_id': case 'templates_id': case 'templateID': case 'templatesID': $value = $this->template ? $this->template->id : 0; break; case 'template': case 'templatePrevious': case 'parentPrevious': case 'namePrevious': case 'statusPrevious': case 'isLoaded': case 'isNew': case 'pageNum': case 'instanceID': $value = $this->{$key}; break; case 'out': case 'output': $value = $this->output(); break; case 'filesManager': $value = $this->filesManager(); break; case 'name': $value = $this->settings['name']; break; case 'modified_users_id': case 'modifiedUsersID': case 'modifiedUserID': $value = (int) $this->settings['modified_users_id']; break; case 'created_users_id': case 'createdUsersID': case 'createdUserID': $value = (int) $this->settings['created_users_id']; break; case 'modifiedUser': case 'createdUser': if (!$this->{$key}) { $_key = str_replace('User', '', $key) . '_users_id'; $u = $this->wire('user'); if ($this->settings[$_key] == $u->id) { $this->set($key, $u); // prevent possible recursion loop } else { $u = $this->wire('users')->get((int) $this->settings[$_key]); $this->set($key, $u); } } $value = $this->{$key}; if ($value) { $value->of($this->of()); } break; case 'urlSegment': $value = $this->wire('input')->urlSegment1; // deprecated, but kept for backwards compatibility break; case 'accessTemplate': $value = $this->getAccessTemplate(); break; case 'num_children': case 'numChildren': $value = $this->settings['numChildren']; break; case 'numChildrenVisible': case 'numVisibleChildren': case 'hasChildren': $value = $this->numChildren(true); break; case 'editUrl': case 'editURL': $value = $this->editUrl(); break; case 'statusStr': $value = implode(' ', $this->status(true)); break; case 'modifiedStr': case 'createdStr': case 'publishedStr': $value = $this->settings[str_replace('Str', '', $key)]; $value = $value ? wireDate($this->wire('config')->dateFormat, $value) : ''; break; default: if ($key && isset($this->settings[(string) $key])) { return $this->settings[$key]; } // populate a formatted string with {tag} vars if (strpos($key, '{') !== false && strpos($key, '}')) { return $this->getMarkup($key); } if (($value = $this->getFieldFirstValue($key)) !== null) { return $value; } if (($value = $this->getFieldValue($key)) !== null) { return $value; } // if there is a selector, we'll assume they are using the get() method to get a child if (Selectors::stringHasOperator($key)) { return $this->child($key); } // check if it's a field.subfield property if (strpos($key, '.') && ($value = $this->getFieldSubfieldValue($key)) !== null) { return $value; } // optionally let a hook look at it if (self::isHooked('Page::getUnknown()')) { $value = $this->getUnknown($key); } } return $value; }
/** * Filter out Wires that don't match the selector. * * This is applicable to and destructive to the WireArray. * This function contains additions and modifications by @niklaka. * * @param string|Selectors $selectors AttributeSelector string to use as the filter. * @param bool $not Make this a "not" filter? (default is false) * @return WireArray reference to current [filtered] instance * */ protected function filterData($selectors, $not = false) { if (is_object($selectors) && $selectors instanceof Selectors) { // fantastic } else { if (ctype_digit("{$selectors}")) { $selectors = "id={$selectors}"; } $selectors = new Selectors($selectors); } $sort = array(); $start = 0; $limit = null; // leave sort, limit and start away from filtering selectors foreach ($selectors as $selector) { $remove = true; if ($selector->field === 'sort') { // use all sort selectors $sort[] = $selector->value; } else { if ($selector->field === 'start') { // use only the last start selector $start = (int) $selector->value; } else { if ($selector->field === 'limit') { // use only the last limit selector $limit = (int) $selector->value; } else { // everything else is to be saved for filtering $remove = false; } } } if ($remove) { $selectors->remove($selector); } } // now filter the data according to the selectors that remain foreach ($this->data as $key => $item) { 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)) { unset($this->data[$key]); } } } // if $limit has been given, tell sort the amount of rows that will be used if (count($sort)) { $this->_sort($sort, $limit ? $start + $limit : null); } if ($start || $limit) { $this->data = array_slice($this->data, $start, $limit, true); } $this->trackChange("filterData:{$selectors}"); return $this; }
/** * Does this page have the specified status number or template name? * * See status flag constants at top of Page class * * @param int|string $status Status number or Template name * @return bool * */ public function is($status) { if (is_int($status)) { return $this->status & $status; } else { if (is_string($status) && $this->sanitizer->name($status) == $status) { // valid template name if ($this->template->name == $status) { return true; } } else { if (Selectors::stringHasOperator($status)) { $matches = false; $selectors = new Selectors($status); foreach ($selectors as $selector) { $matches = true; $value = $this->get($selector->field); if (!$selector->matches("{$value}")) { $matches = false; break; } } return $matches; } } } return false; }