1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-10 16:54:44 +02:00

Add new Page methods: $page->links() which returns PageArray of other pages linking to it; $page->references() which returns PageArray of other pages referencing it in Page fields; $page->urls() which returns array of all URLs that can refer to the page, in all languages, and including both previous and current URLs. Plus add these properties: $page->references; $page->links; $page->numLinks; $page->numReferences; $page->hasLinks; $page->hasReferences; $page->referencing; $page->urls; see phpdoc for details on these properties.

This commit is contained in:
Ryan Cramer
2018-06-28 12:49:27 -04:00
parent 94524a8776
commit 54fad20ffd
2 changed files with 356 additions and 9 deletions

View File

@@ -8,7 +8,7 @@
* 1. Providing get/set access to the Page's properties
* 2. Accessing the related hierarchy of pages (i.e. parents, children, sibling pages)
*
* ProcessWire 3.x, Copyright 2017 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Class used by all Page objects in ProcessWire.
@@ -29,9 +29,10 @@
* @property int $id The numbered ID of the current page #pw-group-system
* @property string $name The name assigned to the page, as it appears in the URL #pw-group-system #pw-group-common
* @property string $namePrevious Previous name, if changed. Blank if not. #pw-advanced
* @property string $title The page's title (headline) text
* @property string $path The page's URL path from the homepage (i.e. /about/staff/ryan/)
* @property string $url The page's URL path from the server's document root
* @property string $title The pages title (headline) text
* @property string $path The pages URL path from the homepage (i.e. /about/staff/ryan/)
* @property string $url The pages URL path from the server's document root
* @property array $urls All URLs the page is accessible from, whether current, former and multi-language. #pw-advanced
* @property string $httpUrl Same as $page->url, except includes scheme (http or https) and hostname.
* @property Page|string|int $parent The parent Page object or a NullPage if there is no parent. For assignment, you may also use the parent path (string) or id (integer). #pw-group-traversal
* @property Page|null $parentPrevious Previous parent, if parent was changed. #pw-group-traversal
@@ -74,6 +75,11 @@
* @property string $editURL Alias of $editUrl. #pw-internal
* @property PageRender $render May be used for field markup rendering like $page->render->title. #pw-advanced
* @property bool $loaderCache Whether or not pages loaded as a result of this one may be cached by PagesLoaderCache. #pw-internal
* @property int $numReferences Total number of pages referencing this page with Page reference fields. #pw-group-traversal
* @property int $hasReferences Number of visible pages (to current user) referencing this page with page reference fields. #pw-group-traversal
* @property int $numReferencing Total number of other pages this page is pointing to (referencing) with Page fields. #pw-group-traversal
* @property int $numLinks Total number of pages manually linking to this page in Textarea/HTML fields. #pw-group-traversal
* @property int $hasLinks Number of visible pages (to current user) linking to this page in Textarea/HTML fields. #pw-group-traversal
*
* @property Page|null $_cloning Internal runtime use, contains Page being cloned (source), when this Page is the new copy (target). #pw-internal
* @property bool|null $_hasAutogenName Internal runtime use, set by Pages class when page as auto-generated name. #pw-internal
@@ -92,6 +98,7 @@
* @method bool deleteable() Returns true if the page is deleteable by the current user, false if not. #pw-group-access
* @method bool deletable() Alias of deleteable(). #pw-group-access
* @method bool trashable($orDeleteable = false) Returns true if the page is trashable by the current user, false if not. #pw-group-access
* @method bool restorable() Returns true if page is in the trash and is capable of being restored to its original location. #pw-group-access
* @method bool addable($pageToAdd = null) Returns true if the current user can add children to the page, false if not. Optionally specify the page to be added for additional access checking. #pw-group-access
* @method bool moveable($newParent = null) Returns true if the current user can move this page. Optionally specify the new parent to check if the page is moveable to that parent. #pw-group-access
* @method bool sortable() Returns true if the current user can change the sort order of the current page (within the same parent). #pw-group-access
@@ -132,6 +139,8 @@
* @method string getMarkup($key) Return the markup value for a given field name or {tag} string. #pw-internal
* @method string|mixed renderField($fieldName, $file = '') Returns rendered field markup, optionally with file relative to templates/fields/. #pw-internal
* @method string|mixed renderValue($value, $file) Returns rendered markup for $value using $file relative to templates/fields/. #pw-internal
* @method PageArray references($selector = '', $field = '') Return pages that are pointing to this one by way of Page reference fields. #pw-group-traversal
* @method PageArray links($selector = '', $field = '') Return pages that link to this one contextually in Textarea/HTML fields. #pw-group-traversal
*
*/
@@ -178,6 +187,12 @@ class Page extends WireData implements \Countable, WireMatchable {
*/
const statusSystem = 16;
/**
* Page has a globally unique name and no other pages may have the same name
*
*/
const statusUnique = 32;
/**
* Page has pending draft changes (name: "draft").
* #pw-internal
@@ -535,6 +550,7 @@ class Page extends WireData implements \Countable, WireMatchable {
* - "p": Property name maps to same property name in $this
* - "m": Property name maps to same method name in $this
* - "n": Property name maps to same method name in $this, but may be overridden by custom field
* - "t": Property name maps to PageTraversal method with same name, if not overridden by custom field
* - [blank]: needs additional logic to be handled ([blank]='')
*
* @var array
@@ -555,8 +571,10 @@ class Page extends WireData implements \Countable, WireMatchable {
'editUrl' => 'm',
'fieldgroup' => '',
'filesManager' => 'm',
'hasParent' => 'parents',
'hasChildren' => 'm',
'hasLinks' => 't',
'hasParent' => 'parents',
'hasReferences' => 't',
'httpUrl' => 'm',
'id' => 's',
'index' => 'n',
@@ -568,6 +586,7 @@ class Page extends WireData implements \Countable, WireMatchable {
'isPublic' => 'm',
'isTrash' => 'm',
'isUnpublished' => 'm',
'links' => 'n',
'listable' => 'm',
'modified' => 's',
'modifiedStr' => '',
@@ -578,6 +597,8 @@ class Page extends WireData implements \Countable, WireMatchable {
'namePrevious' => 'p',
'next' => 'm',
'numChildren' => 's',
'numLinks' => 't',
'numReferences' => 't',
'output' => 'm',
'outputFormatting' => 'p',
'parent' => 'm',
@@ -589,6 +610,8 @@ class Page extends WireData implements \Countable, WireMatchable {
'publishable' => 'm',
'published' => 's',
'publishedStr' => '',
'references' => 'n',
'referencing' => 't',
'render' => '',
'rootParent' => 'm',
'siblings' => 'm',
@@ -603,6 +626,7 @@ class Page extends WireData implements \Countable, WireMatchable {
'templatePrevious' => 'p',
'trashable' => 'm',
'url' => 'm',
'urls' => 'm',
'viewable' => 'm'
);
@@ -1006,18 +1030,21 @@ class Page extends WireData implements \Countable, WireMatchable {
if(isset(self::$basePropertiesAlternates[$key])) $key = self::$basePropertiesAlternates[$key];
if(isset(self::$baseProperties[$key])) {
$type = self::$baseProperties[$key];
if($type == 'p') {
if($type === 'p') {
// local property
return $this->$key;
} else if($type == 'm') {
} else if($type === 'm') {
// local method
return $this->{$key}();
} else if($type == 'n') {
} else if($type === 'n') {
// local method, possibly overridden by $field
if(!$this->wire('fields')->get($key)) return $this->{$key}();
} else if($type == 's') {
} else if($type === 's') {
// settings property
return $this->settings[$key];
} else if($type === 't') {
// map to method in PageTraversal, if not overridden by field
if(!$this->wire('fields')->get($key)) return $this->traversal()->{$key}($this);
} else if($type) {
// defined local method
return $this->{$type}();
@@ -2346,6 +2373,42 @@ class Page extends WireData implements \Countable, WireMatchable {
if($siblings) return $this->traversal()->prevUntilSiblings($this, $selector, $filter, $siblings);
return $this->traversal()->prevUntil($this, $selector, $filter);
}
/**
* Return pages that have Page reference fields pointing to this one (references)
*
* By default this excludes pages that are hidden, unpublished and pages excluded due to access control for the current user.
* To prevent these exclusions specify an include mode in the selector, i.e. `include=all`, or you can use
* boolean `true` as a shortcut to specify that you do not want any exclusions.
*
* #pw-group-traversal
*
* @param string|bool $selector Optional selector to filter results by, or boolean true as shortcut for `include=all`.
* @param Field|string|bool $field Optionally limit to pages using specified field (name or Field object),
* - OR specify boolean TRUE to return array of PageArrays indexed by field names.
* - If $field argument not specified, it searches all applicable Page fields.
* @return PageArray|array
*
*/
public function ___references($selector = '', $field = '') {
return $this->traversal()->references($this, $selector, $field);
}
/**
* Return pages linking to this one (in Textarea/HTML fields)
*
* Applies only to Textarea fields with “html” content-type and link abstraction enabled.
*
* #pw-group-traversal
*
* @param string|bool $selector Optional selector to filter by or boolean true for “include=all”. (default='')
* @param string|Field $field Optionally limit results to specified field. (default=all applicable Textarea fields)
* @return PageArray
*
*/
public function ___links($selector = '', $field = '') {
return $this->traversal()->links($this, $selector, $field);
}
/**
* Get languages active for this page and viewable by current user
@@ -2812,6 +2875,35 @@ class Page extends WireData implements \Countable, WireMatchable {
return $url;
}
/**
* Return all URLs that this page can be accessed from (excluding URL segments and pagination)
*
* This includes the current page URL, any other language URLs (for which page is active), and
* any past (historical) URLs the page was previously available at (which will redirect to it).
*
* - Returned URLs do not include additional URL segments or pagination numbers.
* - Returned URLs are indexed by language name, i.e. “default”, “fr”, “es”, etc.
* - If multi-language URLs not installed, then index is just “default”.
* - Past URLs are indexed by language; then ISO-8601 date, i.e. “default;2016-08-11T07:44:43-04:00”,
* where the date represents the last date that URL was considered current.
* - If PagePathHistory core module is not installed then past/historical URLs are excluded.
* - You can disable past/historical or multi-language URLs by using the $options argument.
*
* #pw-advanced
*
* @param array $options Options to modify default behavior:
* - `http` (bool): Make URLs include current scheme and hostname (default=false).
* - `past` (bool): Include past/historical URLs? (default=true)
* - `languages` (bool): Include other language URLs when supported/available? (default=true).
* - `language` (Language|int|string): Include only URLs for this language (default=null).
* Note: the `languages` option must be true if using the `language` option.
* @return array
*
*/
public function urls($options = array()) {
return $this->traversal()->urls($this, $options);
}
/**
* Returns the URL to the page, including scheme and hostname
*
@@ -2886,6 +2978,10 @@ class Page extends WireData implements \Countable, WireMatchable {
$url = ($https ? 'https://' : 'http://') . $config->httpHost . $url;
}
}
if($this->wire('languages') && $this->wire('page')->template->id != $adminTemplate->id) {
$language = $this->wire('user')->language;
if($language) $url .= "&language=$language->id";
}
$append = $this->wire('session')->getFor($this, 'appendEditUrl');
if($append) $url .= $append;
return $url;

View File

@@ -576,7 +576,258 @@ class PageTraversal {
return $url;
}
/**
* Return all URLs that this page can be accessed from (excluding URL segments and pagination)
*
* This includes the current page URL, any other language URLs (for which page is active), and
* any past (historical) URLs the page was previously available at (which will redirect to it).
*
* - Returned URLs do not include additional URL segments or pagination numbers.
* - Returned URLs are indexed by language name, i.e. “default”, “fr”, “es”, etc.
* - If multi-language URLs not installed, then index is just “default”.
* - Past URLs are indexed by language; then ISO-8601 date, i.e. “default;2016-08-11T07:44:43-04:00”,
* where the date represents the last date that URL was considered current.
* - If PagePathHistory core module is not installed then past/historical URLs are excluded.
* - You can disable past/historical or multi-language URLs by using the $options argument.
*
* @param Page $page
* @param array $options Options to modify default behavior:
* - `http` (bool): Make URLs include current scheme and hostname (default=false).
* - `past` (bool): Include past/historical URLs? (default=true)
* - `languages` (bool): Include other language URLs when supported/available? (default=true).
* - `language` (Language|int|string): Include only URLs for this language (default=null).
* Note: the `languages` option must be true if using the `language` option.
* @return array
*
*/
public function urls(Page $page, $options = array()) {
$defaults = array(
'http' => false,
'past' => true,
'languages' => true,
'language' => null,
);
/** @var Modules $modules */
$modules = $page->wire('modules');
$options = array_merge($defaults, $options);
$languages = $options['languages'] ? $page->wire('languages') : null;
$slashUrls = $page->template->slashUrls;
$httpHostUrl = $options['http'] ? $page->wire('input')->httpHostUrl() : '';
if($options['language'] && $languages) {
if(!$options['language'] instanceof Page) {
$options['language'] = $languages->get($options['language']);
}
if($options['language'] && $options['language']->id) {
$languages = array($options['language']);
}
}
// include other language URLs
if($languages && $modules->isInstalled('LanguageSupportPageNames')) {
foreach($languages as $language) {
if(!$language->isDefault() && !$page->get("status$language")) continue;
$urls[$language->name] = $page->localUrl($language);
}
} else {
$urls = array('default' => $page->url());
}
// add in historical URLs
if($options['past'] && $modules->isInstalled('PagePathHistory')) {
$history = $modules->get('PagePathHistory');
$rootUrl = $page->wire('config')->urls->root;
$pastPaths = $history->getPathHistory($page, array(
'language' => $options['language'],
'verbose' => true
));
foreach($pastPaths as $pathInfo) {
$key = '';
if(!empty($pathInfo['language'])) {
if($options['languages']) {
$key .= $pathInfo['language']->name . ';';
} else {
// they asked to have multi-language excluded
if(!$pathInfo['language']->isDefault()) continue;
}
}
$key .= wireDate('c', $pathInfo['date']);
$urls[$key] = $rootUrl . ltrim($pathInfo['path'], '/');
}
}
// update URLs for current expected slash and http settings
foreach($urls as $key => $url) {
if($url !== '/') $url = $slashUrls ? rtrim($url, '/') . '/' : rtrim($url, '/');
if($options['http']) $url = $httpHostUrl . $url;
$urls[$key] = $url;
}
return $urls;
}
/**
* Return pages that are referencing the given one by way of Page references
*
* @param Page $page
* @param string|bool $selector Optional selector to filter results by or boolean true as shortcut for `include=all`.
* @param Field|string $field Limit to follower pages using this field,
* - or specify boolean TRUE to make it return array of PageArrays indexed by field name.
* @param bool $getCount Specify true to return counts rather than PageArray(s)
* @return PageArray|array|int
* @throws WireException Highly unlikely
*
*/
public function references(Page $page, $selector = '', $field = '', $getCount = false) {
$fieldtype = $page->wire('fieldtypes')->get('FieldtypePage');
if(!$fieldtype) throw new WireException('Unable to find FieldtypePage');
if($selector === true) $selector = "include=all";
return $fieldtype->findReferences($page, $selector, $field, $getCount);
}
/**
* Return number of VISIBLE pages that are following (referencing) the given one by way of Page references
*
* Note that this excludes hidden, unpublished and otherwise non-accessible pages (access control).
* If you do not want to exclude these, use the numFollowers() function instead, OR specify "include=all" for
* the $selector argument.
*
* @param Page $page
* @param string $selector Filter count by this selector
* @param string|Field|bool $field Limit count to given Field or specify boolean true to return array of counts.
* @return int|array Returns count, or array of counts (if $field==true)
*
*/
public function hasReferences(Page $page, $selector = '', $field = '') {
return $this->references($page, $selector, $field, true);
}
/**
* Return number of ANY pages that are following (referencing) the given one by way of Page references
*
* @param Page $page
* @param string $selector Filter count by this selector
* @param string|Field|bool $field Limit count to given Field or specify boolean true to return array of counts.
* @return int|array Returns count, or array of counts (if $field==true)
*
*/
public function numReferences(Page $page, $selector = '', $field = '') {
if(stripos($selector, "include=") === false) $selector = rtrim("include=all, $selector", ', ');
return $this->hasReferences($page, $selector, $field);
}
/**
* Return pages that this page is referencing by way of Page reference fields
*
* @param Page $page
* @param bool $field Limit results to requested field, or specify boolean true to return array indexed by field names.
* @param bool $getCount Specify true to return count(s) rather than pages.
* @return PageArray|int|array
*
*/
public function referencing(Page $page, $field = false, $getCount = false) {
$fieldName = '';
if(is_bool($field) || is_null($field)) {
$byField = $field ? true : false;
} else if(is_string($field)) {
$fieldName = $page->wire('sanitizer')->fieldName($field);
} else if(is_int($field)) {
$field = $page->wire('fields')->get($field);
if($field) $fieldName = $field->name;
} else if($field instanceof Field) {
$fieldName = $field->name;
}
// results
$fieldCounts = array(); // counts indexed by field name (if count mode)
$items = $page->wire('pages')->newPageArray();
$itemsByField = array();
foreach($page->template->fieldgroup as $f) {
if($fieldName && $field->name != $fieldName) continue;
if(!$f->type instanceof FieldtypePage) continue;
if($byField) $itemsByField[$f->name] = $this->wire('pages')->newPageArray();
$value = $page->get($f->name);
if($value instanceof Page && $value->id) {
$items->add($value);
if($byField) $itemsByField[$f->name]->add($value);
$fieldCounts[$f->name] = 1;
} else if($value instanceof PageArray && $value->count()) {
$items->import($value);
if($byField) $itemsByField[$f->name]->import($value);
$fieldCounts[$f->name] = $value->count();
} else {
unset($itemsByField[$f->name]);
}
}
if($getCount) return $byField ? $fieldCounts : $items->count();
if($byField) return $itemsByField;
return $items;
}
/**
* Return number of pages this one is following (referencing) by way of Page references
*
* @param Page $page
* @param bool $field Optionally limit to field, or specify boolean true to return array of counts per field.
* @return int|array
*
*/
public function numReferencing(Page $page, $field = false) {
return $this->referencing($page, $field, true);
}
/**
* Find other pages linking to the given one by way contextual links is textarea/html fields
*
* @param Page $page
* @param string $selector
* @param bool|string|Field $field
* @param array $options
* - `getIDs` (bool): Return array of page IDs rather than Page instances. (default=false)
* - `getCount` (bool): Return a total count (int) of found pages rather than Page instances. (default=false)
* - `confirm` (bool): Confirm that the links are present by looking at the actual page field data. (default=true)
* You can specify false for this option to make it perform faster, but with a potentially less accurate result.
* @return PageArray|array|int
* @throws WireException
*
*/
public function links(Page $page, $selector = '', $field = false, array $options = array()) {
/** @var FieldtypeTextarea $fieldtype */
$fieldtype = $page->wire('fieldtypes')->get('FieldtypeTextarea');
if(!$fieldtype) throw new WireException('Unable to find FieldtypeTextarea');
return $fieldtype->findLinks($page, $selector, $field, $options);
}
/**
* Return total found number of pages linking to this one with no exclusions
*
* @param Page $page
* @param bool $field
* @return int
*
*/
public function numLinks(Page $page, $field = false) {
return $this->links($page, true, $field, array('getCount' => true));
}
/**
* Return total number of pages visible to current user linking to this one
*
* @param Page $page
* @param bool $field
* @return array|int|PageArray
*
*/
public function hasLinks(Page $page, $field = false) {
return $this->links($page, '', $field, array('getCount' => true));
}
/******************************************************************************************************************
* LEGACY METHODS