diff --git a/wire/core/.phpstorm.meta.php b/wire/core/.phpstorm.meta.php index 95e8d0f9..82d37b02 100644 --- a/wire/core/.phpstorm.meta.php +++ b/wire/core/.phpstorm.meta.php @@ -15,6 +15,7 @@ namespace PHPSTORM_META { \wire('') => [ '' == '@', 'config' instanceof \ProcessWire\Config, + 'cache' instanceof \ProcessWire\WireCache, 'wire' instanceof \ProcessWire\ProcessWire, 'log' instanceof \ProcessWire\WireLog, 'notices' instanceof \ProcessWire\Notices, @@ -46,6 +47,7 @@ namespace PHPSTORM_META { // this one does not appear to work, leaving in case someone knows how to make it work '' == '@', 'config' instanceof \ProcessWire\Config, + 'cache' instanceof \ProcessWire\WireCache, 'wire' instanceof \ProcessWire\ProcessWire, 'log' instanceof \ProcessWire\WireLog, 'notices' instanceof \ProcessWire\Notices, diff --git a/wire/core/Interfaces.php b/wire/core/Interfaces.php index 01e5e792..e4c0a6b7 100644 --- a/wire/core/Interfaces.php +++ b/wire/core/Interfaces.php @@ -455,6 +455,56 @@ interface FieldtypeLanguageInterface { */ } +/** + * Interface for objects that carry a Field value for a Page + * + * Optional, but enables Page to do some of the work rather than the Fieldtype + * + */ +interface PageFieldValueInterface { + + /** + * Get or set formatted state + * + * @param bool|null $set Specify bool to set formatted state or omit to retrieve formatted state + * @return bool + * + */ + public function formatted($set = null); + + /** + * Set the Page + * + * @param Page $page + * + */ + public function setPage(Page $page); + + /** + * Set the Field + * + * @param Field $field + * + */ + public function setField(Field $field); + + /** + * Get the page or null if not set + * + * @return Page|null + * + */ + public function getPage(); + + /** + * Get the field or null if not set + * + * @return Field|null + * + */ + public function getField(); +} + /** * Interface for tracking runtime events * diff --git a/wire/core/Page.php b/wire/core/Page.php index f9ec60be..ed86ea6f 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -630,8 +630,7 @@ class Page extends WireData implements \Countable, WireMatchable { $value2 = clone $value; $this->set($name, $value2); // commit cloned value // if value is Pagefiles, then tell it the new page - if($value2 instanceof Pagefiles) $value2->setPage($this); - + if($value2 instanceof PageFieldValueInterface) $value2->setPage($this); } $this->instanceID .= ".clone"; if($track) $this->setTrackChanges(true); @@ -656,7 +655,7 @@ class Page extends WireData implements \Countable, WireMatchable { * * @param string $key Name of property to set * @param mixed $value Value to set - * @return Page Reference to this Page + * @return Page|WireData Reference to this Page * @see __set * @throws WireException * @@ -783,9 +782,9 @@ class Page extends WireData implements \Countable, WireMatchable { * * #pw-internal * - * @param string $key - * @param mixed $value - * @return $this + * @param string $key Name of field/property to set + * @param mixed $value Value to set + * @return Page|WireData Returns reference to this page * */ public function setForced($key, $value) { @@ -804,7 +803,7 @@ class Page extends WireData implements \Countable, WireMatchable { * @param string $key * @param mixed $value * @param bool $load Should the existing value be loaded for change comparisons? (applicable only to non-autoload fields) - * @return $this + * @return Page|WireData Returns reference to this Page * @throws WireException * */ @@ -855,15 +854,24 @@ class Page extends WireData implements \Countable, WireMatchable { // retrieve old value first in case it's not autojoined so that change comparisons and save's work if($load && $this->isLoaded) $this->get($key); - } else if($this->outputFormatting && $field->type->formatValue($this, $field, $value) != $value) { - // The field has been loaded or dereferenced from the API, and this field changes when formatters are applied to it. - // There is a good chance they are trying to set a formatted value, and we don't allow this situation because the - // possibility of data corruption is high. We set the Page::statusCorrupted status so that Pages::save() can abort. - $this->set('status', $this->status | self::statusCorrupted); - $corruptedFields = $this->get('_statusCorruptedFields'); - if(!is_array($corruptedFields)) $corruptedFields = array(); - $corruptedFields[$field->name] = $field->name; - $this->set('_statusCorruptedFields', $corruptedFields); + } else { + // check if the field is corrupted + $isCorrupted = false; + if(is_object($value) && $value instanceof PageFieldValueInterface) { + if($value->formatted()) $isCorrupted = true; + } else if($this->outputFormatting && $field->type->formatValue($this, $field, $value) != $value) { + $isCorrupted = true; + } + if($isCorrupted) { + // The field has been loaded or dereferenced from the API, and this field changes when formatters are applied to it. + // There is a good chance they are trying to set a formatted value, and we don't allow this situation because the + // possibility of data corruption is high. We set the Page::statusCorrupted status so that Pages::save() can abort. + $this->set('status', $this->status | self::statusCorrupted); + $corruptedFields = $this->get('_statusCorruptedFields'); + if(!is_array($corruptedFields)) $corruptedFields = array(); + $corruptedFields[$field->name] = $field->name; + $this->set('_statusCorruptedFields', $corruptedFields); + } } // isLoaded so sanitizeValue can determine if it can perform a typecast rather than a full sanitization (when helpful) @@ -1223,13 +1231,13 @@ class Page extends WireData implements \Countable, WireMatchable { // note: we do not store this blank value in the Page, so that // the real value can potentially be loaded later without output formatting $value = $field->type->getBlankValue($this, $field); - return $field->type->formatValue($this, $field, $value); + return $this->formatFieldValue($field, $value); } } if(!is_null($value) && empty($selector)) { // if the non-filtered value is already loaded, return it - return $this->outputFormatting ? $field->type->formatValue($this, $field, $value) : $value; + return $this->formatFieldValue($field, $value); } $track = $this->trackChanges(); @@ -1261,8 +1269,40 @@ class Page extends WireData implements \Countable, WireMatchable { if(is_object($value) && $value instanceof Wire) $value->resetTrackChanges(true); if($track) $this->setTrackChanges(true); + return $this->formatFieldValue($field, $value); + } + + /** + * Return a value consistent with the page’s output formatting state + * + * This is primarily for use as a helper to the getFieldValue() method. + * + * @param Field $field + * @param mixed $value + * @return mixed + * + */ + protected function formatFieldValue(Field $field, $value) { + + $hasInterface = is_object($value) && $value instanceof PageFieldValueInterface; - return $this->outputFormatting ? $field->type->formatValue($this, $field, $value) : $value; + if($hasInterface) { + $value->setPage($this); + $value->setField($field); + } + + if($this->outputFormatting) { + // output formatting is enabled so return a formatted value + $value = $field->type->formatValue($this, $field, $value); + if($hasInterface) $value->formatted(true); + + } else if($hasInterface && $value->formatted()) { + // unformatted requested, and value is already formatted so load a fresh copy + $this->__unset($field->name); + $value = $this->getFieldValue($field->name); + } + + return $value; } /** @@ -2570,6 +2610,7 @@ class Page extends WireData implements \Countable, WireMatchable { * * @return string Returns page URL, for example: `/my-site/about/contact/` * @see Page::path(), Page::httpUrl(), Page::editUrl(), Page::localUrl() + * @todo add $options support * */ public function url() { @@ -3099,7 +3140,7 @@ class Page extends WireData implements \Countable, WireMatchable { * - `true` (boolean): To return an array of status names (indexed by status number). * - `integer|string|array`: Status number(s) or status name(s) to set the current page status (same as $page->status = $value) * @param int|null $status If you specified `true` for first argument, optionally specify status value you want to use (if not the current). - * @return int|array|$this If setting status, `$this` is returned. If getting status: current status or array of status names is returned. + * @return int|array|Page If setting status, `$this` is returned. If getting status: current status or array of status names is returned. * @see Page::addStauts(), Page::removeStatus(), Page::hasStatus() * */ diff --git a/wire/core/Pagefile.php b/wire/core/Pagefile.php index 1ccf0a8f..22c8f5c1 100644 --- a/wire/core/Pagefile.php +++ b/wire/core/Pagefile.php @@ -21,7 +21,7 @@ * @property-read string $name Returns the filename without the path, same as the "basename" property. * @property-read string $hash Get a unique hash (for the page) representing this Pagefile. * @property-read string $tagsArray Get file tags as an array. #pw-group-tags @since 3.0.17 - * @property-read int $sort Sort order in database. #pw-group-other + * @property int $sort Sort order in database. #pw-group-other * @property string $basename Returns the filename without the path. * @property string $description Value of the file’s description field (string), if enabled. Note you can also set this property directly. * @property string $tags Value of the file’s tags field (string), if enabled. #pw-group-tags @@ -159,7 +159,7 @@ class Pagefile extends WireData { * * @param string $key * @param mixed $value - * @return $this + * @return Pagefile|WireData * */ public function set($key, $value) { diff --git a/wire/core/Pagefiles.php b/wire/core/Pagefiles.php index c008a7c2..a6589930 100644 --- a/wire/core/Pagefiles.php +++ b/wire/core/Pagefiles.php @@ -50,7 +50,7 @@ * */ -class Pagefiles extends WireArray { +class Pagefiles extends WireArray implements PageFieldValueInterface { /** * The Page object associated with these Pagefiles @@ -83,6 +83,14 @@ class Pagefiles extends WireArray { * */ protected $hookIDs = array(); + + /** + * Whether or not this is a formatted value + * + * @var bool + * + */ + protected $formatted = false; /** * Construct a Pagefiles object @@ -161,7 +169,7 @@ class Pagefiles extends WireArray { * * #pw-internal * - * @return WireArray + * @return Pagefiles|WireArray * */ public function makeNew() { @@ -310,10 +318,13 @@ class Pagefiles extends WireArray { public function add($item) { if(is_string($item)) { + /** @var Pagefile $item */ $item = $this->wire(new Pagefile($this, $item)); } - return parent::add($item); + /** @var Pagefiles $result */ + $result = parent::add($item); + return $result; } /** @@ -522,10 +533,11 @@ class Pagefiles extends WireArray { * @return $this * */ - public function trackChange($what, $old = null, $new = null) { if($this->field && $this->page) $this->page->trackChange($this->field->name); - return parent::trackChange($what, $old, $new); + /** @var Pagefiles $result */ + $result = parent::trackChange($what, $old, $new); + return $result; } /** @@ -677,13 +689,14 @@ class Pagefiles extends WireArray { * @return $this * */ - public function resetTrackChanges($trackChanges = true) { $this->unlinkQueue = array(); if($this->page && $this->page->id && $this->field) { $this->page->untrackChange($this->field->name); } - return parent::resetTrackChanges($trackChanges); + /** @var Pagefiles $result */ + $result = parent::resetTrackChanges($trackChanges); + return $result; } /** @@ -696,6 +709,17 @@ class Pagefiles extends WireArray { //$this->page = null; } + /** + * Get or set formatted state + * + * @param bool|null $set + * @return bool + * + */ + public function formatted($set = null) { + if(is_bool($set)) $this->formatted = $set; + return $this->formatted; + } } diff --git a/wire/core/Pages.php b/wire/core/Pages.php index 5f5ce2a7..4ef0c06d 100644 --- a/wire/core/Pages.php +++ b/wire/core/Pages.php @@ -26,7 +26,7 @@ * ================ * @method PageArray find() find($selectorString, array $options = array()) Find and return all pages matching the given selector string. Returns a PageArray. #pw-group-retrieval * @method bool save() save(Page $page) Save any changes made to the given $page. Same as : $page->save() Returns true on success. #pw-group-manipulation - * @method bool saveField() saveField(Page $page, $field) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation + * @method bool saveField() saveField(Page $page, $field, array $options = array()) Save just the named field from $page. Same as: $page->save('field') #pw-group-manipulation * @method bool trash() trash(Page $page, $save = true) Move a page to the trash. If you have already set the parent to somewhere in the trash, then this method won't attempt to set it again. #pw-group-manipulation * @method bool restore(Page $page, $save = true) Restore a trashed page to its original location. #pw-group-manipulation * @method int emptyTrash() Empty the trash and return number of pages deleted. #pw-group-manipulation @@ -843,10 +843,11 @@ class Pages extends Wire { * #pw-internal * * @param Page $page + * @return void * */ public function cache(Page $page) { - return $this->cacher->cache($page); + $this->cacher->cache($page); } /** @@ -1141,7 +1142,7 @@ class Pages extends Wire { * */ public function executeQuery(\PDOStatement $query, $throw = true, $maxTries = 3) { - $this->wire('database')->execute($query, $throw, $maxTries); + return $this->wire('database')->execute($query, $throw, $maxTries); } /** @@ -1152,7 +1153,7 @@ class Pages extends Wire { * When given an array, it calls $pages->getById($key); * * @param string|int|array $key - * @return Page|PageArray + * @return Page|Pages|PageArray * */ public function __invoke($key) { @@ -1262,7 +1263,9 @@ class Pages extends Wire { $str = "Saved page"; if(count($changes)) $str .= " (Changes: " . implode(', ', $changes) . ")"; $this->log($str, $page); - $this->wire('cache')->maintenance($page); + /** @var WireCache $cache */ + $cache = $this->wire('cache'); + $cache->maintenance($page); if($page->className() != 'Page') { $manager = $page->getPagesManager(); if($manager instanceof PagesType) $manager->saved($page, $changes, $values); @@ -1395,7 +1398,9 @@ class Pages extends Wire { */ public function ___deleted(Page $page) { $this->log("Deleted page", $page); - $this->wire('cache')->maintenance($page); + /** @var WireCache $cache */ + $cache = $this->wire('cache'); + $cache->maintenance($page); if($page->className() != 'Page') { $manager = $page->getPagesManager(); if($manager instanceof PagesType) $manager->deleted($page); diff --git a/wire/core/PagesLoaderCache.php b/wire/core/PagesLoaderCache.php index 82fcdff7..0e9931bb 100644 --- a/wire/core/PagesLoaderCache.php +++ b/wire/core/PagesLoaderCache.php @@ -66,6 +66,7 @@ class PagesLoaderCache extends Wire { * Cache the given page. * * @param Page $page + * @return void * */ public function cache(Page $page) {