mirror of
https://github.com/processwire/processwire.git
synced 2025-08-20 05:21:29 +02:00
Update $pages->getByPath() to support new options such as allowing URL segments to appear in given path, disabling partial path matching, and supporting paths that might have a root subdirectory url in it. Related to processwire/processwire-issues#1116
This commit is contained in:
@@ -1155,6 +1155,12 @@ class Pages extends Wire {
|
|||||||
* - `getID` (int): Specify true to just return the page ID (default=false).
|
* - `getID` (int): Specify true to just return the page ID (default=false).
|
||||||
* - `useLanguages` (bool): Specify true to allow retrieval by language-specific paths (default=false).
|
* - `useLanguages` (bool): Specify true to allow retrieval by language-specific paths (default=false).
|
||||||
* - `useHistory` (bool): Allow use of previous paths used by the page, if PagePathHistory module is installed (default=false).
|
* - `useHistory` (bool): Allow use of previous paths used by the page, if PagePathHistory module is installed (default=false).
|
||||||
|
* - `allowUrl` (bool): Allow getting page by path OR url? Specify false to find only by path. This option only applies if
|
||||||
|
* the site happens to run from a subdirectory. (default=true) 3.0.184+
|
||||||
|
* - `allowPartial` (bool): Allow partial paths to match? (default=true) 3.0.184+
|
||||||
|
* - `allowUrlSegments` (bool): Allow paths with URL segments to match? When true and page match cannot be found, the closest
|
||||||
|
* parent page that allows URL segments will be returned. Found URL segments are populated to a `_urlSegments` array
|
||||||
|
* property on the returned page object. This also cancels the allowPartial setting. (default=false) 3.0.184+
|
||||||
* @return Page|int
|
* @return Page|int
|
||||||
* @since 3.0.6
|
* @since 3.0.6
|
||||||
*
|
*
|
||||||
|
@@ -1354,20 +1354,35 @@ class PagesLoader extends Wire {
|
|||||||
* 3) Partial paths may also match, so long as the partial path is completely unique in the site.
|
* 3) Partial paths may also match, so long as the partial path is completely unique in the site.
|
||||||
* If you don't want that behavior, double check the path of the returned page.
|
* If you don't want that behavior, double check the path of the returned page.
|
||||||
*
|
*
|
||||||
* @param $path
|
* @param string $path
|
||||||
* @param array|bool $options array of options (below), or specify boolean for $useLanguages option only.
|
* @param array|bool $options array of options (below), or specify boolean for $useLanguages option only.
|
||||||
* - getID: Specify true to just return the page ID (default=false)
|
* - `getID` (bool): Specify true to just return the page ID (default=false)
|
||||||
* - useLanguages: Specify true to allow retrieval by language-specific paths (default=false)
|
* - `useLanguages` (bool): Specify true to allow retrieval by language-specific paths (default=false)
|
||||||
* - useHistory: Allow use of previous paths used by the page, if PagePathHistory module is installed (default=false)
|
* - `useHistory` (bool): Allow use of previous paths used by the page, if PagePathHistory module is installed (default=false)
|
||||||
|
* - `allowUrl` (bool): Allow getting page by path OR url? Specify false to find only by path. This option only applies if
|
||||||
|
* the site happens to run from a subdirectory. (default=true) 3.0.184+
|
||||||
|
* - `allowPartial` (bool): Allow partial paths to match? (default=true) 3.0.184+
|
||||||
|
* - `allowUrlSegments` (bool): Allow paths with URL segments to match? When true and page match cannot be found, the closest
|
||||||
|
* parent page that allows URL segments will be returned. Found URL segments are populated to a `_urlSegments` array
|
||||||
|
* property on the returned page object. This also cancels the allowPartial setting. (default=false) 3.0.184+
|
||||||
* @return Page|int
|
* @return Page|int
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function getByPath($path, $options = array()) {
|
public function getByPath($path, $options = array()) {
|
||||||
|
|
||||||
|
$modules = $this->wire()->modules;
|
||||||
|
$sanitizer = $this->wire()->sanitizer;
|
||||||
|
$config = $this->wire()->config;
|
||||||
|
$database = $this->wire()->database;
|
||||||
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'getID' => false,
|
'getID' => false,
|
||||||
'useLanguages' => false,
|
'useLanguages' => false,
|
||||||
'useHistory' => false,
|
'useHistory' => false,
|
||||||
|
'allowUrl' => true,
|
||||||
|
'allowPartial' => true,
|
||||||
|
'allowUrlSegments' => false,
|
||||||
|
'_isRecursive' => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if(!is_array($options)) {
|
if(!is_array($options)) {
|
||||||
@@ -1377,20 +1392,44 @@ class PagesLoader extends Wire {
|
|||||||
|
|
||||||
$options = array_merge($defaults, $options);
|
$options = array_merge($defaults, $options);
|
||||||
if(isset($options['getId'])) $options['getID'] = $options['getId']; // case alternate
|
if(isset($options['getId'])) $options['getID'] = $options['getId']; // case alternate
|
||||||
$homepageID = (int) $this->wire('config')->rootPageID;
|
$homepageID = (int) $config->rootPageID;
|
||||||
|
$rootUrl = $this->wire()->config->urls->root;
|
||||||
|
|
||||||
|
if($options['allowUrl'] && $rootUrl !== '/' && strpos($path, $rootUrl) === 0) {
|
||||||
|
// root URL is subdirectory and path has that subdirectory
|
||||||
|
$rootName = trim($rootUrl, '/');
|
||||||
|
if(strpos($rootName, '/')) {
|
||||||
|
// root URL has multiple levels of subdirectories, remove them from path
|
||||||
|
list(,$path) = explode(rtrim($rootUrl, '/'), $path, 2);
|
||||||
|
} else {
|
||||||
|
// one subdirectory, see if a page has the same name
|
||||||
|
$query = $database->prepare('SELECT id FROM pages WHERE parent_id=1 AND name=:name');
|
||||||
|
$query->bindValue(':name', $rootName);
|
||||||
|
$query->execute();
|
||||||
|
if($query->rowCount() > 0) {
|
||||||
|
// leave subdirectory in path because page in site also matches subdirectory name
|
||||||
|
} else {
|
||||||
|
// remove root URL subdirectory from path
|
||||||
|
list(,$path) = explode(rtrim($rootUrl, '/'), $path, 2);
|
||||||
|
}
|
||||||
|
$query->closeCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($path === '/') {
|
if($path === '/') {
|
||||||
// this can only be homepage
|
// this can only be homepage
|
||||||
return $options['getID'] ? $homepageID : $this->getById($homepageID, array('getOne' => true));
|
return $options['getID'] ? $homepageID : $this->getById($homepageID, array('getOne' => true));
|
||||||
} else if(empty($path)) {
|
} else if(empty($path)) {
|
||||||
|
// path is empty and cannot match anything
|
||||||
return $options['getID'] ? 0 : $this->pages->newNullPage();
|
return $options['getID'] ? 0 : $this->pages->newNullPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$_path = $path;
|
$_path = $path;
|
||||||
$path = $this->wire('sanitizer')->pagePathName($path, Sanitizer::toAscii);
|
$path = $sanitizer->pagePathName($path, Sanitizer::toAscii);
|
||||||
$pathParts = explode('/', trim($path, '/'));
|
$pathParts = explode('/', trim($path, '/'));
|
||||||
$languages = $options['useLanguages'] ? $this->wire('languages') : null;
|
$_pathParts = $pathParts;
|
||||||
if($languages && !$this->wire('modules')->isInstalled('LanguageSupportPageNames')) $languages = null;
|
$languages = $options['useLanguages'] ? $this->wire()->languages : null;
|
||||||
|
if($languages && !$modules->isInstalled('LanguageSupportPageNames')) $languages = null;
|
||||||
|
|
||||||
$langKeys = array(':name' => 'name');
|
$langKeys = array(':name' => 'name');
|
||||||
if($languages) foreach($languages as $language) {
|
if($languages) foreach($languages as $language) {
|
||||||
@@ -1399,11 +1438,13 @@ class PagesLoader extends Wire {
|
|||||||
$langKeys[":name$languageID"] = "name$languageID";
|
$langKeys[":name$languageID"] = "name$languageID";
|
||||||
}
|
}
|
||||||
|
|
||||||
// first see if we can find a single page just having the name that's the last path part
|
|
||||||
// this is an optimization if the page name happens to be globally unique in the system, which is often the case
|
|
||||||
$pageID = 0;
|
$pageID = 0;
|
||||||
$templatesID = 0;
|
$templatesID = 0;
|
||||||
$parentID = 0;
|
$parentID = 0;
|
||||||
|
|
||||||
|
if($options['allowPartial'] && !$options['allowUrlSegments']) {
|
||||||
|
// first see if we can find a single page just having the name that's the last path part
|
||||||
|
// this is an optimization if the page name happens to be globally unique in the system, which is often the case
|
||||||
$name = end($pathParts);
|
$name = end($pathParts);
|
||||||
$binds = array(':name' => $name);
|
$binds = array(':name' => $name);
|
||||||
$wheres = array();
|
$wheres = array();
|
||||||
@@ -1427,27 +1468,28 @@ class PagesLoader extends Wire {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql .= 'LIMIT 2';
|
$sql .= 'LIMIT 2';
|
||||||
$database = $this->wire('database');
|
|
||||||
$query = $database->prepare($sql);
|
$query = $database->prepare($sql);
|
||||||
foreach($binds as $key => $value) $query->bindValue($key, $value);
|
foreach($binds as $key => $value) $query->bindValue($key, $value);
|
||||||
$database->execute($query);
|
$database->execute($query);
|
||||||
$numRows = $query->rowCount();
|
$numRows = $query->rowCount();
|
||||||
|
if($numRows == 1) {
|
||||||
if(!$numRows) {
|
// if only 1 page matches then we’ve found what we’re looking for
|
||||||
// no matches - no page in the system can possibly match
|
|
||||||
$query->closeCursor();
|
|
||||||
|
|
||||||
} else if($numRows == 1) {
|
|
||||||
// just one page has this name - we can stop now, avoiding further checks
|
|
||||||
list($pageID, $templatesID, $parentID) = $query->fetch(\PDO::FETCH_NUM);
|
list($pageID, $templatesID, $parentID) = $query->fetch(\PDO::FETCH_NUM);
|
||||||
|
} else if($numRows == 0) {
|
||||||
|
// no page can possibly match last segment
|
||||||
|
} else if($numRows > 1) {
|
||||||
|
// multiple pages match
|
||||||
|
}
|
||||||
$query->closeCursor();
|
$query->closeCursor();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
if(!$pageID) {
|
||||||
// multiple pages have the name - go back and query again, joining all the path parts
|
// multiple pages have the name or partial path match is not allowed
|
||||||
$query->closeCursor();
|
// build a query joining all the path parts
|
||||||
$sql = "SELECT pages.id, pages.templates_id, pages.parent_id FROM pages ";
|
$joins = array();
|
||||||
$n = 0;
|
$wheres = array();
|
||||||
$binds = array();
|
$binds = array();
|
||||||
|
$n = 0;
|
||||||
$lastAlias = "pages";
|
$lastAlias = "pages";
|
||||||
$lastPart = array_pop($pathParts);
|
$lastPart = array_pop($pathParts);
|
||||||
|
|
||||||
@@ -1461,17 +1503,20 @@ class PagesLoader extends Wire {
|
|||||||
$wheres[] = "$alias.$colName=$bindKey";
|
$wheres[] = "$alias.$colName=$bindKey";
|
||||||
$binds[$bindKey] = $part;
|
$binds[$bindKey] = $part;
|
||||||
}
|
}
|
||||||
$sql .= "JOIN pages AS $alias ON $lastAlias.parent_id=$alias.id AND (" . implode(' OR ', $wheres) . ') ';
|
$joins[] = "\nJOIN pages AS $alias ON $lastAlias.parent_id=$alias.id AND (" . implode(' OR ', $wheres) . ')';
|
||||||
$lastAlias = $alias;
|
$lastAlias = $alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
$wheres = array();
|
|
||||||
foreach($langKeys as $bindKey => $colName) {
|
foreach($langKeys as $bindKey => $colName) {
|
||||||
$wheres[] = "pages.$colName=$bindKey";
|
$wheres[] = "pages.$colName=$bindKey";
|
||||||
$binds[$bindKey] = $lastPart;
|
$binds[$bindKey] = $lastPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql .= 'WHERE (' . implode(' OR ', $wheres) . ') ';
|
$sql =
|
||||||
|
'SELECT pages.id, pages.templates_id, pages.parent_id ' .
|
||||||
|
'FROM pages ' . implode(' ', $joins) . " \n" .
|
||||||
|
'WHERE (' . implode(' AND ', $wheres) . ') ';
|
||||||
|
|
||||||
$query = $database->prepare($sql);
|
$query = $database->prepare($sql);
|
||||||
foreach($binds as $key => $value) $query->bindValue($key, $value);
|
foreach($binds as $key => $value) $query->bindValue($key, $value);
|
||||||
$database->execute($query);
|
$database->execute($query);
|
||||||
@@ -1481,17 +1526,49 @@ class PagesLoader extends Wire {
|
|||||||
$query->closeCursor();
|
$query->closeCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$pageID && $options['useHistory'] && $this->wire('modules')->isInstalled('PagePathHistory')) {
|
if(!$pageID && $options['useHistory'] && $modules->isInstalled('PagePathHistory')) {
|
||||||
// if finding failed, check if there is a previous path it lived at, if history module available
|
// if finding failed, check if there is a previous path it lived at, if history module available
|
||||||
$page = $this->wire('modules')->get('PagePathHistory')->getPage($this->wire('sanitizer')->pagePathNameUTF8($_path));
|
$pph = $modules->get('PagePathHistory'); /** @var PagePathHistory $pph */
|
||||||
return $options['getID'] ? $page->id : $page;
|
$page = $pph->getPage($sanitizer->pagePathNameUTF8($_path));
|
||||||
|
if($page->id) return $options['getID'] ? $page->id : $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$pageID && $options['allowUrlSegments'] && !$options['_isRecursive'] && count($_pathParts)) {
|
||||||
|
// attempt to match parent pages that allow URL segments
|
||||||
|
$pathParts = $_pathParts;
|
||||||
|
$urlSegments = array();
|
||||||
|
$recursiveOptions = array_merge($options, array(
|
||||||
|
'getID' => false,
|
||||||
|
'allowUrlSegments' => false,
|
||||||
|
'allowPartial' => false,
|
||||||
|
'_isRecursive' => true
|
||||||
|
));
|
||||||
|
|
||||||
|
do {
|
||||||
|
$urlSegment = array_pop($pathParts);
|
||||||
|
array_unshift($urlSegments, $urlSegment);
|
||||||
|
$path = '/' . implode('/', $pathParts);
|
||||||
|
$page = $this->getByPath($path, $recursiveOptions);
|
||||||
|
} while(count($pathParts) && !$page->id);
|
||||||
|
|
||||||
|
if($page->id) {
|
||||||
|
if($page->template->urlSegments) {
|
||||||
|
// matched page template allows URL segments
|
||||||
|
$page->setQuietly('_urlSegments', $urlSegments);
|
||||||
|
if(!$options['getID']) return $page;
|
||||||
|
$pageID = $page->id;
|
||||||
|
} else {
|
||||||
|
// page template does not allow URL segments, so path cannot match
|
||||||
|
$pageID = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($options['getID']) return (int) $pageID;
|
if($options['getID']) return (int) $pageID;
|
||||||
if(!$pageID) return $this->pages->newNullPage();
|
if(!$pageID) return $this->pages->newNullPage();
|
||||||
|
|
||||||
return $this->getById((int) $pageID, array(
|
return $this->getById((int) $pageID, array(
|
||||||
'template' => $templatesID ? $this->wire('templates')->get((int) $templatesID) : null,
|
'template' => $templatesID ? $this->wire()->templates->get((int) $templatesID) : null,
|
||||||
'parent_id' => (int) $parentID,
|
'parent_id' => (int) $parentID,
|
||||||
'getOne' => true
|
'getOne' => true
|
||||||
));
|
));
|
||||||
|
Reference in New Issue
Block a user