mirror of
https://github.com/processwire/processwire.git
synced 2025-08-12 17:54:44 +02:00
Updates to PagePaths and PagePathHistory modules, adding support for root segments, among other things
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user