Example #1
0
 /**
  * Given a Selector string, return the Page objects that match in a PageArray. 
  * 
  * Non-visible pages are excluded unless an include=hidden|unpublished|all mode is specified in the selector string, 
  * or in the $options array. If 'all' mode is specified, then non-accessible pages (via access control) can also be included. 
  *
  * @param string|int|array $selectorString Specify selector string (standard usage), but can also accept page ID or array of page IDs.
  * @param array|string $options Optional one or more options that can modify certain behaviors. May be assoc array or key=value string.
  *	- findOne: boolean - apply optimizations for finding a single page and include pages with 'hidden' status (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 $selectorString 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 
  * 		directly in the selector string, so the option is mainly useful if your first argument is not a string. 
  * 	- loadOptions: array - Optional assoc array of options to pass to getById() load options.
  * @return PageArray
  *
  */
 public function ___find($selectorString, $options = array())
 {
     if (is_string($options)) {
         $options = Selectors::keyValueStringToArray($options);
     }
     if (is_array($selectorString)) {
         if (ctype_digit(implode('', array_keys($selectorString))) && ctype_digit(implode('', $selectorString))) {
             // if given a regular array of page IDs, we delegate that to getById() method, but with access/visibility control
             return $this->filterListable($this->getById($selectorString), isset($options['include']) ? $options['include'] : '');
         } else {
             // some other type of array/values that we don't yet recognize
             // @todo add support for array selectors, per Selectors::arrayToSelectorString()
             return new PageArray();
         }
     }
     $loadPages = true;
     $loadOptions = isset($options['loadOptions']) && is_array($options['loadOptions']) ? $options['loadOptions'] : array();
     $debug = $this->wire('config')->debug;
     if (array_key_exists('loadPages', $options)) {
         $loadPages = (bool) $options['loadPages'];
     }
     if (!strlen($selectorString)) {
         return new PageArray();
     }
     if ($selectorString === '/' || $selectorString === 'path=/') {
         $selectorString = 1;
     }
     if ($selectorString[0] == '/') {
         // if selector begins with a slash, then we'll assume it's referring to a path
         $selectorString = "path={$selectorString}";
     } else {
         if (strpos($selectorString, ",") === false && strpos($selectorString, "|") === false) {
             // there is just one param. Lets see if we can find a shortcut.
             if (ctype_digit("{$selectorString}") || strpos($selectorString, "id=") === 0) {
                 // if selector is just a number, or a string like "id=123" then we're going to do a shortcut
                 $s = str_replace("id=", '', $selectorString);
                 if (ctype_digit("{$s}")) {
                     $value = $this->getById(array((int) $s), $loadOptions);
                     if (empty($options['findOne'])) {
                         $value = $this->filterListable($value, isset($options['include']) ? $options['include'] : '');
                     }
                     if ($debug) {
                         $this->debugLog('find', $selectorString . " [optimized]", $value);
                     }
                     return $value;
                 }
             }
         }
     }
     if (isset($options['include']) && in_array($options['include'], array('hidden', 'unpublished', 'all'))) {
         $selectorString .= ", include={$options['include']}";
     }
     // see if this has been cached and return it if so
     $pages = $this->getSelectorCache($selectorString, $options);
     if (!is_null($pages)) {
         if ($debug) {
             $this->debugLog('find', $selectorString, $pages . ' [from-cache]');
         }
         return $pages;
     }
     // check if this find has already been executed, and return the cached results if so
     // if(null !== ($pages = $this->getSelectorCache($selectorString, $options))) return clone $pages;
     // if a specific parent wasn't requested, then we assume they don't want results with status >= Page::statusUnsearchable
     // if(strpos($selectorString, 'parent_id') === false) $selectorString .= ", status<" . Page::statusUnsearchable;
     $caller = isset($options['caller']) ? $options['caller'] : 'pages.find';
     $selectors = new Selectors($selectorString);
     $pageFinder = $this->getPageFinder();
     if ($debug) {
         Debug::timer("{$caller}({$selectorString})", true);
     }
     $pagesInfo = $pageFinder->find($selectors, $options);
     // note that we save this pagination state here and set it at the end of this method
     // because it's possible that more find operations could be executed as the pages are loaded
     $total = $pageFinder->getTotal();
     $limit = $pageFinder->getLimit();
     $start = $pageFinder->getStart();
     if ($loadPages) {
         // parent_id is null unless a single parent was specified in the selectors
         $parent_id = $pageFinder->getParentID();
         $idsSorted = array();
         $idsByTemplate = array();
         // organize the pages by template ID
         foreach ($pagesInfo as $page) {
             $tpl_id = $page['templates_id'];
             if (!isset($idsByTemplate[$tpl_id])) {
                 $idsByTemplate[$tpl_id] = array();
             }
             $idsByTemplate[$tpl_id][] = $page['id'];
             $idsSorted[] = $page['id'];
         }
         if (count($idsByTemplate) > 1) {
             // perform a load for each template, which results in unsorted pages
             $unsortedPages = new PageArray();
             foreach ($idsByTemplate as $tpl_id => $ids) {
                 $opt = $loadOptions;
                 $opt['template'] = $this->templates->get($tpl_id);
                 $opt['parent_id'] = $parent_id;
                 $unsortedPages->import($this->getById($ids, $opt));
             }
             // put pages back in the order that the selectorEngine returned them in, while double checking that the selector matches
             $pages = new PageArray();
             foreach ($idsSorted as $id) {
                 foreach ($unsortedPages as $page) {
                     if ($page->id == $id) {
                         $pages->add($page);
                         break;
                     }
                 }
             }
         } else {
             // there is only one template used, so no resorting is necessary
             $pages = new PageArray();
             reset($idsByTemplate);
             $opt = $loadOptions;
             $opt['template'] = $this->templates->get(key($idsByTemplate));
             $opt['parent_id'] = $parent_id;
             $pages->import($this->getById($idsSorted, $opt));
         }
     } else {
         $pages = new PageArray();
     }
     $pages->setTotal($total);
     $pages->setLimit($limit);
     $pages->setStart($start);
     $pages->setSelectors($selectors);
     $pages->setTrackChanges(true);
     if ($loadPages) {
         $this->selectorCache($selectorString, $options, $pages);
     }
     if ($this->config->debug) {
         $this->debugLog('find', $selectorString, $pages);
     }
     if ($debug) {
         $count = $pages->count();
         $note = ($count == $total ? $count : $count . "/{$total}") . " page(s)";
         if ($count) {
             $note .= ": " . $pages->first()->path;
             if ($count > 1) {
                 $note .= " ... " . $pages->last()->path;
             }
         }
         Debug::saveTimer("{$caller}({$selectorString})", $note);
     }
     if ($this->hasHook('found()')) {
         $this->found($pages, array('pageFinder' => $pageFinder, 'pagesInfo' => $pagesInfo, 'options' => $options));
     }
     return $pages;
 }
