From abcce91e4bfefed33b5767c819e9ca4fbaaa289a Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Wed, 13 Oct 2021 13:32:59 -0400 Subject: [PATCH] Updates to PagePaths and PagePathHistory modules, adding support for root segments, among other things --- wire/modules/PagePathHistory.module | 423 +++++++++++++++++----------- wire/modules/PagePaths.module | 126 ++++++++- 2 files changed, 379 insertions(+), 170 deletions(-) diff --git a/wire/modules/PagePathHistory.module b/wire/modules/PagePathHistory.module index 0e99c06d..a472d82e 100644 --- a/wire/modules/PagePathHistory.module +++ b/wire/modules/PagePathHistory.module @@ -6,11 +6,12 @@ * Keeps track of past URLs where pages have lived and automatically 301 redirects * to the new location whenever the past URL is accessed. * - * ProcessWire 3.x, Copyright 2019 by Ryan Cramer + * ProcessWire 3.x, Copyright 2021 by Ryan Cramer * https://processwire.com * * @method upgrade($fromVersion, $toVersion) * @property int $minimumAge + * @property array|bool $rootSegments * * */ @@ -20,7 +21,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => 'Page Path History', - 'version' => 7, + 'version' => 8, 'summary' => "Keeps track of past URLs where pages have lived and automatically redirects (301 permament) to the new location whenever the past URL is accessed.", 'singular' => true, 'autoload' => true, @@ -62,6 +63,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { public function __construct() { parent::__construct(); $this->set('minimumAge', self::minimumAge); + $this->set('rootSegments', false); } /** @@ -143,7 +145,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { if($result) { // delete any possible entries that overlap with the $page current path since are no longer applicable $query = $database->prepare("DELETE FROM $table WHERE path=:path LIMIT 1"); - $query->bindValue(":path", rtrim($this->wire('sanitizer')->pagePathName($page->path, Sanitizer::toAscii), '/')); + $query->bindValue(":path", rtrim($this->wire()->sanitizer->pagePathName($page->path, Sanitizer::toAscii), '/')); $query->execute(); } @@ -160,14 +162,16 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { * */ public function addPathHistory(Page $page, $path, $language = null) { - - $database = $this->wire('database'); + + $sanitizer = $this->wire()->sanitizer; + $database = $this->wire()->database; + $modules = $this->wire()->modules; $table = self::dbTableName; - $path = $this->wire('sanitizer')->pagePathName('/' . trim($path, '/'), Sanitizer::toAscii); + $path = $sanitizer->pagePathName('/' . trim($path, '/'), Sanitizer::toAscii); $selector = "path=$path"; - if($this->wire('modules')->isInstalled('PagePaths')) $selector .= ", id!=$page->id"; - if($this->wire('pages')->count($selector)) return false; + if($modules->isInstalled('PagePaths')) $selector .= ", id!=$page->id"; + if($this->wire()->pages->count($selector)) return false; $language = $this->getLanguage($language); @@ -186,6 +190,8 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { $result = false; } + $this->addRootSegment($path); + return $result; } @@ -199,9 +205,9 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { */ public function deletePathHistory(Page $page, $path) { - $database = $this->wire('database'); + $database = $this->wire()->database; $table = self::dbTableName; - $path = $this->wire('sanitizer')->pagePathName('/' . trim($path, '/'), Sanitizer::toAscii); + $path = $this->wire()->sanitizer->pagePathName('/' . trim($path, '/'), Sanitizer::toAscii); $sql = "DELETE FROM $table WHERE path=:path AND pages_id=:pages_id LIMIT 1"; $query = $database->prepare($sql); @@ -234,6 +240,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { } else { throw new WireException("Invalid param: instance of Page or boolean true expected"); } + $this->rebuildRootSegments(); } /** @@ -515,141 +522,6 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { return $paths; } - /** - * Hook called when a page is moved or renamed - * - * @param HookEvent $event - * - */ - public function hookPageMoved(HookEvent $event) { - - /** @var Page $page */ - $page = $event->arguments[0]; - /** @var Languages $languages */ - $languages = $this->getLanguages(); - - $age = time() - $page->created; - if($page->template->name === 'admin' || $this->wire()->pages->cloning || $age < $this->minimumAge) return; - - // note that the paths we store have no trailing slash - - if($languages) { - $parent = $page->parent(); - $parentPrevious = $page->parentPrevious; - if($parentPrevious && $parentPrevious->id == $parent->id) $parentPrevious = null; - foreach($languages as $language) { - /** @var Language $language */ - if($language->isDefault()) continue; - $namePrevious = $page->get("-name$language"); - if(!$namePrevious && !$parentPrevious) continue; - if(!$namePrevious) $namePrevious = $page->name; - $languages->setLanguage($language); - $pathPrevious = $parentPrevious ? $parentPrevious->path() : $page->parent()->path(); - $pathPrevious = rtrim($pathPrevious, '/') . "/$namePrevious"; - $this->setPathHistory($page, $pathPrevious, $language->id); - $languages->unsetLanguage(); - } - } - - if(!$page->namePrevious) { - // abort saving a former URL if it looks like there isn't going to be one - if(!$page->parentPrevious || $page->parentPrevious->id == $page->parent->id) return; - } - - if($page->parentPrevious) { - - // if former or current parent is in trash, then don't bother saving redirects - if($page->parentPrevious->isTrash() || $page->parent->isTrash()) return; - - // the start of our redirect URL will be the previous parent's URL - $path = $page->parentPrevious->path; - - } else { - // the start of our redirect URL will be the current parent's URL (i.e. name changed) - $path = $page->parent->path; - } - - if($page->namePrevious) { - $path = rtrim($path, '/') . '/' . $page->namePrevious; - } else { - $path = rtrim($path, '/') . '/' . $page->name; - } - - // do not save paths that reference recovery format used by trash - // example: /blog/posts/5134.3096.83_page-name - if(strpos($path, '.') !== false && strpos($path, '_') !== false) { - if(preg_match('!/\d+\.\d+\.\d+_!', $path)) return; - } - - // do not save paths that match any untitled page name - // example: /blog/posts/untitled-123123 - $untitled = $this->wire()->pages->names()->untitledPageName(); - if(strpos($path, $untitled) !== false) { - if(preg_match('!/' . preg_quote($untitled) . '[-]!', $path)) return; - } - - if($languages) $languages->setDefault(); - $this->setPathHistory($page, $path); - if($languages) $languages->unsetDefault(); - } - - /** - * Hook called upon 404 from ProcessPageView::pageNotFound - * - * @param HookEvent $event - * - */ - public function hookPageNotFound(HookEvent $event) { - - /** @var Page $page */ - $page = $event->arguments(0); - /** @var Wire404Exception $exception */ - $exception = $event->arguments(4); - - // If there is a page object set, then it means the 404 was triggered - // by the user not having access to it, or by the $page's template - // throwing a 404 exception. In either case, we don't want to do a - // redirect if there is a $page since any 404 is intentional there. - if($page && $page->id) { - // it did resolve to a Page: maybe a front-end 404 - if(!$exception) { - // pageNotFound was called without an Exception - return; - } else if($exception->getCode() == Wire404Exception::codeFunction) { - // the wire404() function was called: allow PagePathHistory - } else if($exception->getMessage() === "1") { - // also allow PagePathHistory to operate when: throw new WireException(true); - } else { - // likely user didn't have access or intentional 404 that should not redirect - return; - } - } - - $languages = $this->getLanguages(); - $languagePageNames = $languages ? $languages->pageNames() : null; - if($languagePageNames) { - // the LanguageSupportPageNames may change the original requested path, so we ask it for the original - $path = $languagePageNames->getRequestPath(); - $path = $path ? $this->wire()->sanitizer->pagePathName($path) : $event->arguments(1); - } else { - $path = $event->arguments(1); - } - - $page = $this->getPage($path); - - if($page->id && $page->viewable()) { - // if a page was found, redirect to it... - $language = $page->get('_language'); - if($language && $languages) { - // ...optionally for a specific language - if($page->get("status$language")) { - $languages->setLanguage($language); - } - } - $this->session->redirect($page->url); - } - } - /** * Get array of info about a path if it is in history * @@ -846,7 +718,11 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { $pathRemoved = ''; $cnt = 0; - if(!$level) $path = $sanitizer->pagePathName($path, Sanitizer::toAscii); + if(!$level) { + $path = $sanitizer->pagePathName($path, Sanitizer::toAscii); + if(!$this->isRootSegment($path)) return $pages->newNullPage(); + } + $path = '/' . trim($path, '/'); while(strlen($path) && !$page->id && $cnt < self::maxSegments) { @@ -915,6 +791,216 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { return $page; } + /*** ROOT SEGMENTS ***********************************************************/ + + /** + * Get all root segments + * + * @return array + * @since 3.0.186 + * + */ + public function getRootSegments() { + if(is_array($this->rootSegments)) return $this->rootSegments; + return $this->rebuildRootSegments(); + } + + /** + * Is/was given segment ever a root segment? + * + * @param string $segment Segment or path containing it (in ascii format) + * @return bool + * @since 3.0.186 + * + */ + public function isRootSegment($segment) { + $segment = trim($segment, '/'); + if(strpos($segment, '/')) list($segment,) = explode('/', $segment, 2); + $segments = $this->getRootSegments(); + return in_array($segment, $segments, true); + } + + /** + * Add a root segment + * + * @param string $segment May be a segment or path to extract it from (in ascii format) + * @return bool True if added, false if it was already present + * @since 3.0.186 + * + */ + protected function addRootSegment($segment) { + $segment = trim($segment, '/'); + if(strpos($segment, '/')) list($segment,) = explode('/', $segment, 2); + $rootSegments = $this->rootSegments; + if(!is_array($rootSegments)) $rootSegments = array(); + if(in_array($segment, $rootSegments, true)) return false; + $rootSegments[] = $segment; + $this->rootSegments = $rootSegments; + $this->wire()->modules->saveConfig($this, 'rootSegments', $rootSegments); + return true; + } + + /** + * Rebuild all root segments + * + * @return array + * @since 3.0.186 + * + */ + protected function rebuildRootSegments() { + $segments = array(); + $sql = 'SELECT path FROM ' . self::dbTableName; + $query = $this->wire()->database->prepare($sql); + $query->execute(); + while($row = $query->fetch(\PDO::FETCH_NUM)) { + $path = trim($row[0], '/'); + list($segment,) = explode('/', $path, 2); + $segments[$segment] = $segment; + } + $query->closeCursor(); + $segments = array_values($segments); + $this->rootSegments = $segments; + $this->wire()->modules->saveConfig($this, 'rootSegments', $segments); + return $segments; + } + + /*** HOOKS *******************************************************************/ + + /** + * Hook called when a page is moved or renamed + * + * @param HookEvent $event + * + */ + public function hookPageMoved(HookEvent $event) { + + /** @var Page $page */ + $page = $event->arguments[0]; + /** @var Languages $languages */ + $languages = $this->getLanguages(); + + $age = time() - $page->created; + if($page->template->name === 'admin' || $this->wire()->pages->cloning || $age < $this->minimumAge) return; + + // note that the paths we store have no trailing slash + + if($languages) { + $parent = $page->parent(); + $parentPrevious = $page->parentPrevious; + if($parentPrevious && $parentPrevious->id == $parent->id) $parentPrevious = null; + foreach($languages as $language) { + /** @var Language $language */ + if($language->isDefault()) continue; + $namePrevious = $page->get("-name$language"); + if(!$namePrevious && !$parentPrevious) continue; + if(!$namePrevious) $namePrevious = $page->name; + $languages->setLanguage($language); + $pathPrevious = $parentPrevious ? $parentPrevious->path() : $page->parent()->path(); + $pathPrevious = rtrim($pathPrevious, '/') . "/$namePrevious"; + $this->setPathHistory($page, $pathPrevious, $language->id); + $languages->unsetLanguage(); + } + } + + if(!$page->namePrevious) { + // abort saving a former URL if it looks like there isn't going to be one + if(!$page->parentPrevious || $page->parentPrevious->id == $page->parent->id) return; + } + + if($page->parentPrevious) { + + // if former or current parent is in trash, then don't bother saving redirects + if($page->parentPrevious->isTrash() || $page->parent->isTrash()) return; + + // the start of our redirect URL will be the previous parent's URL + $path = $page->parentPrevious->path; + + } else { + // the start of our redirect URL will be the current parent's URL (i.e. name changed) + $path = $page->parent->path; + } + + if($page->namePrevious) { + $path = rtrim($path, '/') . '/' . $page->namePrevious; + } else { + $path = rtrim($path, '/') . '/' . $page->name; + } + + // do not save paths that reference recovery format used by trash + // example: /blog/posts/5134.3096.83_page-name + if(strpos($path, '.') !== false && strpos($path, '_') !== false) { + if(preg_match('!/\d+\.\d+\.\d+_!', $path)) return; + } + + // do not save paths that match any untitled page name + // example: /blog/posts/untitled-123123 + $untitled = $this->wire()->pages->names()->untitledPageName(); + if(strpos($path, $untitled) !== false) { + if(preg_match('!/' . preg_quote($untitled) . '[-]!', $path)) return; + } + + if($languages) $languages->setDefault(); + $this->setPathHistory($page, $path); + if($languages) $languages->unsetDefault(); + } + + /** + * Hook called upon 404 from ProcessPageView::pageNotFound + * + * @param HookEvent $event + * + */ + public function hookPageNotFound(HookEvent $event) { + + /** @var Page $page */ + $page = $event->arguments(0); + /** @var Wire404Exception $exception */ + $exception = $event->arguments(4); + + // If there is a page object set, then it means the 404 was triggered + // by the user not having access to it, or by the $page's template + // throwing a 404 exception. In either case, we don't want to do a + // redirect if there is a $page since any 404 is intentional there. + if($page && $page->id) { + // it did resolve to a Page: maybe a front-end 404 + if(!$exception) { + // pageNotFound was called without an Exception + return; + } else if($exception->getCode() == Wire404Exception::codeFunction) { + // the wire404() function was called: allow PagePathHistory + } else if($exception->getMessage() === "1") { + // also allow PagePathHistory to operate when: throw new WireException(true); + } else { + // likely user didn't have access or intentional 404 that should not redirect + return; + } + } + + $languages = $this->getLanguages(); + $languagePageNames = $languages ? $languages->pageNames() : null; + if($languagePageNames) { + // the LanguageSupportPageNames may change the original requested path, so we ask it for the original + $path = $languagePageNames->getRequestPath(); + $path = $path ? $this->wire()->sanitizer->pagePathName($path) : $event->arguments(1); + } else { + $path = $event->arguments(1); + } + + $page = $this->getPage($path); + + if($page->id && $page->viewable()) { + // if a page was found, redirect to it... + $language = $page->get('_language'); + if($language && $languages) { + // ...optionally for a specific language + if($page->get("status$language")) { + $languages->setLanguage($language); + } + } + $this->session->redirect($page->url); + } + } + /** * When a page is deleted, remove it from our redirects list as well * @@ -923,8 +1009,9 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { */ public function hookPageDeleted(HookEvent $event) { $page = $event->arguments[0]; - $database = $this->wire('database'); - $query = $database->prepare("DELETE FROM " . self::dbTableName . " WHERE pages_id=:pages_id"); + $database = $this->wire()->database; + $table = self::dbTableName; + $query = $database->prepare("DELETE FROM $table WHERE pages_id=:pages_id"); $query->bindValue(":pages_id", $page->id, \PDO::PARAM_INT); $query->execute(); } @@ -958,6 +1045,8 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { $url = $event->arguments(0); $event->return = (bool) $this->deletePathHistory($page, $this->urlToPath($url)); } + + /*** MODULE ******************************************************************/ /** * Given URL that may include a root subdirectory, convert it to path relative to root subdirectory @@ -967,7 +1056,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { * */ protected function urlToPath($url) { - $rootUrl = $this->wire('config')->urls->root; + $rootUrl = $this->wire()->config->urls->root; if(strlen($rootUrl) > 1 && strpos($url, $rootUrl) === 0) { $path = substr($url, strlen($rootUrl) - 1); } else { @@ -1006,21 +1095,28 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { * */ public function ___install() { - - $len = $this->wire('database')->getMaxIndexLength(); + + $database = $this->wire()->database; + $len = $database->getMaxIndexLength(); + $table = self::dbTableName; + + if($database->tableExists($table)) { + $this->checkTableSchema(); + return; + } - $sql = "CREATE TABLE " . self::dbTableName . " (" . - "path VARCHAR($len) NOT NULL, " . - "pages_id INT UNSIGNED NOT NULL, " . - "language_id INT UNSIGNED DEFAULT 0, " . // v2 - "created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " . - "PRIMARY KEY path (path), " . - "INDEX pages_id (pages_id), " . - "INDEX created (created) " . - ") ENGINE={$this->config->dbEngine} DEFAULT CHARSET={$this->config->dbCharset}"; - - $this->wire('database')->exec($sql); + $sql = + "CREATE TABLE $table (" . + "path VARCHAR($len) NOT NULL, " . + "pages_id INT UNSIGNED NOT NULL, " . + "language_id INT UNSIGNED DEFAULT 0, " . // v2 + "created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " . + "PRIMARY KEY path (path), " . + "INDEX pages_id (pages_id), " . + "INDEX created (created) " . + ") ENGINE={$this->config->dbEngine} DEFAULT CHARSET={$this->config->dbCharset}"; + $database->exec($sql); } /** @@ -1028,7 +1124,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { * */ public function ___uninstall() { - $this->wire('database')->query("DROP TABLE " . self::dbTableName); + $this->wire()->database->query("DROP TABLE " . self::dbTableName); } /** @@ -1042,6 +1138,7 @@ class PagePathHistory extends WireData implements Module, ConfigurableModule { if($this->checkTableSchema()) { if($fromVersion != $toVersion) $this->message("PagePathHistory v$fromVersion => v$toVersion"); } + $this->rebuildRootSegments(); } /** diff --git a/wire/modules/PagePaths.module b/wire/modules/PagePaths.module index 79091bad..b063f4a5 100644 --- a/wire/modules/PagePaths.module +++ b/wire/modules/PagePaths.module @@ -8,6 +8,8 @@ * * ProcessWire 3.x, Copyright 2021 by Ryan Cramer * https://processwire.com + * + * @property array $rootSegments * */ @@ -16,7 +18,7 @@ class PagePaths extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => 'Page Paths', - 'version' => 3, + 'version' => 4, 'summary' => "Enables page paths/urls to be queryable by selectors. Also offers potential for improved load performance. Builds an index at install (may take time on a large site).", 'singular' => true, 'autoload' => true, @@ -35,6 +37,15 @@ class PagePaths extends WireData implements Module, ConfigurableModule { */ protected $languages = null; + /** + * Construct + * + */ + public function __construct() { + $this->set('rootSegments', array()); + parent::__construct(); + } + /** * Initialize the hooks * @@ -71,9 +82,8 @@ class PagePaths extends WireData implements Module, ConfigurableModule { $this->updatePagePaths($page); } - /** - * When a page is deleted + * Hook called when a page is deleted * * @param HookEvent $event * @@ -85,6 +95,7 @@ class PagePaths extends WireData implements Module, ConfigurableModule { $query = $database->prepare("DELETE FROM $table WHERE pages_id=:pages_id"); $query->bindValue(":pages_id", $page->id, \PDO::PARAM_INT); $query->execute(); + $this->rebuildRootSegments(); } /** @@ -325,7 +336,6 @@ class PagePaths extends WireData implements Module, ConfigurableModule { return $row; } - /** * Rebuild all paths table starting with $page and descending to its children * @@ -342,7 +352,8 @@ class PagePaths extends WireData implements Module, ConfigurableModule { $this->wire()->database->exec("DELETE FROM $table"); $page = $this->wire()->pages->get('/'); } - return $this->updatePagePaths($page, true); + $result = $this->updatePagePaths($page, true); + return $result; } /** @@ -411,12 +422,14 @@ class PagePaths extends WireData implements Module, ConfigurableModule { static $level = 0; + $rootPageId = $this->wire()->config->rootPageID; $database = $this->wire()->database; $sanitizer = $this->wire()->sanitizer; $languages = $this->getLanguages(); $table = self::dbTableName; $numUpdated = 1; $homeDefaultName = ''; + $rebuildRoot = false; $level++; if($hasChildren === null) { @@ -429,6 +442,7 @@ class PagePaths extends WireData implements Module, ConfigurableModule { throw new WireException('Page object required on first call to updatePagePaths'); } $pageId = $page->id; + if($page->parent_id === $rootPageId) $rebuildRoot = true; if($languages) { // multi-language foreach($languages as $language) { @@ -444,6 +458,8 @@ class PagePaths extends WireData implements Module, ConfigurableModule { // $paths already populated $pageId = (int) "$page"; } + + if($pageId === $rootPageId) $rebuildRoot = true; // sanitize and prepare paths for DB storage foreach($paths as $languageId => $path) { @@ -483,6 +499,8 @@ class PagePaths extends WireData implements Module, ConfigurableModule { } $level--; + + if($rebuildRoot && !$level) $this->rebuildRootSegments(); return $numUpdated; } @@ -542,13 +560,107 @@ class PagePaths extends WireData implements Module, ConfigurableModule { return $numUpdated; } + + + /*** ROOT SEGMENTS ******************************************************************************/ + + /** + * Is given segment/page name a root segment? + * + * A root segment is one that is owned by the homepage or a direct parent of the homepage, i.e. + * /about/ might be a root page segment and /de/ might be a root language segment. If it is a + * root page segment like /about/ then this will return the ID of that page. If it is a root + * language segment like /de/ then it will return the homepage ID (1). + * + * @param string $segment Page name string or path containing it + * @return int Returns page ID or 0 for no match. + * @since 3.0.186 + * + */ + public function isRootSegment($segment) { + $segment = trim($segment, '/'); + if(strpos($segment, '/')) list($segment,) = explode('/', $segment, 2); + $rootSegments = $this->getRootSegments(); + $key = array_search($segment, $rootSegments); + if($key === false) return 0; + $key = ltrim($key, '_'); + if(strpos($key, '.')) { + list($pageId, /*$languageId*/) = explode('.', $key, 2); + } else { + $pageId = $key; + } + return (int) $pageId; + } + + /** + * Get root segments + * + * @param bool $rebuild + * @return array + * @since 3.0.186 + * + */ + public function getRootSegments($rebuild = false) { + if(empty($this->rootSegments) || $rebuild) $this->rebuildRootSegments(); + return $this->rootSegments; + } + + /** + * Rebuild root segments stored in module config + * + * @since 3.0.186 + * + */ + protected function rebuildRootSegments() { + + $database = $this->wire()->database; + $config = $this->wire()->config; + $languages = $this->getLanguages(); + $cols = array('id', 'name'); + $segments = array(); + + if($languages) { + foreach($languages as $language) { + if(!$language->isDefault()) $cols[] = "name$language->id"; + } + } + + $sql = 'SELECT ' . implode(',', $cols) . ' FROM pages WHERE parent_id=:id '; + if($languages) $sql .= 'OR id=:id'; + $query = $database->prepare($sql); + $query->bindValue(':id', $config->rootPageID, \PDO::PARAM_INT); + $query->execute(); + + while($row = $query->fetch(\PDO::FETCH_ASSOC)){ + $id = (int) $row['id']; + unset($row['id']); + foreach($row as $col => $name) { + if(!strlen($name)) continue; + if($id === 1 && $col === 'name' && $name === Pages::defaultRootName) continue; // skip "/home/" + $col = str_replace('name', '', $col); + if(strlen($col)) { + $segments["_$id.$col"] = $name; // _pageID.languageID i.e. 123.456 + } else { + $segments["_$id"] = $name; // _pageID i.e. 123 + } + } + } + + $query->closeCursor(); + + $this->rootSegments = $segments; + $this->wire()->modules->saveConfig($this, 'rootSegments', $segments); + + return $segments; + } + /*** LANGUAGES **********************************************************************************/ /** * Returns Languages object or false if not available * - * @return Languages|null|false + * @return Languages|false * */ public function getLanguages() { @@ -564,7 +676,6 @@ class PagePaths extends WireData implements Module, ConfigurableModule { return $this->languages; } - /** * @param Language|int|string $language * @return int Returns language ID or 0 for default language @@ -650,6 +761,7 @@ class PagePaths extends WireData implements Module, ConfigurableModule { public function ___upgrade($fromVersion, $toVersion) { if($fromVersion && $toVersion) {} // ignore $this->checkTableSchema(); + $this->rebuildRootSegments(); } /**