diff --git a/system/blueprints/flex/accounts.yaml b/system/blueprints/flex/accounts.yaml index 14fd36b07..52eff696d 100644 --- a/system/blueprints/flex/accounts.yaml +++ b/system/blueprints/flex/accounts.yaml @@ -2,7 +2,7 @@ title: Flex User Accounts description: Manage your User Accounts in Flex. type: flex-objects -# Deprecated in Grav 1.7.0-rc.4: file was renamed. +# Deprecated in Grav 1.7.0-rc.4: file was renamed to user-accounts.yaml extends@: type: user-accounts context: blueprints://flex diff --git a/system/blueprints/flex/user-accounts.yaml b/system/blueprints/flex/user-accounts.yaml index bf978f397..56aad88bb 100644 --- a/system/blueprints/flex/user-accounts.yaml +++ b/system/blueprints/flex/user-accounts.yaml @@ -113,6 +113,7 @@ config: pattern: '{FOLDER}/{KEY}{EXT}' indexed: true key: username + case_sensitive: false search: options: contains: 1 diff --git a/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php index 30b4b7467..ec61532bb 100644 --- a/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php +++ b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php @@ -15,6 +15,21 @@ use Grav\Framework\Flex\Storage\FileStorage; class UserFileStorage extends FileStorage { + public $caseSensitive; + + /** + * @param string $key + * @return string + */ + public function normalizeKey(string $key): string + { + if ($this->caseSensitive === true) { + return $key; + } + + return mb_strtolower($key); + } + /** * {@inheritdoc} * @see FlexStorageInterface::getMediaPath() @@ -40,4 +55,15 @@ class UserFileStorage extends FileStorage $row['access'] = $access; } } + + /** + * @param array $options + * @return void + */ + protected function initOptions(array $options): void + { + parent::initOptions($options); + + $this->caseSensitive = $options['case_sensitive'] ?? false; + } } diff --git a/system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php index 911ec5222..579e02bb5 100644 --- a/system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php +++ b/system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php @@ -15,6 +15,21 @@ use Grav\Framework\Flex\Storage\FolderStorage; class UserFolderStorage extends FolderStorage { + public $caseSensitive; + + /** + * @param string $key + * @return string + */ + public function normalizeKey(string $key): string + { + if ($this->caseSensitive === true) { + return $key; + } + + return mb_strtolower($key); + } + /** * Prepares the row for saving and returns the storage key for the record. * @@ -30,4 +45,15 @@ class UserFolderStorage extends FolderStorage $row['access'] = $access; } } + + /** + * @param array $options + * @return void + */ + protected function initOptions(array $options): void + { + parent::initOptions($options); + + $this->caseSensitive = $options['case_sensitive'] ?? false; + } } diff --git a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php index 4874c68f9..70bc68555 100644 --- a/system/src/Grav/Common/Flex/Types/Users/UserCollection.php +++ b/system/src/Grav/Common/Flex/Types/Users/UserCollection.php @@ -43,7 +43,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface public function load($username): UserInterface { if ($username !== '') { - $key = mb_strtolower($username); + $key = $this->filterUsername($username); $user = $this->get($key); if ($user) { return $user; @@ -84,7 +84,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface } elseif ($field === 'flex_key') { $user = $this->withKeyField('flex_key')->get($query); } elseif ($field === 'username') { - $user = $this->get(mb_strtolower($query)); + $user = $this->get($this->filterUsername($query)); } else { $user = parent::find($query, $field); } @@ -114,4 +114,18 @@ class UserCollection extends FlexCollection implements UserCollectionInterface return $exists; } + + /** + * @param string $key + * @return bool|false|string|string[]|null + */ + protected function filterUsername(string $key) + { + $storage = $this->getFlexDirectory()->getStorage(); + if (method_exists($storage, 'normalizeKey')) { + return $storage->normalizeKey($key); + } + + return mb_strtolower($key); + } } diff --git a/system/src/Grav/Common/Flex/Types/Users/UserIndex.php b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php index 56c95376f..06d6851be 100644 --- a/system/src/Grav/Common/Flex/Types/Users/UserIndex.php +++ b/system/src/Grav/Common/Flex/Types/Users/UserIndex.php @@ -53,12 +53,13 @@ class UserIndex extends FlexIndex /** * @param array $meta * @param array $data + * @param FlexStorageInterface $storage */ - public static function updateObjectMeta(array &$meta, array $data) + public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage) { // Username can also be number and stored as such. $key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']); - $meta['key'] = mb_strtolower($key); + $meta['key'] = static::filterUsername($key, $storage); $meta['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null; } @@ -73,7 +74,7 @@ class UserIndex extends FlexIndex public function load($username): UserInterface { if ($username !== '') { - $key = mb_strtolower($username); + $key = static::filterUsername($username, $this->getFlexDirectory()->getStorage()); $user = $this->get($key); if ($user) { return $user; @@ -116,7 +117,7 @@ class UserIndex extends FlexIndex } elseif ($field === 'email') { $user = $this->withKeyField('email')->get($query); } elseif ($field === 'username') { - $user = $this->get(mb_strtolower($query)); + $user = $this->get(static::filterUsername($query, $this->getFlexDirectory()->getStorage())); } else { $user = $this->__call('find', [$query, $field]); } @@ -129,6 +130,20 @@ class UserIndex extends FlexIndex return $this->load(''); } + /** + * @param string $key + * @param FlexStorageInterface $storage + * @return string + */ + protected static function filterUsername(string $key, FlexStorageInterface $storage): string + { + if ($storage && \method_exists($storage, 'normalizeKey')) { + return $storage->normalizeKey($key); + } + + return mb_strtolower($key); + } + /** * @param FlexStorageInterface $storage * @return CompiledYamlFile|null diff --git a/system/src/Grav/Common/User/DataUser/User.php b/system/src/Grav/Common/User/DataUser/User.php index 8428ab5d9..1c82b442d 100644 --- a/system/src/Grav/Common/User/DataUser/User.php +++ b/system/src/Grav/Common/User/DataUser/User.php @@ -116,11 +116,11 @@ class User extends Data implements UserInterface } if ($file) { - $username = $this->get('username'); + $username = $this->filterUsername($this->get('username')); if (!$file->filename()) { $locator = Grav::instance()['locator']; - $file->filename($locator->findResource('account://' . mb_strtolower($username) . YAML_EXT, true, true)); + $file->filename($locator->findResource('account://' . $username . YAML_EXT, true, true)); } // if plain text password, hash it and remove plain text @@ -176,6 +176,8 @@ class User extends Data implements UserInterface /** * Serialize user. + * + * @return array */ public function __sleep() { @@ -272,6 +274,18 @@ class User extends Data implements UserInterface return parent::count(); } + /** + * @param string $username + * @return string + */ + protected function filterUsername(string $username): string + { + return mb_strtolower($username); + } + + /** + * @return string|null + */ protected function getAvatarFile(): ?string { $avatars = $this->get('avatar'); diff --git a/system/src/Grav/Common/User/DataUser/UserCollection.php b/system/src/Grav/Common/User/DataUser/UserCollection.php index 55a0968df..4d10a2aa8 100644 --- a/system/src/Grav/Common/User/DataUser/UserCollection.php +++ b/system/src/Grav/Common/User/DataUser/UserCollection.php @@ -45,8 +45,8 @@ class UserCollection implements UserCollectionInterface /** @var UniformResourceLocator $locator */ $locator = $grav['locator']; - // force lowercase of username - $username = mb_strtolower($username); + // Filter username. + $username = $this->filterUsername($username); $filename = 'account://' . $username . YAML_EXT; $path = $locator->findResource($filename) ?: $locator->findResource($filename, true, true); @@ -138,4 +138,14 @@ class UserCollection implements UserCollectionInterface return count($accounts); } + + /** + * @param string $username + * @return string + */ + protected function filterUsername(string $username): string + { + return mb_strtolower($username); + } + } diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php index aebd1d937..ded360b1a 100644 --- a/system/src/Grav/Framework/Flex/FlexIndex.php +++ b/system/src/Grav/Framework/Flex/FlexIndex.php @@ -20,7 +20,6 @@ use Grav\Framework\Flex\Interfaces\FlexCollectionInterface; use Grav\Framework\Flex\Interfaces\FlexIndexInterface; use Grav\Framework\Flex\Interfaces\FlexObjectInterface; use Grav\Framework\Flex\Interfaces\FlexStorageInterface; -use Grav\Framework\Object\Interfaces\ObjectCollectionInterface; use Grav\Framework\Object\Interfaces\ObjectInterface; use Grav\Framework\Object\ObjectIndex; use Monolog\Logger; @@ -77,8 +76,9 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde * * @param array $meta * @param array $data + * @param FlexStorageInterface $storage */ - public static function updateObjectMeta(array &$meta, array $data) + public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage) { // For backwards compatibility, no need to call this method when you override this method. static::updateIndexData($meta, $data); @@ -709,7 +709,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde if ($keyField !== 'storage_key' && isset($row[$keyField])) { $entry['key'] = $row[$keyField]; } - static::updateObjectMeta($entry, $row ?? []); + static::updateObjectMeta($entry, $row ?? [], $storage); if (isset($row['__ERROR'])) { $entry['__ERROR'] = true; static::onException(new \RuntimeException(sprintf('Object failed to load: %s (%s)', $key, diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 0ab04747e..842bbc801 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -623,7 +623,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface if ($meta) { /** @var FlexIndex $indexClass */ $indexClass = $this->getFlexDirectory()->getIndexClass(); - $indexClass::updateObjectMeta($meta, $value); + $indexClass::updateObjectMeta($meta, $value, $storage); $this->_meta = $meta; }