/** * Match a number of children count * */ protected function getQueryNumChildren(DatabaseQuerySelect $query, $selector) { if (!in_array($selector->operator, array('=', '<', '>', '<=', '>=', '!='))) { throw new PageFinderSyntaxException("Operator '{$selector->operator}' not allowed for 'num_children' selector."); } $value = (int) $selector->value; $this->getQueryNumChildren++; $n = (int) $this->getQueryNumChildren; $a = "pages_num_children{$n}"; $b = "num_children{$n}"; if (in_array($selector->operator, array('<', '<=', '!=')) && $value || in_array($selector->operator, array('>', '>=', '!=')) && $value < 0 || ($selector->operator == '=' || $selector->operator == '>=') && !$value) { // allow for zero values $query->select("COUNT({$a}.id) AS {$b}"); $query->leftjoin("pages AS {$a} ON ({$a}.parent_id=pages.id)"); $query->groupby("HAVING COUNT({$a}.id){$selector->operator}{$value}"); /* FOR REFERENCE $query->select("count(pages_num_children$n.id) AS num_children$n"); $query->leftjoin("pages AS pages_num_children$n ON (pages_num_children$n.parent_id=pages.id)"); $query->groupby("HAVING count(pages_num_children$n.id){$selector->operator}$value"); */ return $b; } else { // non zero values $query->select("{$a}.{$b} AS {$b}"); $query->leftjoin("(" . "SELECT p{$n}.parent_id, COUNT(p{$n}.id) AS {$b} " . "FROM pages AS p{$n} " . "GROUP BY p{$n}.parent_id " . "HAVING {$b}{$selector->operator}{$value} " . ") {$a} ON {$a}.parent_id=pages.id"); $where = "{$a}.{$b}{$selector->operator}{$value}"; $query->where($where); /* FOR REFERENCE $query->select("pages_num_children$n.num_children$n AS num_children$n"); $query->leftjoin( "(" . "SELECT p$n.parent_id, count(p$n.id) AS num_children$n " . "FROM pages AS p$n " . "GROUP BY p$n.parent_id " . "HAVING num_children$n{$selector->operator}$value" . ") pages_num_children$n ON pages_num_children$n.parent_id=pages.id"); $query->where("pages_num_children$n.num_children$n{$selector->operator}$value"); */ return "{$a}.{$b}"; } }
/** * Match a number of children count * */ protected function getQueryNumChildren(DatabaseQuerySelect $query, $selector) { if (!in_array($selector->operator, array('=', '<', '>', '<=', '>=', '!='))) { throw new WireException("Operator '{$selector->operator}' not allowed for 'num_children' selector."); } if ($this->getQueryNumChildren) { throw new WireException("You may only have one 'children.count' selector per query"); } $value = (int) $selector->value; $this->getQueryNumChildren++; $n = $this->getQueryNumChildren; if (in_array($selector->operator, array('<', '<=', '!=')) && $value || ($selector->operator == '=' || $selector->operator == '>=') && !$value) { // allow for zero values $query->select("count(pages_num_children{$n}.id) AS num_children{$n}"); $query->leftjoin("pages AS pages_num_children{$n} ON (pages_num_children{$n}.parent_id=pages.id)"); $query->groupby("HAVING count(pages_num_children{$n}.id){$selector->operator}{$value}"); } else { // non zero values $query->select("pages_num_children{$n}.num_children{$n} AS num_children{$n}"); $query->leftjoin("(" . "SELECT p{$n}.parent_id, count(p{$n}.id) AS num_children{$n} " . "FROM pages AS p{$n} " . "GROUP BY p{$n}.parent_id " . "HAVING num_children{$n}{$selector->operator}{$value}" . ") pages_num_children{$n} ON pages_num_children{$n}.parent_id=pages.id"); $query->where("pages_num_children{$n}.num_children{$n}{$selector->operator}{$value}"); } }
/** * Given an array or CSV string of Page IDs, return a PageArray * * Optionally specify an $options array rather than a template for argument 2. When present, the 'template' and 'parent_id' arguments may be provided * in the given $options array. These options may be specified: * * LOAD OPTIONS (argument 2 array): * - cache: boolean, default=true. place loaded pages in memory cache? * - getFromCache: boolean, default=true. Allow use of previously cached pages in memory (rather than re-loading it from DB)? * - template: instance of Template (see $template argument) * - parent_id: integer (see $parent_id argument) * - getNumChildren: boolean, default=true. Specify false to disable retrieval and population of 'numChildren' Page property. * - getOne: boolean, default=false. Specify true to return just one Page object, rather than a PageArray. * - autojoin: boolean, default=true. Allow use of autojoin option? * - joinFields: array, default=empty. Autojoin the field names specified in this array, regardless of field settings (requires autojoin=true). * - joinSortfield: boolean, default=true. Whether the 'sortfield' property will be joined to the page. * - findTemplates: boolean, default=true. Determine which templates will be used (when no template specified) for more specific autojoins. * - pageClass: string, default=auto-detect. Class to instantiate Page objects with. Leave blank to determine from template. * - pageArrayClass: string, default=PageArray. PageArray-derived class to store pages in (when 'getOne' is false). * * Use the $options array for potential speed optimizations: * - Specify a 'template' with your call, when possible, so that this method doesn't have to determine it separately. * - Specify false for 'getNumChildren' for potential speed optimization when you know for certain pages will not have children. * - Specify false for 'autojoin' for potential speed optimization in certain scenarios (can also be a bottleneck, so be sure to test). * - Specify false for 'joinSortfield' for potential speed optimization when you know the Page will not have children or won't need to know the order. * - Specify false for 'findTemplates' so this method doesn't have to look them up. Potential speed optimization if you have few autojoin fields globally. * - Note that if you specify false for 'findTemplates' the pageClass is assumed to be 'Page' unless you specify something different for the 'pageClass' option. * * @param array|WireArray|string $_ids Array of IDs or CSV string of IDs * @param Template|array|null $template Specify a template to make the load faster, because it won't have to attempt to join all possible fields... just those used by the template. * Optionally specify an $options array instead, see the method notes above. * @param int|null $parent_id Specify a parent to make the load faster, as it reduces the possibility for full table scans. * This argument is ignored when an options array is supplied for the $template. * @return PageArray|Page Returns Page only if the 'getOne' option is specified, otherwise always returns a PageArray. * @throws WireException * */ public function getById($_ids, $template = null, $parent_id = null) { static $instanceID = 0; $options = array('cache' => true, 'getFromCache' => true, 'template' => null, 'parent_id' => null, 'getNumChildren' => true, 'getOne' => false, 'autojoin' => true, 'findTemplates' => true, 'joinSortfield' => true, 'joinFields' => array(), 'pageClass' => '', 'pageArrayClass' => 'PageArray'); if (is_array($template)) { // $template property specifies an array of options $options = array_merge($options, $template); $template = $options['template']; $parent_id = $options['parent_id']; } else { if (!is_null($template) && !$template instanceof Template) { throw new WireException('getById argument 2 must be Template or $options array'); } } $pageArrayClass = $options['pageArrayClass']; if (!is_null($parent_id) && !is_int($parent_id)) { // convert Page object or string to integer id $parent_id = (int) (string) $parent_id; } if (!is_null($template) && !is_object($template)) { // convert template string or id to Template object $template = $this->wire('templates')->get($template); } if (is_string($_ids)) { // convert string of IDs to array if (strpos($_ids, '|') !== false) { $_ids = explode('|', $_ids); } else { $_ids = explode(",", $_ids); } } else { if (is_int($_ids)) { $_ids = array($_ids); } } if (!WireArray::iterable($_ids) || !count($_ids)) { // return blank if $_ids isn't iterable or is empty return $options['getOne'] ? new NullPage() : new $pageArrayClass(); } if (is_object($_ids)) { $_ids = $_ids->getArray(); } // ArrayObject or the like $loaded = array(); // array of id => Page objects that have been loaded $ids = array(); // sanitized version of $_ids // sanitize ids and determine which pages we can pull from cache foreach ($_ids as $key => $id) { $id = (int) $id; if ($id < 1) { continue; } if ($options['getFromCache'] && ($page = $this->getCache($id))) { // page is already available in the cache $loaded[$id] = $page; } else { if (isset(Page::$loadingStack[$id])) { // if the page is already in the process of being loaded, point to it rather than attempting to load again. // the point of this is to avoid a possible infinite loop with autojoin fields referencing each other. $loaded[$id] = Page::$loadingStack[$id]; // cache the pre-loaded version so that other pages referencing it point to this instance rather than loading again $this->cache($loaded[$id]); } else { $loaded[$id] = ''; // reserve the spot, in this order $ids[(int) $key] = $id; // queue id to be loaded } } } $idCnt = count($ids); // idCnt contains quantity of remaining page ids to load if (!$idCnt) { // if there are no more pages left to load, we can return what we've got if ($options['getOne']) { return count($loaded) ? reset($loaded) : new NullPage(); } $pages = new $pageArrayClass(); $pages->import($loaded); return $pages; } $database = $this->wire('database'); $idsByTemplate = array(); if (is_null($template) && $options['findTemplates']) { // template was not defined with the function call, so we determine // which templates are used by each of the pages we have to load $sql = "SELECT id, templates_id FROM pages WHERE "; if ($idCnt == 1) { $sql .= "id=" . (int) reset($ids); } else { $sql .= "id IN(" . implode(",", $ids) . ")"; } $query = $database->prepare($sql); $result = $this->executeQuery($query); if ($result) { while ($row = $query->fetch(PDO::FETCH_NUM)) { list($id, $templates_id) = $row; $id = (int) $id; $templates_id = (int) $templates_id; if (!isset($idsByTemplate[$templates_id])) { $idsByTemplate[$templates_id] = array(); } $idsByTemplate[$templates_id][] = $id; } } $query->closeCursor(); } else { if (is_null($template)) { // no template provided, and autojoin not needed (so we don't need to know template) $idsByTemplate = array(0 => $ids); } else { // template was provided $idsByTemplate = array($template->id => $ids); } } foreach ($idsByTemplate as $templates_id => $ids) { if ($templates_id && (!$template || $template->id != $templates_id)) { $template = $this->wire('templates')->get($templates_id); } if ($template) { $fields = $template->fieldgroup; } else { $fields = $this->wire('fields'); } $query = new DatabaseQuerySelect(); $sortfield = $template ? $template->sortfield : ''; $joinSortfield = empty($sortfield) && $options['joinSortfield']; $query->select("false AS isLoaded, pages.templates_id AS templates_id, pages.*, " . ($joinSortfield ? 'pages_sortfields.sortfield, ' : '') . ($options['getNumChildren'] ? '(SELECT COUNT(*) FROM pages AS children WHERE children.parent_id=pages.id) AS numChildren' : '')); if ($joinSortfield) { $query->leftjoin('pages_sortfields ON pages_sortfields.pages_id=pages.id'); } $query->groupby('pages.id'); if ($options['autojoin'] && $this->autojoin) { foreach ($fields as $field) { if (!empty($options['joinFields']) && in_array($field->name, $options['joinFields'])) { // joinFields option specified to force autojoin this field } else { if (!($field->flags & Field::flagAutojoin)) { continue; } // autojoin not enabled for field if ($fields instanceof Fields && !($field->flags & Field::flagGlobal)) { continue; } // non-fieldgroup, autojoin only if global flag is set } $table = $database->escapeTable($field->table); if (!$field->type || !$field->type->getLoadQueryAutojoin($field, $query)) { continue; } // autojoin not allowed $query->leftjoin("{$table} ON {$table}.pages_id=pages.id"); // QA } } if (!is_null($parent_id)) { $query->where("pages.parent_id=" . (int) $parent_id); } if ($template) { $query->where("pages.templates_id=" . (int) $template->id); } // QA $query->where("pages.id IN(" . implode(',', $ids) . ") "); // QA $query->from("pages"); $stmt = $query->prepare(); $this->executeQuery($stmt); $class = $options['pageClass']; if (empty($class)) { if ($template) { $class = $template->pageClass && class_exists($template->pageClass) ? $template->pageClass : 'Page'; } else { $class = 'Page'; } } if ($class != 'Page' && !class_exists($class)) { $this->error("Class '{$class}' for Pages::getById() does not exist", Notice::log); $class = 'Page'; } try { while ($page = $stmt->fetchObject($class, array($template))) { $page->instanceID = ++$instanceID; $page->setIsLoaded(true); $page->setIsNew(false); $page->setTrackChanges(true); $page->setOutputFormatting($this->outputFormatting); $loaded[$page->id] = $page; if ($options['cache']) { $this->cache($page); } } } catch (Exception $e) { $error = $e->getMessage() . " [pageClass={$class}, template={$template}]"; if ($this->wire('user')->isSuperuser()) { $this->error($error); } $this->wire('log')->error($error); $this->trackException($e, false); } $stmt->closeCursor(); $template = null; } if ($options['getOne']) { return count($loaded) ? reset($loaded) : new NullPage(); } $pages = new $pageArrayClass(); return $pages->import($loaded); }
/** * Given an array or CSV string of Page IDs, return a PageArray * * @param array|WireArray|string $ids Array of IDs or CSV string of IDs * @param Template $template Specify a template to make the load faster, because it won't have to attempt to join all possible fields... just those used by the template. * @param int $parent_id Specify a parent to make the load faster, as it reduces the possibility for full table scans * @return PageArray * */ public function getById($ids, Template $template = null, $parent_id = null) { static $instanceID = 0; $pages = new PageArray(); if (is_string($ids)) { $ids = explode(",", $ids); } if (!WireArray::iterable($ids) || !count($ids)) { return $pages; } if (is_object($ids)) { $ids = $ids->getArray(); } $loaded = array(); foreach ($ids as $key => $id) { $id = (int) $id; $ids[$key] = $id; if ($page = $this->getCache($id)) { $loaded[$id] = $page; unset($ids[$key]); } else { if (isset(Page::$loadingStack[$id])) { // if the page is already in the process of being loaded, point to it rather than attempting to load again. // the point of this is to avoid a possible infinite loop with autojoin fields referencing each other. $loaded[$id] = Page::$loadingStack[$id]; // cache the pre-loaded version so that other pages referencing it point to this instance rather than loading again $this->cache($loaded[$id]); unset($ids[$key]); } else { $loaded[$id] = ''; // reserve the spot, in this order } } } $idCnt = count($ids); if (!$idCnt) { return $pages->import($loaded); } $idsByTemplate = array(); if (is_null($template)) { $sql = "SELECT id, templates_id FROM pages WHERE "; if ($idCnt == 1) { $sql .= "id=" . (int) reset($ids); } else { $sql .= "id IN(" . implode(",", $ids) . ")"; } $result = $this->db->query($sql); if ($result && $result->num_rows) { while ($row = $result->fetch_row()) { list($id, $templates_id) = $row; if (!isset($idsByTemplate[$templates_id])) { $idsByTemplate[$templates_id] = array(); } $idsByTemplate[$templates_id][] = $id; } } $result->free(); } else { $idsByTemplate = array($template->id => $ids); } foreach ($idsByTemplate as $templates_id => $ids) { if (!$template || $template->id != $templates_id) { $template = $this->fuel('templates')->get($templates_id); } $fields = $template->fieldgroup; $query = new DatabaseQuerySelect(); $query->select("false AS isLoaded, pages.templates_id AS templates_id, pages.*, pages_sortfields.sortfield, " . "(SELECT COUNT(*) FROM pages AS children WHERE children.parent_id=pages.id) AS numChildren"); $query->leftjoin("pages_sortfields ON pages_sortfields.pages_id=pages.id"); $query->groupby("pages.id"); foreach ($fields as $field) { if (!($field->flags & Field::flagAutojoin)) { continue; } $table = $field->table; if (!$field->type->getLoadQueryAutojoin($field, $query)) { continue; } // autojoin not allowed $query->leftjoin("{$table} ON {$table}.pages_id=pages.id"); } if (!is_null($parent_id)) { $query->where("pages.parent_id=" . (int) $parent_id); } $query->where("pages.templates_id={$template->id}"); $query->where("pages.id IN(" . implode(',', $ids) . ") "); $query->from("pages"); if (!($result = $query->execute())) { throw new WireException($this->db->error); } $class = $template->pageClass && class_exists($template->pageClass) ? $template->pageClass : 'Page'; while ($page = $result->fetch_object($class, array($template))) { $page->instanceID = ++$instanceID; $page->setIsLoaded(true); $page->setIsNew(false); $page->setTrackChanges(true); $page->setOutputFormatting($this->outputFormatting); $loaded[$page->id] = $page; $this->cache($page); } $template = null; $result->free(); } return $pages->import($loaded); }
/** * Given one or more selectors, create the SQL query for finding pages. * * @TODO split this method up into more parts, it's too long * * @param array $selectors Array of selectors. * @return string SQL statement. * */ protected function getQuery($selectors) { $where = ''; $cnt = 1; $fieldtypes = $this->fieldtypes; $fieldCnt = array(); // counts number of instances for each field to ensure unique table aliases for ANDs on the same field $lastSelector = null; $sortSelectors = array(); // selector containing 'sort=', which gets added last $joins = array(); $nullPage = new NullPage(); $query = new DatabaseQuerySelect(); $query->select(array('pages.id', 'pages.templates_id')); $query->from("pages"); $query->groupby("pages.id"); foreach ($selectors as $selectorCnt => $selector) { if (is_null($lastSelector)) { $lastSelector = $selector; } $fields = $selector->field; $fields = is_array($fields) ? $fields : array($fields); $field = reset($fields); // first field // TODO Make native fields and path/url multi-field and multi-value aware if ($field == 'sort') { $sortSelectors[] = $selector; continue; } else { if ($field == 'limit' || $field == 'start') { $this->getQueryStartLimit($query, $selectors); continue; } else { if ($field == 'path' || $field == 'url') { $this->getQueryJoinPath($query, $selector); continue; } else { if ($field == 'has_parent') { $this->getQueryHasParent($query, $selector); continue; } else { if ($this->getFuel('fields')->isNativeName($field)) { $this->getQueryNativeField($query, $selector, $field); continue; } } } } } foreach ($fields as $n => $field) { // if a specific DB field from the table has been specified, then get it, otherwise assume 'data' if (strpos($field, ".")) { list($field, $subfield) = explode(".", $field); } else { $subfield = 'data'; } if (!($field = $this->fuel('fields')->get($field))) { throw new WireException("Field does not exist: {$fields[$n]}"); } // keep track of number of times this table name has appeared in the query if (!isset($fieldCnt[$field->table])) { $fieldCnt[$field->table] = 0; } else { $fieldCnt[$field->table]++; } // use actual table name if first instance, if second instance of table then add a number at the end $tableAlias = $field->table . ($fieldCnt[$field->table] ? $fieldCnt[$field->table] : ''); $valueArray = is_array($selector->value) ? $selector->value : array($selector->value); $join = ''; $fieldtype = $field->type; foreach ($valueArray as $value) { if (isset($subqueries[$tableAlias])) { $q = $subqueries[$tableAlias]; } else { $q = new DatabaseQuerySelect(); } //if($subfield == 'data' && in_array($selector->operator, array('=', '!=', '<>')) && $value === $fieldtype->getBlankValue($nullPage, $field)) { if ($subfield == 'data' && in_array($selector->operator, array('=', '!=', '<>')) && empty($value)) { // handle blank values -- look in table that has no pages_id relation back to pages, using the LEFT JOIN / IS NULL trick $query->leftjoin("{$tableAlias} ON {$tableAlias}.pages_id=pages.id"); $query->where("{$tableAlias}.pages_id " . ($selector->operator == '=' ? "IS" : "IS NOT") . " NULL"); continue; } else { $q = $fieldtype->getMatchQuery($q, $tableAlias, $subfield, $selector->operator, $value); $query->select($q->select); $query->orderby($q->orderby); } if (count($q->where)) { $and = $selector->not ? "AND NOT" : "AND"; $sql = ''; foreach ($q->where as $w) { $sql .= $sql ? "{$and} {$w} " : "{$w} "; } $sql = "({$sql}) "; if ($selector->not) { $sql = "(NOT {$sql})"; } $join .= $join ? "\n\t\tOR {$sql} " : $sql; } $cnt++; } if ($join) { $joinType = "join"; if (count($fields) > 1) { $joinType = "leftjoin"; if ($where) { $whereType = $lastSelector->str == $selector->str ? "OR" : ") AND ("; $where .= "\n\t{$whereType} ({$join}) "; } else { $where .= "({$join}) "; } } // we compile the joins after going through all the selectors, so that we can // match up conditions to the same tables if (isset($joins[$tableAlias])) { $joins[$tableAlias]['join'] .= " AND ({$join}) "; } else { $joins[$tableAlias] = array('joinType' => $joinType, 'table' => $field->table, 'tableAlias' => $tableAlias, 'join' => "({$join})"); } } $lastSelector = $selector; } // fields } // selectors if ($where) { $query->where("({$where})"); } // complete the joins, matching up any conditions for the same table foreach ($joins as $j) { $joinType = $j['joinType']; $query->{$joinType}("{$j['table']} AS {$j['tableAlias']} ON {$j['tableAlias']}.pages_id=pages.id AND ({$j['join']})"); } if (count($sortSelectors)) { foreach (array_reverse($sortSelectors) as $s) { $this->getQuerySortSelector($query, $s); } } return $query; }
/** * Given an array or CSV string of Page IDs, return a PageArray * * Optionally specify an $options array rather than a template for argument 2. When present, the 'template' and 'parent_id' arguments may be provided * in the given $options array. These options may be specified: * * - template: instance of Template (see $template argument) * - parent_id: integer (see $parent_id argument) * - getNumChildren: boolean, default=true. Specify false to disable retrieval and population of 'numChildren' Page property. * * @param array|WireArray|string $ids Array of IDs or CSV string of IDs * @param Template|array|null $template Specify a template to make the load faster, because it won't have to attempt to join all possible fields... just those used by the template. * Optionally specify an $options array instead, see the method notes above. * @param int|null $parent_id Specify a parent to make the load faster, as it reduces the possibility for full table scans. * This argument is ignored when an options array is supplied for the $template. * @return PageArray * @throws WireException * */ public function getById($ids, $template = null, $parent_id = null) { $options = array('template' => null, 'parent_id' => null, 'getNumChildren' => true); if (is_array($template)) { // $template property specifies an array of options $options = array_merge($options, $template); $template = $options['template']; $parent_id = $options['parent_id']; } else { if (!is_null($template) && !$template instanceof Template) { throw new WireException('getById argument 2 must be Template or $options array'); } } static $instanceID = 0; $database = $this->wire('database'); $pages = new PageArray(); if (is_string($ids)) { $ids = explode(",", $ids); } if (!WireArray::iterable($ids) || !count($ids)) { return $pages; } if (is_object($ids)) { $ids = $ids->getArray(); } $loaded = array(); foreach ($ids as $key => $id) { $id = (int) $id; $ids[$key] = $id; if ($page = $this->getCache($id)) { $loaded[$id] = $page; unset($ids[$key]); } else { if (isset(Page::$loadingStack[$id])) { // if the page is already in the process of being loaded, point to it rather than attempting to load again. // the point of this is to avoid a possible infinite loop with autojoin fields referencing each other. $loaded[$id] = Page::$loadingStack[$id]; // cache the pre-loaded version so that other pages referencing it point to this instance rather than loading again $this->cache($loaded[$id]); unset($ids[$key]); } else { $loaded[$id] = ''; // reserve the spot, in this order } } } $idCnt = count($ids); if (!$idCnt) { return $pages->import($loaded); } $idsByTemplate = array(); if (is_null($template)) { $sql = "SELECT id, templates_id FROM pages WHERE "; if ($idCnt == 1) { $sql .= "id=" . (int) reset($ids); } else { $sql .= "id IN(" . implode(",", $ids) . ")"; } $query = $database->prepare($sql); $result = $query->execute(); if ($result) { while ($row = $query->fetch(PDO::FETCH_NUM)) { list($id, $templates_id) = $row; if (!isset($idsByTemplate[$templates_id])) { $idsByTemplate[$templates_id] = array(); } $idsByTemplate[$templates_id][] = $id; } } $query->closeCursor(); } else { $idsByTemplate = array($template->id => $ids); } foreach ($idsByTemplate as $templates_id => $ids) { if (!$template || $template->id != $templates_id) { $template = $this->wire('templates')->get($templates_id); } $fields = $template->fieldgroup; $query = new DatabaseQuerySelect(); $joinSortfield = empty($template->sortfield); $query->select("false AS isLoaded, pages.templates_id AS templates_id, pages.*, " . ($joinSortfield ? 'pages_sortfields.sortfield, ' : '') . ($options['getNumChildren'] ? '(SELECT COUNT(*) FROM pages AS children WHERE children.parent_id=pages.id) AS numChildren' : '')); if ($joinSortfield) { $query->leftjoin('pages_sortfields ON pages_sortfields.pages_id=pages.id'); } $query->groupby('pages.id'); foreach ($fields as $field) { if (!($field->flags & Field::flagAutojoin)) { continue; } $table = $database->escapeTable($field->table); if (!$field->type || !$field->type->getLoadQueryAutojoin($field, $query)) { continue; } // autojoin not allowed $query->leftjoin("{$table} ON {$table}.pages_id=pages.id"); // QA } if (!is_null($parent_id)) { $query->where("pages.parent_id=" . (int) $parent_id); } $query->where("pages.templates_id=" . (int) $template->id); // QA $query->where("pages.id IN(" . implode(',', $ids) . ") "); // QA $query->from("pages"); $stmt = $query->execute(); if ($stmt->errorCode() > 0) { $errorInfo = $result->errorInfo(); throw new WireException($errorInfo[2]); } $class = $template->pageClass && class_exists($template->pageClass) ? $template->pageClass : 'Page'; while ($page = $stmt->fetchObject($class, array($template))) { $page->instanceID = ++$instanceID; $page->setIsLoaded(true); $page->setIsNew(false); $page->setTrackChanges(true); $page->setOutputFormatting($this->outputFormatting); $loaded[$page->id] = $page; $this->cache($page); } $stmt->closeCursor(); $template = null; } return $pages->import($loaded); }
/** * Given an array or CSV string of Page IDs, return a PageArray * * @param array|WireArray|string $ids Array of IDs or CSV string of IDs * @param Template $template Specify a template to make the load faster, because it won't have to attempt to join all possible fields... just those used by the template. * @param int $parent_id Specify a parent to make the load faster, as it reduces the possibility for full table scans * @return PageArray * */ public function getById($ids, Template $template = null, $parent_id = null) { static $instanceID = 0; $pages = new PageArray(); if (is_string($ids)) { $ids = explode(",", $ids); } if (!WireArray::iterable($ids) || !count($ids)) { return $pages; } if (is_object($ids)) { $ids = $ids->getArray(); } $loaded = array(); foreach ($ids as $key => $id) { $id = (int) $id; $ids[$key] = $id; if ($page = $this->getCache($id)) { $loaded[$id] = $page; unset($ids[$key]); } else { $loaded[$id] = ''; // reserve the spot, in this order } } $idCnt = count($ids); $fields = is_null($template) ? $this->fuel->fields : $template->fieldgroup; if ($idCnt) { // Optimization to only load the fields specific to the page, if just one page. // Without it, all autojoin fields have to be attempted, whether they are applicable to the pages loaded or not. // Even though this increases queries, it does result in a slight overall speed and memory improvement. if (is_null($template) && $idCnt == 1) { $result = $this->db->query("SELECT templates_id FROM pages WHERE id=" . (int) reset($ids)); if ($result) { list($tpl_id) = $result->fetch_row(); $template = $this->fuel('templates')->get($tpl_id); if ($template) { $fields = $template->fieldgroup; } $result->free(); } } $query = new DatabaseQuerySelect(); $query->select("false AS isLoaded, pages.templates_id AS templates_id, pages.*, pages_sortfields.sortfield, " . "(SELECT COUNT(*) FROM pages AS children WHERE children.parent_id=pages.id) AS numChildren"); $query->leftjoin("pages_sortfields ON pages_sortfields.pages_id=pages.id"); $query->groupby("pages.id"); foreach ($fields as $field) { if (!($field->flags & Field::flagAutojoin)) { continue; } //if($field->type instanceof FieldtypeMulti) continue; $table = $field->table; // if($field->type instanceof FieldtypeMulti) { // $sel .= "(SELECT COUNT(*) FROM $table WHERE $table.pages_id=pages.id) AS {$field->name}, "; // } else { if (!$field->type->getLoadQueryAutojoin($field, $query)) { continue; } // autojoin not allowed // $query->select("$table.data AS {$field->name}"); $query->leftjoin("{$table} ON {$table}.pages_id=pages.id"); } if (!is_null($parent_id)) { $query->where("pages.parent_id=" . (int) $parent_id); } if (!is_null($template)) { $query->where("pages.templates_id={$template->id}"); } $query->where("pages.id IN(" . implode(',', $ids) . ") "); $query->from("pages"); if (!($result = $query->execute())) { throw new WireException($this->db->error); } while ($page = $result->fetch_object('Page', array($template))) { $page->instanceID = ++$instanceID; $page->setIsLoaded(true); $page->setIsNew(false); $page->setTrackChanges(true); $page->setOutputFormatting($this->outputFormatting); $loaded[$page->id] = $page; $this->cache($page); } $result->free(); } $pages->import($loaded); return $pages; }