mirror of
https://github.com/processwire/processwire.git
synced 2025-08-09 08:17:12 +02:00
Add support for $page->meta() method for maintaining persistent meta data for pages independent of fields and the normal load/save process.
This commit is contained in:
@@ -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 2018 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* #pw-summary Class used by all Page objects in ProcessWire.
|
||||
@@ -88,6 +88,9 @@
|
||||
* @property int $hasLinks Number of visible pages (to current user) linking to this page in Textarea/HTML fields. #pw-group-traversal
|
||||
* @property int $instanceID #pw-internal
|
||||
* @property bool $quietMode #pw-internal
|
||||
* @property WireData|null $_meta #pw-internal
|
||||
* @property WireData $meta #pw-internal
|
||||
*
|
||||
*
|
||||
* @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
|
||||
@@ -601,7 +604,15 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
'created' => 0,
|
||||
'modified' => 0,
|
||||
'published' => 0,
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Page meta data
|
||||
*
|
||||
* @var null|WireDataDB
|
||||
*
|
||||
*/
|
||||
protected $_meta = null;
|
||||
|
||||
/**
|
||||
* Properties that can be accessed, mapped to method of access (excluding custom fields of course)
|
||||
@@ -777,6 +788,7 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
$this->filesManager = clone $this->filesManager;
|
||||
$this->filesManager->setPage($this);
|
||||
}
|
||||
$this->_meta = null;
|
||||
foreach($this->template->fieldgroup as $field) {
|
||||
$name = $field->name;
|
||||
if(!$field->type->isAutoload() && !isset($this->data[$name])) continue; // important for draft loading
|
||||
@@ -1176,9 +1188,13 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
case 'loaderCache':
|
||||
$value = $this->loaderCache;
|
||||
break;
|
||||
case '_meta':
|
||||
$value = $this->_meta; // null or WireDataDB
|
||||
break;
|
||||
|
||||
default:
|
||||
if($key && isset($this->settings[(string)$key])) return $this->settings[$key];
|
||||
if($key === 'meta' && !$this->wire('fields')->get('meta')) return $this->meta(); // always WireDataDB
|
||||
|
||||
// populate a formatted string with {tag} vars
|
||||
if(strpos($key, '{') !== false && strpos($key, '}')) return $this->getMarkup($key);
|
||||
@@ -4364,5 +4380,55 @@ class Page extends WireData implements \Countable, WireMatchable {
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get or set page’s persistent meta data
|
||||
*
|
||||
* This meta data is managed in the DB. Setting a value immediately saves it in the DB, while
|
||||
* getting a value immediately loads it from the DB. As a result, this data is independent of the
|
||||
* usual Page load and save operations. This is primarily for internal core use, but may be
|
||||
* useful for other specific non-core purposes as well.
|
||||
*
|
||||
* Note that this meta data is completely free-form and has no connection to ProcessWire fields.
|
||||
* Values for meta data must be basic PHP types, whether arrays, strings, numbers, etc. Please do
|
||||
* not use objects for meta values at this time.
|
||||
*
|
||||
* ~~~~~
|
||||
* // set and save a meta value
|
||||
* $page->meta()->set('colors', [ 'red, 'green', 'blue' ]);
|
||||
*
|
||||
* // get a meta value
|
||||
* $colors = $page->meta()->get('colors');
|
||||
*
|
||||
* // alternate shorter syntax for either of the above
|
||||
* $page->meta('colors', [ 'red', 'green', 'blue' ]); // set
|
||||
* $colors = $page->meta('colors'); // get
|
||||
*
|
||||
* // delete a meta value
|
||||
* $page->meta()->remove('colors');
|
||||
*
|
||||
* // get the WireDataDB instance that stores the meta values,
|
||||
* // it has all the same methods as WireData objects...
|
||||
* $meta = $page->meta();
|
||||
*
|
||||
* // ...such as, get all values in an array:
|
||||
* $values = $meta->getArray();
|
||||
* ~~~~~
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|bool $key Omit to get the WireData instance or specify property name to get or set.
|
||||
* @param null|mixed $value Value to set for given $key or omit if getting a value.
|
||||
* @return WireDataDB|string|array|int|float
|
||||
* @since 3.0.133
|
||||
*
|
||||
*/
|
||||
public function meta($key = '', $value = null) {
|
||||
/** @var Pages $pages */
|
||||
if($this->_meta === null) $this->_meta = $this->wire(new WireDataDB($this->id, 'pages_meta'));
|
||||
if(empty($key)) return $this->_meta; // return instance
|
||||
if($value === null) return $this->_meta->get($key); // get value
|
||||
return $this->_meta->set($key, $value); // set value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1062,6 +1062,8 @@ class PagesEditor extends Wire {
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
|
||||
$page->meta()->removeAll();
|
||||
|
||||
/** @var PagesAccess $access */
|
||||
$access = $this->wire(new PagesAccess());
|
||||
$access->deletePage($page);
|
||||
@@ -1220,6 +1222,7 @@ class PagesEditor extends Wire {
|
||||
$copy->setQuietly('_cloning', null);
|
||||
$copy->of($of);
|
||||
$page->of($of);
|
||||
$page->meta()->copyTo($copy->id);
|
||||
$copy->resetTrackChanges();
|
||||
$this->pages->cloned($page, $copy);
|
||||
$this->pages->debugLog('clone', "page=$page, parent=$parent", $copy);
|
||||
|
358
wire/core/WireDataDB.php
Normal file
358
wire/core/WireDataDB.php
Normal file
@@ -0,0 +1,358 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* WireData with database storage
|
||||
*
|
||||
* A WireData object that maintains its data in a database table rather than just in memory.
|
||||
* An example of usage is the `$page->meta()` method.
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2019
|
||||
* https://processwire.com
|
||||
*
|
||||
*/
|
||||
class WireDataDB extends WireData implements \Countable {
|
||||
|
||||
/**
|
||||
* True when all data from the table has been loaded (a call to getArray will trigger this)
|
||||
*
|
||||
* @var bool
|
||||
*
|
||||
*/
|
||||
protected $fullyLoaded = false;
|
||||
|
||||
/**
|
||||
* ID of the source object for this WireData
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
*/
|
||||
protected $sourceID = 0;
|
||||
|
||||
/**
|
||||
* Name of the table that data will be stored in
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $table = '';
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param int $sourceID ID of the source item this WireData is maintaining/persisting data for.
|
||||
* @param string $tableName Name of the table to store data in. If it does not exist, it will be created.
|
||||
*
|
||||
*/
|
||||
public function __construct($sourceID, $tableName) {
|
||||
$this->table($tableName);
|
||||
$this->sourceID($sourceID);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a specific property/name/key
|
||||
*
|
||||
* @param string $key
|
||||
* @return array|mixed|null
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
$value = parent::get($key);
|
||||
if($value !== null) return $value;
|
||||
$value = $this->load($key);
|
||||
parent::set($key, $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all values in an associative array
|
||||
*
|
||||
* @return array|mixed|null
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function getArray() {
|
||||
return $this->load(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and save a value for a specific property/name/key
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
if(parent::get($key) === $value) return $this; // no change
|
||||
if($value === null) return $this->remove($key); // remove
|
||||
$this->save($key, $value); // set
|
||||
parent::set($key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove value for a specific property/name/key
|
||||
*
|
||||
* @param string $key
|
||||
* @return self
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function remove($key) {
|
||||
$this->delete("$key");
|
||||
parent::remove($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all values for sourceID from the DB
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
*/
|
||||
public function removeAll() {
|
||||
$this->delete(true);
|
||||
$this->reset();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all loaded data so that it will re-load from DB on next access
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
*/
|
||||
public function reset() {
|
||||
$this->data = array();
|
||||
$this->fullyLoaded = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta value or all meta values (if you specify true)
|
||||
*
|
||||
* @param string|bool $name Meta property name to delete or specify boolean true for all
|
||||
* @return int Number of rows deleted
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
protected function delete($name) {
|
||||
if(empty($name)) return 0;
|
||||
$table = $this->table();
|
||||
$sql = "DELETE FROM `$table` WHERE source_id=:source_id ";
|
||||
if($name !== true) $sql .= "AND name=:name";
|
||||
$query = $this->wire('database')->prepare($sql);
|
||||
$query->bindValue(':source_id', $this->sourceID(), \PDO::PARAM_INT);
|
||||
if($name !== true) $query->bindValue(':name', $name);
|
||||
try {
|
||||
$query->execute();
|
||||
$result = $query->rowCount();
|
||||
$query->closeCursor();
|
||||
} catch(\Exception $e) {
|
||||
$result = 0;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value or all values
|
||||
*
|
||||
* @param string|bool $name Property name to load or boolean true to load all
|
||||
* @return array|mixed|null
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
protected function load($name) {
|
||||
if(empty($name)) return null;
|
||||
if($this->fullyLoaded) return $name === true ? parent::getArray() : parent::get($name);
|
||||
$table = $this->table();
|
||||
$sql = "SELECT name, data FROM `$table` WHERE source_id=:source_id ";
|
||||
if($name !== true) $sql .= "AND name=:name ";
|
||||
$query = $this->wire('database')->prepare($sql);
|
||||
$query->bindValue(':source_id', $this->sourceID(), \PDO::PARAM_INT);
|
||||
if($name !== true) $query->bindValue(':name', $name);
|
||||
try {
|
||||
$query->execute();
|
||||
} catch(\Exception $e) {
|
||||
return $name === true ? array() : null;
|
||||
}
|
||||
if($query->rowCount()) {
|
||||
$meta = array();
|
||||
while($row = $query->fetch(\PDO::FETCH_NUM)) {
|
||||
list($key, $data) = $row;
|
||||
$meta[$key] = json_decode($data, true);
|
||||
parent::set($key, $meta[$key]);
|
||||
if($name !== true) break;
|
||||
}
|
||||
if($name !== true) $meta = empty($meta) ? null : $meta[$name];
|
||||
} else {
|
||||
$meta = null;
|
||||
}
|
||||
if($name === true) $this->fullyLoaded = true;
|
||||
$query->closeCursor();
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a value
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @param bool $recursive
|
||||
* @return bool
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
protected function save($name, $value, $recursive = false) {
|
||||
if(is_object($value)) return false; // we do not currently save objects
|
||||
$data = json_encode($value);
|
||||
$table = $this->table();
|
||||
$sourceID = $this->sourceID();
|
||||
if(!$sourceID) return false;
|
||||
$sql =
|
||||
"INSERT INTO `$table` (source_id, name, data) VALUES(:source_id, :name, :data) " .
|
||||
"ON DUPLICATE KEY UPDATE source_id=VALUES(source_id), name=VALUES(name), data=VALUES(data)";
|
||||
$query = $this->wire('database')->prepare($sql);
|
||||
$query->bindValue(':source_id', $this->sourceID(), \PDO::PARAM_INT);
|
||||
$query->bindValue(':name', $name);
|
||||
$query->bindValue(':data', $data);
|
||||
try {
|
||||
$query->execute();
|
||||
$result = $query->rowCount();
|
||||
} catch(\Exception $e) {
|
||||
if($recursive) throw $e;
|
||||
// table might not yet exist, try to create and save() again
|
||||
$result = $this->install() ? $this->save($name, $value, true) : false;
|
||||
}
|
||||
return $result ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the the source ID for this instance
|
||||
*
|
||||
* @param int|null $id
|
||||
* @return int
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function sourceID($id = null) {
|
||||
if(!is_int($id)) return $this->sourceID;
|
||||
if($id < 1) throw new WireException($this->className() . ' sourceID must be greater than 0');
|
||||
$this->sourceID = $id;
|
||||
return $this->sourceID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of rows this WireDataDB maintains in the database for source ID.
|
||||
*
|
||||
* This implements the \Countable interface.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
*/
|
||||
public function count() {
|
||||
$table = $this->table();
|
||||
$sql = "SELECT COUNT(*) FROM `$table` WHERE source_id=:source_id";
|
||||
$query = $this->wire('database')->prepare($sql);
|
||||
$query->bindValue(':source_id', $this->sourceID(), \PDO::PARAM_INT);
|
||||
try {
|
||||
$query->execute();
|
||||
$count = (int) $query->fetchColumn();
|
||||
} catch(\Exception $e) {
|
||||
$count = 0;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all data to a new source ID
|
||||
*
|
||||
* Useful to call on the source object after a clone has been created from it.
|
||||
*
|
||||
* @param int $newSourceID
|
||||
* @throws WireException
|
||||
* @return int Number of rows copied
|
||||
*
|
||||
*/
|
||||
public function copyTo($newSourceID) {
|
||||
if(!$this->count()) return 0;
|
||||
$sourceID = $this->sourceID;
|
||||
if($newSourceID == $sourceID) return 0;
|
||||
$data = $this->getArray();
|
||||
$this->sourceID($newSourceID); // temporarily set new
|
||||
foreach($data as $key => $value) {
|
||||
$this->save($key, $value);
|
||||
}
|
||||
$this->sourceID($sourceID); // set back
|
||||
return count($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current table name
|
||||
*
|
||||
* @param string $tableName
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function table($tableName = '') {
|
||||
if($tableName !== '') $this->table = strtolower($this->wire('database')->escapeTable($tableName));
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DB schema in an array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function schema() {
|
||||
return array(
|
||||
"source_id INT UNSIGNED NOT NULL",
|
||||
"name VARCHAR(128) NOT NULL",
|
||||
"data MEDIUMTEXT NOT NULL",
|
||||
"PRIMARY KEY (source_id, name)",
|
||||
"INDEX name (name)",
|
||||
"FULLTEXT KEY data (data)"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the table
|
||||
*
|
||||
* @return bool
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function install() {
|
||||
$engine = $this->wire('config')->dbEngine;
|
||||
$charset = $this->wire('config')->dbCharset;
|
||||
$table = $this->table();
|
||||
if($this->wire('database')->tableExists($table)) return false;
|
||||
$schema = implode(', ', $this->schema());
|
||||
$sql = "CREATE TABLE `$table` ($schema) ENGINE=$engine DEFAULT CHARSET=$charset";
|
||||
$this->wire('database')->exec($sql);
|
||||
$this->message("Added '$table' table to database");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall the table
|
||||
*
|
||||
* @return bool
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function uninstall() {
|
||||
$table = $this->table();
|
||||
$this->wire('database')->exec("DROP TABLE `$table`");
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getIterator() {
|
||||
return new \ArrayObject($this->getArray());
|
||||
}
|
||||
|
||||
}
|
@@ -266,7 +266,8 @@ class SystemUpdater extends WireData implements Module, ConfigurableModule {
|
||||
$f = $this->wire('modules')->get('InputfieldMarkup');
|
||||
$f->attr('name', '_log');
|
||||
$f->label = $this->_('System Update Log');
|
||||
$f->value = '<pre>' . $this->wire('sanitizer')->entities(file_get_contents($logfile)) . '</pre>';
|
||||
$logContent = $this->wire('sanitizer')->unentities(file_get_contents($logfile));
|
||||
$f->value = '<pre>' . $this->wire('sanitizer')->entities($logContent) . '</pre>';
|
||||
$inputfields->add($f);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user