diff --git a/CHANGELOG.md b/CHANGELOG.md index 146610a68..78c0ec531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,19 @@ ## mm/dd/2022 1. [](#new) - * Added support to get image size for SVG vector images [#3533](https://github.com/getgrav/grav/pull/3533) - * Added XSS check for uploaded SVG files before they get stored - * Fixed phpstan issues (All level 2, Framework level 5) -2. [](#bugfix) - * Fixed `'mbstring' extension is not loaded` error, use Polyfill instead [#3504](https://github.com/getgrav/grav/pull/3504) - * Fixed new `Utils::pathinfo()` and `Utils::basename()` being too strict for legacy use [#3542](https://github.com/getgrav/grav/issues/3542) - * Fixed non-standard video html atributes generated by `{{ media.html() }}` [#3540](https://github.com/getgrav/grav/issues/3540) - * Fixed entity sanitization for XSS detection - * Fixed avatar save location when `account://` stream points to custom directory - * Fixed bug in `Utils::url()` when path contains root + * Added new local Multiavatar (local generation). **This will be default in Grav 1.8** + * Added support to get image size for SVG vector images [#3533](https://github.com/getgrav/grav/pull/3533) + * Added XSS check for uploaded SVG files before they get stored + * Fixed phpstan issues (All level 2, Framework level 5) +2. [](#improved) + * Moved Accounts out of Experimental section of System configuration +3. [](#bugfix) + * Fixed `'mbstring' extension is not loaded` error, use Polyfill instead [#3504](https://github.com/getgrav/grav/pull/3504) + * Fixed new `Utils::pathinfo()` and `Utils::basename()` being too strict for legacy use [#3542](https://github.com/getgrav/grav/issues/3542) + * Fixed non-standard video html atributes generated by `{{ media.html() }}` [#3540](https://github.com/getgrav/grav/issues/3540) + * Fixed entity sanitization for XSS detection + * Fixed avatar save location when `account://` stream points to custom directory + * Fixed bug in `Utils::url()` when path contains part of root # v1.7.30 ## 02/07/2022 diff --git a/composer.json b/composer.json index 9ad0b9c9c..3b6ff0c74 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,8 @@ "itsgoingd/clockwork": "^5.0", "symfony/http-client": "^4.4", "composer/semver": "^1.4", - "rhukster/dom-sanitizer": "^1.0" + "rhukster/dom-sanitizer": "^1.0", + "multiavatar/multiavatar-php": "^1.0" }, "require-dev": { "codeception/codeception": "^4.1", diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index b6beb41b9..787fcd577 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -1786,35 +1786,15 @@ form: validate: type: bool - experimental: + + accounts: type: tab - title: PLUGIN_ADMIN.EXPERIMENTAL + title: PLUGIN_ADMIN.ACCOUNTS fields: - experimental_section: - type: section - title: PLUGIN_ADMIN.EXPERIMENTAL - underline: true - -# flex_pages: -# type: section -# title: Flex Pages -# -# pages.type: -# type: select -# label: PLUGIN_ADMIN.PAGES_TYPE -# highlight: regular -# help: PLUGIN_ADMIN.PAGES_TYPE_HELP -# options: -# regular: PLUGIN_ADMIN.REGULAR -# flex: PLUGIN_ADMIN.FLEX - - pages.type: - type: hidden - flex_accounts: type: section - title: Flex Accounts + title: User Accounts accounts.type: type: select @@ -1833,3 +1813,41 @@ form: options: file: PLUGIN_ADMIN.FILE folder: PLUGIN_ADMIN.FOLDER + + accounts.avatar: + type: select + label: PLUGIN_ADMIN.AVATAR + default: gravatar + help: PLUGIN_ADMIN.AVATAR_HELP + options: + multiavatar: Multiavatar [local] + gravatar: Gravatar [external] + +# experimental: +# type: tab +# title: PLUGIN_ADMIN.EXPERIMENTAL +# +# fields: +# experimental_section: +# type: section +# title: PLUGIN_ADMIN.EXPERIMENTAL +# underline: true +# +# flex_pages: +# type: section +# title: Flex Pages +# +# pages.type: +# type: select +# label: PLUGIN_ADMIN.PAGES_TYPE +# highlight: regular +# help: PLUGIN_ADMIN.PAGES_TYPE_HELP +# options: +# regular: PLUGIN_ADMIN.REGULAR +# flex: PLUGIN_ADMIN.FLEX +# +# pages.type: +# type: hidden + + + diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml index 391c7370a..ef5f25b04 100644 --- a/system/blueprints/user/account.yaml +++ b/system/blueprints/user/account.yaml @@ -15,6 +15,17 @@ form: multiple: false random_name: true + multiavatar_only: + type: conditional + condition: config.system.accounts.avatar == 'multiavatar' + fields: + avatar_hash: + type: text + label: '' + placeholder: 'e.g. dceaadcfda491f4e45' + description: PLUGIN_ADMIN.AVATAR_HASH + size: large + content: type: section title: PLUGIN_ADMIN.ACCOUNT diff --git a/system/config/system.yaml b/system/config/system.yaml index 2de075b26..380d650eb 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -208,6 +208,7 @@ http: accounts: type: regular # EXPERIMENTAL: Account type: regular or flex storage: file # EXPERIMENTAL: Flex storage type: file or folder + avatar: gravatar # Avatar generator [multiavatar|gravatar] flex: cache: diff --git a/system/src/Grav/Common/User/Traits/UserTrait.php b/system/src/Grav/Common/User/Traits/UserTrait.php index 0608caf5e..91c41aa11 100644 --- a/system/src/Grav/Common/User/Traits/UserTrait.php +++ b/system/src/Grav/Common/User/Traits/UserTrait.php @@ -9,12 +9,15 @@ namespace Grav\Common\User\Traits; +use Grav\Common\Filesystem\Folder; use Grav\Common\Grav; use Grav\Common\Page\Medium\ImageMedium; use Grav\Common\Page\Medium\Medium; use Grav\Common\Page\Medium\StaticImageMedium; use Grav\Common\User\Authentication; use Grav\Common\Utils; +use Multiavatar; +use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use function is_array; use function is_string; @@ -175,9 +178,52 @@ trait UserTrait } $email = $this->get('email'); + $avatar_generator = Grav::instance()['config']->get('system.accounts.avatar', 'multiavatar'); + if ($avatar_generator === 'gravatar') { + if (!$email) { + return ''; + } + + $hash = md5(strtolower(trim($email))); + + return 'https://www.gravatar.com/avatar/' . $hash; + } + + $hash = $this->get('avatar_hash'); + if (!$hash) { + $username = $this->get('username'); + $hash = md5(strtolower(trim($email ?? $username))); + } + + return $this->generateMultiavatar($hash); + } + + /** + * @param string $hash + * @return string + */ + protected function generateMultiavatar(string $hash): string + { + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $storage = $locator->findResource('image://multiavatar', true, true); + $avatar_file = "{$storage}/{$hash}.svg"; + + if (!file_exists($storage)) { + Folder::create($storage); + } + + if (!file_exists($avatar_file)) { + $mavatar = new Multiavatar(); + + file_put_contents($avatar_file, $mavatar->generate($hash, null, null)); + } + + $avatar_url = $locator->findResource("image://multiavatar/{$hash}.svg", false, true); + + return Utils::url($avatar_url); - // By default fall back to gravatar image. - return $email ? 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($email))) : ''; } abstract public function get($name, $default = null, $separator = null); diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index dff54fe1a..91393725a 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -134,14 +134,13 @@ abstract class Utils $resource = $locator->findResource($input, false); } } else { -// $root = $uri->rootUrl(); -// $pattern = '/(' . '\\' . $root . '[\s\/])/'; -// if (preg_match($pattern, $input, $matches)) { -// $input = static::replaceFirstOccurrence($matches[0], '', $input); -// } + $root = preg_quote($uri->rootUrl(), '#'); + $pattern = '#(' . $root . '$|' . $root . '/)#'; + if (!empty($root) && preg_match($pattern, $input, $matches)) { + $input = static::replaceFirstOccurrence($matches[0], '', $input); + } $input = ltrim($input, '/'); - $resource = $input; } diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php index 5cf554adc..37e863714 100644 --- a/tests/unit/Grav/Common/UtilsTest.php +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -503,21 +503,25 @@ class UtilsTest extends \Codeception\TestCase\Test self::assertSame('http://testing.dev/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true)); // Absolute Paths including the grav base. - self::assertSame('/subdir/subdir', Utils::url('/subdir')); - self::assertSame('/subdir/subdir/path1', Utils::url('/subdir/path1')); - self::assertSame('/subdir/subdir/path1/path2', Utils::url('/subdir/path1/path2')); - self::assertSame('/subdir/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg')); - self::assertSame('/subdir/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg')); + self::assertSame('/subdir/', Utils::url('/subdir')); + self::assertSame('/subdir/', Utils::url('/subdir/')); + self::assertSame('/subdir/path1', Utils::url('/subdir/path1')); + self::assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2')); + self::assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg')); + self::assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg')); // Absolute paths from Grav root with domain. - self::assertSame('http://testing.dev/subdir/subdir', Utils::url('/subdir', true)); - self::assertSame('http://testing.dev/subdir/subdir/path1', Utils::url('/subdir/path1', true)); - self::assertSame('http://testing.dev/subdir/subdir/path1/path2', Utils::url('/subdir/path1/path2', true)); - self::assertSame('http://testing.dev/subdir/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true)); - self::assertSame('http://testing.dev/subdir/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/', Utils::url('/subdir', true)); + self::assertSame('http://testing.dev/subdir/', Utils::url('/subdir/', true)); + self::assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true)); + self::assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/subdir/path1/path2', true)); + self::assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true)); + self::assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true)); // Relative paths from Grav root. + self::assertSame('/subdir/sub', Utils::url('/sub')); self::assertSame('/subdir/subdir', Utils::url('subdir')); + self::assertSame('/subdir/subdir2/sub', Utils::url('/subdir2/sub')); self::assertSame('/subdir/subdir/path1', Utils::url('subdir/path1')); self::assertSame('/subdir/subdir/path1/path2', Utils::url('subdir/path1/path2')); self::assertSame('/subdir/path1', Utils::url('path1'));