diff --git a/CHANGELOG.md b/CHANGELOG.md index 86939c616..a56830006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Grav 1.6: Renamed `$grav['users']` service to `$grav['accounts']` * Added `Flex::getObjects()` and `Flex::getMixedCollection()` methods for co-mingled collections * Added support to use single Flex key parameter in `Flex::getObject()` method + * Added `FlexObjectInterface::search()` and `FlexCollectionInterface::search()` methods 1. [](#improved) * Renamed `Grav\Framework\File\Formatter\FormatterInterface` to `Grav\Framework\File\Interfaces\FileFormatterInterface` * Improved `File::save()` to use a temporary file if file isn't locked diff --git a/system/src/Grav/Common/Service/AccountsServiceProvider.php b/system/src/Grav/Common/Service/AccountsServiceProvider.php index cc49fb7ff..7924eb0db 100644 --- a/system/src/Grav/Common/Service/AccountsServiceProvider.php +++ b/system/src/Grav/Common/Service/AccountsServiceProvider.php @@ -61,7 +61,16 @@ class AccountsServiceProvider implements ServiceProviderInterface 'object' => User::class, // Use User class for backwards compatibility. 'collection' => FlexUser\UserCollection::class, 'index' => FlexUser\UserIndex::class, - 'storage' => $this->getFlexStorage($config->get('system.accounts.storage', 'file')) + 'storage' => $this->getFlexStorage($config->get('system.accounts.storage', 'file')), + 'search' => [ + 'options' => [ + 'contains' => true + ], + 'fields' => [ + 'key', + 'email' + ] + ] ] ] + ($config->get('plugins.flex-objects.object') ?: []); diff --git a/system/src/Grav/Common/User/FlexUser/UserCollection.php b/system/src/Grav/Common/User/FlexUser/UserCollection.php index a62c18c65..0cc9a280d 100644 --- a/system/src/Grav/Common/User/FlexUser/UserCollection.php +++ b/system/src/Grav/Common/User/FlexUser/UserCollection.php @@ -98,34 +98,4 @@ class UserCollection extends FlexCollection implements UserCollectionInterface return $exists; } - - /** - * @param string $text - * @param bool $case_sensitive - * @param bool $strict - * @return UserCollection - */ - public function search(string $text, bool $case_sensitive = false, bool $strict = false) : UserCollection - { - $text = trim($text); - - if (!$text) { - return $this; - } - - $matching = []; - /** - * @var string $key - * @var User $object - */ - foreach ($this as $key => $object) { - if ($strict && Utils::startsWith($object->getProperty('email'), $text, $case_sensitive)) { - $matching[$key] = $object; - } elseif (!$strict && Utils::contains($object->getProperty('email'), $text, $case_sensitive)) { - $matching[$key] = $object; - } - } - - return $this->createFrom($matching); - } } diff --git a/system/src/Grav/Framework/Flex/FlexCollection.php b/system/src/Grav/Framework/Flex/FlexCollection.php index 90b38c9da..3a11136cf 100644 --- a/system/src/Grav/Framework/Flex/FlexCollection.php +++ b/system/src/Grav/Framework/Flex/FlexCollection.php @@ -53,6 +53,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface 'render' => false, 'isAuthorized' => 'session', + 'search' => true, 'sort' => true, ]; } @@ -124,6 +125,27 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface return $type . $this->_flexDirectory->getType(); } + + /** + * @param string $search + * @param string|string[]|null $properties + * @param array|null $options + * @return FlexCollectionInterface + */ + public function search(string $search, $properties = null, array $options = null) // : FlexCollectionInterface + { + $matching = $this->call('search', [$search, $properties, $options]); + $matching = array_filter($matching); + + if ($matching) { + uksort($matching, function ($a, $b) { + return -($a <=> $b); + }); + } + + return $this->select(array_keys($matching)); + } + public function sort(array $order) // : FlexCollection { $criteria = Criteria::create()->orderBy($order); diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 3d0b076cd..a19ce64b4 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -108,6 +108,86 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface $this->objectConstruct($elements, $key); } + /** + * @param string $search + * @param string|string[]|null $properties + * @param array|null $options + * @return float + */ + public function search(string $search, $properties = null, array $options = null): float + { + $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []); + $properties = $properties ?? $this->getFlexDirectory()->getConfig('data.search.fields', []); + + $weight = 0; + foreach ((array)$properties as $property) { + $weight += $this->searchNestedProperty($property, $search, $options); + } + + return $weight > 0 ? min($weight, 1) : 0; + } + + /** + * @param string $property + * @param string $search + * @param array|null $options + * @return float + */ + public function searchProperty(string $property, string $search, array $options = null): float + { + $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []); + $value = $this->getProperty($property); + + return $this->searchValue($property, $value, $search, $options); + } + + /** + * @param string $property + * @param string $search + * @param array|null $options + * @return float + */ + public function searchNestedProperty(string $property, string $search, array $options = null): float + { + $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []); + $value = $this->getNestedProperty($property); + + return $this->searchValue($property, $value, $search, $options); + } + + /** + * @param string $name + * @param $value + * @param string $search + * @param array|null $options + * @return float + */ + protected function searchValue(string $name, $value, string $search, array $options = null): float + { + $search = trim($search); + + if ($search === '') { + return 0; + } + + if (!\is_string($value) || $value === '') { + return 0; + } + + $tested = false; + if (($tested |= !empty($options['starts_with'])) && Utils::startsWith($value, $search, $options['case_sensitive'] ?? false)) { + return (float)$options['starts_with']; + } + if (($tested |= !empty($options['ends_with'])) && Utils::endsWith($value, $search, $options['case_sensitive'] ?? false)) { + return (float)$options['ends_with']; + } + if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $options['case_sensitive'] ?? false)) { + return (float)$options['contains']; + } + + return 0; + } + /** * @param array $data * @param array $files diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexCollectionInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexCollectionInterface.php index 210c075f9..5c99733a3 100644 --- a/system/src/Grav/Framework/Flex/Interfaces/FlexCollectionInterface.php +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexCollectionInterface.php @@ -37,6 +37,14 @@ interface FlexCollectionInterface extends ObjectCollectionInterface, NestedObjec */ public function __construct(array $elements, FlexDirectory $type); + /** + * @param string $search + * @param string|string[]|null $properties + * @param array|null $options + * @return FlexCollectionInterface + */ + public function search(string $search, $properties = null, array $options = null); // : FlexCollection + /** * @return FlexDirectory */ diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php index 4f03a3984..fe5bd152c 100644 --- a/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php +++ b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php @@ -31,6 +31,18 @@ interface FlexObjectInterface extends NestedObjectInterface, \ArrayAccess */ public function __construct(array $elements, $key, FlexDirectory $type); + /** + * Search object, returns weight between 0 and 1. + * + * Note: If you override this function, make sure you return value in range 0...1! + * + * @param string $search + * @param string|string[]|null $properties + * @param array|null $options + * @return float Weight between 0...1 + */ + public function search(string $search, $properties = null, array $options = null): float; + /** * Returns the directory where the object belongs into. *