From 76c870ce049285f5cf698233b77ca8ede7d646c2 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 10 Feb 2017 20:53:35 +0200 Subject: [PATCH 01/32] Add new collection and object classes --- .../Common/Collection/AbstractCollection.php | 59 +++ .../Collection/CloneCollectionInterface.php | 15 + .../Collection/CloneCollectionTrait.php | 29 ++ .../Common/Collection/CollectionInterface.php | 21 + .../Collection/ObjectCollectionInterface.php | 25 ++ .../Collection/ObjectCollectionTrait.php | 127 ++++++ .../Collection/SortingCollectionTrait.php | 84 ++++ system/src/Grav/Common/Filesystem/Folder.php | 21 +- .../src/Grav/Common/Object/AbstractObject.php | 374 ++++++++++++++++++ .../Common/Object/AbstractObjectFinder.php | 279 +++++++++++++ .../Grav/Common/Object/ObjectCollection.php | 20 + .../Common/Object/ObjectFinderInterface.php | 80 ++++ .../Grav/Common/Object/ObjectInterface.php | 100 +++++ .../Object/Storage/FilesystemStorage.php | 67 ++++ .../Object/Storage/StorageInterface.php | 43 ++ 15 files changed, 1341 insertions(+), 3 deletions(-) create mode 100644 system/src/Grav/Common/Collection/AbstractCollection.php create mode 100644 system/src/Grav/Common/Collection/CloneCollectionInterface.php create mode 100644 system/src/Grav/Common/Collection/CloneCollectionTrait.php create mode 100644 system/src/Grav/Common/Collection/CollectionInterface.php create mode 100644 system/src/Grav/Common/Collection/ObjectCollectionInterface.php create mode 100644 system/src/Grav/Common/Collection/ObjectCollectionTrait.php create mode 100644 system/src/Grav/Common/Collection/SortingCollectionTrait.php create mode 100644 system/src/Grav/Common/Object/AbstractObject.php create mode 100644 system/src/Grav/Common/Object/AbstractObjectFinder.php create mode 100644 system/src/Grav/Common/Object/ObjectCollection.php create mode 100644 system/src/Grav/Common/Object/ObjectFinderInterface.php create mode 100644 system/src/Grav/Common/Object/ObjectInterface.php create mode 100644 system/src/Grav/Common/Object/Storage/FilesystemStorage.php create mode 100644 system/src/Grav/Common/Object/Storage/StorageInterface.php diff --git a/system/src/Grav/Common/Collection/AbstractCollection.php b/system/src/Grav/Common/Collection/AbstractCollection.php new file mode 100644 index 000000000..03add33e0 --- /dev/null +++ b/system/src/Grav/Common/Collection/AbstractCollection.php @@ -0,0 +1,59 @@ +items = $variables['items']; + + return $instance; + } + + /** + * Add item to the list. + * + * @param mixed $item + * @param string $key + * @return $this + */ + public function add($item, $key = null) + { + $this->offsetSet($key, $item); + + return $this; + } + + /** + * Remove item from the list. + * + * @param $key + */ + public function remove($key) + { + $this->offsetUnset($key); + } + + /** + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->items); + } +} diff --git a/system/src/Grav/Common/Collection/CloneCollectionInterface.php b/system/src/Grav/Common/Collection/CloneCollectionInterface.php new file mode 100644 index 000000000..b29c2951d --- /dev/null +++ b/system/src/Grav/Common/Collection/CloneCollectionInterface.php @@ -0,0 +1,15 @@ + $value) { + if (is_object($value)) { + $this->offsetSet($key, clone $value); + } + } + } + + /** + * + * Create a clone from this collection. + * + * @return static + */ + public function getClone() + { + return clone $this; + } +} diff --git a/system/src/Grav/Common/Collection/CollectionInterface.php b/system/src/Grav/Common/Collection/CollectionInterface.php new file mode 100644 index 000000000..25fea590a --- /dev/null +++ b/system/src/Grav/Common/Collection/CollectionInterface.php @@ -0,0 +1,21 @@ +getObjectKey($object); + $this->offsetSet(is_null($objKey) ? $key : $objKey, $object); + + return $this; + } + + /** + * Remove item from the list. + * + * @param string|object $key + * @return $this + */ + public function remove($key) + { + if (is_object($key)) { + $key = $this->getObjectKey($key); + if (is_null($key)) { + return $this; + } + } + $this->offsetUnset($key); + + return $this; + } + + /** + * @param string $property Object property to be fetched. + * @return array Values of the property. + */ + public function get($property) + { + $list = []; + + foreach ($this as $id => $object) { + $key = $this->getObjectKey($object); + $list[is_null($key) ? $id : $key] = $this->getObjectValue($object, $property); + } + + return $list; + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @return $this + */ + public function set($property, $value) + { + foreach ($this as $object) { + $object->{$property} = $value; + } + + return $this; + } + + /** + * @param string $name Method name. + * @param array $arguments List of arguments passed to the function. + * @return array Return values. + */ + public function __call($name, array $arguments) + { + $list = []; + + foreach ($this as $id => $object) { + $key = $this->getObjectKey($object); + $list[is_null($key) ? $id : $key] = $this->getObjectCallResult($object, $name, $arguments); + } + + return $list; + } + + /** + * @param object $object + * @return string|null + */ + protected function getObjectKey($object) + { + $keyProperty = $this->keyProperty; + + return $keyProperty && isset($object->{$keyProperty}) ? (string) $object->{$keyProperty} : null; + } + + /** + * @param object $object + * @param string $property + * @return mixed + */ + protected function getObjectValue($object, $property) + { + return isset($object->{$property}) ? $object->{$property} : null; + } + + /** + * @param object $object + * @param string $name; + * @param array $arguments + * @return mixed + */ + protected function getObjectCallResult($object, $name, $arguments) + { + return method_exists($object, $name) ? call_user_func_array([$object, $name], $arguments) : null; + } +} diff --git a/system/src/Grav/Common/Collection/SortingCollectionTrait.php b/system/src/Grav/Common/Collection/SortingCollectionTrait.php new file mode 100644 index 000000000..9a3fdfd8f --- /dev/null +++ b/system/src/Grav/Common/Collection/SortingCollectionTrait.php @@ -0,0 +1,84 @@ +items = array_reverse($this->items); + + return $this; + } + + /** + * Shuffle items. + * + * @return $this + */ + public function shuffle() + { + $keys = array_keys($this->items); + shuffle($keys); + + $this->items = array_replace(array_flip($keys), $this->items); + + return $this; + } + + /** + * Sort collection by values using a user-defined comparison function. + * + * @param callable $callback + * @return $this + */ + public function sort(callable $callback) + { + uasort($this->items, $callback); + + return $this; + } + /** + * Sort collection by keys. + * + * @return $this + */ + public function ksort($sort_flags = SORT_REGULAR) + { + ksort($this->items, $sort_flags); + + return $this; + } + + + /** + * Sort collection by keys in reverse order. + * + * @return $this + */ + public function krsort($sort_flags = SORT_REGULAR) + { + krsort($this->items, $sort_flags); + + return $this; + } + + /** + * Sort collection by keys using a user-defined comparison function. + * + * @return $this + */ + public function uksort(callable $key_compare_func) + { + uksort($this->items, $key_compare_func); + + return $this; + }} diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 402e1ed5f..2c70ced58 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -332,7 +332,8 @@ abstract class Folder */ public static function move($source, $target) { - if (!is_dir($source)) { + if (!file_exists($target) || !is_dir($source)) { + // Rename fails if source folder does not exist. throw new \RuntimeException('Cannot move non-existing folder.'); } @@ -341,11 +342,24 @@ abstract class Folder return; } + if (file_exists($target)) { + // Rename fails if target folder exists. + throw new \RuntimeException('Cannot move files to existing folder/file.'); + } + // Make sure that path to the target exists before moving. self::create(dirname($target)); - $success = @rename($source, $target); - if (!$success) { + // Silence warnings (chmod failed etc). + @rename($source, $target); + + // Rename function can fail while still succeeding, so let's check if the folder exists. + if (!file_exists($target) || !is_dir($target)) { + // In some rare cases rename() creates file, not a folder. Get rid of it. + if (file_exists($target)) { + @unlink($target); + } + // Rename doesn't support moving folders across filesystems. Use copy instead. self::copy($source, $target); self::delete($source); } @@ -353,6 +367,7 @@ abstract class Folder // Make sure that the change will be detected when caching. @touch(dirname($source)); @touch(dirname($target)); + @touch($target); } /** diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php new file mode 100644 index 000000000..6b80201b1 --- /dev/null +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -0,0 +1,374 @@ + null]; + + /** + * @var string + */ + static protected $collectionClass = 'Grav\\Common\\Object\\ObjectCollection'; + + /** + * Properties of the object. + * @var array + */ + protected $items; + + /** + * Does object exist in storage? + * @var boolean + */ + protected $exists = false; + + /** + * Readonly object. + * @var bool + */ + protected $readonly = false; + + /** + * @var bool + */ + protected $initialized = false; + + + /** + * Returns the global instance to the object. + * + * Note that using array of fields will always make a query, but it's very useful feature if you want to search one + * item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned. + * + * @param string|array $keys An optional primary key value to load the object by, or an array of fields to match. + * @param boolean $reload Force object to reload. + * + * @return Object + */ + static public function instance($keys = null, $reload = false) + { + // If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object. + if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) { + $c = get_called_class(); + $instance = new $c($keys); + + /** @var Object $instance */ + if (!$instance->exists()) return $instance; + + // Instance exists: make sure that we return the global instance. + $keys = $instance->id; + } + + // Return global instance from the identifier. + $instance = static::$instances[$keys]; + + if ($reload) { + $instance->load(); + } + + return $instance; + } + + /** + * @param array $ids List of primary Ids or null to return everything that has been loaded. + * @param bool $readonly + * @return ObjectCollection + */ + static public function instances(array $ids = null, $readonly = true) + { + $collectionClass = static::$collectionClass; + + if (is_null($ids)) { + return new $collectionClass(static::$instances); + } + + if (empty($ids)) { + return new $collectionClass([]); + } + + $results = []; + $list = []; + + foreach ($ids as $id) { + if (!isset(static::$instances[$id])) { + $list[] = $id; + } + } + + if ($list) { + $c = get_called_class(); + $storage = static::getStorage(); + $list = $storage->loadList($list); + foreach ($list as $keys) { + /** @var static $instance */ + $instance = new $c(); + $instance->set($keys); + $instance->exists(true); + $instance->initialize(); + $id = $instance->id; + if ($id && !isset(static::$instances[$id])) { + static::$instances[$id] = $instance; + } + } + } + + foreach ($ids as $id) { + if (isset(static::$instances[$id])) { + $results[$id] = $readonly ? clone static::$instances[$id] : static::$instances[$id]; + } + } + + return new $collectionClass($results); + } + + /** + * Removes all or selected instances from the object cache. + * + * @param null|string|array $ids An optional primary key or list of keys. + */ + static public function freeInstances($ids = null) + { + if ($ids === null) { + $ids = array_keys(static::$instances); + } + $ids = (array) $ids; + + foreach ($ids as $id) { + unset(static::$instances[$id]); + } + } + + /** + * Class constructor, overridden in descendant classes. + * + * @param string|array $identifier Identifier. + */ + public function __construct($identifier = null) + { + if ($identifier) { + $this->load($identifier); + } + } + + /** + * Override this function if you need to initialize object right after creating it. + * + * Can be used for example if the fields need to be converted from json strings to array. + * + * @return bool True if initialization was done, false if object was already initialized. + */ + public function initialize() + { + $initialized = $this->initialized; + $this->initialized = true; + + return !$initialized; + } + + /** + * Convert instance to a read only object. + * + * @return $this + */ + public function readonly() + { + $this->readonly = true; + + return $this; + } + + /** + * Returns true if the object exists. + * + * @param boolean $exists Internal parameter to change state. + * + * @return boolean True if object exists. + */ + public function exists($exists = null) + { + $return = $this->exists; + if ($exists !== null) { + $this->exists = (bool) $exists; + } + + return $return; + } + + /** + * Method to load object from the storage. + * + * @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not + * set the instance key value is used. + * + * @return boolean True on success, false if the object doesn't exist. + */ + public function load($keys = null) + { + if (is_scalar($keys)) { + $keys = ['id' => (string) $keys]; + } + + // Get storage. + $storage = $this->getStorage(); + + // Load the object based on the keys. + $this->items = $storage->load($keys); + $this->exists = !empty($this->items); + + // Append the keys and defaults if they were not set by load(). + $this->items += $keys + static::$defaults; + + $this->initialize(); + + if ($this->id) { + if (!isset(static::$instances[$this->id])) { + static::$instances[$this->id] = $this; + } + } + + return $this->exists; + } + + /** + * Method to save the object to the storage. + * + * Before saving the object, this method checks if object can be safely saved. + * + * @return boolean True on success. + */ + public function save() + { + // Check the object. + if ($this->readonly || !$this->check() || !$this->onBeforeSave()) { + return false; + } + + // Get storage. + $storage = $this->getStorage(); + + // Save the object. + $id = $storage->save($this); + if (!$id) { + return false; + } + + // If item was created, load the object. + if (!$this->exists) { + $this->load($id); + } + + $this->onAfterSave(); + + return true; + } + + /** + * Method to delete the object from the database. + * + * @return boolean True on success. + */ + public function delete() + { + if ($this->readonly || !$this->exists || !$this->onBeforeDelete()) { + return false; + } + + // Get storage. + $storage = $this->getStorage(); + + if (!$storage->delete($this)) { + return false; + } + + $this->exists = false; + + $this->onAfterDelete(); + + return true; + } + + /** + * Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage. + * + * Child classes should override this method to make sure the data they are storing in the storage is safe and as + * expected before saving the object. + * + * @return boolean True if the instance is sane and able to be stored in the storage. + */ + public function check() + { + return !empty($this->id); + } + + // Internal functions + + /** + * @param array $items + * @return $this + */ + protected function set(array $items) + { + $this->items = $items; + + return $this; + } + + /** + * @return boolean + */ + protected function onBeforeSave() + { + return true; + } + + protected function onAfterSave() + { + } + + /** + * @return boolean + */ + protected function onBeforeDelete() + { + return true; + } + + protected function onAfterDelete() + { + } + + /** + * @return StorageInterface + */ + static protected function getStorage() + { + return static::$storage; + } +} diff --git a/system/src/Grav/Common/Object/AbstractObjectFinder.php b/system/src/Grav/Common/Object/AbstractObjectFinder.php new file mode 100644 index 000000000..91d891da7 --- /dev/null +++ b/system/src/Grav/Common/Object/AbstractObjectFinder.php @@ -0,0 +1,279 @@ +storage) { + throw new \DomainException('Storage missing from ' . get_class($this)); + } + + if ($options) { + $this->parse($options); + } + } + + /** + * Parse query options. + * + * @param array $options + * @return $this + */ + public function parse(array $options) + { + foreach ($options as $func => $params) { + if (method_exists($this, $func)) { + call_user_func_array([$this, $func], (array) $params); + } + } + + return $this; + } + + /** + * Set start position for the query. + * + * @param int $start + * + * @return $this + */ + public function start($start = null) + { + if (!is_null($start)) { + if ((string)(int)$start === (string)$start && $start>=0) { + throw new \RuntimeException('$start needs to be zero or a positive integer'); + } + + $this->start = (int)$start; + } + + return $this; + } + + /** + * Set limit to the query. + * + * If this function isn't used, RokClub will use threads per page configuration setting. + * + * @param int $limit + * + * @return $this + */ + public function limit($limit = null) + { + if (!is_null($limit)) + { + if ((string)(int)$limit === (string)$limit && $limit>=0) { + throw new \RuntimeException('$limit needs to be zero or a positive integer'); + } + + $this->limit = (int)$limit; + } + + return $this; + } + + /** + * Set order by field and direction. + * + * This function can be used more than once to chain order by. + * + * @param string $field + * @param int $direction + * + * @return $this + */ + public function order($field, $direction = 1) + { + if (!is_string($field)) { + throw new \RuntimeException('$field needs to be a string'); + } + if ((string)(int)$direction !== (string)$direction || $direction <= -1 || $direction >= 1) { + throw new \RuntimeException('$direction needs to be 1 (ascending), 0 (undefined) or -1 (descending)'); + } + + if ($direction) { + $this->query[] = ['order', $field, (int)$direction]; + } + + return $this; + } + + /** + * Filter by field. + * + * @param string $field Field name. + * @param string $operation Operation (>|>=|<|<=|==|!=|BETWEEN|NOT BETWEEN|IN|NOT IN) + * @param mixed $value Value. + * + * @return $this + */ + public function where($field, $operation, $value = null) + { + if (!is_string($field)) { + throw new \RuntimeException('$field needs to be a string'); + } + + $operation = strtoupper((string)$operation); + + switch ($operation) + { + case '>': + case '>=': + case '<': + case '<=': + $this->checkOperator($value); + $this->query[] = ['where', $field, $operation, $value]; + break; + case '==': + case '!=': + $this->checkEqOperator($value); + $this->query[] = ['where', $field, $operation, $value]; + break; + case 'BETWEEN': + case 'NOT BETWEEN': + $this->checkBetweenOperator($value); + $this->query[] = ['where', $field, $operation, array_values($value)]; + break; + case 'IN': + if (is_null($value) || (is_array($value) && empty($value))) { + // IN (nothing): optimized to empty collection. + $this->skip = true; + } else { + $this->checkInOperator($value); + $this->query[] = ['where', $field, $operation, array_values((array)$value)]; + } + break; + case 'NOT IN': + if (is_null($value) || (is_array($value) && empty($value))) { + // NOT IN (nothing): optimized away. + } else { + $this->checkInOperator($value); + $this->query[] = ['where', $field, $operation, array_values((array)$value)]; + } + break; + default: + throw new \RuntimeException('$operation needs to be one of: > , >= , < , <= , == , != , BETWEEN , NOT BETWEEN , IN , NOT IN'); + } + + return $this; + } + + /** + * Get items. + * + * Derived classes should generally override this function to return correct objects. + * + * @param int $start + * @param int $limit + * @return array + */ + public function find($start = null, $limit = null) + { + if ($this->skip) + { + return array(); + } + + $query = $this->query; + $this->start($start)->limit($limit); + $this->prepare(); + + $results = (array) $this->storage->find($this->query); + + $this->query = $query; + + return $results; + } + + /** + * Count items. + * + * @return int + */ + public function count() + { + $query = $this->query; + $this->prepare(); + + $count = (int) $this->storage->count($this->query); + + $this->query = $query; + + return $count; + } + + /** + * Override to include common rules. + * + * @return void + */ + protected function prepare() + { + } + + protected function checkOperator($value) + { + if (!is_scalar($value)) { + throw new \RuntimeException('$value needs to be a scalar'); + } + } + + protected function checkEqOperator($value) + { + if (!is_null($value) || !is_scalar($value)) { + throw new \RuntimeException('$value needs to be a scalar or null'); + } + } + + protected function checkBetweenOperator($value) + { + if (!is_array($value) || count($value) !== 2) { + throw new \RuntimeException('$value needs to be a list with two values in it'); + } + } + + protected function checkInOperator($value) + { + if (!is_scalar($value) || !is_array($value)) { + throw new \RuntimeException('$value needs to be a single value or list of values'); + } + } +} diff --git a/system/src/Grav/Common/Object/ObjectCollection.php b/system/src/Grav/Common/Object/ObjectCollection.php new file mode 100644 index 000000000..e9a7df3b3 --- /dev/null +++ b/system/src/Grav/Common/Object/ObjectCollection.php @@ -0,0 +1,20 @@ +items = $items; + } +} diff --git a/system/src/Grav/Common/Object/ObjectFinderInterface.php b/system/src/Grav/Common/Object/ObjectFinderInterface.php new file mode 100644 index 000000000..a518fbfcb --- /dev/null +++ b/system/src/Grav/Common/Object/ObjectFinderInterface.php @@ -0,0 +1,80 @@ +|>=|<|<=|==|!=|BETWEEN|NOT BETWEEN|IN|NOT IN) + * @param mixed $value Value. + * + * @return $this + */ + public function where($field, $operation, $value = null); + + /** + * Get items. + * + * Derived classes should generally override this function to return correct objects. + * + * @param int $start + * @param int $limit + * @return array + */ + public function find($start = null, $limit = null); + + /** + * Count items. + * + * @return int + */ + public function count(); +} diff --git a/system/src/Grav/Common/Object/ObjectInterface.php b/system/src/Grav/Common/Object/ObjectInterface.php new file mode 100644 index 000000000..2684f77d4 --- /dev/null +++ b/system/src/Grav/Common/Object/ObjectInterface.php @@ -0,0 +1,100 @@ + Date: Wed, 15 Feb 2017 11:44:14 +0200 Subject: [PATCH 02/32] Update Collection classes to be based on Doctrine Collections --- composer.json | 1 + composer.lock | 182 ++++++++++++------ .../Common/Collection/AbstractCollection.php | 59 ------ .../Collection/CloneCollectionInterface.php | 15 -- .../Collection/CloneCollectionTrait.php | 29 --- .../src/Grav/Common/Collection/Collection.php | 30 +++ .../Common/Collection/CollectionInterface.php | 18 +- .../Collection/ObjectCollectionInterface.php | 13 +- .../Collection/ObjectCollectionTrait.php | 101 ++-------- .../Collection/SortingCollectionTrait.php | 84 -------- .../src/Grav/Common/Object/AbstractObject.php | 30 +++ .../Common/Object/AbstractObjectFinder.php | 16 +- .../Grav/Common/Object/ObjectCollection.php | 13 +- .../Common/Object/ObjectFinderInterface.php | 2 - .../Object/Storage/StorageInterface.php | 10 +- system/src/Grav/Common/User/Group.php | 2 +- 16 files changed, 239 insertions(+), 366 deletions(-) delete mode 100644 system/src/Grav/Common/Collection/AbstractCollection.php delete mode 100644 system/src/Grav/Common/Collection/CloneCollectionInterface.php delete mode 100644 system/src/Grav/Common/Collection/CloneCollectionTrait.php create mode 100644 system/src/Grav/Common/Collection/Collection.php delete mode 100644 system/src/Grav/Common/Collection/SortingCollectionTrait.php diff --git a/composer.json b/composer.json index 832587378..bddc05f67 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "symfony/var-dumper": "~2.8", "symfony/polyfill-iconv": "~1.0", "doctrine/cache": "~1.5", + "doctrine/collections": "^1.4", "filp/whoops": "~2.0", "matthiasmullie/minify": "^1.3", "monolog/monolog": "~1.0", diff --git a/composer.lock b/composer.lock index 5b083f85d..e248b3bcd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "4e5b9333d3ac2de823c68c9c8e0f2017", + "hash": "d71bd78770ae90cfdd59960d2829cb75", + "content-hash": "7f68c667c95b191cfd0b6eeef614dc3d", "packages": [ { "name": "antoligy/dom-string-iterators", @@ -48,7 +49,7 @@ } ], "description": "Composer package for DOMWordsIterator and DOMLettersIterator", - "time": "2015-11-04T17:33:14+00:00" + "time": "2015-11-04 17:33:14" }, { "name": "doctrine/cache", @@ -118,7 +119,74 @@ "cache", "caching" ], - "time": "2016-10-29T11:16:17+00:00" + "time": "2016-10-29 11:16:17" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03 10:49:41" }, { "name": "donatj/phpuseragentparser", @@ -169,7 +237,7 @@ "user agent", "useragent" ], - "time": "2017-01-23T19:32:09+00:00" + "time": "2017-01-23 19:32:09" }, { "name": "erusev/parsedown", @@ -255,7 +323,7 @@ "parsedown", "parser" ], - "time": "2015-11-01T10:19:22+00:00" + "time": "2015-11-01 10:19:22" }, { "name": "filp/whoops", @@ -315,7 +383,7 @@ "whoops", "zf2" ], - "time": "2016-12-26T16:13:31+00:00" + "time": "2016-12-26 16:13:31" }, { "name": "gregwar/cache", @@ -358,7 +426,7 @@ "file-system", "system" ], - "time": "2016-09-23T08:16:04+00:00" + "time": "2016-09-23 08:16:04" }, { "name": "gregwar/image", @@ -461,7 +529,7 @@ "php", "terminal" ], - "time": "2016-04-04T20:24:59+00:00" + "time": "2016-04-04 20:24:59" }, { "name": "matthiasmullie/minify", @@ -518,7 +586,7 @@ "minifier", "minify" ], - "time": "2017-01-26T15:48:07+00:00" + "time": "2017-01-26 15:48:07" }, { "name": "matthiasmullie/path-converter", @@ -567,7 +635,7 @@ "paths", "relative" ], - "time": "2017-01-26T08:54:49+00:00" + "time": "2017-01-26 08:54:49" }, { "name": "maximebf/debugbar", @@ -628,7 +696,7 @@ "debug", "debugbar" ], - "time": "2017-01-05T08:46:19+00:00" + "time": "2017-01-05 08:46:19" }, { "name": "monolog/monolog", @@ -706,7 +774,7 @@ "logging", "psr-3" ], - "time": "2016-11-26T00:15:39+00:00" + "time": "2016-11-26 00:15:39" }, { "name": "pimple/pimple", @@ -752,7 +820,7 @@ "container", "dependency injection" ], - "time": "2015-09-11T15:10:35+00:00" + "time": "2015-09-11 15:10:35" }, { "name": "psr/log", @@ -799,7 +867,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2016-10-10 12:19:37" }, { "name": "rockettheme/toolbox", @@ -847,7 +915,7 @@ "php", "rockettheme" ], - "time": "2016-10-06T18:02:45+00:00" + "time": "2016-10-06 18:02:45" }, { "name": "seld/cli-prompt", @@ -895,7 +963,7 @@ "input", "prompt" ], - "time": "2016-04-18T09:31:41+00:00" + "time": "2016-04-18 09:31:41" }, { "name": "symfony/console", @@ -956,7 +1024,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-02-06T12:04:06+00:00" + "time": "2017-02-06 12:04:06" }, { "name": "symfony/debug", @@ -1013,7 +1081,7 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-07-30T07:22:48+00:00" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/event-dispatcher", @@ -1073,7 +1141,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:30:24+00:00" + "time": "2017-01-02 20:30:24" }, { "name": "symfony/polyfill-iconv", @@ -1132,7 +1200,7 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/polyfill-mbstring", @@ -1191,7 +1259,7 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/var-dumper", @@ -1254,7 +1322,7 @@ "debug", "dump" ], - "time": "2017-01-24T13:02:12+00:00" + "time": "2017-01-24 13:02:12" }, { "name": "symfony/yaml", @@ -1303,7 +1371,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-01-21T16:40:50+00:00" + "time": "2017-01-21 16:40:50" }, { "name": "twig/twig", @@ -1364,7 +1432,7 @@ "keywords": [ "templating" ], - "time": "2017-01-11T19:36:15+00:00" + "time": "2017-01-11 19:36:15" } ], "packages-dev": [ @@ -1425,7 +1493,7 @@ "gherkin", "parser" ], - "time": "2016-10-30T11:50:56+00:00" + "time": "2016-10-30 11:50:56" }, { "name": "codeception/codeception", @@ -1517,7 +1585,7 @@ "functional testing", "unit testing" ], - "time": "2017-02-04T02:04:21+00:00" + "time": "2017-02-04 02:04:21" }, { "name": "doctrine/instantiator", @@ -1571,7 +1639,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2015-06-14 21:17:01" }, { "name": "facebook/webdriver", @@ -1620,7 +1688,7 @@ "selenium", "webdriver" ], - "time": "2017-01-13T15:48:08+00:00" + "time": "2017-01-13 15:48:08" }, { "name": "fzaninotto/faker", @@ -1668,7 +1736,7 @@ "faker", "fixtures" ], - "time": "2016-04-29T12:21:54+00:00" + "time": "2016-04-29 12:21:54" }, { "name": "guzzlehttp/guzzle", @@ -1730,7 +1798,7 @@ "rest", "web service" ], - "time": "2016-10-08T15:01:37+00:00" + "time": "2016-10-08 15:01:37" }, { "name": "guzzlehttp/promises", @@ -1781,7 +1849,7 @@ "keywords": [ "promise" ], - "time": "2016-12-20T10:07:11+00:00" + "time": "2016-12-20 10:07:11" }, { "name": "guzzlehttp/psr7", @@ -1839,7 +1907,7 @@ "stream", "uri" ], - "time": "2016-06-24T23:00:38+00:00" + "time": "2016-06-24 23:00:38" }, { "name": "phpdocumentor/reflection-common", @@ -1893,7 +1961,7 @@ "reflection", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" + "time": "2015-12-27 11:43:31" }, { "name": "phpdocumentor/reflection-docblock", @@ -1938,7 +2006,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2016-09-30 07:12:33" }, { "name": "phpdocumentor/type-resolver", @@ -1985,7 +2053,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2016-11-25 06:54:22" }, { "name": "phpspec/prophecy", @@ -2048,7 +2116,7 @@ "spy", "stub" ], - "time": "2016-11-21T14:58:47+00:00" + "time": "2016-11-21 14:58:47" }, { "name": "phpunit/php-code-coverage", @@ -2110,7 +2178,7 @@ "testing", "xunit" ], - "time": "2015-10-06T15:47:00+00:00" + "time": "2015-10-06 15:47:00" }, { "name": "phpunit/php-file-iterator", @@ -2157,7 +2225,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -2198,7 +2266,7 @@ "keywords": [ "template" ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", @@ -2242,7 +2310,7 @@ "keywords": [ "timer" ], - "time": "2016-05-12T18:03:57+00:00" + "time": "2016-05-12 18:03:57" }, { "name": "phpunit/php-token-stream", @@ -2291,7 +2359,7 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15T14:06:22+00:00" + "time": "2016-11-15 14:06:22" }, { "name": "phpunit/phpunit", @@ -2363,7 +2431,7 @@ "testing", "xunit" ], - "time": "2017-02-06T05:18:07+00:00" + "time": "2017-02-06 05:18:07" }, { "name": "phpunit/phpunit-mock-objects", @@ -2419,7 +2487,7 @@ "mock", "xunit" ], - "time": "2015-10-02T06:51:40+00:00" + "time": "2015-10-02 06:51:40" }, { "name": "psr/http-message", @@ -2469,7 +2537,7 @@ "request", "response" ], - "time": "2016-08-06T14:39:51+00:00" + "time": "2016-08-06 14:39:51" }, { "name": "sebastian/comparator", @@ -2533,7 +2601,7 @@ "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2017-01-29 09:50:25" }, { "name": "sebastian/diff", @@ -2585,7 +2653,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2015-12-08 07:14:41" }, { "name": "sebastian/environment", @@ -2635,7 +2703,7 @@ "environment", "hhvm" ], - "time": "2016-08-18T05:49:44+00:00" + "time": "2016-08-18 05:49:44" }, { "name": "sebastian/exporter", @@ -2702,7 +2770,7 @@ "export", "exporter" ], - "time": "2016-06-17T09:04:28+00:00" + "time": "2016-06-17 09:04:28" }, { "name": "sebastian/global-state", @@ -2753,7 +2821,7 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2015-10-12 03:26:01" }, { "name": "sebastian/recursion-context", @@ -2806,7 +2874,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11T19:50:13+00:00" + "time": "2015-11-11 19:50:13" }, { "name": "sebastian/version", @@ -2841,7 +2909,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21T13:59:46+00:00" + "time": "2015-06-21 13:59:46" }, { "name": "symfony/browser-kit", @@ -2898,7 +2966,7 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2017-01-31T21:49:23+00:00" + "time": "2017-01-31 21:49:23" }, { "name": "symfony/css-selector", @@ -2951,7 +3019,7 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:32:22+00:00" + "time": "2017-01-02 20:32:22" }, { "name": "symfony/dom-crawler", @@ -3007,7 +3075,7 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:14:11+00:00" + "time": "2017-01-21 17:14:11" }, { "name": "symfony/finder", @@ -3056,7 +3124,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:32:22+00:00" + "time": "2017-01-02 20:32:22" }, { "name": "symfony/process", @@ -3105,7 +3173,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-02-03T12:11:38+00:00" + "time": "2017-02-03 12:11:38" }, { "name": "webmozart/assert", @@ -3155,7 +3223,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2016-11-23 20:04:58" } ], "aliases": [ diff --git a/system/src/Grav/Common/Collection/AbstractCollection.php b/system/src/Grav/Common/Collection/AbstractCollection.php deleted file mode 100644 index 03add33e0..000000000 --- a/system/src/Grav/Common/Collection/AbstractCollection.php +++ /dev/null @@ -1,59 +0,0 @@ -items = $variables['items']; - - return $instance; - } - - /** - * Add item to the list. - * - * @param mixed $item - * @param string $key - * @return $this - */ - public function add($item, $key = null) - { - $this->offsetSet($key, $item); - - return $this; - } - - /** - * Remove item from the list. - * - * @param $key - */ - public function remove($key) - { - $this->offsetUnset($key); - } - - /** - * @return \ArrayIterator - */ - public function getIterator() - { - return new \ArrayIterator($this->items); - } -} diff --git a/system/src/Grav/Common/Collection/CloneCollectionInterface.php b/system/src/Grav/Common/Collection/CloneCollectionInterface.php deleted file mode 100644 index b29c2951d..000000000 --- a/system/src/Grav/Common/Collection/CloneCollectionInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - $value) { - if (is_object($value)) { - $this->offsetSet($key, clone $value); - } - } - } - - /** - * - * Create a clone from this collection. - * - * @return static - */ - public function getClone() - { - return clone $this; - } -} diff --git a/system/src/Grav/Common/Collection/Collection.php b/system/src/Grav/Common/Collection/Collection.php new file mode 100644 index 000000000..0cc6e0a8a --- /dev/null +++ b/system/src/Grav/Common/Collection/Collection.php @@ -0,0 +1,30 @@ +createFrom(array_reverse($this->toArray())); + } + + /** + * Shuffle items. + * + * @return static + */ + public function shuffle() + { + $keys = $this->getKeys(); + shuffle($keys); + + return $this->createFrom(array_replace(array_flip($keys), $this->toArray())); + } +} diff --git a/system/src/Grav/Common/Collection/CollectionInterface.php b/system/src/Grav/Common/Collection/CollectionInterface.php index 25fea590a..85da4d8ac 100644 --- a/system/src/Grav/Common/Collection/CollectionInterface.php +++ b/system/src/Grav/Common/Collection/CollectionInterface.php @@ -1,21 +1,21 @@ getObjectKey($object); - $this->offsetSet(is_null($objKey) ? $key : $objKey, $object); - - return $this; - } - - /** - * Remove item from the list. - * - * @param string|object $key - * @return $this - */ - public function remove($key) - { - if (is_object($key)) { - $key = $this->getObjectKey($key); - if (is_null($key)) { - return $this; - } + $list = []; + foreach ($this as $key => $value) { + $list[$key] = is_object($value) ? clone $value : $value; } - $this->offsetUnset($key); - return $this; + return $this->createFrom($list); } /** * @param string $property Object property to be fetched. - * @return array Values of the property. + * @return array Key/Value pairs of the properties. */ - public function get($property) + public function getProperty($property) { $list = []; - foreach ($this as $id => $object) { - $key = $this->getObjectKey($object); - $list[is_null($key) ? $id : $key] = $this->getObjectValue($object, $property); + foreach ($this as $id => $element) { + $list[$id] = isset($element->{$property}) ? $element->{$property} : null; } return $list; @@ -65,63 +36,27 @@ trait ObjectCollectionTrait /** * @param string $property Object property to be updated. * @param string $value New value. - * @return $this */ - public function set($property, $value) + public function setProperty($property, $value) { - foreach ($this as $object) { - $object->{$property} = $value; + foreach ($this as $element) { + $element->{$property} = $value; } - - return $this; } /** - * @param string $name Method name. + * @param string $method Method name. * @param array $arguments List of arguments passed to the function. * @return array Return values. */ - public function __call($name, array $arguments) + public function call($method, array $arguments) { $list = []; - foreach ($this as $id => $object) { - $key = $this->getObjectKey($object); - $list[is_null($key) ? $id : $key] = $this->getObjectCallResult($object, $name, $arguments); + foreach ($this as $id => $element) { + $list[$id] = method_exists($element, $method) ? call_user_func_array([$element, $method], $arguments) : null; } return $list; } - - /** - * @param object $object - * @return string|null - */ - protected function getObjectKey($object) - { - $keyProperty = $this->keyProperty; - - return $keyProperty && isset($object->{$keyProperty}) ? (string) $object->{$keyProperty} : null; - } - - /** - * @param object $object - * @param string $property - * @return mixed - */ - protected function getObjectValue($object, $property) - { - return isset($object->{$property}) ? $object->{$property} : null; - } - - /** - * @param object $object - * @param string $name; - * @param array $arguments - * @return mixed - */ - protected function getObjectCallResult($object, $name, $arguments) - { - return method_exists($object, $name) ? call_user_func_array([$object, $name], $arguments) : null; - } } diff --git a/system/src/Grav/Common/Collection/SortingCollectionTrait.php b/system/src/Grav/Common/Collection/SortingCollectionTrait.php deleted file mode 100644 index 9a3fdfd8f..000000000 --- a/system/src/Grav/Common/Collection/SortingCollectionTrait.php +++ /dev/null @@ -1,84 +0,0 @@ -items = array_reverse($this->items); - - return $this; - } - - /** - * Shuffle items. - * - * @return $this - */ - public function shuffle() - { - $keys = array_keys($this->items); - shuffle($keys); - - $this->items = array_replace(array_flip($keys), $this->items); - - return $this; - } - - /** - * Sort collection by values using a user-defined comparison function. - * - * @param callable $callback - * @return $this - */ - public function sort(callable $callback) - { - uasort($this->items, $callback); - - return $this; - } - /** - * Sort collection by keys. - * - * @return $this - */ - public function ksort($sort_flags = SORT_REGULAR) - { - ksort($this->items, $sort_flags); - - return $this; - } - - - /** - * Sort collection by keys in reverse order. - * - * @return $this - */ - public function krsort($sort_flags = SORT_REGULAR) - { - krsort($this->items, $sort_flags); - - return $this; - } - - /** - * Sort collection by keys using a user-defined comparison function. - * - * @return $this - */ - public function uksort(callable $key_compare_func) - { - uksort($this->items, $key_compare_func); - - return $this; - }} diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php index 6b80201b1..781551b4d 100644 --- a/system/src/Grav/Common/Object/AbstractObject.php +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -26,6 +26,12 @@ abstract class AbstractObject implements ObjectInterface */ static protected $storage; + /** + * If you don't have global storage, override this in extending class. + * @var ObjectFinderInterface + */ + static protected $finder; + /** * Default properties for the object. * @var array @@ -61,6 +67,22 @@ abstract class AbstractObject implements ObjectInterface protected $initialized = false; + /** + * @param StorageInterface $storage + */ + static public function setStorage(StorageInterface $storage) + { + static::$storage = $storage; + } + + /** + * @param ObjectFinderInterface $finder + */ + static public function setFinder(ObjectFinderInterface $finder) + { + static::$finder = $finder; + } + /** * Returns the global instance to the object. * @@ -148,6 +170,14 @@ abstract class AbstractObject implements ObjectInterface return new $collectionClass($results); } + /** + * @return ObjectFinderInterface + */ + static public function search() + { + return static::$finder; + } + /** * Removes all or selected instances from the object cache. * diff --git a/system/src/Grav/Common/Object/AbstractObjectFinder.php b/system/src/Grav/Common/Object/AbstractObjectFinder.php index 91d891da7..9c93f12c0 100644 --- a/system/src/Grav/Common/Object/AbstractObjectFinder.php +++ b/system/src/Grav/Common/Object/AbstractObjectFinder.php @@ -90,8 +90,6 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface /** * Set limit to the query. * - * If this function isn't used, RokClub will use threads per page configuration setting. - * * @param int $limit * * @return $this @@ -130,7 +128,7 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface } if ($direction) { - $this->query[] = ['order', $field, (int)$direction]; + $this->query['order'][] = [$field, (int)$direction]; } return $this; @@ -160,17 +158,17 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface case '<': case '<=': $this->checkOperator($value); - $this->query[] = ['where', $field, $operation, $value]; + $this->query['where'][] = [$field, $operation, $value]; break; case '==': case '!=': $this->checkEqOperator($value); - $this->query[] = ['where', $field, $operation, $value]; + $this->query['where'][] = [$field, $operation, $value]; break; case 'BETWEEN': case 'NOT BETWEEN': $this->checkBetweenOperator($value); - $this->query[] = ['where', $field, $operation, array_values($value)]; + $this->query['where'][] = [$field, $operation, array_values($value)]; break; case 'IN': if (is_null($value) || (is_array($value) && empty($value))) { @@ -178,7 +176,7 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface $this->skip = true; } else { $this->checkInOperator($value); - $this->query[] = ['where', $field, $operation, array_values((array)$value)]; + $this->query['where'][] = [$field, $operation, array_values((array)$value)]; } break; case 'NOT IN': @@ -186,7 +184,7 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface // NOT IN (nothing): optimized away. } else { $this->checkInOperator($value); - $this->query[] = ['where', $field, $operation, array_values((array)$value)]; + $this->query['where'][] = [$field, $operation, array_values((array)$value)]; } break; default: @@ -216,7 +214,7 @@ abstract class AbstractObjectFinder implements ObjectFinderInterface $this->start($start)->limit($limit); $this->prepare(); - $results = (array) $this->storage->find($this->query); + $results = (array) $this->storage->find($this->query, $this->start, $this->limit); $this->query = $query; diff --git a/system/src/Grav/Common/Object/ObjectCollection.php b/system/src/Grav/Common/Object/ObjectCollection.php index e9a7df3b3..c6d29176d 100644 --- a/system/src/Grav/Common/Object/ObjectCollection.php +++ b/system/src/Grav/Common/Object/ObjectCollection.php @@ -1,20 +1,11 @@ items = $items; - } } diff --git a/system/src/Grav/Common/Object/ObjectFinderInterface.php b/system/src/Grav/Common/Object/ObjectFinderInterface.php index a518fbfcb..9bcf267fd 100644 --- a/system/src/Grav/Common/Object/ObjectFinderInterface.php +++ b/system/src/Grav/Common/Object/ObjectFinderInterface.php @@ -30,8 +30,6 @@ interface ObjectFinderInterface /** * Set limit to the query. * - * If this function isn't used, RokClub will use threads per page configuration setting. - * * @param int $limit * * @return $this diff --git a/system/src/Grav/Common/Object/Storage/StorageInterface.php b/system/src/Grav/Common/Object/Storage/StorageInterface.php index d617321bc..f1889f8f7 100644 --- a/system/src/Grav/Common/Object/Storage/StorageInterface.php +++ b/system/src/Grav/Common/Object/Storage/StorageInterface.php @@ -13,7 +13,7 @@ interface StorageInterface /** * @param AbstractObject $object - * @return string|int Id + * @return string Id */ public function save(AbstractObject $object); @@ -24,7 +24,7 @@ interface StorageInterface public function delete(AbstractObject $object); /** - * @param array|int[]|string[] $list + * @param array|string[] $list * @return array */ public function loadList(array $list); @@ -37,7 +37,9 @@ interface StorageInterface /** * @param array $query - * @return array|int[]|string[] + * @param int $start + * @param int $limit + * @return array|string[] */ - public function find(array $query); + public function find(array $query, $start, $limit); } diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php index 2a29340db..5ef45a018 100644 --- a/system/src/Grav/Common/User/Group.php +++ b/system/src/Grav/Common/User/Group.php @@ -33,7 +33,7 @@ class Group extends Data * * @param string $groupname * - * @return object + * @return bool */ public static function groupExists($groupname) { From 863d92cabfa3769eefc815c43fd37969c001260e Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 15 Feb 2017 12:25:36 +0200 Subject: [PATCH 03/32] Collection now implements JsonSerializable --- system/src/Grav/Common/Collection/Collection.php | 11 +++++++++++ .../Grav/Common/Collection/CollectionInterface.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Collection/Collection.php b/system/src/Grav/Common/Collection/Collection.php index 0cc6e0a8a..62b9907dd 100644 --- a/system/src/Grav/Common/Collection/Collection.php +++ b/system/src/Grav/Common/Collection/Collection.php @@ -27,4 +27,15 @@ class Collection extends ArrayCollection implements CollectionInterface return $this->createFrom(array_replace(array_flip($keys), $this->toArray())); } + + /** + * Implementes JsonSerializable interface. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + } diff --git a/system/src/Grav/Common/Collection/CollectionInterface.php b/system/src/Grav/Common/Collection/CollectionInterface.php index 85da4d8ac..7d3ea9a28 100644 --- a/system/src/Grav/Common/Collection/CollectionInterface.php +++ b/system/src/Grav/Common/Collection/CollectionInterface.php @@ -3,7 +3,7 @@ namespace Grav\Common\Collection; use Doctrine\Common\Collections\Collection; -interface CollectionInterface extends Collection +interface CollectionInterface extends Collection, \JsonSerializable { /** * Reverse the order of the items. From c7c3659312281162c98972095d671362727f8160 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 27 Feb 2017 20:28:37 +0200 Subject: [PATCH 04/32] Fixed Folder::move() --- system/src/Grav/Common/Filesystem/Folder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 2c70ced58..599ab2ec7 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -332,7 +332,7 @@ abstract class Folder */ public static function move($source, $target) { - if (!file_exists($target) || !is_dir($source)) { + if (!file_exists($source) || !is_dir($source)) { // Rename fails if source folder does not exist. throw new \RuntimeException('Cannot move non-existing folder.'); } From d0f4fbdfcccb5bb4c2fb66a6065dd7596b36d80a Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 28 Feb 2017 19:09:35 +0200 Subject: [PATCH 05/32] Implement Object\FilesystemStorage class --- .../Grav/Common/Object/ObjectInterface.php | 2 +- .../Object/Storage/FilesystemStorage.php | 106 ++++++++++++++---- .../Object/Storage/StorageInterface.php | 23 ++-- 3 files changed, 99 insertions(+), 32 deletions(-) diff --git a/system/src/Grav/Common/Object/ObjectInterface.php b/system/src/Grav/Common/Object/ObjectInterface.php index 2684f77d4..971ee1f57 100644 --- a/system/src/Grav/Common/Object/ObjectInterface.php +++ b/system/src/Grav/Common/Object/ObjectInterface.php @@ -1,7 +1,7 @@ path = $path; + $this->type = $type; + $this->extension = $extension; + } + + /** + * @param string $key * @return array */ - public function load(array $keys) + public function load($key) { - // TODO - return []; + if ($key === null) { + return []; + } + + $file = $this->getFile($key); + $content = (array)$file->content(); + $file->free(); + + return $content; } /** - * @param AbstractObject $object - * @return string|int Id + * @param string $key + * @param array $data + * @return string */ - public function save(AbstractObject $object) + public function save($key, array $data) { - // TODO - return 'xxx'; + $file = $this->getFile($key); + $file->save($data); + $file->free(); + + return $key; } /** - * @param AbstractObject $object + * @param string $key * @return bool */ - public function delete(AbstractObject $object) + public function delete($key) { - // TODO - return false; + $file = $this->getFile($key); + $result = $file->delete(); + $file->free(); + + return $result; } /** - * @param array|int[]|string[] $list + * @param string[] $list * @return array */ public function loadList(array $list) { - // TODO - return []; + $results = []; + foreach ($list as $id) { + $results[$id] = $this->load(['id' => $id]); + } + + return $results; } /** @@ -57,11 +104,32 @@ class FilesystemStorage implements StorageInterface /** * @param array $query - * @return array|int[]|string[] + * @return string[] */ - public function find(array $query) + public function find(array $query, $start = 0, $limit = 0) { // TODO return []; } + + /** + * @param string $key + * @return FileInterface + */ + protected function getFile($key) + { + if ($key === null) { + throw new \RuntimeException('Id not defined'); + } + + $filename = "{$this->path}/{$key}{$this->extension}"; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + /** @var FileInterface $type */ + $type = $this->type; + + return $type::instance($locator->findResource($filename, true) ?: $locator->findResource($filename, true, true)); + } } diff --git a/system/src/Grav/Common/Object/Storage/StorageInterface.php b/system/src/Grav/Common/Object/Storage/StorageInterface.php index f1889f8f7..c1f72124e 100644 --- a/system/src/Grav/Common/Object/Storage/StorageInterface.php +++ b/system/src/Grav/Common/Object/Storage/StorageInterface.php @@ -1,30 +1,29 @@ Date: Tue, 28 Feb 2017 19:10:42 +0200 Subject: [PATCH 06/32] Implement nested saving support for Object\AbstractObject class --- .../src/Grav/Common/Object/AbstractObject.php | 120 +++++++++++++++--- 1 file changed, 103 insertions(+), 17 deletions(-) diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php index 781551b4d..82cee22c8 100644 --- a/system/src/Grav/Common/Object/AbstractObject.php +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -256,20 +256,31 @@ abstract class AbstractObject implements ObjectInterface * * @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not * set the instance key value is used. + * @param bool $getKey Internal parameter, please do not use. * * @return boolean True on success, false if the object doesn't exist. */ - public function load($keys = null) + public function load($keys = null, $getKey = true) { - if (is_scalar($keys)) { - $keys = ['id' => (string) $keys]; + if ($getKey) { + if (is_scalar($keys)) { + $keys = ['id' => (string) $keys]; + } + + // Fetch internal key. + $key = $this->getStorageKey($keys); + + } else { + // Internal key was passed. + $key = $keys; + $keys = []; } // Get storage. $storage = $this->getStorage(); // Load the object based on the keys. - $this->items = $storage->load($keys); + $this->items = $storage->load($key); $this->exists = !empty($this->items); // Append the keys and defaults if they were not set by load(). @@ -293,25 +304,38 @@ abstract class AbstractObject implements ObjectInterface * * @return boolean True on success. */ - public function save() + public function save($includeChildren = false) { // Check the object. - if ($this->readonly || !$this->check() || !$this->onBeforeSave()) { + if ($this->readonly || !$this->check($includeChildren) || !$this->onBeforeSave()) { return false; } - // Get storage. - $storage = $this->getStorage(); + try { + // Get storage. + $storage = $this->getStorage(); + $key = $this->getStorageKey(); + + // Get data to be saved. + $data = $this->prepareSave($this->toArray()); + + // Save the object. + $id = $storage->save($key, $data); + } catch (\Exception $e) { + return false; + } - // Save the object. - $id = $storage->save($this); if (!$id) { return false; } // If item was created, load the object. if (!$this->exists) { - $this->load($id); + $this->load($id, false); + } + + if ($includeChildren) { + $this->saveChildren(); } $this->onAfterSave(); @@ -322,18 +346,23 @@ abstract class AbstractObject implements ObjectInterface /** * Method to delete the object from the database. * + * @param bool $includeChildren * @return boolean True on success. */ - public function delete() + public function delete($includeChildren = false) { if ($this->readonly || !$this->exists || !$this->onBeforeDelete()) { return false; } + if ($includeChildren) { + $this->deleteChildren(); + } + // Get storage. $storage = $this->getStorage(); - if (!$storage->delete($this)) { + if (!$storage->delete($this->getStorageKey())) { return false; } @@ -352,9 +381,29 @@ abstract class AbstractObject implements ObjectInterface * * @return boolean True if the instance is sane and able to be stored in the storage. */ - public function check() + public function check($includeChildren = false) { - return !empty($this->id); + $result = true; + + if ($includeChildren) { + foreach ($this->items as $field => $value) { + if (is_object($value) && method_exists($value, 'check')) { + $result = $result && $value->check(); + } + } + } + + return $result && !empty($this->items['id']); + } + + /** + * Implementes JsonSerializable interface. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); } // Internal functions @@ -371,7 +420,7 @@ abstract class AbstractObject implements ObjectInterface } /** - * @return boolean + * @return bool */ protected function onBeforeSave() { @@ -383,7 +432,7 @@ abstract class AbstractObject implements ObjectInterface } /** - * @return boolean + * @return bool */ protected function onBeforeDelete() { @@ -394,6 +443,43 @@ abstract class AbstractObject implements ObjectInterface { } + protected function saveChildren() + { + foreach ($this->items as $field => $value) { + if (is_object($value) && method_exists($value, 'save')) { + $value->save(true); + } + } + } + + protected function deleteChildren() + { + foreach ($this->items as $field => $value) { + if (is_object($value) && method_exists($value, 'delete')) { + $value->delete(true); + } + } + } + + protected function prepareSave(array $data) + { + foreach ($data as $field => $value) { + if (is_object($value) && method_exists($value, 'save')) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * Method to get the storage key for the object. + * + * @param array + * @return string + */ + abstract protected function getStorageKey(array $keys = null); + /** * @return StorageInterface */ From 80328308731a6a85a1553cdce5c9a2787bd0a00d Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Sat, 4 Mar 2017 13:28:43 -0700 Subject: [PATCH 07/32] Added block/line option to process markdown --- CHANGELOG.md | 1 + system/src/Grav/Common/Twig/TwigExtension.php | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 620be3db5..4feb2da40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ 1. [](#new) * Added default setting to only allow `direct-installs` from official GPM. Can be configured in `system.yaml` * Added a new `Utils::isValidUrl()` method + * Added optional parameter to `|markdown(false)` filter to toggle block/line processing (default|true = `block`) 1. [](#improved) * Genericized `direct-install` so it can be called via Admin plugin 1. [](#bugfix) diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index fff032e9c..eb6b960ad 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -432,9 +432,10 @@ class TwigExtension extends \Twig_Extension /** * @param $string * + * @param bool $block Block or Line processing * @return mixed|string */ - public function markdownFilter($string) + public function markdownFilter($string, $block = true) { $page = $this->grav['page']; $defaults = $this->config->get('system.pages.markdown'); @@ -446,7 +447,12 @@ class TwigExtension extends \Twig_Extension $parsedown = new Parsedown($page, $defaults); } - $string = $parsedown->text($string); + if ($block) { + $string = $parsedown->text($string); + } else { + $string = $parsedown->line($string); + } + return $string; } From 34cff72fcef4414ca42c8ee07d525f4f24ffbe57 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 6 Mar 2017 14:27:40 +0200 Subject: [PATCH 08/32] Add FilesystemStorage::exists() --- .../src/Grav/Common/Object/AbstractObject.php | 18 +++++++++++------- .../Object/Storage/FilesystemStorage.php | 17 +++++++++++++++++ .../Common/Object/Storage/StorageInterface.php | 6 ++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php index 82cee22c8..c4ef3487b 100644 --- a/system/src/Grav/Common/Object/AbstractObject.php +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -99,7 +99,10 @@ abstract class AbstractObject implements ObjectInterface // If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object. if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) { $c = get_called_class(); - $instance = new $c($keys); + + /** @var ObjectStorageTrait $instance */ + $instance = new $c(); + $instance->load($keys); /** @var Object $instance */ if (!$instance->exists()) return $instance; @@ -258,7 +261,7 @@ abstract class AbstractObject implements ObjectInterface * set the instance key value is used. * @param bool $getKey Internal parameter, please do not use. * - * @return boolean True on success, false if the object doesn't exist. + * @return bool True on success, false if the object doesn't exist. */ public function load($keys = null, $getKey = true) { @@ -302,7 +305,8 @@ abstract class AbstractObject implements ObjectInterface * * Before saving the object, this method checks if object can be safely saved. * - * @return boolean True on success. + * @params bool $includeChildren + * @return bool True on success. */ public function save($includeChildren = false) { @@ -346,8 +350,8 @@ abstract class AbstractObject implements ObjectInterface /** * Method to delete the object from the database. * - * @param bool $includeChildren - * @return boolean True on success. + * @param bool $includeChildren + * @return bool True on success. */ public function delete($includeChildren = false) { @@ -379,7 +383,7 @@ abstract class AbstractObject implements ObjectInterface * Child classes should override this method to make sure the data they are storing in the storage is safe and as * expected before saving the object. * - * @return boolean True if the instance is sane and able to be stored in the storage. + * @return bool True if the instance is sane and able to be stored in the storage. */ public function check($includeChildren = false) { @@ -478,7 +482,7 @@ abstract class AbstractObject implements ObjectInterface * @param array * @return string */ - abstract protected function getStorageKey(array $keys = null); + abstract public function getStorageKey(array $keys = []); /** * @return StorageInterface diff --git a/system/src/Grav/Common/Object/Storage/FilesystemStorage.php b/system/src/Grav/Common/Object/Storage/FilesystemStorage.php index dd221478f..a9ee5495c 100644 --- a/system/src/Grav/Common/Object/Storage/FilesystemStorage.php +++ b/system/src/Grav/Common/Object/Storage/FilesystemStorage.php @@ -34,6 +34,21 @@ class FilesystemStorage implements StorageInterface $this->extension = $extension; } + /** + * @param string $key + * @return bool + */ + public function exists($key) + { + if ($key === null) { + return false; + } + + $file = $this->getFile($key); + + return $file->exists(); + } + /** * @param string $key * @return array @@ -104,6 +119,8 @@ class FilesystemStorage implements StorageInterface /** * @param array $query + * @param int $start + * @param int $limit * @return string[] */ public function find(array $query, $start = 0, $limit = 0) diff --git a/system/src/Grav/Common/Object/Storage/StorageInterface.php b/system/src/Grav/Common/Object/Storage/StorageInterface.php index c1f72124e..a13ad396c 100644 --- a/system/src/Grav/Common/Object/Storage/StorageInterface.php +++ b/system/src/Grav/Common/Object/Storage/StorageInterface.php @@ -3,6 +3,12 @@ namespace Grav\Common\Object\Storage; interface StorageInterface { + /** + * @param string $key + * @return bool + */ + public function exists($key); + /** * @param string $key * @return array From 9ba038b2b39fa388d30c66d024f7e0a10f450012 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 6 Mar 2017 18:58:55 +0200 Subject: [PATCH 09/32] Implement ObjectStorageTrait --- .../Grav/Common/Object/ObjectStorageTrait.php | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 system/src/Grav/Common/Object/ObjectStorageTrait.php diff --git a/system/src/Grav/Common/Object/ObjectStorageTrait.php b/system/src/Grav/Common/Object/ObjectStorageTrait.php new file mode 100644 index 000000000..9d94de5c3 --- /dev/null +++ b/system/src/Grav/Common/Object/ObjectStorageTrait.php @@ -0,0 +1,360 @@ +load($keys)) { + return $instance; + } + + // Instance exists in storage: make sure that we return the global instance. + $keys = $instance->getInstanceId(); + $reload = false; + } + + // Return global instance from the identifier. + $instance = static::$instances[$keys]; + + if ($reload) { + $instance->load(); + } + + return $instance; + } + + /** + * Removes all or selected instances from the object cache. + * + * @param null|string|array $ids An optional primary key or list of keys. + */ + static public function freeInstances($ids = null) + { + if ($ids === null) { + $ids = array_keys(static::$instances); + } + $ids = (array) $ids; + + foreach ($ids as $id) { + unset(static::$instances[$id]); + } + } + + /** + * Override this function if you need to initialize object right after creating it. + * + * Can be used for example if the fields need to be converted from json strings to array. + * + * @return bool True if initialization was done, false if object was already initialized. + */ + public function initialize() + { + $initialized = $this->initialized; + $this->initialized = true; + + return !$initialized; + } + + /** + * Convert instance to a read only object. + * + * @return $this + */ + public function readonly() + { + $this->readonly = true; + + return $this; + } + + /** + * Returns true if the object has been stored. + * + * @return boolean True if object exists in storage. + */ + public function isSaved() + { + return $this->getStorage()->exists($this->getStorageKey()); + } + + /** + * Method to load object from the storage. + * + * @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not + * set the instance key value is used. + * @param bool $getKey Internal parameter, please do not use. + * + * @return bool True on success, false if the object doesn't exist. + */ + public function load($keys = null, $getKey = true) + { + if ($getKey) { + if (is_scalar($keys)) { + $keys = ['id' => (string) $keys]; + } + + // Fetch internal key. + $key = $this->getStorageKey($keys); + + } else { + // Internal key was passed. + $key = $keys; + $keys = []; + } + + // Get storage. + $storage = $this->getStorage(); + + // Load the object based on the keys. + $this->items = $storage->load($key); + + $this->initialize(); + + $id = $this->getInstanceId(); + if ($id) { + if (!isset(static::$instances[$id])) { + static::$instances[$id] = $this; + } + } + + return $this->isSaved(); + } + + /** + * Method to save the object to the storage. + * + * Before saving the object, this method checks if object can be safely saved. + * + * @param bool $includeChildren + * @return bool True on success. + */ + public function save($includeChildren = false) + { + // Check the object. + if ($this->readonly || !$this->check($includeChildren) || !$this->onBeforeSave()) { + return false; + } + + try { + // Get storage. + $storage = $this->getStorage(); + $key = $this->getStorageKey(); + $exists = $this->getStorage()->exists($key); + + // Get data to be saved. + $data = $this->prepareSave(); + + // Save the object. + $id = $storage->save($key, $data); + } catch (\Exception $e) { + return false; + } + + if (!$id) { + return false; + } + + // If item was created, load the object (making sure it has been properly initialized). + if (!$exists) { + $this->load($id, false); + } + + if ($includeChildren) { + $this->saveChildren(); + } + + $this->onAfterSave(); + + return true; + } + + /** + * Method to delete the object from the database. + * + * @param bool $includeChildren + * @return bool True on success. + */ + public function delete($includeChildren = false) + { + if ($this->readonly || !$this->isSaved() || !$this->onBeforeDelete()) { + return false; + } + + if ($includeChildren) { + $this->deleteChildren(); + } + + // Get storage. + $storage = $this->getStorage(); + + if (!$storage->delete($this->getStorageKey())) { + return false; + } + + $this->onAfterDelete(); + + return true; + } + + /** + * Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage. + * + * Child classes should override this method to make sure the data they are storing in the storage is safe and as + * expected before saving the object. + * + * @return bool True if the instance is sane and able to be stored in the storage. + */ + public function check($includeChildren = false) + { + $result = true; + + if ($includeChildren) { + foreach ($this as $field => $value) { + if (is_object($value) && method_exists($value, 'check')) { + $result = $result && $value->check(); + } + } + } + + return $result; + } + + // Internal functions + + /** + * @return bool + */ + protected function onBeforeSave() + { + return true; + } + + protected function onAfterSave() + { + } + + /** + * @return bool + */ + protected function onBeforeDelete() + { + return true; + } + + protected function onAfterDelete() + { + } + + protected function saveChildren() + { + foreach ($this as $field => $value) { + if (is_object($value) && method_exists($value, 'save')) { + $value->save(true); + } + } + } + + protected function deleteChildren() + { + foreach ($this as $field => $value) { + if (is_object($value) && method_exists($value, 'delete')) { + $value->delete(true); + } + } + } + + protected function prepareSave(array $data = null) + { + if ($data === null) { + $data = $this->toArray(); + } + + foreach ($data as $field => $value) { + if (is_object($value) && method_exists($value, 'save')) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * Method to get the storage key for the object. + * + * @param array + * @return string + */ + abstract public function getStorageKey(array $keys = []); + + public function getInstanceId(array $keys = []) + { + return $this->getStorageKey($keys); + } + + //abstract public function setStorageKey(array $keys = []); + + /** + * @return StorageInterface + */ + protected function getStorage() + { + return static::$storage; + } +} From c1ee217cef6a2066b5cc6a1cc6dd7c9457173c30 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 7 Mar 2017 12:06:26 +0200 Subject: [PATCH 10/32] Update Object classes --- .../src/Grav/Common/Object/AbstractObject.php | 55 ++++++++----------- .../Grav/Common/Object/ObjectInterface.php | 7 --- .../Grav/Common/Object/ObjectStorageTrait.php | 33 ++++++----- 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php index c4ef3487b..a2b07e848 100644 --- a/system/src/Grav/Common/Object/AbstractObject.php +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -198,18 +198,6 @@ abstract class AbstractObject implements ObjectInterface } } - /** - * Class constructor, overridden in descendant classes. - * - * @param string|array $identifier Identifier. - */ - public function __construct($identifier = null) - { - if ($identifier) { - $this->load($identifier); - } - } - /** * Override this function if you need to initialize object right after creating it. * @@ -283,11 +271,11 @@ abstract class AbstractObject implements ObjectInterface $storage = $this->getStorage(); // Load the object based on the keys. - $this->items = $storage->load($key); - $this->exists = !empty($this->items); + $items = $storage->load($key); + $this->exists = !empty($items); - // Append the keys and defaults if they were not set by load(). - $this->items += $keys + static::$defaults; + // Append the defaults and keys if they were not set by load(). + $this->items = array_merge(static::$defaults, $keys, $this->items, $items); $this->initialize(); @@ -315,22 +303,18 @@ abstract class AbstractObject implements ObjectInterface return false; } - try { - // Get storage. - $storage = $this->getStorage(); - $key = $this->getStorageKey(); + // Get storage. + $storage = $this->getStorage(); + $key = $this->getStorageKey(); - // Get data to be saved. - $data = $this->prepareSave($this->toArray()); + // Get data to be saved. + $data = $this->prepareSave($this->toArray()); - // Save the object. - $id = $storage->save($key, $data); - } catch (\Exception $e) { - return false; - } + // Save the object. + $id = $storage->save($key, $data); if (!$id) { - return false; + throw new \LogicException('No id specified'); } // If item was created, load the object. @@ -449,7 +433,7 @@ abstract class AbstractObject implements ObjectInterface protected function saveChildren() { - foreach ($this->items as $field => $value) { + foreach ($this->toArray() as $field => $value) { if (is_object($value) && method_exists($value, 'save')) { $value->save(true); } @@ -458,7 +442,7 @@ abstract class AbstractObject implements ObjectInterface protected function deleteChildren() { - foreach ($this->items as $field => $value) { + foreach ($this->toArray() as $field => $value) { if (is_object($value) && method_exists($value, 'delete')) { $value->delete(true); } @@ -487,8 +471,17 @@ abstract class AbstractObject implements ObjectInterface /** * @return StorageInterface */ - static protected function getStorage() + protected static function getStorage() { + if (!static::$storage) { + static::loadStorage(); + } + return static::$storage; } + + protected static function loadStorage() + { + throw new \RuntimeException('Storage has not been set.'); + } } diff --git a/system/src/Grav/Common/Object/ObjectInterface.php b/system/src/Grav/Common/Object/ObjectInterface.php index 971ee1f57..5d8c82a2e 100644 --- a/system/src/Grav/Common/Object/ObjectInterface.php +++ b/system/src/Grav/Common/Object/ObjectInterface.php @@ -30,13 +30,6 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable */ static public function freeInstances($ids = null); - /** - * Class constructor, overridden in descendant classes. - * - * @param string|array $identifier Identifier. - */ - public function __construct($identifier = null); - /** * Override this function if you need to initialize object right after creating it. * diff --git a/system/src/Grav/Common/Object/ObjectStorageTrait.php b/system/src/Grav/Common/Object/ObjectStorageTrait.php index 9d94de5c3..d005d62bb 100644 --- a/system/src/Grav/Common/Object/ObjectStorageTrait.php +++ b/system/src/Grav/Common/Object/ObjectStorageTrait.php @@ -192,23 +192,19 @@ trait ObjectStorageTrait return false; } - try { - // Get storage. - $storage = $this->getStorage(); - $key = $this->getStorageKey(); - $exists = $this->getStorage()->exists($key); + // Get storage. + $storage = $this->getStorage(); + $key = $this->getStorageKey(); + $exists = $this->getStorage()->exists($key); - // Get data to be saved. - $data = $this->prepareSave(); + // Get data to be saved. + $data = $this->prepareSave(); - // Save the object. - $id = $storage->save($key, $data); - } catch (\Exception $e) { - return false; - } + // Save the object. + $id = $storage->save($key, $data); if (!$id) { - return false; + throw new \LogicException('No id specified'); } // If item was created, load the object (making sure it has been properly initialized). @@ -353,8 +349,17 @@ trait ObjectStorageTrait /** * @return StorageInterface */ - protected function getStorage() + protected static function getStorage() { + if (!static::$storage) { + static::loadStorage(); + } + return static::$storage; } + + protected static function loadStorage() + { + throw new \RuntimeException('Storage has not been set.'); + } } From 703080a3293f9dca737e2bcb947ced5dc3d22226 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 7 Mar 2017 15:45:15 +0200 Subject: [PATCH 11/32] Start using StorageTrait for AbstractObject and AbstractObjectCollection --- .../src/Grav/Common/Object/AbstractObject.php | 426 +++--------------- .../Object/AbstractObjectCollection.php | 23 + .../Grav/Common/Object/ObjectCollection.php | 11 - .../Grav/Common/Object/ObjectInterface.php | 8 +- .../Grav/Common/Object/ObjectStorageTrait.php | 47 +- 5 files changed, 130 insertions(+), 385 deletions(-) create mode 100644 system/src/Grav/Common/Object/AbstractObjectCollection.php delete mode 100644 system/src/Grav/Common/Object/ObjectCollection.php diff --git a/system/src/Grav/Common/Object/AbstractObject.php b/system/src/Grav/Common/Object/AbstractObject.php index a2b07e848..9a462afbb 100644 --- a/system/src/Grav/Common/Object/AbstractObject.php +++ b/system/src/Grav/Common/Object/AbstractObject.php @@ -1,7 +1,6 @@ null + ]; + /** * Default properties for the object. * @var array */ - static protected $defaults = ['id' => null]; + static protected $defaults = []; /** * @var string */ - static protected $collectionClass = 'Grav\\Common\\Object\\ObjectCollection'; + static protected $collectionClass = 'Grav\\Common\\Object\\AbstractObjectCollection'; /** * Properties of the object. @@ -49,32 +47,6 @@ abstract class AbstractObject implements ObjectInterface */ protected $items; - /** - * Does object exist in storage? - * @var boolean - */ - protected $exists = false; - - /** - * Readonly object. - * @var bool - */ - protected $readonly = false; - - /** - * @var bool - */ - protected $initialized = false; - - - /** - * @param StorageInterface $storage - */ - static public function setStorage(StorageInterface $storage) - { - static::$storage = $storage; - } - /** * @param ObjectFinderInterface $finder */ @@ -83,48 +55,10 @@ abstract class AbstractObject implements ObjectInterface static::$finder = $finder; } - /** - * Returns the global instance to the object. - * - * Note that using array of fields will always make a query, but it's very useful feature if you want to search one - * item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned. - * - * @param string|array $keys An optional primary key value to load the object by, or an array of fields to match. - * @param boolean $reload Force object to reload. - * - * @return Object - */ - static public function instance($keys = null, $reload = false) - { - // If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object. - if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) { - $c = get_called_class(); - - /** @var ObjectStorageTrait $instance */ - $instance = new $c(); - $instance->load($keys); - - /** @var Object $instance */ - if (!$instance->exists()) return $instance; - - // Instance exists: make sure that we return the global instance. - $keys = $instance->id; - } - - // Return global instance from the identifier. - $instance = static::$instances[$keys]; - - if ($reload) { - $instance->load(); - } - - return $instance; - } - /** * @param array $ids List of primary Ids or null to return everything that has been loaded. * @param bool $readonly - * @return ObjectCollection + * @return AbstractObjectCollection */ static public function instances(array $ids = null, $readonly = true) { @@ -154,11 +88,10 @@ abstract class AbstractObject implements ObjectInterface foreach ($list as $keys) { /** @var static $instance */ $instance = new $c(); - $instance->set($keys); - $instance->exists(true); - $instance->initialize(); - $id = $instance->id; + $instance->doLoad($keys); + $id = $instance->getId(); if ($id && !isset(static::$instances[$id])) { + $instance->initialize(); static::$instances[$id] = $instance; } } @@ -173,6 +106,14 @@ abstract class AbstractObject implements ObjectInterface return new $collectionClass($results); } + /** + * @return string + */ + public function getId() + { + return implode('-', $this->getKeys()); + } + /** * @return ObjectFinderInterface */ @@ -181,186 +122,6 @@ abstract class AbstractObject implements ObjectInterface return static::$finder; } - /** - * Removes all or selected instances from the object cache. - * - * @param null|string|array $ids An optional primary key or list of keys. - */ - static public function freeInstances($ids = null) - { - if ($ids === null) { - $ids = array_keys(static::$instances); - } - $ids = (array) $ids; - - foreach ($ids as $id) { - unset(static::$instances[$id]); - } - } - - /** - * Override this function if you need to initialize object right after creating it. - * - * Can be used for example if the fields need to be converted from json strings to array. - * - * @return bool True if initialization was done, false if object was already initialized. - */ - public function initialize() - { - $initialized = $this->initialized; - $this->initialized = true; - - return !$initialized; - } - - /** - * Convert instance to a read only object. - * - * @return $this - */ - public function readonly() - { - $this->readonly = true; - - return $this; - } - - /** - * Returns true if the object exists. - * - * @param boolean $exists Internal parameter to change state. - * - * @return boolean True if object exists. - */ - public function exists($exists = null) - { - $return = $this->exists; - if ($exists !== null) { - $this->exists = (bool) $exists; - } - - return $return; - } - - /** - * Method to load object from the storage. - * - * @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not - * set the instance key value is used. - * @param bool $getKey Internal parameter, please do not use. - * - * @return bool True on success, false if the object doesn't exist. - */ - public function load($keys = null, $getKey = true) - { - if ($getKey) { - if (is_scalar($keys)) { - $keys = ['id' => (string) $keys]; - } - - // Fetch internal key. - $key = $this->getStorageKey($keys); - - } else { - // Internal key was passed. - $key = $keys; - $keys = []; - } - - // Get storage. - $storage = $this->getStorage(); - - // Load the object based on the keys. - $items = $storage->load($key); - $this->exists = !empty($items); - - // Append the defaults and keys if they were not set by load(). - $this->items = array_merge(static::$defaults, $keys, $this->items, $items); - - $this->initialize(); - - if ($this->id) { - if (!isset(static::$instances[$this->id])) { - static::$instances[$this->id] = $this; - } - } - - return $this->exists; - } - - /** - * Method to save the object to the storage. - * - * Before saving the object, this method checks if object can be safely saved. - * - * @params bool $includeChildren - * @return bool True on success. - */ - public function save($includeChildren = false) - { - // Check the object. - if ($this->readonly || !$this->check($includeChildren) || !$this->onBeforeSave()) { - return false; - } - - // Get storage. - $storage = $this->getStorage(); - $key = $this->getStorageKey(); - - // Get data to be saved. - $data = $this->prepareSave($this->toArray()); - - // Save the object. - $id = $storage->save($key, $data); - - if (!$id) { - throw new \LogicException('No id specified'); - } - - // If item was created, load the object. - if (!$this->exists) { - $this->load($id, false); - } - - if ($includeChildren) { - $this->saveChildren(); - } - - $this->onAfterSave(); - - return true; - } - - /** - * Method to delete the object from the database. - * - * @param bool $includeChildren - * @return bool True on success. - */ - public function delete($includeChildren = false) - { - if ($this->readonly || !$this->exists || !$this->onBeforeDelete()) { - return false; - } - - if ($includeChildren) { - $this->deleteChildren(); - } - - // Get storage. - $storage = $this->getStorage(); - - if (!$storage->delete($this->getStorageKey())) { - return false; - } - - $this->exists = false; - - $this->onAfterDelete(); - - return true; - } - /** * Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage. * @@ -371,17 +132,46 @@ abstract class AbstractObject implements ObjectInterface */ public function check($includeChildren = false) { - $result = true; + return $this->checkKeys() && $this->traitCheck($includeChildren); + } - if ($includeChildren) { - foreach ($this->items as $field => $value) { - if (is_object($value) && method_exists($value, 'check')) { - $result = $result && $value->check(); + /** + * @param array $keys + * @return array + */ + public function getKeys(array $keys = []) + { + foreach (static::$primaryKey as $key => $value) { + if (!isset($keys[$key])) { + if (isset($this->items[$key])) { + $keys[$key] = $this->items[$key]; + } else { + $keys[$key] = $value; } + } } - return $result && !empty($this->items['id']); + return $keys; + } + + /** + * @param array $keys + * @return bool + */ + public function checkKeys(array $keys = []) + { + if (!$keys) { + $keys = $this->getKeys(); + } + + foreach ($keys as $key => $value) { + if ($value === null) { + return false; + } + } + + return true; } /** @@ -394,94 +184,24 @@ abstract class AbstractObject implements ObjectInterface return $this->toArray(); } + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return __CLASS__ . '@' . spl_object_hash($this); + } + // Internal functions /** * @param array $items - * @return $this + * @param array $keys */ - protected function set(array $items) + protected function doLoad(array $items, array $keys = []) { - $this->items = $items; - - return $this; - } - - /** - * @return bool - */ - protected function onBeforeSave() - { - return true; - } - - protected function onAfterSave() - { - } - - /** - * @return bool - */ - protected function onBeforeDelete() - { - return true; - } - - protected function onAfterDelete() - { - } - - protected function saveChildren() - { - foreach ($this->toArray() as $field => $value) { - if (is_object($value) && method_exists($value, 'save')) { - $value->save(true); - } - } - } - - protected function deleteChildren() - { - foreach ($this->toArray() as $field => $value) { - if (is_object($value) && method_exists($value, 'delete')) { - $value->delete(true); - } - } - } - - protected function prepareSave(array $data) - { - foreach ($data as $field => $value) { - if (is_object($value) && method_exists($value, 'save')) { - unset($data[$field]); - } - } - - return $data; - } - - /** - * Method to get the storage key for the object. - * - * @param array - * @return string - */ - abstract public function getStorageKey(array $keys = []); - - /** - * @return StorageInterface - */ - protected static function getStorage() - { - if (!static::$storage) { - static::loadStorage(); - } - - return static::$storage; - } - - protected static function loadStorage() - { - throw new \RuntimeException('Storage has not been set.'); + $this->items = array_replace(static::$defaults, $this->items, $this->getKeys($keys), $items); } } diff --git a/system/src/Grav/Common/Object/AbstractObjectCollection.php b/system/src/Grav/Common/Object/AbstractObjectCollection.php new file mode 100644 index 000000000..4cd61bff3 --- /dev/null +++ b/system/src/Grav/Common/Object/AbstractObjectCollection.php @@ -0,0 +1,23 @@ +id = $id; + } + + public function getId() + { + return $this->id; + } +} diff --git a/system/src/Grav/Common/Object/ObjectCollection.php b/system/src/Grav/Common/Object/ObjectCollection.php deleted file mode 100644 index c6d29176d..000000000 --- a/system/src/Grav/Common/Object/ObjectCollection.php +++ /dev/null @@ -1,11 +0,0 @@ - (string) $keys]; + } + $id = $keys ? static::getInstanceId($keys) : null; + // If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object. - if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) { + if (!$id || !isset(static::$instances[$id])) { $c = get_called_class(); /** @var ObjectStorageTrait|ObjectInterface $instance */ @@ -66,12 +71,12 @@ trait ObjectStorageTrait } // Instance exists in storage: make sure that we return the global instance. - $keys = $instance->getInstanceId(); + $id = $instance->getId(); $reload = false; } // Return global instance from the identifier. - $instance = static::$instances[$keys]; + $instance = static::$instances[$id]; if ($reload) { $instance->load(); @@ -151,7 +156,7 @@ trait ObjectStorageTrait } // Fetch internal key. - $key = $this->getStorageKey($keys); + $key = $keys ? $this->getStorageKey($keys) : null; } else { // Internal key was passed. @@ -159,15 +164,10 @@ trait ObjectStorageTrait $keys = []; } - // Get storage. - $storage = $this->getStorage(); - - // Load the object based on the keys. - $this->items = $storage->load($key); - + $this->doLoad($this->getStorage()->load($key), $keys); $this->initialize(); - $id = $this->getInstanceId(); + $id = $this->getId(); if ($id) { if (!isset(static::$instances[$id])) { static::$instances[$id] = $this; @@ -195,12 +195,12 @@ trait ObjectStorageTrait // Get storage. $storage = $this->getStorage(); $key = $this->getStorageKey(); - $exists = $this->getStorage()->exists($key); // Get data to be saved. $data = $this->prepareSave(); // Save the object. + $exists = $storage->exists($key); $id = $storage->save($key, $data); if (!$id) { @@ -262,7 +262,7 @@ trait ObjectStorageTrait $result = true; if ($includeChildren) { - foreach ($this as $field => $value) { + foreach ($this->toArray() as $field => $value) { if (is_object($value) && method_exists($value, 'check')) { $result = $result && $value->check(); } @@ -274,6 +274,8 @@ trait ObjectStorageTrait // Internal functions + abstract protected function doLoad(array $items, array $keys = []); + /** * @return bool */ @@ -300,7 +302,7 @@ trait ObjectStorageTrait protected function saveChildren() { - foreach ($this as $field => $value) { + foreach ($this->toArray() as $field => $value) { if (is_object($value) && method_exists($value, 'save')) { $value->save(true); } @@ -309,7 +311,7 @@ trait ObjectStorageTrait protected function deleteChildren() { - foreach ($this as $field => $value) { + foreach ($this->toArray() as $field => $value) { if (is_object($value) && method_exists($value, 'delete')) { $value->delete(true); } @@ -339,11 +341,24 @@ trait ObjectStorageTrait */ abstract public function getStorageKey(array $keys = []); - public function getInstanceId(array $keys = []) + /** + * @param array $keys + * @return string + */ + public function getInstanceId(array $keys) { return $this->getStorageKey($keys); } + /** + * @return string + */ + public function getId() + { + return $this->getStorageKey(); + } + + //abstract public function setStorageKey(array $keys = []); /** From ebb8786cd92e9b26357a30c9b6dbdb72732bfe3e Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Wed, 8 Mar 2017 15:34:54 -0700 Subject: [PATCH 12/32] Fixed `Page::expires(0)` that was not getting picked up --- CHANGELOG.md | 1 + system/src/Grav/Common/Page/Page.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4feb2da40..e75f40782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ 1. [](#bugfix) * Fixed a minor bug in Number validation [#1329](https://github.com/getgrav/grav/issues/1329) * Fixed exception when trying to find user account and there is no `user://accounts` folder + * Fixed issue when setting `Page::expires(0)` [Admin #1009](https://github.com/getgrav/grav-plugin-admin/issues/1009) # v1.1.17 ## 02/17/2017 diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 010c906e1..c1bd10606 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -1208,7 +1208,7 @@ class Page $this->expires = $var; } - return empty($this->expires) ? Grav::instance()['config']->get('system.pages.expires') : $this->expires; + return !isset($this->expires) ? Grav::instance()['config']->get('system.pages.expires') : $this->expires; } /** From 3897506c7fac229d9c277d77193dcabbf2088e77 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 17 Mar 2017 11:18:50 +0200 Subject: [PATCH 13/32] Add ObjectCollectionTrait::group() function --- .../src/Grav/Common/Collection/Collection.php | 1 - .../Common/Collection/ObjectCollectionTrait.php | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Collection/Collection.php b/system/src/Grav/Common/Collection/Collection.php index 62b9907dd..5ee2198f0 100644 --- a/system/src/Grav/Common/Collection/Collection.php +++ b/system/src/Grav/Common/Collection/Collection.php @@ -37,5 +37,4 @@ class Collection extends ArrayCollection implements CollectionInterface { return $this->toArray(); } - } diff --git a/system/src/Grav/Common/Collection/ObjectCollectionTrait.php b/system/src/Grav/Common/Collection/ObjectCollectionTrait.php index 4e1046cb7..a05b92dc9 100644 --- a/system/src/Grav/Common/Collection/ObjectCollectionTrait.php +++ b/system/src/Grav/Common/Collection/ObjectCollectionTrait.php @@ -59,4 +59,21 @@ trait ObjectCollectionTrait return $list; } + + + /** + * Group items in the collection by a field. + * + * @param string $property + * @return array + */ + public function group($property) + { + $list = []; + foreach ($this as $element) { + $list[$element->{$property}][] = $element; + } + + return $list; + } } From 22effeac4208fa3b3adda4a41af9c8ea6c00da80 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 13 Apr 2017 12:04:14 +0300 Subject: [PATCH 14/32] Move new Collection/Object classes into Grav\Framework --- .../Object/AbstractObjectCollection.php | 23 ------------- .../Collection/Collection.php | 14 +++++++- .../Collection/CollectionInterface.php | 14 +++++++- .../Object/AbstractObject.php | 12 +++++-- .../Object/AbstractObjectCollection.php | 32 +++++++++++++++++++ .../Object/AbstractObjectFinder.php | 15 +++++++-- .../Object}/ObjectCollectionInterface.php | 15 ++++++++- .../Object}/ObjectCollectionTrait.php | 19 ++++++++++- .../Object/ObjectFinderInterface.php | 13 +++++++- .../Object/ObjectInterface.php | 13 +++++++- .../Object/ObjectStorageTrait.php | 17 ++++++---- .../Object/Storage/FilesystemStorage.php | 13 +++++++- .../Object/Storage/StorageInterface.php | 13 +++++++- 13 files changed, 172 insertions(+), 41 deletions(-) delete mode 100644 system/src/Grav/Common/Object/AbstractObjectCollection.php rename system/src/Grav/{Common => Framework}/Collection/Collection.php (70%) rename system/src/Grav/{Common => Framework}/Collection/CollectionInterface.php (52%) rename system/src/Grav/{Common => Framework}/Object/AbstractObject.php (93%) create mode 100644 system/src/Grav/Framework/Object/AbstractObjectCollection.php rename system/src/Grav/{Common => Framework}/Object/AbstractObjectFinder.php (95%) rename system/src/Grav/{Common/Collection => Framework/Object}/ObjectCollectionInterface.php (71%) rename system/src/Grav/{Common/Collection => Framework/Object}/ObjectCollectionTrait.php (81%) rename system/src/Grav/{Common => Framework}/Object/ObjectFinderInterface.php (84%) rename system/src/Grav/{Common => Framework}/Object/ObjectInterface.php (91%) rename system/src/Grav/{Common => Framework}/Object/ObjectStorageTrait.php (96%) rename system/src/Grav/{Common => Framework}/Object/Storage/FilesystemStorage.php (90%) rename system/src/Grav/{Common => Framework}/Object/Storage/StorageInterface.php (73%) diff --git a/system/src/Grav/Common/Object/AbstractObjectCollection.php b/system/src/Grav/Common/Object/AbstractObjectCollection.php deleted file mode 100644 index 4cd61bff3..000000000 --- a/system/src/Grav/Common/Object/AbstractObjectCollection.php +++ /dev/null @@ -1,23 +0,0 @@ -id = $id; - } - - public function getId() - { - return $this->id; - } -} diff --git a/system/src/Grav/Common/Collection/Collection.php b/system/src/Grav/Framework/Collection/Collection.php similarity index 70% rename from system/src/Grav/Common/Collection/Collection.php rename to system/src/Grav/Framework/Collection/Collection.php index 5ee2198f0..ec23e60bc 100644 --- a/system/src/Grav/Common/Collection/Collection.php +++ b/system/src/Grav/Framework/Collection/Collection.php @@ -1,8 +1,20 @@ id = $id; + } + + public function getId() + { + return $this->id; + } +} diff --git a/system/src/Grav/Common/Object/AbstractObjectFinder.php b/system/src/Grav/Framework/Object/AbstractObjectFinder.php similarity index 95% rename from system/src/Grav/Common/Object/AbstractObjectFinder.php rename to system/src/Grav/Framework/Object/AbstractObjectFinder.php index 9c93f12c0..fc638d0d1 100644 --- a/system/src/Grav/Common/Object/AbstractObjectFinder.php +++ b/system/src/Grav/Framework/Object/AbstractObjectFinder.php @@ -1,8 +1,19 @@ getStorageKey(); } - - //abstract public function setStorageKey(array $keys = []); - /** * @return StorageInterface */ diff --git a/system/src/Grav/Common/Object/Storage/FilesystemStorage.php b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php similarity index 90% rename from system/src/Grav/Common/Object/Storage/FilesystemStorage.php rename to system/src/Grav/Framework/Object/Storage/FilesystemStorage.php index a9ee5495c..196be8f7c 100644 --- a/system/src/Grav/Common/Object/Storage/FilesystemStorage.php +++ b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php @@ -1,10 +1,21 @@ Date: Thu, 13 Apr 2017 12:04:31 +0300 Subject: [PATCH 15/32] Add new ContentBlock classes --- .../Framework/ContentBlock/ContentBlock.php | 229 +++++++++++ .../ContentBlock/ContentBlockInterface.php | 75 ++++ .../Grav/Framework/ContentBlock/HtmlBlock.php | 374 ++++++++++++++++++ .../ContentBlock/HtmlBlockInterface.php | 94 +++++ 4 files changed, 772 insertions(+) create mode 100644 system/src/Grav/Framework/ContentBlock/ContentBlock.php create mode 100644 system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php create mode 100644 system/src/Grav/Framework/ContentBlock/HtmlBlock.php create mode 100644 system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlock.php b/system/src/Grav/Framework/ContentBlock/ContentBlock.php new file mode 100644 index 000000000..c446c3a1b --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/ContentBlock.php @@ -0,0 +1,229 @@ +setContent('my inner content'); + * $outerBlock = ContentBlock::create(); + * $outerBlock->setContent(sprintf('Inside my outer block I have %s.', $innerBlock->getToken())); + * $outerBlock->addBlock($innerBlock); + * echo $outerBlock; + * + * @package Grav\Framework\ContentBlock + */ +class ContentBlock implements ContentBlockInterface +{ + protected $version = 1; + protected $id; + protected $tokenTemplate = '@@BLOCK-%s@@'; + protected $content = ''; + protected $blocks = []; + + /** + * @param string $id + * @return static + */ + public static function create($id = null) + { + return new static($id); + } + + /** + * @param array $serialized + * @return ContentBlockInterface + */ + public static function fromArray(array $serialized) + { + try { + $type = isset($serialized['_type']) ? $serialized['_type'] : null; + $id = isset($serialized['id']) ? $serialized['id'] : null; + + if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) { + throw new \RuntimeException('Bad data'); + } + + /** @var ContentBlockInterface $instance */ + $instance = new $type($id); + $instance->build($serialized); + } catch (\Exception $e) { + throw new \RuntimeException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e); + } + + return $instance; + } + + /** + * Block constructor. + * + * @param string $id + */ + public function __construct($id = null) + { + $this->id = $id ? (string) $id : $this->generateId(); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getToken() + { + return sprintf($this->tokenTemplate, $this->getId()); + } + + /** + * @return array + */ + public function toArray() + { + $blocks = []; + /** + * @var string $id + * @var ContentBlockInterface $block + */ + foreach ($this->blocks as $block) { + $blocks[$block->getId()] = $block->toArray(); + } + + $array = [ + '_type' => get_class($this), + '_version' => $this->version, + 'id' => $this->id, + ]; + + if ($this->content) { + $array['content'] = $this->content; + } + + if ($blocks) { + $array['blocks'] = $blocks; + } + + return $array; + } + + /** + * @return string + */ + public function toString() + { + if (!$this->blocks) { + return (string) $this->content; + } + + $tokens = []; + $replacements = []; + foreach ($this->blocks as $block) { + $tokens[] = $block->getToken(); + $replacements[] = $block->toString(); + } + + return str_replace($tokens, $replacements, (string) $this->content); + } + + /** + * @return string + */ + public function __toString() + { + try { + return $this->toString(); + } catch (\Exception $e) { + return sprintf('Error while rendering block: %s', $e->getMessage()); + } + } + + /** + * @param array $serialized + */ + public function build(array $serialized) + { + $this->checkVersion($serialized); + + $this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId(); + + if (isset($serialized['content'])) { + $this->setContent($serialized['content']); + } + + $blocks = isset($serialized['blocks']) ? (array) $serialized['blocks'] : []; + foreach ($blocks as $block) { + $this->addBlock(self::fromArray($block)); + } + } + + /** + * @param string $content + * @return $this + */ + public function setContent($content) + { + $this->content = $content; + + return $this; + } + + /** + * @param ContentBlockInterface $block + * @return $this + */ + public function addBlock(ContentBlockInterface $block) + { + $this->blocks[$block->getId()] = $block; + + return $this; + } + + /** + * @return string + */ + public function serialize() + { + return serialize($this->toArray()); + } + + /** + * @param string $serialized + */ + public function unserialize($serialized) + { + $array = unserialize($serialized); + $this->build($array); + } + + /** + * @return string + */ + protected function generateId() + { + return uniqid('', true); + } + + /** + * @param array $serialized + * @throws \RuntimeException + */ + protected function checkVersion(array $serialized) + { + $version = isset($serialized['_version']) ? (string) $serialized['_version'] : 1; + if ($version != $this->version) { + throw new \RuntimeException(sprintf('Unsupported version %s', $version)); + } + } +} \ No newline at end of file diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php new file mode 100644 index 000000000..9412ef82f --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php @@ -0,0 +1,75 @@ +getAssetsFast(); + + $this->sortAssets($assets['styles']); + $this->sortAssets($assets['scripts']); + $this->sortAssets($assets['html']); + + return $assets; + } + + /** + * @return array + */ + public function getFrameworks() + { + $assets = $this->getAssetsFast(); + + return array_keys($assets['frameworks']); + } + + /** + * @param string $location + * @return array + */ + public function getStyles($location = 'head') + { + return $this->getAssetsInLocation('styles', $location); + } + + /** + * @param string $location + * @return array + */ + public function getScripts($location = 'head') + { + return $this->getAssetsInLocation('scripts', $location); + } + + /** + * @param string $location + * @return array + */ + public function getHtml($location = 'bottom') + { + return $this->getAssetsInLocation('html', $location); + } + + /** + * @return array + */ + public function toArray() + { + $array = parent::toArray(); + + if ($this->frameworks) { + $array['frameworks'] = $this->frameworks; + } + if ($this->styles) { + $array['styles'] = $this->styles; + } + if ($this->scripts) { + $array['scripts'] = $this->scripts; + } + if ($this->html) { + $array['html'] = $this->html; + } + + return $array; + } + + /** + * @param array $serialized + */ + public function build(array $serialized) + { + parent::build($serialized); + + $this->frameworks = isset($serialized['frameworks']) ? (array) $serialized['frameworks'] : []; + $this->styles = isset($serialized['styles']) ? (array) $serialized['styles'] : []; + $this->scripts = isset($serialized['scripts']) ? (array) $serialized['scripts'] : []; + $this->html = isset($serialized['html']) ? (array) $serialized['html'] : []; + } + + /** + * @param string $framework + * @return $this + */ + public function addFramework($framework) + { + $this->frameworks[$framework] = 1; + + return $this; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + * + * @example $block->addStyle('assets/js/my.js'); + * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']); + */ + public function addStyle($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['href' => (string) $element]; + } + if (empty($element['href'])) { + return false; + } + if (!isset($this->styles[$location])) { + $this->styles[$location] = []; + } + + $id = !empty($element['id']) ? ['id' => (string) $element['id']] : []; + $href = $element['href']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/css'; + $media = !empty($element['media']) ? (string) $element['media'] : null; + unset($element['tag'], $element['id'], $element['rel'], $element['content'], $element['href'], $element['type'], $element['media']); + + $this->styles[$location][md5($href) . sha1($href)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'href' => $href, + 'type' => $type, + 'media' => $media, + 'element' => $element + ] + $id; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineStyle($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + if (empty($element['content'])) { + return false; + } + if (!isset($this->styles[$location])) { + $this->styles[$location] = []; + } + + $content = (string) $element['content']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/css'; + + $this->styles[$location][md5($content) . sha1($content)] = [ + ':type' => 'inline', + ':priority' => (int) $priority, + 'content' => $content, + 'type' => $type + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addScript($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['src' => (string) $element]; + } + if (empty($element['src'])) { + return false; + } + if (!isset($this->scripts[$location])) { + $this->scripts[$location] = []; + } + + $src = $element['src']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; + $defer = isset($element['defer']) ? true : false; + $async = isset($element['async']) ? true : false; + $handle = !empty($element['handle']) ? (string) $element['handle'] : ''; + + $this->scripts[$location][md5($src) . sha1($src)] = [ + ':type' => 'file', + ':priority' => (int) $priority, + 'src' => $src, + 'type' => $type, + 'defer' => $defer, + 'async' => $async, + 'handle' => $handle + ]; + + return true; + } + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineScript($element, $priority = 0, $location = 'head') + { + if (!is_array($element)) { + $element = ['content' => (string) $element]; + } + if (empty($element['content'])) { + return false; + } + if (!isset($this->scripts[$location])) { + $this->scripts[$location] = []; + } + + $content = (string) $element['content']; + $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript'; + + $this->scripts[$location][md5($content) . sha1($content)] = [ + ':type' => 'inline', + ':priority' => (int) $priority, + 'content' => $content, + 'type' => $type + ]; + + return true; + } + + /** + * @param string $html + * @param int $priority + * @param string $location + * @return bool + */ + public function addHtml($html, $priority = 0, $location = 'bottom') + { + if (empty($html) || !is_string($html)) { + return false; + } + if (!isset($this->html[$location])) { + $this->html[$location] = []; + } + + $this->html[$location][md5($html) . sha1($html)] = [ + ':priority' => (int) $priority, + 'html' => $html + ]; + + return true; + } + + /** + * @return array + */ + protected function getAssetsFast() + { + $assets = [ + 'frameworks' => $this->frameworks, + 'styles' => $this->styles, + 'scripts' => $this->scripts, + 'html' => $this->html + ]; + + foreach ($this->blocks as $block) { + if ($block instanceof HtmlBlock) { + $blockAssets = $block->getAssetsFast(); + $assets['frameworks'] += $blockAssets['frameworks']; + + foreach ($blockAssets['styles'] as $location => $styles) { + if (!isset($assets['styles'][$location])) { + $assets['styles'][$location] = $styles; + } elseif ($styles) { + $assets['styles'][$location] += $styles; + } + } + + foreach ($blockAssets['scripts'] as $location => $scripts) { + if (!isset($assets['scripts'][$location])) { + $assets['scripts'][$location] = $scripts; + } elseif ($scripts) { + $assets['scripts'][$location] += $scripts; + } + } + + foreach ($blockAssets['html'] as $location => $htmls) { + if (!isset($assets['html'][$location])) { + $assets['html'][$location] = $htmls; + } elseif ($htmls) { + $assets['html'][$location] += $htmls; + } + } + } + } + + return $assets; + } + + /** + * @param string $type + * @param string $location + * @return array + */ + protected function getAssetsInLocation($type, $location) + { + $assets = $this->getAssetsFast(); + + if (empty($assets[$type][$location])) { + return []; + } + + $styles = $assets[$type][$location]; + $this->sortAssetsInLocation($styles); + + return $styles; + } + + /** + * @param array $items + */ + protected function sortAssetsInLocation(array &$items) + { + $count = 0; + foreach ($items as &$item) { + $item[':order'] = ++$count; + } + uasort( + $items, + function ($a, $b) { + return ($a[':priority'] == $b[':priority']) ? $a[':order'] - $b[':order'] : $a[':priority'] - $b[':priority']; + } + ); + } + + /** + * @param array $array + */ + protected function sortAssets(array &$array) + { + foreach ($array as $location => &$items) { + $this->sortAssetsInLocation($items); + } + } +} \ No newline at end of file diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php new file mode 100644 index 000000000..08e4caad3 --- /dev/null +++ b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php @@ -0,0 +1,94 @@ +addStyle('assets/js/my.js'); + * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']); + */ + public function addStyle($element, $priority = 0, $location = 'head'); + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineStyle($element, $priority = 0, $location = 'head'); + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addScript($element, $priority = 0, $location = 'head'); + + + /** + * @param string|array $element + * @param int $priority + * @param string $location + * @return bool + */ + public function addInlineScript($element, $priority = 0, $location = 'head'); + + /** + * @param string $html + * @param int $priority + * @param string $location + * @return bool + */ + public function addHtml($html, $priority = 0, $location = 'bottom'); +} From bc6bf737b92d2f5b790c6f1ad11a88c40392fa12 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 19 Apr 2017 14:30:34 +0300 Subject: [PATCH 16/32] Add Pages::baseUrl(), Pages::homeUrl() and Pages::url() functions and use them --- system/src/Grav/Common/Page/Pages.php | 79 ++++++++++++++++++++++++++- system/src/Grav/Common/Twig/Twig.php | 17 +++--- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index ab8825538..653ce65ab 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -43,7 +43,12 @@ class Pages /** * @var string */ - protected $base; + protected $base = ''; + + /** + * @var array|string[] + */ + protected $baseUrl = []; /** * @var array|string[] @@ -100,7 +105,6 @@ class Pages public function __construct(Grav $c) { $this->grav = $c; - $this->base = ''; } /** @@ -115,11 +119,82 @@ class Pages if ($path !== null) { $path = trim($path, '/'); $this->base = $path ? '/' . $path : null; + $this->baseUrl = []; } return $this->base; } + /** + * + * Get base URL for Grav pages. + * + * @param string $lang Optional language code for multilingual links. + * @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * + * @return string + */ + public function baseUrl($lang = null, $absolute = null) + { + $lang = (string) $lang; + $type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative'); + $key = "{$lang} {$type}"; + + if (!isset($this->baseUrl[$key])) { + /** @var Config $config */ + $config = $this->grav['config']; + + /** @var Language $language */ + $language = $this->grav['language']; + + if (!$lang) { + $lang = $language->getActive(); + } + + $path_append = rtrim($this->grav['pages']->base(), '/'); + if ($language->getDefault() != $lang || $config->get('system.languages.include_default_lang') === true) { + $path_append .= $lang ? '/' . $lang : ''; + } + + $this->baseUrl[$key] = $this->grav[$type] . $path_append; + } + + return $this->baseUrl[$key]; + } + + /** + * + * Get home URL for Grav site. + * + * @param string $lang Optional language code for multilingual links. + * @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * + * @return string + */ + public function homeUrl($lang = null, $absolute = null) + { + return $this->baseUrl($lang, $absolute) ?: '/'; + } + + /** + * + * Get home URL for Grav site. + * + * @param string $route Optional route to the page. + * @param string $lang Optional language code for multilingual links. + * @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default. + * + * @return string + */ + public function url($route = '/', $lang = null, $absolute = null) + { + if ($route === '/') { + return $this->homeUrl($lang, $absolute); + } + + return $this->baseUrl($lang, $absolute) . $route; + } + /** * Class initialization. Must be called before using this class. */ diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php index 7eec5b775..4fe1a492f 100644 --- a/system/src/Grav/Common/Twig/Twig.php +++ b/system/src/Grav/Common/Twig/Twig.php @@ -13,6 +13,7 @@ use Grav\Common\Config\Config; use Grav\Common\Language\Language; use Grav\Common\Language\LanguageCodes; use Grav\Common\Page\Page; +use Grav\Common\Page\Pages; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use RocketTheme\Toolbox\Event\Event; @@ -84,11 +85,6 @@ class Twig $active_language = $language->getActive(); - $path_append = rtrim($this->grav['pages']->base(), '/'); - if ($language->getDefault() != $active_language || $config->get('system.languages.include_default_lang') === true) { - $path_append .= $active_language ? '/' . $active_language : ''; - } - // handle language templates if available if ($language->enabled()) { $lang_templates = $locator->findResource('theme://templates/' . ($active_language ? $active_language : $language->getDefault())); @@ -153,7 +149,8 @@ class Twig $this->grav->fireEvent('onTwigExtensions'); - $base_url = $this->grav['base_url'] . $path_append; + /** @var Pages $pages */ + $pages = $this->grav['pages']; // Set some standard variables for twig $this->twig_vars = $this->twig_vars + [ @@ -166,11 +163,11 @@ class Twig 'taxonomy' => $this->grav['taxonomy'], 'browser' => $this->grav['browser'], 'base_dir' => rtrim(ROOT_DIR, '/'), - 'base_url' => $base_url, + 'home_url' => $pages->homeUrl($active_language), + 'base_url' => $pages->baseUrl($active_language), + 'base_url_absolute' => $pages->baseUrl($active_language, true), + 'base_url_relative' => $pages->baseUrl($active_language, false), 'base_url_simple' => $this->grav['base_url'], - 'base_url_absolute' => $this->grav['base_url_absolute'] . $path_append, - 'base_url_relative' => $this->grav['base_url_relative'] . $path_append, - 'home_url' => $base_url == '' ? '/' : $base_url, 'theme_dir' => $locator->findResource('theme://'), 'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false), 'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'), From 6539931387ec472e9e634567134510930161ed1b Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 20 Apr 2017 15:46:59 +0300 Subject: [PATCH 17/32] Add support for extra context vars in Twig::processSite() --- system/src/Grav/Common/Twig/Twig.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php index 4fe1a492f..bccc77976 100644 --- a/system/src/Grav/Common/Twig/Twig.php +++ b/system/src/Grav/Common/Twig/Twig.php @@ -312,7 +312,7 @@ class Twig * @return string the rendered output * @throws \RuntimeException */ - public function processSite($format = null) + public function processSite($format = null, array $vars = []) { // set the page now its been processed $this->grav->fireEvent('onTwigSiteVariables'); @@ -340,7 +340,7 @@ class Twig $template = $this->template($page->template() . $ext); try { - $output = $this->twig->render($template, $twig_vars); + $output = $this->twig->render($template, $vars + $twig_vars); } catch (\Twig_Error_Loader $e) { $error_msg = $e->getMessage(); // Try html version of this template if initial template was NOT html From aee07203a203863bd738dba78efea245facac792 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 11:22:08 +0300 Subject: [PATCH 18/32] Add support for custom output providers like Slim Framework --- system/src/Grav/Common/Grav.php | 12 +++++++++--- .../Grav/Common/Service/OutputServiceProvider.php | 10 +++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 8a300e4cf..227f9e225 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -127,10 +127,16 @@ class Grav extends Container }); } - // Set the header type - $this->header(); + $output = $this->output; + + // Support for custom output providers like Slim Framework. + if (!$output instanceof \Psr\Http\Message\ResponseInterface) { + // Set the header type + $this->header(); + + echo $output; + } - echo $this->output; $debugger->render(); $this->fireEvent('onOutputRendered'); diff --git a/system/src/Grav/Common/Service/OutputServiceProvider.php b/system/src/Grav/Common/Service/OutputServiceProvider.php index 41e3822a2..0f4567a60 100644 --- a/system/src/Grav/Common/Service/OutputServiceProvider.php +++ b/system/src/Grav/Common/Service/OutputServiceProvider.php @@ -8,6 +8,8 @@ namespace Grav\Common\Service; +use Grav\Common\Page\Page; +use Grav\Common\Twig\Twig; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -16,7 +18,13 @@ class OutputServiceProvider implements ServiceProviderInterface public function register(Container $container) { $container['output'] = function ($c) { - return $c['twig']->processSite($c['page']->templateFormat()); + /** @var Twig $twig */ + $twig = $c['twig']; + + /** @var Page $page */ + $page = $c['page']; + + return $twig->processSite($page->templateFormat()); }; } } From bc9dfe736dc1bd2383b8d7a347d23c18006de4e8 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 12:23:19 +0300 Subject: [PATCH 19/32] Move output handling into the render processor --- system/src/Grav/Common/Grav.php | 17 ++--------------- .../Common/Processors/RenderProcessor.php | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 227f9e225..c2ccbbcb9 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -116,9 +116,6 @@ class Grav extends Container */ public function process() { - /** @var Debugger $debugger */ - $debugger = $this['debugger']; - // process all processors (e.g. config, initialize, assets, ..., render) foreach ($this->processors as $processor) { $processor = $this[$processor]; @@ -127,20 +124,10 @@ class Grav extends Container }); } - $output = $this->output; - - // Support for custom output providers like Slim Framework. - if (!$output instanceof \Psr\Http\Message\ResponseInterface) { - // Set the header type - $this->header(); - - echo $output; - } - + /** @var Debugger $debugger */ + $debugger = $this['debugger']; $debugger->render(); - $this->fireEvent('onOutputRendered'); - register_shutdown_function([$this, 'shutdown']); } diff --git a/system/src/Grav/Common/Processors/RenderProcessor.php b/system/src/Grav/Common/Processors/RenderProcessor.php index b0b99120a..548151fe5 100644 --- a/system/src/Grav/Common/Processors/RenderProcessor.php +++ b/system/src/Grav/Common/Processors/RenderProcessor.php @@ -15,7 +15,22 @@ class RenderProcessor extends ProcessorBase implements ProcessorInterface public function process() { - $this->container->output = $this->container['output']; - $this->container->fireEvent('onOutputGenerated'); + $container = $this->container; + $output = $container['output']; + + if ($output instanceof \Psr\Http\Message\ResponseInterface) { + // Support for custom output providers like Slim Framework. + } else { + // Use internal Grav output. + $container->output = $output; + $container->fireEvent('onOutputGenerated'); + + // Set the header type + $container->header(); + + echo $output; + } + + $container->fireEvent('onOutputRendered'); } } From 28d3866eb0ae7327ef7974d8ae5b0eda535b0719 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 13:26:39 +0300 Subject: [PATCH 20/32] Minor code improvements on processors --- system/src/Grav/Common/Processors/ConfigurationProcessor.php | 2 +- system/src/Grav/Common/Processors/ProcessorBase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Common/Processors/ConfigurationProcessor.php b/system/src/Grav/Common/Processors/ConfigurationProcessor.php index e819f3536..e50e40db7 100644 --- a/system/src/Grav/Common/Processors/ConfigurationProcessor.php +++ b/system/src/Grav/Common/Processors/ConfigurationProcessor.php @@ -16,6 +16,6 @@ class ConfigurationProcessor extends ProcessorBase implements ProcessorInterface public function process() { $this->container['config']->init(); - return $this->container['plugins']->setup(); + $this->container['plugins']->setup(); } } diff --git a/system/src/Grav/Common/Processors/ProcessorBase.php b/system/src/Grav/Common/Processors/ProcessorBase.php index 91ced397f..d8e27f141 100644 --- a/system/src/Grav/Common/Processors/ProcessorBase.php +++ b/system/src/Grav/Common/Processors/ProcessorBase.php @@ -10,7 +10,7 @@ namespace Grav\Common\Processors; use Grav\Common\Grav; -class ProcessorBase +abstract class ProcessorBase { /** * @var Grav From bf42db3af1746c8a4e695c2f9abf87a389036b85 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 13:42:38 +0300 Subject: [PATCH 21/32] Update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e51b27f..0c348894d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ * Allow multiple calls to `Themes::initTheme()` without throwing errors * Fixed querystrings in root pages with multi-lang enabled [#1436](https://github.com/getgrav/grav/issues/1436) +# Feature/Objects Branch +## xx/xx/2017 + +1. [](#new) + * Added `Pages::baseUrl()`, `Pages::homeUrl()` and `Pages::url()` functions + * Added support for custom output providers like Slim Framework + * Added `Grav\Framework\Collection` classes for creating collections + * Added `Grav\Framework\ContentBlock` classes which add better support for nested HTML blocks with assets + * Added `Grav\Framework\Object` classes to support general objects and their collections +1. [](#improved) + * Improve error handling in Folder::move() + * Added extra parameter for Twig::processSite() to include custom context + # v1.2.3 ## 04/19/2017 From 48d5183e0724848f2c5695b45a154faccd6d4a43 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 21:33:35 +0300 Subject: [PATCH 22/32] Added `Debugger::getCaller()` to figure out where the method was called from --- CHANGELOG.md | 1 + system/src/Grav/Common/Debugger.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c348894d..5c9434989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ 1. [](#new) * Added `Pages::baseUrl()`, `Pages::homeUrl()` and `Pages::url()` functions + * Added `Debugger::getCaller()` to figure out where the method was called from * Added support for custom output providers like Slim Framework * Added `Grav\Framework\Collection` classes for creating collections * Added `Grav\Framework\ContentBlock` classes which add better support for nested HTML blocks with assets diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php index 5d78b6451..7165883bc 100644 --- a/system/src/Grav/Common/Debugger.php +++ b/system/src/Grav/Common/Debugger.php @@ -120,6 +120,13 @@ class Debugger return $this; } + public function getCaller($ignore = 2) + { + $trace = debug_backtrace(false, $ignore); + + return array_pop($trace); + } + /** * Adds a data collector * From 7371a1189109abd5510ac5ec051d0abe4107324f Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 21 Apr 2017 21:34:41 +0300 Subject: [PATCH 23/32] Deprecated GravTrait --- CHANGELOG.md | 1 + system/src/Grav/Common/GravTrait.php | 7 +++++++ system/src/Grav/Console/ConsoleTrait.php | 3 --- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9434989..e7d848e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Added `Grav\Framework\Collection` classes for creating collections * Added `Grav\Framework\ContentBlock` classes which add better support for nested HTML blocks with assets * Added `Grav\Framework\Object` classes to support general objects and their collections + * Deprecated GravTrait 1. [](#improved) * Improve error handling in Folder::move() * Added extra parameter for Twig::processSite() to include custom context diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php index 9746d3924..c0d9f2ada 100644 --- a/system/src/Grav/Common/GravTrait.php +++ b/system/src/Grav/Common/GravTrait.php @@ -8,6 +8,9 @@ namespace Grav\Common; +/** + * @deprecated Remove in Grav 2.0 + */ trait GravTrait { protected static $grav; @@ -20,6 +23,10 @@ trait GravTrait if (!self::$grav) { self::$grav = Grav::instance(); } + + $caller = self::$grav['debugger']->getCaller(); + self::$grav['debugger']->addMessage("Deprecated GravTrait used in {$caller['file']}", 'deprecated'); + return self::$grav; } } diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php index 78826d226..be020adfc 100644 --- a/system/src/Grav/Console/ConsoleTrait.php +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -10,7 +10,6 @@ namespace Grav\Console; use Grav\Common\Grav; use Grav\Common\Composer; -use Grav\Common\GravTrait; use Grav\Console\Cli\ClearCacheCommand; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\ArrayInput; @@ -20,8 +19,6 @@ use Symfony\Component\Yaml\Yaml; trait ConsoleTrait { - use GravTrait; - /** * @var */ From 718608cd550e37e152d9353bd37a00259ffb08d8 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 5 May 2017 13:38:29 +0300 Subject: [PATCH 24/32] Improve object classes --- CHANGELOG.md | 2 +- .../Grav/Framework/Object/AbstractObject.php | 2 +- .../Grav/Framework/Object/ObjectInterface.php | 44 +------------- .../Object/Storage/FilesystemStorage.php | 18 +++--- .../Object/Storage/StorageInterface.php | 2 +- .../Object/StoredObjectInterface.php | 57 +++++++++++++++++++ 6 files changed, 73 insertions(+), 52 deletions(-) create mode 100644 system/src/Grav/Framework/Object/StoredObjectInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd44a21b..0286b81a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Feature/Objects Branch -## xx/xx/2017 +## mm/dd/2017 1. [](#new) * Added `Pages::baseUrl()`, `Pages::homeUrl()` and `Pages::url()` functions diff --git a/system/src/Grav/Framework/Object/AbstractObject.php b/system/src/Grav/Framework/Object/AbstractObject.php index 8d3b3d180..097edccd9 100644 --- a/system/src/Grav/Framework/Object/AbstractObject.php +++ b/system/src/Grav/Framework/Object/AbstractObject.php @@ -17,7 +17,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export; * @property string $id * @package Grav\Framework\Object */ -abstract class AbstractObject implements ObjectInterface +abstract class AbstractObject implements StoredObjectInterface { use ObjectStorageTrait { check as traitcheck; diff --git a/system/src/Grav/Framework/Object/ObjectInterface.php b/system/src/Grav/Framework/Object/ObjectInterface.php index ac5e29d39..b4ed09ef3 100644 --- a/system/src/Grav/Framework/Object/ObjectInterface.php +++ b/system/src/Grav/Framework/Object/ObjectInterface.php @@ -20,7 +20,7 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable * Note that using array of fields will always make a query, but it's very useful feature if you want to search one * item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned. * - * @param int|array $keys An optional primary key value to load the object by, or an array of fields to match. + * @param null|int|string|array $keys An optional primary key value to load the object by, or an array of fields to match. * @param boolean $reload Force object to reload. * * @return Object @@ -37,7 +37,7 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable /** * Removes all or selected instances from the object cache. * - * @param null|int|array $ids An optional primary key or list of keys. + * @param null|int|string|array $ids An optional primary key or list of keys. */ static public function freeInstances($ids = null); @@ -50,46 +50,6 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable */ public function initialize(); - /** - * Convert instance to a read only object. - * - * @return $this - */ - public function readonly(); - - /** - * Returns true if the object exists in the storage. - * - * @return boolean True if object exists. - */ - public function isSaved(); - - /** - * Method to load object from the storage. - * - * @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not - * set the instance key value is used. - * - * @return boolean True on success, false if the object doesn't exist. - */ - public function load($keys = null); - - /** - * Method to save the object to the storage. - * - * Before saving the object, this method checks if object can be safely saved. - * - * @return boolean True on success. - */ - public function save(); - - /** - * Method to delete the object from the database. - * - * @return boolean True on success. - */ - public function delete(); - /** * Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage. * diff --git a/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php index 196be8f7c..895c975f2 100644 --- a/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php +++ b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php @@ -26,23 +26,27 @@ class FilesystemStorage implements StorageInterface /** * @var string */ - protected $type; + protected $type = 'Grav\\Common\\File\\CompiledJsonFile'; /** * @var string */ - protected $extension; + protected $extension = '.json'; /** * @param string $path * @param string $extension * @param string $type */ - public function __construct($path, $type = 'Grav\\Common\\File\\CompiledJsonFile', $extension = '.json') + public function __construct($path, $type = null, $extension = null) { $this->path = $path; - $this->type = $type; - $this->extension = $extension; + if ($type) { + $this->type = $type; + } + if ($extension) { + $this->extension = $extension; + } } /** @@ -112,7 +116,7 @@ class FilesystemStorage implements StorageInterface { $results = []; foreach ($list as $id) { - $results[$id] = $this->load(['id' => $id]); + $results[$id] = $this->load($id); } return $results; @@ -147,7 +151,7 @@ class FilesystemStorage implements StorageInterface protected function getFile($key) { if ($key === null) { - throw new \RuntimeException('Id not defined'); + throw new \RuntimeException('Storage key not defined'); } $filename = "{$this->path}/{$key}{$this->extension}"; diff --git a/system/src/Grav/Framework/Object/Storage/StorageInterface.php b/system/src/Grav/Framework/Object/Storage/StorageInterface.php index a05e38740..e6d781eba 100644 --- a/system/src/Grav/Framework/Object/Storage/StorageInterface.php +++ b/system/src/Grav/Framework/Object/Storage/StorageInterface.php @@ -29,7 +29,7 @@ interface StorageInterface /** * @param string $key * @param array $data - * @return mixed + * @return string */ public function save($key, array $data); diff --git a/system/src/Grav/Framework/Object/StoredObjectInterface.php b/system/src/Grav/Framework/Object/StoredObjectInterface.php new file mode 100644 index 000000000..ee436205c --- /dev/null +++ b/system/src/Grav/Framework/Object/StoredObjectInterface.php @@ -0,0 +1,57 @@ + Date: Fri, 5 May 2017 14:26:12 +0300 Subject: [PATCH 25/32] Add storage interface also to object collection --- system/src/Grav/Framework/Object/AbstractObject.php | 2 +- system/src/Grav/Framework/Object/AbstractObjectCollection.php | 2 +- system/src/Grav/Framework/Object/StoredObjectInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/src/Grav/Framework/Object/AbstractObject.php b/system/src/Grav/Framework/Object/AbstractObject.php index 097edccd9..935971b20 100644 --- a/system/src/Grav/Framework/Object/AbstractObject.php +++ b/system/src/Grav/Framework/Object/AbstractObject.php @@ -17,7 +17,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export; * @property string $id * @package Grav\Framework\Object */ -abstract class AbstractObject implements StoredObjectInterface +abstract class AbstractObject implements ObjectInterface, StoredObjectInterface { use ObjectStorageTrait { check as traitcheck; diff --git a/system/src/Grav/Framework/Object/AbstractObjectCollection.php b/system/src/Grav/Framework/Object/AbstractObjectCollection.php index 1878b1b35..485bb03ef 100644 --- a/system/src/Grav/Framework/Object/AbstractObjectCollection.php +++ b/system/src/Grav/Framework/Object/AbstractObjectCollection.php @@ -14,7 +14,7 @@ use Grav\Framework\Collection\Collection; * Abstract Object Collection * @package Grav\Framework\Object */ -abstract class AbstractObjectCollection extends Collection implements ObjectCollectionInterface +abstract class AbstractObjectCollection extends Collection implements ObjectCollectionInterface, StoredObjectInterface { use ObjectCollectionTrait, ObjectStorageTrait; diff --git a/system/src/Grav/Framework/Object/StoredObjectInterface.php b/system/src/Grav/Framework/Object/StoredObjectInterface.php index ee436205c..d17c324bf 100644 --- a/system/src/Grav/Framework/Object/StoredObjectInterface.php +++ b/system/src/Grav/Framework/Object/StoredObjectInterface.php @@ -12,7 +12,7 @@ namespace Grav\Framework\Object; * Stored Object Interface * @package Grav\Framework\Object */ -interface StoredObjectInterface extends ObjectInterface +interface StoredObjectInterface { /** From bd7469d8e08c1897a1ab104ab382f41cc9a80148 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 5 May 2017 15:21:16 +0300 Subject: [PATCH 26/32] Fix StoredObjectInterface::save() to have parameter to include children --- system/src/Grav/Framework/Object/StoredObjectInterface.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Framework/Object/StoredObjectInterface.php b/system/src/Grav/Framework/Object/StoredObjectInterface.php index d17c324bf..33f4153a3 100644 --- a/system/src/Grav/Framework/Object/StoredObjectInterface.php +++ b/system/src/Grav/Framework/Object/StoredObjectInterface.php @@ -44,9 +44,10 @@ interface StoredObjectInterface * * Before saving the object, this method checks if object can be safely saved. * + * @param bool $includeChildren * @return boolean True on success. */ - public function save(); + public function save($includeChildren = false); /** * Method to delete the object from the database. From ad66f0b6373302780561785c26a5cbbe6704b324 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 5 May 2017 15:59:44 +0300 Subject: [PATCH 27/32] Fix saving stored collections --- system/src/Grav/Framework/Object/AbstractObject.php | 8 -------- .../Grav/Framework/Object/AbstractObjectCollection.php | 7 +++++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/system/src/Grav/Framework/Object/AbstractObject.php b/system/src/Grav/Framework/Object/AbstractObject.php index 935971b20..d2150680d 100644 --- a/system/src/Grav/Framework/Object/AbstractObject.php +++ b/system/src/Grav/Framework/Object/AbstractObject.php @@ -114,14 +114,6 @@ abstract class AbstractObject implements ObjectInterface, StoredObjectInterface return new $collectionClass($results); } - /** - * @return string - */ - public function getId() - { - return implode('-', $this->getKeys()); - } - /** * @return ObjectFinderInterface */ diff --git a/system/src/Grav/Framework/Object/AbstractObjectCollection.php b/system/src/Grav/Framework/Object/AbstractObjectCollection.php index 485bb03ef..4af29cbda 100644 --- a/system/src/Grav/Framework/Object/AbstractObjectCollection.php +++ b/system/src/Grav/Framework/Object/AbstractObjectCollection.php @@ -16,7 +16,10 @@ use Grav\Framework\Collection\Collection; */ abstract class AbstractObjectCollection extends Collection implements ObjectCollectionInterface, StoredObjectInterface { - use ObjectCollectionTrait, ObjectStorageTrait; + use ObjectStorageTrait { + getId as getParentId; + } + use ObjectCollectionTrait; protected $id; @@ -27,6 +30,6 @@ abstract class AbstractObjectCollection extends Collection implements ObjectColl public function getId() { - return $this->id; + return $this->id ?: $this->getParentId(); } } From 19ac7f3e3f3fd2f96d0d5e043a94319388dd6647 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 9 May 2017 12:32:07 +0300 Subject: [PATCH 28/32] Add AbstractLazyCollection --- .../Collection/AbstractLazyCollection.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 system/src/Grav/Framework/Collection/AbstractLazyCollection.php diff --git a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php new file mode 100644 index 000000000..ee4c32ec5 --- /dev/null +++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php @@ -0,0 +1,53 @@ +initialize(); + return $this->collection->reverse(); + } + + /** + * {@inheritDoc} + */ + public function shuffle() + { + $this->initialize(); + return $this->collection->shuffle(); + } + + /** + * {@inheritDoc} + */ + public function jsonSerialize() + { + $this->initialize(); + return $this->collection->jsonSerialize(); + } +} From 93c4396bfb1eb3042b5fd1c41f3ec51f0ca8c3fc Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 9 May 2017 20:17:26 +0300 Subject: [PATCH 29/32] Add FileCollection class --- .../Framework/Collection/FileCollection.php | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 system/src/Grav/Framework/Collection/FileCollection.php diff --git a/system/src/Grav/Framework/Collection/FileCollection.php b/system/src/Grav/Framework/Collection/FileCollection.php new file mode 100644 index 000000000..5bac9ae6b --- /dev/null +++ b/system/src/Grav/Framework/Collection/FileCollection.php @@ -0,0 +1,248 @@ +setIterator($path); + $this->flags = (int) ($flags ?: FileCollection::INCLUDE_FILES | FileCollection::INCLUDE_FOLDERS | FileCollection::RECURSIVE); + + $this->setFilter(); + $this->setObjectBuilder(); + $this->setNestingLimit(); + } + + public function setIterator($path) + { + $iteratorFlags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + + \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS; + + if (strpos($path, '://')) { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + $this->iterator = $locator->getRecursiveIterator($path, $iteratorFlags); + } else { + $this->iterator = new \RecursiveDirectoryIterator($path, $iteratorFlags); + } + } + + /** + * @param callable|null $filterFunction + * @return $this + */ + public function setFilter(callable $filterFunction = null) + { + $this->filterFunction = $filterFunction; + + return $this; + } + + /** + * @param callable $filterFunction + * @return $this + */ + public function addFilter(callable $filterFunction) + { + if ($this->filterFunction) { + $oldFilterFunction = $this->filterFunction; + $this->filterFunction = function ($expr) use ($oldFilterFunction, $filterFunction) { + return $oldFilterFunction($expr) && $filterFunction($expr); + }; + } else { + $this->filterFunction = $filterFunction; + } + + return $this; + } + + /** + * @param callable|null $objectFunction + * @return $this + */ + public function setObjectBuilder(callable $objectFunction = null) + { + $this->createObjectFunction = $objectFunction ?: [$this, 'createObject']; + + return $this; + } + + public function setNestingLimit($limit = 99) + { + $this->nestingLimit = (int) $limit; + + return $this; + } + + /** + * @param Criteria $criteria + * @return Collection + * @todo Implement lazy matching + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + + $oldFilter = $this->filterFunction; + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $this->addFilter($filter); + } + + $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit); + $this->filterFunction = $oldFilter; + + if ($orderings = $criteria->getOrderings()) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next); + } + + uasort($filtered, $next); + } else { + ksort($filtered); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + return new Collection($filtered); + } + + /** + * {@inheritDoc} + */ + protected function doInitialize() + { + $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit); + ksort($filtered); + + $this->collection = new Collection($filtered); + } + + protected function doInitializeByIterator(\SeekableIterator $iterator, $nestingLimit) + { + $children = []; + $objects = []; + $filter = $this->filterFunction; + $objectFunction = $this->createObjectFunction; + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($iterator as $file) { + // Skip files if they shouldn't be included. + if (!($this->flags & static::INCLUDE_FILES) && $file->isFile()) { + continue; + } + + // Apply main filter. + if ($filter && !$filter($file)) { + continue; + } + + // Include children if the recursive flag is set. + if (($this->flags & static::RECURSIVE) && $nestingLimit > 0 && $file->hasChildren()) { + $children[] = $file->getChildren(); + } + + // Skip folders if they shouldn't be included. + if (!($this->flags & static::INCLUDE_FOLDERS) && $file->isDir()) { + continue; + } + + $object = $objectFunction($file); + $objects[$object->key] = $object; + } + + if ($children) { + $objects += $this->doInitializeChildren($children, $nestingLimit - 1); + } + + return $objects; + } + + /** + * @param \RecursiveDirectoryIterator[] $children + * @return array + */ + protected function doInitializeChildren(array $children, $nestingLimit) + { + $objects = []; + + foreach ($children as $iterator) { + $objects += $this->doInitializeByIterator($iterator, $nestingLimit); + } + + return $objects; + } + + /** + * @param \RecursiveDirectoryIterator $file + * @return object + */ + protected function createObject($file) + { + return (object) [ + 'key' => $file->getSubPathName(), + 'type' => $file->isDir() ? 'folder' : 'file:' . $file->getExtension(), + 'url' => method_exists($file, 'getUrl') ? $file->getUrl() : null, + 'pathname' => $file->getPathname(), + 'mtime' => $file->getMTime() + ]; + } +} From 3c468a23c12894d3bf4aa36c0c958b905805c17b Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 9 May 2017 20:18:32 +0300 Subject: [PATCH 30/32] Remove Object Finder classes (replaced by FileCollection) --- .../Grav/Framework/Object/AbstractObject.php | 22 -- .../Framework/Object/AbstractObjectFinder.php | 288 ------------------ .../Object/ObjectFinderInterface.php | 89 ------ .../Object/Storage/FilesystemStorage.php | 22 -- .../Object/Storage/StorageInterface.php | 14 - 5 files changed, 435 deletions(-) delete mode 100644 system/src/Grav/Framework/Object/AbstractObjectFinder.php delete mode 100644 system/src/Grav/Framework/Object/ObjectFinderInterface.php diff --git a/system/src/Grav/Framework/Object/AbstractObject.php b/system/src/Grav/Framework/Object/AbstractObject.php index d2150680d..0508162e2 100644 --- a/system/src/Grav/Framework/Object/AbstractObject.php +++ b/system/src/Grav/Framework/Object/AbstractObject.php @@ -24,12 +24,6 @@ abstract class AbstractObject implements ObjectInterface, StoredObjectInterface } use ArrayAccessWithGetters, Export; - /** - * If you don't have global storage, override this in extending class. - * @var ObjectFinderInterface - */ - static protected $finder; - /** * Primary key for the object. * @var array @@ -55,14 +49,6 @@ abstract class AbstractObject implements ObjectInterface, StoredObjectInterface */ protected $items; - /** - * @param ObjectFinderInterface $finder - */ - static public function setFinder(ObjectFinderInterface $finder) - { - static::$finder = $finder; - } - /** * @param array $ids List of primary Ids or null to return everything that has been loaded. * @param bool $readonly @@ -114,14 +100,6 @@ abstract class AbstractObject implements ObjectInterface, StoredObjectInterface return new $collectionClass($results); } - /** - * @return ObjectFinderInterface - */ - static public function search() - { - return static::$finder; - } - /** * Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage. * diff --git a/system/src/Grav/Framework/Object/AbstractObjectFinder.php b/system/src/Grav/Framework/Object/AbstractObjectFinder.php deleted file mode 100644 index fc638d0d1..000000000 --- a/system/src/Grav/Framework/Object/AbstractObjectFinder.php +++ /dev/null @@ -1,288 +0,0 @@ -storage) { - throw new \DomainException('Storage missing from ' . get_class($this)); - } - - if ($options) { - $this->parse($options); - } - } - - /** - * Parse query options. - * - * @param array $options - * @return $this - */ - public function parse(array $options) - { - foreach ($options as $func => $params) { - if (method_exists($this, $func)) { - call_user_func_array([$this, $func], (array) $params); - } - } - - return $this; - } - - /** - * Set start position for the query. - * - * @param int $start - * - * @return $this - */ - public function start($start = null) - { - if (!is_null($start)) { - if ((string)(int)$start === (string)$start && $start>=0) { - throw new \RuntimeException('$start needs to be zero or a positive integer'); - } - - $this->start = (int)$start; - } - - return $this; - } - - /** - * Set limit to the query. - * - * @param int $limit - * - * @return $this - */ - public function limit($limit = null) - { - if (!is_null($limit)) - { - if ((string)(int)$limit === (string)$limit && $limit>=0) { - throw new \RuntimeException('$limit needs to be zero or a positive integer'); - } - - $this->limit = (int)$limit; - } - - return $this; - } - - /** - * Set order by field and direction. - * - * This function can be used more than once to chain order by. - * - * @param string $field - * @param int $direction - * - * @return $this - */ - public function order($field, $direction = 1) - { - if (!is_string($field)) { - throw new \RuntimeException('$field needs to be a string'); - } - if ((string)(int)$direction !== (string)$direction || $direction <= -1 || $direction >= 1) { - throw new \RuntimeException('$direction needs to be 1 (ascending), 0 (undefined) or -1 (descending)'); - } - - if ($direction) { - $this->query['order'][] = [$field, (int)$direction]; - } - - return $this; - } - - /** - * Filter by field. - * - * @param string $field Field name. - * @param string $operation Operation (>|>=|<|<=|==|!=|BETWEEN|NOT BETWEEN|IN|NOT IN) - * @param mixed $value Value. - * - * @return $this - */ - public function where($field, $operation, $value = null) - { - if (!is_string($field)) { - throw new \RuntimeException('$field needs to be a string'); - } - - $operation = strtoupper((string)$operation); - - switch ($operation) - { - case '>': - case '>=': - case '<': - case '<=': - $this->checkOperator($value); - $this->query['where'][] = [$field, $operation, $value]; - break; - case '==': - case '!=': - $this->checkEqOperator($value); - $this->query['where'][] = [$field, $operation, $value]; - break; - case 'BETWEEN': - case 'NOT BETWEEN': - $this->checkBetweenOperator($value); - $this->query['where'][] = [$field, $operation, array_values($value)]; - break; - case 'IN': - if (is_null($value) || (is_array($value) && empty($value))) { - // IN (nothing): optimized to empty collection. - $this->skip = true; - } else { - $this->checkInOperator($value); - $this->query['where'][] = [$field, $operation, array_values((array)$value)]; - } - break; - case 'NOT IN': - if (is_null($value) || (is_array($value) && empty($value))) { - // NOT IN (nothing): optimized away. - } else { - $this->checkInOperator($value); - $this->query['where'][] = [$field, $operation, array_values((array)$value)]; - } - break; - default: - throw new \RuntimeException('$operation needs to be one of: > , >= , < , <= , == , != , BETWEEN , NOT BETWEEN , IN , NOT IN'); - } - - return $this; - } - - /** - * Get items. - * - * Derived classes should generally override this function to return correct objects. - * - * @param int $start - * @param int $limit - * @return array - */ - public function find($start = null, $limit = null) - { - if ($this->skip) - { - return array(); - } - - $query = $this->query; - $this->start($start)->limit($limit); - $this->prepare(); - - $results = (array) $this->storage->find($this->query, $this->start, $this->limit); - - $this->query = $query; - - return $results; - } - - /** - * Count items. - * - * @return int - */ - public function count() - { - $query = $this->query; - $this->prepare(); - - $count = (int) $this->storage->count($this->query); - - $this->query = $query; - - return $count; - } - - /** - * Override to include common rules. - * - * @return void - */ - protected function prepare() - { - } - - protected function checkOperator($value) - { - if (!is_scalar($value)) { - throw new \RuntimeException('$value needs to be a scalar'); - } - } - - protected function checkEqOperator($value) - { - if (!is_null($value) || !is_scalar($value)) { - throw new \RuntimeException('$value needs to be a scalar or null'); - } - } - - protected function checkBetweenOperator($value) - { - if (!is_array($value) || count($value) !== 2) { - throw new \RuntimeException('$value needs to be a list with two values in it'); - } - } - - protected function checkInOperator($value) - { - if (!is_scalar($value) || !is_array($value)) { - throw new \RuntimeException('$value needs to be a single value or list of values'); - } - } -} diff --git a/system/src/Grav/Framework/Object/ObjectFinderInterface.php b/system/src/Grav/Framework/Object/ObjectFinderInterface.php deleted file mode 100644 index 04391719d..000000000 --- a/system/src/Grav/Framework/Object/ObjectFinderInterface.php +++ /dev/null @@ -1,89 +0,0 @@ -|>=|<|<=|==|!=|BETWEEN|NOT BETWEEN|IN|NOT IN) - * @param mixed $value Value. - * - * @return $this - */ - public function where($field, $operation, $value = null); - - /** - * Get items. - * - * Derived classes should generally override this function to return correct objects. - * - * @param int $start - * @param int $limit - * @return array - */ - public function find($start = null, $limit = null); - - /** - * Count items. - * - * @return int - */ - public function count(); -} diff --git a/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php index 895c975f2..671827acc 100644 --- a/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php +++ b/system/src/Grav/Framework/Object/Storage/FilesystemStorage.php @@ -122,28 +122,6 @@ class FilesystemStorage implements StorageInterface return $results; } - /** - * @param array $query - * @return int - */ - public function count(array $query) - { - // TODO - return 0; - } - - /** - * @param array $query - * @param int $start - * @param int $limit - * @return string[] - */ - public function find(array $query, $start = 0, $limit = 0) - { - // TODO - return []; - } - /** * @param string $key * @return FileInterface diff --git a/system/src/Grav/Framework/Object/Storage/StorageInterface.php b/system/src/Grav/Framework/Object/Storage/StorageInterface.php index e6d781eba..e53cbcc8e 100644 --- a/system/src/Grav/Framework/Object/Storage/StorageInterface.php +++ b/system/src/Grav/Framework/Object/Storage/StorageInterface.php @@ -44,18 +44,4 @@ interface StorageInterface * @return array */ public function loadList(array $list); - - /** - * @param array $query - * @return int - */ - public function count(array $query); - - /** - * @param array $query - * @param int $start - * @param int $limit - * @return string[] - */ - public function find(array $query, $start = 0, $limit = 0); } From 5f00e1f8eeb7dd43d61a342bc8c2a3145aace9dd Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 9 May 2017 21:27:33 +0300 Subject: [PATCH 31/32] Rename Collection class to ArrayCollection --- .../Grav/Framework/Collection/AbstractLazyCollection.php | 2 +- .../Collection/{Collection.php => ArrayCollection.php} | 4 ++-- system/src/Grav/Framework/Collection/FileCollection.php | 6 +++--- .../src/Grav/Framework/Object/AbstractObjectCollection.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename system/src/Grav/Framework/Collection/{Collection.php => ArrayCollection.php} (86%) diff --git a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php index ee4c32ec5..ad0830d94 100644 --- a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php +++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php @@ -20,7 +20,7 @@ abstract class AbstractLazyCollection extends BaseAbstractLazyCollection impleme /** * The backed collection to use * - * @var Collection + * @var ArrayCollection */ protected $collection; diff --git a/system/src/Grav/Framework/Collection/Collection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php similarity index 86% rename from system/src/Grav/Framework/Collection/Collection.php rename to system/src/Grav/Framework/Collection/ArrayCollection.php index ec23e60bc..d123f8926 100644 --- a/system/src/Grav/Framework/Collection/Collection.php +++ b/system/src/Grav/Framework/Collection/ArrayCollection.php @@ -8,14 +8,14 @@ namespace Grav\Framework\Collection; -use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\ArrayCollection as BaseArrayCollection; /** * General JSON serializable collection. * * @package Grav\Framework\Collection */ -class Collection extends ArrayCollection implements CollectionInterface +class ArrayCollection extends BaseArrayCollection implements CollectionInterface { /** * Reverse the order of the items. diff --git a/system/src/Grav/Framework/Collection/FileCollection.php b/system/src/Grav/Framework/Collection/FileCollection.php index 5bac9ae6b..68f969422 100644 --- a/system/src/Grav/Framework/Collection/FileCollection.php +++ b/system/src/Grav/Framework/Collection/FileCollection.php @@ -127,7 +127,7 @@ class FileCollection extends AbstractLazyCollection /** * @param Criteria $criteria - * @return Collection + * @return ArrayCollection * @todo Implement lazy matching */ public function matching(Criteria $criteria) @@ -162,7 +162,7 @@ class FileCollection extends AbstractLazyCollection $filtered = array_slice($filtered, (int)$offset, $length); } - return new Collection($filtered); + return new ArrayCollection($filtered); } /** @@ -173,7 +173,7 @@ class FileCollection extends AbstractLazyCollection $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit); ksort($filtered); - $this->collection = new Collection($filtered); + $this->collection = new ArrayCollection($filtered); } protected function doInitializeByIterator(\SeekableIterator $iterator, $nestingLimit) diff --git a/system/src/Grav/Framework/Object/AbstractObjectCollection.php b/system/src/Grav/Framework/Object/AbstractObjectCollection.php index 4af29cbda..ecad694d9 100644 --- a/system/src/Grav/Framework/Object/AbstractObjectCollection.php +++ b/system/src/Grav/Framework/Object/AbstractObjectCollection.php @@ -8,13 +8,13 @@ namespace Grav\Framework\Object; -use Grav\Framework\Collection\Collection; +use Grav\Framework\Collection\ArrayCollection; /** * Abstract Object Collection * @package Grav\Framework\Object */ -abstract class AbstractObjectCollection extends Collection implements ObjectCollectionInterface, StoredObjectInterface +abstract class AbstractObjectCollection extends ArrayCollection implements ObjectCollectionInterface, StoredObjectInterface { use ObjectStorageTrait { getId as getParentId; From 10b4a90501866b3be556ef2e0ef405ed0fb256fa Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 9 May 2017 21:40:35 +0300 Subject: [PATCH 32/32] Add useful functions to FileCollection class --- .../Framework/Collection/FileCollection.php | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/system/src/Grav/Framework/Collection/FileCollection.php b/system/src/Grav/Framework/Collection/FileCollection.php index 68f969422..da9764448 100644 --- a/system/src/Grav/Framework/Collection/FileCollection.php +++ b/system/src/Grav/Framework/Collection/FileCollection.php @@ -25,6 +25,11 @@ class FileCollection extends AbstractLazyCollection const INCLUDE_FOLDERS = 2; const RECURSIVE = 4; + /** + * @var string + */ + protected $path; + /** * @var \RecursiveDirectoryIterator|RecursiveUniformResourceIterator */ @@ -56,25 +61,50 @@ class FileCollection extends AbstractLazyCollection */ public function __construct($path, $flags = null) { - $this->setIterator($path); + $this->path = $path; $this->flags = (int) ($flags ?: FileCollection::INCLUDE_FILES | FileCollection::INCLUDE_FOLDERS | FileCollection::RECURSIVE); + $this->setIterator(); $this->setFilter(); $this->setObjectBuilder(); $this->setNestingLimit(); } - public function setIterator($path) + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return int + */ + public function getFlags() + { + return $this->flags; + } + + /** + * @return int + */ + public function getNestingLimit() + { + return $this->nestingLimit; + } + + public function setIterator() { $iteratorFlags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS; - if (strpos($path, '://')) { + if (strpos($this->path, '://')) { /** @var UniformResourceLocator $locator */ $locator = Grav::instance()['locator']; - $this->iterator = $locator->getRecursiveIterator($path, $iteratorFlags); + $this->iterator = $locator->getRecursiveIterator($this->path, $iteratorFlags); } else { - $this->iterator = new \RecursiveDirectoryIterator($path, $iteratorFlags); + $this->iterator = new \RecursiveDirectoryIterator($this->path, $iteratorFlags); } }