Blame | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire Page Paths** Keeps a cache of page paths to improve performance and* make paths more queryable by selectors.*** ProcessWire 3.x, Copyright 2016 by Ryan Cramer* https://processwire.com***/class PagePaths extends WireData implements Module {public static function getModuleInfo() {return array('title' => 'Page Paths','version' => 1,'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). Currently supports only single languages sites.",'singular' => true,'autoload' => true,);}/*** Table created by this module**/const dbTableName = 'pages_paths';/*** @var Languages|false**/protected $languages = null;/*** Initialize the hooks**/public function init() {$this->pages->addHook('moved', $this, 'hookPageMoved');$this->pages->addHook('renamed', $this, 'hookPageMoved');$this->pages->addHook('added', $this, 'hookPageMoved');$this->pages->addHook('deleted', $this, 'hookPageDeleted');}public function ready() {$page = $this->wire('page');if($page->template == 'admin' && $page->name == 'module') {$this->wire('modules')->addHookAfter('refresh', $this, 'hookModulesRefresh');}}/*** Returns Languages object or false if not available** @return Languages|null**/public function getLanguages() {if(!is_null($this->languages)) return $this->languages;$languages = $this->wire('languages');if(!$languages) return null;if(!$this->wire('modules')->isInstalled('LanguageSupportPageNames')) {$this->languages = false;} else {$this->languages = $this->wire('languages');}return $this->languages;}/*** Hook to ProcessModule::refresh** @param HookEvent $event**/public function hookModulesRefresh(HookEvent $event) {if($event) {} // ignoreif($this->getLanguages()) {$this->wire('session')->warning($this->_('Please uninstall the Core > PagePaths module (it is not compatible with LanguageSupportPageNames)'));}}/*** Hook called when a page is moved or renamed** @param HookEvent $event**/public function hookPageMoved(HookEvent $event) {$page = $event->arguments[0];$this->updatePagePath($page->id, $page->path);}/*** When a page is deleted** @param HookEvent $event**/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");$query->bindValue(":pages_id", $page->id, \PDO::PARAM_INT);$query->execute();}/*** Given a page ID, return the page path, NULL if not found, or boolean false if cannot be determined.** @param int $id* @return string|null|false**/public function getPath($id) {if($this->getLanguages()) return false; // we do not support multi-language yet for this module$table = self::dbTableName;$database = $this->wire('database');$query = $database->prepare("SELECT path FROM `$table` WHERE pages_id=:pages_id");$query->bindValue(":pages_id", $id, \PDO::PARAM_INT);$query->execute();if(!$query->rowCount()) return null;$path = $query->fetchColumn();$path = strlen($path) ? $this->wire('sanitizer')->pagePathName("/$path/", Sanitizer::toUTF8) : "/";return $path;}/*** Given a page path, return the page ID or NULL if not found.** @param string $path* @return int|null**/public function getID($path) {$table = self::dbTableName;$database = $this->wire('database');$path = $this->wire('sanitizer')->pagePathName($path, Sanitizer::toAscii);$path = trim($path, '/');$query = $database->prepare("SELECT pages_id FROM $table WHERE path=:path");$query->bindValue(":path", $path);$query->execute();if(!$query->rowCount()) return null;$id = $query->fetchColumn();return (int) $id;}/*** Perform a path match for use by PageFinder** @param DatabaseQuerySelect $query* @param Selector $selector* @throws PageFinderSyntaxException**/public function getMatchQuery(DatabaseQuerySelect $query, Selector $selector) {static $n = 0;$n++;$table = self::dbTableName;$alias = "$table$n";$value = $selector->value;// $joinType = $selector->not ? 'leftjoin' : 'join';$query->join("$table AS $alias ON pages.id=$alias.pages_id");if(in_array($selector->operator, array('=', '!=', '<>', '>', '<', '>=', '<='))) {if(!is_array($value)) $value = array($value);$where = '';foreach($value as $path) {if($where) $where .= $selector->not ? " AND " : " OR ";$path = $this->wire('sanitizer')->pagePathName($path, Sanitizer::toAscii);$path = $this->wire('database')->escapeStr(trim($path, '/'));$where .= ($selector->not ? "NOT " : "") . "$alias.path{$selector->operator}'$path'";}$query->where("($where)");} else {if(is_array($value)) {$error = "Multi value using '|' is not supported with path/url and '$selector->operator' operator";throw new PageFinderSyntaxException($error);}if($selector->not) {$error = "NOT mode isn't yet supported with path/url and '$selector->operator' operator";throw new PageFinderSyntaxException($error);}/** @var DatabaseQuerySelectFulltext $ft */$ft = $this->wire(new DatabaseQuerySelectFulltext($query));$ft->match($alias, 'path', $selector->operator, trim($value, '/'));}}/*** Updates path for $page and all children** @param int $id* @param string $path* @param bool $hasChildren Omit if true or unknown* @param int $level Recursion level, you should omit this param* @return int Number of paths updated**/protected function updatePagePath($id, $path, $hasChildren = true, $level = 0) {$table = self::dbTableName;$id = (int) $id;$database = $this->wire('database');$path = $this->wire('sanitizer')->pagePathName($path, Sanitizer::toAscii);$path = trim($path, '/');$_path = $database->escapeStr($path);$numUpdated = 1;$sql = "INSERT INTO $table (pages_id, path) VALUES(:id, :path) " ."ON DUPLICATE KEY UPDATE pages_id=VALUES(pages_id), path=VALUES(path)";$query = $database->prepare($sql);$query->bindValue(":id", $id, \PDO::PARAM_INT);$query->bindValue(":path", $_path);$query->execute();if($hasChildren) {$sql = "SELECT pages.id, pages.name, COUNT(children.id) FROM pages " ."LEFT JOIN pages AS children ON children.id=pages.parent_id " ."WHERE pages.parent_id=:id " ."GROUP BY pages.id ";$query = $database->prepare($sql);$query->bindValue(":id", $id, \PDO::PARAM_INT);$query->execute();while($row = $query->fetch(\PDO::FETCH_NUM)) {list($id, $name, $numChildren) = $row;$numUpdated += $this->updatePagePath($id, "$path/$name", $numChildren > 0, $level+1);}}if(!$level) $this->message(sprintf($this->_n('Updated %d path', 'Updated %d paths', $numUpdated), $numUpdated));return $numUpdated;}/*** Install the module**/public function ___install() {$table = self::dbTableName;$database = $this->wire('database');$engine = $this->wire('config')->dbEngine;$charset = $this->wire('config')->dbCharset;$database->query("DROP TABLE IF EXISTS $table");$sql = "CREATE TABLE $table (" ."pages_id int(10) unsigned NOT NULL, " ."path text CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL, " ."PRIMARY KEY pages_id (pages_id), " ."UNIQUE KEY path (path(500)), " ."FULLTEXT KEY path_fulltext (path)" .") ENGINE=$engine DEFAULT CHARSET=$charset";$database->query($sql);$numUpdated = $this->updatePagePath(1, '/');if($numUpdated) {} // ignore}/*** Uninstall the module**/public function ___uninstall() {$this->wire('database')->query("DROP TABLE " . self::dbTableName);}}