Example #2
0
 /**
  * Get the role pages that are part of this template
  *
  * This method returns a blank PageArray if roles haven't yet been loaded into the template. 
  * If the roles have previously been loaded as an array, then this method converts that array to a PageArray and returns it. 
  *
  * @return PageArray
  *
  */
 public function getRoles()
 {
     if (is_null($this->_roles)) {
         return new PageArray();
     } else {
         if ($this->_roles instanceof PageArray) {
             return $this->_roles;
         } else {
             if (is_array($this->_roles)) {
                 $errors = array();
                 $roles = new PageArray();
                 if (count($this->_roles)) {
                     $test = implode('0', $this->_roles);
                     // test to see if it's all digits (IDs)
                     if (ctype_digit("{$test}")) {
                         $roles->import($this->pages->getById($this->_roles));
                     } else {
                         // role names
                         foreach ($this->_roles as $name) {
                             $role = $this->wire('roles')->get($name);
                             if ($role->id) {
                                 $roles->add($role);
                             } else {
                                 $errors[] = $name;
                             }
                         }
                     }
                 }
                 if (count($errors) && $this->useRoles) {
                     $this->error("Unable to load role(s): " . implode(', ', $errors));
                 }
                 $this->_roles = $roles;
                 return $this->_roles;
             } else {
                 return new PageArray();
             }
         }
     }
 }
Example #3
0
 /**
  * Get the role pages that are part of this template
  *
  * This method returns a blank PageArray if roles haven't yet been loaded into the template. 
  * If the roles have previously been loaded as an array, then this method converts that array to a PageArray and returns it. 
  *
  * @return PageArray
  *
  */
 protected function getRoles()
 {
     if (is_null($this->_roles)) {
         return new PageArray();
     } else {
         if ($this->_roles instanceof PageArray) {
             return $this->_roles;
         } else {
             if (is_array($this->_roles)) {
                 $roles = new PageArray();
                 if (count($this->_roles)) {
                     $roles->import($this->pages->getById($this->_roles));
                 }
                 $this->_roles = $roles;
                 return $this->_roles;
             } else {
                 return new PageArray();
             }
         }
     }
 }
Example #4
0
 /**
  * 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);
 }
Example #5
0
 /**
  * Get the role pages that are part of this template
  *
  * This method returns a blank PageArray if roles haven't yet been loaded into the template. 
  * If the roles have previously been loaded as an array, then this method converts that array to a PageArray and returns it. 
  *
  * @param string $type Default is 'view', but you may also specify 'edit', 'create' or 'add' to retrieve those
  * @return PageArray
  * @throws WireException if given an unknown roles type
  *
  */
 public function getRoles($type = 'view')
 {
     if (strpos($type, 'page-') === 0) {
         $type = str_replace('page-', '', $type);
     }
     if ($type != 'view') {
         $roles = new PageArray();
         $roleIDs = null;
         if ($type == 'edit') {
             $roleIDs = $this->editRoles;
         } else {
             if ($type == 'create') {
                 $roleIDs = $this->createRoles;
             } else {
                 if ($type == 'add') {
                     $roleIDs = $this->addRoles;
                 } else {
                     throw new WireException("Unknown roles type: {$type}");
                 }
             }
         }
         if (empty($roleIDs)) {
             return $roles;
         }
         return $this->wire('pages')->getById($roleIDs);
     }
     // type=view assumed from this point forward
     if (is_null($this->_roles)) {
         return new PageArray();
     } else {
         if ($this->_roles instanceof PageArray) {
             return $this->_roles;
         } else {
             if (is_array($this->_roles)) {
                 $errors = array();
                 $roles = new PageArray();
                 if (count($this->_roles)) {
                     $test = implode('0', $this->_roles);
                     // test to see if it's all digits (IDs)
                     if (ctype_digit("{$test}")) {
                         $roles->import($this->pages->getById($this->_roles));
                     } else {
                         // role names
                         foreach ($this->_roles as $name) {
                             $role = $this->wire('roles')->get($name);
                             if ($role->id) {
                                 $roles->add($role);
                             } else {
                                 $errors[] = $name;
                             }
                         }
                     }
                 }
                 if (count($errors) && $this->useRoles) {
                     $this->error("Unable to load role(s): " . implode(', ', $errors));
                 }
                 $this->_roles = $roles;
                 return $this->_roles;
             } else {
                 return new PageArray();
             }
         }
     }
 }
Example #6
0
 /**
  * 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;
 }