From 3ef24bb5ad5ae3ac98fe3869c9edc23632586569 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 15 Mar 2024 14:54:02 +0200 Subject: [PATCH] avatars --- application/Espo/Entities/User.php | 5 + application/Espo/EntryPoints/Avatar.php | 76 ++-- .../crm/src/views/meeting/fields/users.js | 2 +- client/src/views/fields/assigned-users.js | 2 +- client/src/views/fields/user-with-avatar.js | 2 +- client/src/views/user/fields/name.js | 2 +- composer.json | 4 +- composer.lock | 427 +++++++++++++++--- frontend/less/espo/custom.less | 2 +- 9 files changed, 419 insertions(+), 103 deletions(-) diff --git a/application/Espo/Entities/User.php b/application/Espo/Entities/User.php index 4128cf6006..6815b4d6f9 100644 --- a/application/Espo/Entities/User.php +++ b/application/Espo/Entities/User.php @@ -267,6 +267,11 @@ class User extends Person return $value; } + public function getAvatarId(): ?string + { + return $this->get('avatarId'); + } + /** * @return ?string */ diff --git a/application/Espo/EntryPoints/Avatar.php b/application/Espo/EntryPoints/Avatar.php index e1ae3248c9..f4dcd5df7b 100644 --- a/application/Espo/EntryPoints/Avatar.php +++ b/application/Espo/EntryPoints/Avatar.php @@ -36,33 +36,39 @@ use Espo\Core\Api\Response; use Espo\Core\Exceptions\ForbiddenSilent; use Espo\Core\Exceptions\NotFound; use Espo\Core\Exceptions\NotFoundSilent; - use Espo\Core\Utils\SystemUser; use Espo\Entities\User; -use Identicon\Identicon; +use LasseRafn\InitialAvatarGenerator\InitialAvatar; + +/** + * @noinspection PhpUnused + */ class Avatar extends Image { protected string $systemColor = '#a4b5bd'; - /** @var array */ + /** @var string[] */ protected $colorList = [ - [111, 168, 214], - [237, 197, 85], - [212, 114, 155], + '#6fa8d6', + '#edc555', + '#d4729b', '#8093BD', - [124, 196, 164], - [138, 124, 194], - [222, 102, 102], + '#7cc4a4', + '#8a7cc2', + '#de6666', '#ABE3A1', '#E8AF64', ]; - /** - * @return string|array{int, int, int} - */ - private function getColor(string $hash) + private function getColor(User $user): string { + if ($user->getUserName() === SystemUser::NAME) { + return $this->metadata->get(['app', 'avatars', 'systemColor']) ?? $this->systemColor; + } + + $hash = $user->getId(); + $length = strlen($hash); $sum = 0; @@ -94,14 +100,13 @@ class Avatar extends Image public function run(Request $request, Response $response): void { $userId = $request->getQueryParam('id'); - $size = $request->getQueryParam('size') ?? null; + $size = $request->getQueryParam('size') ?? 'small'; if (!$userId) { throw new BadRequest(); } - /** @var ?User $user */ - $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $userId); + $user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById( $userId); if (!$user) { $this->renderBlank($response); @@ -109,43 +114,38 @@ class Avatar extends Image return; } - $id = $user->get('avatarId'); - - if ($id) { - $this->show($response, $id, $size, true); + if ($user->getAvatarId()) { + $this->show($response, $user->getAvatarId(), $size, true); return; } - $identicon = new Identicon(); + $sizes = $this->getSizes()[$size]; - if (!$size) { - $size = 'small'; - } - - if (empty($this->getSizes()[$size])) { + if (empty($sizes)) { $this->renderBlank($response); return; } - $width = $this->getSizes()[$size][0]; + $width = $sizes[0]; + $color = $this->getColor($user); + + $image = (new InitialAvatar()) + ->name($user->getName() ?? $user->getUserName() ?? $userId) + ->width($width) + ->height($width) + ->color('#FFF') + ->background($color) + ->fontSize() + //->preferBold() + ->generate(); $response ->setHeader('Cache-Control', 'max-age=360000, must-revalidate') ->setHeader('Content-Type', 'image/png'); - $hash = $userId; - - $color = $this->getColor($userId); - - if ($user->getUserName() === SystemUser::NAME) { - $color = $this->metadata->get(['app', 'avatars', 'systemColor']) ?? $this->systemColor; - } - - $imgContent = $identicon->getImageData($hash, $width, $color); - - $response->writeBody($imgContent); + $response->writeBody($image->stream('png', 100)); } /** diff --git a/client/modules/crm/src/views/meeting/fields/users.js b/client/modules/crm/src/views/meeting/fields/users.js index 0149de8a94..dea2db1f48 100644 --- a/client/modules/crm/src/views/meeting/fields/users.js +++ b/client/modules/crm/src/views/meeting/fields/users.js @@ -49,7 +49,7 @@ define('crm:views/meeting/fields/users', ['crm:views/meeting/fields/attendees'], }, getIconHtml: function (id) { - let iconHtml = this.getHelper().getAvatarHtml(id, 'small', 14, 'avatar-link'); + let iconHtml = this.getHelper().getAvatarHtml(id, 'small', 16, 'avatar-link'); if (iconHtml) { iconHtml += ' '; diff --git a/client/src/views/fields/assigned-users.js b/client/src/views/fields/assigned-users.js index 0c4bb00704..17bba56485 100644 --- a/client/src/views/fields/assigned-users.js +++ b/client/src/views/fields/assigned-users.js @@ -54,7 +54,7 @@ class AssignedUsersFieldView extends LinkMultipleFieldView { let html = super.getDetailLinkHtml(id); let avatarHtml = this.isDetailMode() ? - this.getHelper().getAvatarHtml(id, 'small', 14, 'avatar-link') : ''; + this.getHelper().getAvatarHtml(id, 'small', 16, 'avatar-link') : ''; if (!avatarHtml) { return html; diff --git a/client/src/views/fields/user-with-avatar.js b/client/src/views/fields/user-with-avatar.js index 4bc72ffedc..5bd19227f9 100644 --- a/client/src/views/fields/user-with-avatar.js +++ b/client/src/views/fields/user-with-avatar.js @@ -45,7 +45,7 @@ class UserWithAvatarFieldView extends UserFieldView { } getAvatarHtml() { - return this.getHelper().getAvatarHtml(this.model.get(this.idName), 'small', 14, 'avatar-link'); + return this.getHelper().getAvatarHtml(this.model.get(this.idName), 'small', 16, 'avatar-link'); } } diff --git a/client/src/views/user/fields/name.js b/client/src/views/user/fields/name.js index 90089834ee..38f01ecc25 100644 --- a/client/src/views/user/fields/name.js +++ b/client/src/views/user/fields/name.js @@ -43,7 +43,7 @@ define('views/user/fields/name', ['views/fields/person-name'], function (Dep) { }, getAvatarHtml: function () { - return this.getHelper().getAvatarHtml(this.model.id, 'small', 16, 'avatar-link'); + return this.getHelper().getAvatarHtml(this.model.id, 'small', 18, 'avatar-link'); }, }); }); diff --git a/composer.json b/composer.json index 47f15ac89f..0a8a0eac02 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,6 @@ "laminas/laminas-servicemanager": "^3.22", "laminas/laminas-crypt": "^3.11", "monolog/monolog": "~3.5", - "yzalis/identicon": "~2.0.0", "zordius/lightncandy": "dev-espo#v1.2.5e", "composer/semver": "^3", "spatie/async": "1.5.6", @@ -51,7 +50,8 @@ "brick/phonenumber": "^0.5.0", "picqer/php-barcode-generator": "^2.4", "chillerlan/php-qrcode": "^4.3", - "ext-ctype": "*" + "ext-ctype": "*", + "lasserafn/php-initial-avatar-generator": "^4.3" }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/composer.lock b/composer.lock index c123e4cfec..99f582d8fb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fe1e7604c6062d93a4d4ae62abcc73f6", + "content-hash": "a98b5f556eaf6120fad8b36c44d060bf", "packages": [ { "name": "async-aws/core", @@ -1335,6 +1335,90 @@ ], "time": "2023-04-17T16:00:37+00:00" }, + { + "name": "intervention/image", + "version": "2.7.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2022-05-21T17:30:32+00:00" + }, { "name": "johngrogg/ics-parser", "version": "v3.0.0", @@ -2082,6 +2166,167 @@ }, "time": "2022-09-08T13:45:54+00:00" }, + { + "name": "lasserafn/php-initial-avatar-generator", + "version": "4.3", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-initial-avatar-generator.git", + "reference": "ffe43d8df3f18646d7b23011272cce2d71400e1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-initial-avatar-generator/zipball/ffe43d8df3f18646d7b23011272cce2d71400e1f", + "reference": "ffe43d8df3f18646d7b23011272cce2d71400e1f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "intervention/image": "^2.3", + "lasserafn/php-initials": "^3.0", + "lasserafn/php-string-script-language": "^0.4", + "meyfa/php-svg": "^0.9.0", + "overtrue/pinyin": "^4.0", + "php": "^7.0|^7.1|^7.2|^7.3|^7.4|^8.0|^8.1|^8.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\InitialAvatarGenerator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "A package to generate avatars with initials for PHP", + "keywords": [ + "Initials", + "avatar", + "image", + "svg" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-initial-avatar-generator/issues", + "source": "https://github.com/LasseRafn/php-initial-avatar-generator/tree/4.3" + }, + "funding": [ + { + "url": "https://opencollective.com/ui-avatars", + "type": "open_collective" + } + ], + "time": "2023-07-26T19:17:56+00:00" + }, + { + "name": "lasserafn/php-initials", + "version": "3.1", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-initials.git", + "reference": "d287e1542687390eb68de779949bc0adc49e2d52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-initials/zipball/d287e1542687390eb68de779949bc0adc49e2d52", + "reference": "d287e1542687390eb68de779949bc0adc49e2d52", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0|^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\Initials\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "A package to generate initials in PHP", + "keywords": [ + "Initials", + "php" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-initials/issues", + "source": "https://github.com/LasseRafn/php-initials/tree/3.1" + }, + "time": "2020-12-24T12:25:51+00:00" + }, + { + "name": "lasserafn/php-string-script-language", + "version": "0.4", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-string-script-language.git", + "reference": "cab5612d4382067de855fcecc7c09108dca77fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-string-script-language/zipball/cab5612d4382067de855fcecc7c09108dca77fb5", + "reference": "cab5612d4382067de855fcecc7c09108dca77fb5", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0|^7.1|^8.0|^8.1|^8.2" + }, + "require-dev": { + "doctrine/instantiator": "1.0.5", + "phpunit/phpunit": "^5.6", + "phpunit/phpunit-mock-objects": "3.2.4", + "satooshi/php-coveralls": "^1.0", + "sebastian/exporter": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "Detect language/encoding of a string in PHP", + "keywords": [ + "language", + "php", + "string" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-string-script-language/issues", + "source": "https://github.com/LasseRafn/php-string-script-language/tree/0.4" + }, + "time": "2023-07-26T07:23:39+00:00" + }, { "name": "league/flysystem", "version": "2.1.1", @@ -2611,6 +2856,56 @@ }, "time": "2023-05-10T11:58:31+00:00" }, + { + "name": "meyfa/php-svg", + "version": "v0.9.1", + "source": { + "type": "git", + "url": "https://github.com/meyfa/php-svg.git", + "reference": "34401edef1f724898f468f71b85505fbcc8351bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/meyfa/php-svg/zipball/34401edef1f724898f468f71b85505fbcc8351bb", + "reference": "34401edef1f724898f468f71b85505fbcc8351bb", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-simplexml": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "meyfa/phpunit-assert-gd": "^1.1", + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "SVG\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabian Meyer", + "homepage": "http://meyfa.net" + } + ], + "description": "Read, edit, write, and render SVG files with PHP", + "homepage": "https://github.com/meyfa/php-svg", + "keywords": [ + "svg" + ], + "support": { + "issues": "https://github.com/meyfa/php-svg/issues", + "source": "https://github.com/meyfa/php-svg/tree/v0.9.1" + }, + "time": "2019-07-30T18:41:25+00:00" + }, { "name": "michelf/php-markdown", "version": "1.9.0", @@ -3070,6 +3365,79 @@ ], "time": "2023-11-22T15:34:18+00:00" }, + { + "name": "overtrue/pinyin", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/pinyin.git", + "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/pinyin/zipball/4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7", + "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.7", + "friendsofphp/php-cs-fixer": "^2.16", + "phpunit/phpunit": "~8.0" + }, + "type": "library", + "extra": { + "hooks": { + "pre-commit": [ + "composer test", + "composer fix-style" + ], + "pre-push": [ + "composer test", + "composer check-style" + ] + } + }, + "autoload": { + "files": [ + "src/const.php" + ], + "psr-4": { + "Overtrue\\Pinyin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com", + "homepage": "http://github.com/overtrue" + } + ], + "description": "Chinese to pinyin translator.", + "homepage": "https://github.com/overtrue/pinyin", + "keywords": [ + "Chinese", + "Pinyin", + "cn2pinyin" + ], + "support": { + "issues": "https://github.com/overtrue/pinyin/issues", + "source": "https://github.com/overtrue/pinyin/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2023-04-27T10:17:12+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v2.6.3", @@ -6299,63 +6667,6 @@ }, "time": "2022-06-03T18:03:27+00:00" }, - { - "name": "yzalis/identicon", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/yzalis/Identicon.git", - "reference": "ff5ed090129cab9bfa2a322857d4a01d107aa0ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yzalis/Identicon/zipball/ff5ed090129cab9bfa2a322857d4a01d107aa0ae", - "reference": "ff5ed090129cab9bfa2a322857d4a01d107aa0ae", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "ext-imagick": "*", - "fzaninotto/faker": "^1.2.0", - "phpunit/phpunit": "^4.0 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "Identicon\\": "src/Identicon/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Laugueux", - "email": "benjamin@yzalis.com" - } - ], - "description": "Create awesome unique avatar.", - "homepage": "http://identicon-php.org", - "keywords": [ - "avatar", - "identicon", - "image" - ], - "support": { - "issues": "https://github.com/yzalis/Identicon/issues", - "source": "https://github.com/yzalis/Identicon/tree/master" - }, - "abandoned": true, - "time": "2019-10-14T09:30:57+00:00" - }, { "name": "zbateson/mail-mime-parser", "version": "1.3.1", diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 8072d7af99..99c4063d84 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -1987,7 +1987,7 @@ td > span.color-icon { } img.avatar { - border-radius: var(--border-radius-small); + border-radius: 50%; } img.avatar.avatar-link {