From 0ebedbf94484aa90b694c1dd1b49317fc4a603ae Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Thu, 17 Dec 2020 18:57:36 +0100 Subject: [PATCH 01/17] Adding static analysis --- composer.json | 10 ++++++++++ phpstan.neon | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 3cff8de..8277571 100644 --- a/composer.json +++ b/composer.json @@ -21,5 +21,15 @@ ], "autoload": { "classmap": ["Multiavatar.php"] + }, + "require-dev": { + "phpstan/phpstan": "^0.12.63", + "phpstan/phpstan-strict-rules": "^0.12.7" + }, + "scripts": { + "phpstan": "phpstan analyse --level max Multiavatar.php -c phpstan.neon --ansi" + }, + "scripts-descriptions": { + "phpstan": "Static analysis of PHP scripts" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..deeb025 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon +parameters: + ignoreErrors: + reportUnmatchedIgnoredErrors: true From 44d934cb47fff6cbbfdc136e769d0fe41c2d5af6 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Thu, 17 Dec 2020 18:58:17 +0100 Subject: [PATCH 02/17] Update package settings --- .editorconfig | 18 ++++++++++++++++++ .gitattributes | 7 +++++++ .gitignore | 3 +++ 3 files changed, 28 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c656199 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.neon] +indent_size = 2 + +[*.xml.dist] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0ae277a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto + +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/phpstan.neon export-ignore +/README.md export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c272e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor +.idea +composer.lock From bebb3839059c94165a8e850269e5f7e5c560a83a Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Thu, 17 Dec 2020 18:58:41 +0100 Subject: [PATCH 03/17] Upgrade the implementation --- Multiavatar.php | 697 ++++++++++++++++++++++++++---------------------- README.md | 10 +- index.php | 34 +-- 3 files changed, 393 insertions(+), 348 deletions(-) diff --git a/Multiavatar.php b/Multiavatar.php index 21c6dd6..54ea01b 100644 --- a/Multiavatar.php +++ b/Multiavatar.php @@ -1,5 +1,9 @@ '; + private const SVG_ROOT_CLOSE_TAG = ''; + private const SVG_ELEMENT_ENV = ''; + private const SVG_ELEMENT_HEAD = ''; + private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:'; + + private const COEFFICIENT = 47 / 100; + + /** + * @var array>>> + */ + private static $themes = []; + + /** + * @var array> + */ + private static $shapes = []; + + /** + * @param object|int|float|string $avatarId the avatar id the object must implement the __toString method + * @param bool $sansEnv If this is true, the returns avatar is without the circle background (environment part). + * @param array $ver options to force a specific initial version, for example ['part' => '01', 'theme' => 'A'] + */ + public function __invoke($avatarId, bool $sansEnv = false, array $ver = []): string + { + if (is_object($avatarId) && method_exists($avatarId, '__toString')) { + $avatarId = (string) $avatarId; + } + + if (!is_scalar($avatarId)) { + throw new TypeError('Expected a scalar or a Stringable object; got: '.gettype($avatarId)); + } + + $avatarId = (string) $avatarId; + $avatarId = trim($avatarId); + if ('' === $avatarId) { + return ''; + } + + $svgParts = $this->bodyToSvgParts($this->avatarToBodyParts($avatarId), $sansEnv, $ver); + + return self::SVG_ROOT_OPEN_TAG + . $svgParts['env'] + . $svgParts['head'] + . $svgParts['clo'] + . $svgParts['top'] + . $svgParts['eyes'] + . $svgParts['mouth'] + . self::SVG_ROOT_CLOSE_TAG; + } + + /** + * @return array + */ + private function avatarToBodyParts(string $avatarId): array + { + /** @var string $str */ + $str = preg_replace("/\D/", "", hash('sha256', $avatarId)); + + $hash = substr($str, 0, 12); + + return array_map([$this, 'mapPart'], [ + 'env' => round(self::COEFFICIENT * ($hash[0] . $hash[1])), + 'clo' => round(self::COEFFICIENT * ($hash[2] . $hash[3])), + 'head' => round(self::COEFFICIENT * ($hash[4] . $hash[5])), + 'mouth' => round(self::COEFFICIENT * ($hash[6] . $hash[7])), + 'eyes' => round(self::COEFFICIENT * ($hash[8] . $hash[9])), + 'top' => round(self::COEFFICIENT * ($hash[10] . $hash[11])), + ]); + } + + private function mapPart(float $part): string + { + if ($part > 31) { + $part = $part - 32; + if ($part < 10) { + $part = '0' . $part; + } + + return $part . 'C'; + } + + if ($part > 15) { + $part = $part - 16; + if ($part < 10) { + $part = '0' . $part; + } + + return $part . 'B'; + } -class Multiavatar { + if ($part < 10) { + return '0' . $part . 'A'; + } - // string - public $avatarId; + return $part . 'A'; + } - // boolean - public $sansEnv; + /** + * @param array $bodyParts + * @param bool $sansEnv + * @param array $ver + * + * @return array + */ + private function bodyToSvgParts(array $bodyParts, bool $sansEnv, array $ver): array + { + $svgParts = []; + foreach ($bodyParts as $name => $bodyPart) { + if ('env' === $name && $sansEnv) { + $svgParts[$name] = ''; - // associative array - public $ver; + continue; + } + $part = $ver['part'] ?? substr($bodyPart, 0, 2); + + $theme = $ver['theme'] ?? substr($bodyPart, 2, 3); + $svgParts[$name] = $this->generateSvgPart($part, $name, $theme); + } - public function __construct($avatarId, $sansEnv, $ver) { - $this->svgCode = $this->generate(strval($avatarId), $sansEnv, $ver); + return $svgParts; } + /** + * @param string|int $part + */ + private function generateSvgPart($part, string $name, string $theme): string + { + self::loadAssets(); - public function getFinal($part, $partV, $theme, $themes, $sP) { - - $colors = $themes[$partV][$theme][$part]; - $svgString = $sP[$partV][$part]; + $svgString = self::$shapes[$part][$name]; + $found = preg_match_all('/#(.*?)+(?=;)/', $svgString, $result); + if (0 === $found || false === $found) { + return $svgString; + } - if (preg_match_all('/#(.*?)+(?=;)/', $svgString, $result)) { - $i = 0; - foreach ($result[0] as $var) { - $pos = strpos($svgString, $var); - if ($pos !== false) { - $svgString = substr_replace($svgString, $colors[$i], $pos, strlen($var)); - } - $i++; + $colors = self::$themes[$part][$theme][$name]; + foreach ($result[0] as $i => $var) { + $pos = strpos($svgString, $var); + if ($pos !== false) { + $svgString = substr_replace($svgString, $colors[$i], $pos, strlen($var)); } } - $resultFinal = $svgString; - return $resultFinal; + return $svgString; } + /** + * Lazy loading assets + */ + private static function loadAssets(): void + { + if ([] === self::$shapes) { + self::$shapes = self::shapes(); + } - public function generate($avatarId, $sansEnv, $ver) { - + if ([] === self::$themes) { + self::$themes = self::themes(); + } + } + + /** + * @return array> + */ + private static function shapes(): array + { + $shapes = []; + + // Robo + $shapes['00']['env'] = self::SVG_ELEMENT_ENV; + $shapes['00']['clo'] = ''; + $shapes['00']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['00']['mouth'] = ''; + $shapes['00']['eyes'] = ''; + $shapes['00']['top'] = ''; + + // Girl + $shapes['01'] = []; + $shapes['01']['env'] = self::SVG_ELEMENT_ENV; + $shapes['01']['clo'] = ''; + $shapes['01']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['01']['mouth'] = ''; + $shapes['01']['eyes'] = ''; + $shapes['01']['top'] = ''; + + // Blonde + $shapes['02'] = []; + $shapes['02']['env'] = self::SVG_ELEMENT_ENV; + $shapes['02']['clo'] = ''; + $shapes['02']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['02']['mouth'] = ''; + $shapes['02']['eyes'] = ''; + $shapes['02']['top'] = ''; + + // Guy + $shapes['03'] = []; + $shapes['03']['env'] = self::SVG_ELEMENT_ENV; + $shapes['03']['clo'] = ''; + $shapes['03']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['03']['mouth'] = ''; + $shapes['03']['eyes'] = ''; + $shapes['03']['top'] = ''; + + // Country + $shapes['04'] = []; + $shapes['04']['env'] = self::SVG_ELEMENT_ENV; + $shapes['04']['clo'] = ''; + $shapes['04']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['04']['mouth'] = ''; + $shapes['04']['eyes'] = ''; + $shapes['04']['top'] = ''; + + // Geeknot + $shapes['05'] = []; + $shapes['05']['env'] = self::SVG_ELEMENT_ENV; + $shapes['05']['clo'] = ''; + $shapes['05']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['05']['mouth'] = ''; + $shapes['05']['eyes'] = ''; + $shapes['05']['top'] = ''; + + // Asian + $shapes['06'] = []; + $shapes['06']['env'] = self::SVG_ELEMENT_ENV; + $shapes['06']['clo'] = ''; + $shapes['06']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['06']['mouth'] = ''; + $shapes['06']['eyes'] = ''; + $shapes['06']['top'] = ''; + + // Punk + $shapes['07'] = []; + $shapes['07']['env'] = self::SVG_ELEMENT_ENV; + $shapes['07']['clo'] = ''; + $shapes['07']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['07']['mouth'] = ''; + $shapes['07']['eyes'] = ''; + $shapes['07']['top'] = ''; + + // Afrohair + $shapes['08'] = []; + $shapes['08']['env'] = self::SVG_ELEMENT_ENV; + $shapes['08']['clo'] = ''; + $shapes['08']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['08']['mouth'] = ''; + $shapes['08']['eyes'] = ''; + $shapes['08']['top'] = ''; + + // Normie Female + $shapes['09'] = []; + $shapes['09']['env'] = self::SVG_ELEMENT_ENV; + $shapes['09']['clo'] = ''; + $shapes['09']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['09']['mouth'] = ''; + $shapes['09']['eyes'] = ''; + $shapes['09']['top'] = ''; + + // Older + $shapes['10'] = []; + $shapes['10']['env'] = self::SVG_ELEMENT_ENV; + $shapes['10']['clo'] = ''; + $shapes['10']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['10']['mouth'] = ''; + $shapes['10']['eyes'] = ''; + $shapes['10']['top'] = ''; + + // Firehair + $shapes['11'] = []; + $shapes['11']['env'] = self::SVG_ELEMENT_ENV; + $shapes['11']['clo'] = ''; + $shapes['11']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['11']['mouth'] = ''; + $shapes['11']['eyes'] = ''; + $shapes['11']['top'] = ''; + + // Blond + $shapes['12'] = []; + $shapes['12']['env'] = self::SVG_ELEMENT_ENV; + $shapes['12']['clo'] = ''; + $shapes['12']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['12']['mouth'] = ''; + $shapes['12']['eyes'] = ''; + $shapes['12']['top'] = ''; + + // Ateam + $shapes['13'] = []; + $shapes['13']['env'] = self::SVG_ELEMENT_ENV; + $shapes['13']['clo'] = ''; + $shapes['13']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['13']['mouth'] = ''; + $shapes['13']['eyes'] = ''; + $shapes['13']['top'] = ''; + + // Rasta + $shapes['14'] = []; + $shapes['14']['env'] = self::SVG_ELEMENT_ENV; + $shapes['14']['clo'] = ''; + $shapes['14']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['14']['mouth'] = ''; + $shapes['14']['eyes'] = ''; + $shapes['14']['top'] = ''; + + // Meta + $shapes['15'] = []; + $shapes['15']['env'] = self::SVG_ELEMENT_ENV; + $shapes['15']['clo'] = ''; + $shapes['15']['head'] = self::SVG_ELEMENT_HEAD; + $shapes['15']['mouth'] = ''; + $shapes['15']['eyes'] = ''; + $shapes['15']['top'] = ''; + + return $shapes; + } + + /** + * @return array>>> + */ + private static function themes(): array + { $themes = []; - + // Robo $themes['00']['A']['env'] = [ "#ff2f2b" ]; $themes['00']['A']['clo'] = ["#fff", "#000"]; @@ -57,21 +358,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['00']['A']['mouth'] = ["#fff", "#000", "#000"]; $themes['00']['A']['eyes'] = ["#000", "none", "#00FFFF"]; $themes['00']['A']['top'] = ["#fff", "#fff"]; - + $themes['00']['B']['env'] = ["#ff1ec1"]; $themes['00']['B']['clo'] = ["#000", "#fff" ]; $themes['00']['B']['head'] = ["#ffc1c1"]; $themes['00']['B']['mouth'] = ["#fff", "#000", "#000"]; $themes['00']['B']['eyes'] = ["#FF2D00", "#fff", "none"]; $themes['00']['B']['top'] = ["#a21d00", "#fff"]; - + $themes['00']['C']['env'] = ["#0079b1"]; $themes['00']['C']['clo'] = ["#0e00b1", "#d1fffe"]; $themes['00']['C']['head'] = ["#f5aa77"]; $themes['00']['C']['mouth'] = ["#fff", "#000", "#000"]; $themes['00']['C']['eyes'] = ["#0c00de", "#fff", "none"]; $themes['00']['C']['top'] = ["#acfffd", "#acfffd"]; - + // Girl $themes['01']['A']['env'] = ["#a50000"]; $themes['01']['A']['clo'] = ["#f06", "#8e0039"]; @@ -79,21 +380,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['01']['A']['mouth'] = ["#000"]; $themes['01']['A']['eyes'] = ["#000", "#ff9809"]; $themes['01']['A']['top'] = ["#ff9809", "#ff9809", "none", "none"]; - + $themes['01']['B']['env'] = ["#40E83B"]; $themes['01']['B']['clo'] = ["#00650b", "#62ce5a"]; $themes['01']['B']['head'] = ["#f7c1a6"]; $themes['01']['B']['mouth'] = ["#6e1c1c"]; $themes['01']['B']['eyes'] = ["#000", "#ff833b"]; $themes['01']['B']['top'] = ["#67FFCC", "none", "none", "#ecff3b"]; - + $themes['01']['C']['env'] = ["#ff2c2c"]; $themes['01']['C']['clo'] = ["#fff", "#000"]; $themes['01']['C']['head'] = ["#ffce8b"]; $themes['01']['C']['mouth'] = ["#000"]; $themes['01']['C']['eyes'] = ["#000", "#0072ff"]; $themes['01']['C']['top'] = ["#ff9809", "none", "#ffc809", "none"]; - + // Blonde $themes['02']['A']['env'] = ["#ff7520"]; $themes['02']['A']['clo'] = ["#d12823"]; @@ -101,21 +402,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['02']['A']['mouth'] = ["#d12823"]; $themes['02']['A']['eyes'] = ["#000", "none"]; $themes['02']['A']['top'] = ["#000", "none", "none", "#FFCC00", "red"]; - + $themes['02']['B']['env'] = ["#ff9700"]; $themes['02']['B']['clo'] = ["#000"]; $themes['02']['B']['head'] = ["#d2ad6d"]; $themes['02']['B']['mouth'] = ["#000"]; $themes['02']['B']['eyes'] = ["#000", "#00ffdc"]; $themes['02']['B']['top'] = ["#fdff00", "#fdff00", "none", "none", "none"]; - + $themes['02']['C']['env'] = ["#26a7ff"]; $themes['02']['C']['clo'] = ["#d85cd7"]; $themes['02']['C']['head'] = ["#542e02"]; $themes['02']['C']['mouth'] = ["#f70014"]; $themes['02']['C']['eyes'] = ["#000", "magenta"]; $themes['02']['C']['top'] = ["#FFCC00", "#FFCC00", "#FFCC00", "#ff0000", "yellow"]; - + // Evilnormie $themes['03']['A']['env'] = ["#6FC30E"]; $themes['03']['A']['clo'] = ["#b4e1fa", "#5b5d6e", "#515262", "#a0d2f0", "#a0d2f0"]; @@ -123,21 +424,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['03']['A']['mouth'] = ["#fff", "#000"]; $themes['03']['A']['eyes'] = ["#000"]; $themes['03']['A']['top'] = ["#8eff45", "#8eff45", "none", "none"]; - + $themes['03']['B']['env'] = ["#00a58c"]; $themes['03']['B']['clo'] = ["#000", "#5b00", "#5100", "#a000", "#a000"]; $themes['03']['B']['head'] = ["#FAD2B9"]; $themes['03']['B']['mouth'] = ["#fff", "#000"]; $themes['03']['B']['eyes'] = ["#000"]; $themes['03']['B']['top'] = ["#FFC600", "none", "#FFC600", "none"]; - + $themes['03']['C']['env'] = ["#ff501f"]; $themes['03']['C']['clo'] = ["#000", "#ff0000", "#ff0000", "#7d7d7d", "#7d7d7d"]; $themes['03']['C']['head'] = ["#fff3dc"]; $themes['03']['C']['mouth'] = ["#d2001b", "none"]; $themes['03']['C']['eyes'] = ["#000"]; $themes['03']['C']['top'] = ["#D2001B", "none", "none", "#D2001B"]; - + // Country $themes['04']['A']['env'] = ["#fc0"]; $themes['04']['A']['clo'] = ["#901e0e", "#ffbe1e", "#ffbe1e", "#c55f54"]; @@ -145,21 +446,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['04']['A']['mouth'] = ["#000", "none", "#000", "none"]; $themes['04']['A']['eyes'] = ["#000"]; $themes['04']['A']['top'] = ["#583D00", "#AF892E", "#462D00", "#a0a0a0"]; - + $themes['04']['B']['env'] = ["#386465"]; $themes['04']['B']['clo'] = ["#fff", "#333", "#333", "#333"]; $themes['04']['B']['head'] = ["#FFD79D"]; $themes['04']['B']['mouth'] = ["#000", "#000", "#000", "#000"]; $themes['04']['B']['eyes'] = ["#000"]; $themes['04']['B']['top'] = ["#27363C", "#5DCAD4", "#314652", "#333"]; - + $themes['04']['C']['env'] = ["#DFFF00"]; $themes['04']['C']['clo'] = ["#304267", "#aab0b1", "#aab0b1", "#aab0b1"]; $themes['04']['C']['head'] = ["#e6b876"]; $themes['04']['C']['mouth'] = ["#50230a", "#50230a", "#50230a", "#50230a"]; $themes['04']['C']['eyes'] = ["#000"]; $themes['04']['C']['top'] = ["#333", "#afafaf", "#222", "#6d3a1d"]; - + // Johnyold $themes['05']['A']['env'] = ["#a09300"]; $themes['05']['A']['clo'] = ["#c7d4e2", "#435363", "#435363", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; @@ -167,21 +468,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['05']['A']['mouth'] = ["#000", "#cf9f76"]; $themes['05']['A']['eyes'] = ["#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff", "#000", "#000"]; $themes['05']['A']['top'] = ["none", "#fdff00"]; - + $themes['05']['B']['env'] = ["#b3003e"]; $themes['05']['B']['clo'] = ["#000", "#435363", "#435363", "#000", "none", "#e7ecf2", "#e7ecf2"]; $themes['05']['B']['head'] = ["#f5d4a6"]; $themes['05']['B']['mouth'] = ["#000", "#af9f94"]; $themes['05']['B']['eyes'] = ["#9ff3ffdb", "#000", "#9ff3ffdb", "#000", "#2f508a", "#000", "#000", "#000", "none", "none", "none", "none"]; $themes['05']['B']['top'] = ["#ff9a00", "#ff9a00"]; - + $themes['05']['C']['env'] = ["#884f00"]; $themes['05']['C']['clo'] = ["#ff0000", "#fff", "#fff", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; $themes['05']['C']['head'] = ["#c57b14"]; $themes['05']['C']['mouth'] = ["#000", "#cf9f76"]; $themes['05']['C']['eyes'] = ["none", "#000", "none", "#000", "#5a0000", "#000", "#000", "#000", "none", "none", "none", "none"]; $themes['05']['C']['top'] = ["#efefef", "none"]; - + // Asian $themes['06']['A']['env'] = ["#8acf00"]; $themes['06']['A']['clo'] = ["#ee2829", "#ff0"]; @@ -189,21 +490,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['06']['A']['mouth'] = ["#fff", "#000"]; $themes['06']['A']['eyes'] = ["#000"]; $themes['06']['A']['top'] = ["#000","#000","none", "#000", "#ff4e4e", "#000"]; - + $themes['06']['B']['env'] = ["#00d2a3"]; $themes['06']['B']['clo'] = ["#0D0046", "#ffce73"]; $themes['06']['B']['head'] = ["#ffce73"]; $themes['06']['B']['mouth'] = ["#000", "none"]; $themes['06']['B']['eyes'] = ["#000"]; $themes['06']['B']['top'] = ["#000","#000","#000", "none", "#ffb358", "#000", "none", "none"]; - + $themes['06']['C']['env'] = ["#ff184e"]; $themes['06']['C']['clo'] = ["#000", "none"]; $themes['06']['C']['head'] = ["#ffce73"]; $themes['06']['C']['mouth'] = ["#ff0000", "none"]; $themes['06']['C']['eyes'] = ["#000"]; $themes['06']['C']['top'] = ["none","none","none", "none", "none", "#ffc107", "none", "none"]; - + // Punk $themes['07']['A']['env'] = ["#00deae"]; $themes['07']['A']['clo'] = ["#ff0000"]; @@ -211,21 +512,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['07']['A']['mouth'] = ["#f73b6c", "#000"]; $themes['07']['A']['eyes'] = ["#e91e63", "#000", "#e91e63", "#000", "#000", "#000"]; $themes['07']['A']['top'] = ["#dd104f", "#dd104f", "#f73b6c", "#dd104f"]; - + $themes['07']['B']['env'] = ["#181284"]; $themes['07']['B']['clo'] = ["#491f49", "#ff9809", "#491f49"]; $themes['07']['B']['head'] = ["#f6ba97"]; $themes['07']['B']['mouth'] = ["#ff9809", "#000"]; $themes['07']['B']['eyes'] = ["#c4ffe4", "#000", "#c4ffe4", "#000", "#000", "#000"]; $themes['07']['B']['top'] = [ "none", "none", "#d6f740", "#516303"]; - + $themes['07']['C']['env'] = ["#bcf700"]; $themes['07']['C']['clo'] = ["#ff14e4", "#000", "#14fffd"]; $themes['07']['C']['head'] = ["#7b401e"]; $themes['07']['C']['mouth'] = ["#666", "#000"]; $themes['07']['C']['eyes'] = ["#00b5b4", "#000", "#00b5b4", "#000", "#000", "#000"]; $themes['07']['C']['top'] = ["#14fffd", "#14fffd", "#14fffd", "#0d3a62"]; - + // Afrohair $themes['08']['A']['env'] = ["#0df"]; $themes['08']['A']['clo'] = ["#571e57", "#ff0"]; @@ -233,21 +534,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['08']['A']['mouth'] = ["#795548", "#000"]; $themes['08']['A']['eyes'] = ["#ff0000"]; $themes['08']['A']['top'] = ["#de3b00", "none"]; - + $themes['08']['B']['env'] = ["#B400C2"]; $themes['08']['B']['clo'] = ["#0D204A", "#00ffdf"]; $themes['08']['B']['head'] = ["#ca8628"]; $themes['08']['B']['mouth'] = ["#cbbdaf", "#000"]; $themes['08']['B']['eyes'] = ["#1a1a1a"]; $themes['08']['B']['top'] = ["#000", "#000"]; - + $themes['08']['C']['env'] = ["#ffe926"]; $themes['08']['C']['clo'] = ["#00d6af", "#000"]; $themes['08']['C']['head'] = ["#8c5100"]; $themes['08']['C']['mouth'] = ["none", "#000"]; $themes['08']['C']['eyes'] = ["#7d0000"]; $themes['08']['C']['top'] = ["#f7f7f7", "none"]; - + // Normie female $themes['09']['A']['env'] = ["#4aff0c"]; $themes['09']['A']['clo'] = ["#101010", "#fff", "#fff"]; @@ -255,21 +556,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['09']['A']['mouth'] = ["#000"]; $themes['09']['A']['eyes'] = [ "#000", "none", "none"]; $themes['09']['A']['top'] = ["#531148", "#531148", "#531148", "none"]; - + $themes['09']['B']['env'] = ["#FFC107"]; $themes['09']['B']['clo'] = ["#033c58", "#fff", "#fff"]; $themes['09']['B']['head'] = ["#dbc97f"]; $themes['09']['B']['mouth'] = ["#000"]; $themes['09']['B']['eyes'] = ["none", "#fff", "#000"]; $themes['09']['B']['top'] = ["#FFEB3B", "#FFEB3B", "none", "#FFEB3B"]; - + $themes['09']['C']['env'] = ["#FF9800"]; $themes['09']['C']['clo'] = ["#b40000", "#fff", "#fff"]; $themes['09']['C']['head'] = ["#E2AF6B"]; $themes['09']['C']['mouth'] = ["#000"]; $themes['09']['C']['eyes'] = ["none", "#fff", "#000"]; $themes['09']['C']['top'] = ["#ec0000", "#ec0000", "none", "none"]; - + // Older $themes['10']['A']['env'] = ["#104c8c"]; $themes['10']['A']['clo'] = ["#354B65", "#3D8EBB", "#89D0DA", "#00FFFD" ]; @@ -277,21 +578,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['10']['A']['mouth'] = ["#222", "#fff"]; $themes['10']['A']['eyes'] = ["#000", "#000"]; $themes['10']['A']['top'] = ["#fff", "#fff", "none"]; - + $themes['10']['B']['env'] = ["#0DC15C"]; $themes['10']['B']['clo'] = ["#212121", "#fff", "#212121", "#fff", ]; $themes['10']['B']['head'] = ["#dca45f"]; $themes['10']['B']['mouth'] = ["#111", "#633b1d"]; $themes['10']['B']['eyes'] = ["#000", "#000"]; $themes['10']['B']['top'] = ["none", "#792B74", "#792B74"]; - + $themes['10']['C']['env'] = ["#ffe500"]; $themes['10']['C']['clo'] = ["#1e5e80", "#fff", "#1e5e80", "#fff"]; $themes['10']['C']['head'] = ["#e8bc86"]; $themes['10']['C']['mouth'] = ["#111", "none"]; $themes['10']['C']['eyes'] = ["#000", "#000"]; $themes['10']['C']['top'] = ["none", "none", "#633b1d"]; - + // Firehair $themes['11']['A']['env'] = ["#4a3f73"]; $themes['11']['A']['clo'] = ["#e6e9ee", "#f1543f", "#ff7058", "#fff", "#fff"]; @@ -299,21 +600,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['11']['A']['mouth'] = ["#191919", "#191919"]; $themes['11']['A']['eyes'] = ["#000", "#000", "#57FFFD"]; $themes['11']['A']['top'] = ["#ffc", "#ffc", "#ffc"]; - + $themes['11']['B']['env'] = ["#00a08d"]; $themes['11']['B']['clo'] = ["#FFBA32", "#484848", "#4e4e4e", "#fff", "#fff"]; $themes['11']['B']['head'] = ["#ab5f2c"]; $themes['11']['B']['mouth'] = ["#191919", "#191919"]; $themes['11']['B']['eyes'] = ["#000", "#ff23fa63", "#000"]; $themes['11']['B']['top'] = ["#ff90f4", "#ff90f4", "#ff90f4"]; - + $themes['11']['C']['env'] = ["#22535d"]; $themes['11']['C']['clo'] = ["#000", "#ff2500", "#ff2500", "#fff", "#fff"]; $themes['11']['C']['head'] = ["#a76c44"]; $themes['11']['C']['mouth'] = ["#191919", "#191919"]; $themes['11']['C']['eyes'] = ["#000", "none", "#000"]; $themes['11']['C']['top'] = ["none", "#00efff", "none"]; - + // Blond $themes['12']['A']['env'] = ["#2668DC"]; $themes['12']['A']['clo'] = ["#2385c6", "#b8d0e0", "#b8d0e0"]; @@ -321,21 +622,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['12']['A']['mouth'] = ["#000", "#4d4d4d"]; $themes['12']['A']['eyes'] = ["#7fb5a2", "#d1eddf", "#301e19"]; $themes['12']['A']['top'] = ["#fff510", "#fff510"]; - + $themes['12']['B']['env'] = ["#643869"]; $themes['12']['B']['clo'] = ["#D67D1B", "#b8d0e0", "#b8d0e0"]; $themes['12']['B']['head'] = ["#CC985A", "none0000"]; $themes['12']['B']['mouth'] = ["#000", "#ececec"]; $themes['12']['B']['eyes'] = ["#1f2644", "#9b97ce", "#301e19"]; $themes['12']['B']['top'] = ["#00eaff", "none"]; - + $themes['12']['C']['env'] = ["#F599FF"]; $themes['12']['C']['clo'] = ["#2823C6", "#b8d0e0", "#b8d0e0"]; $themes['12']['C']['head'] = ["#C7873A"]; $themes['12']['C']['mouth'] = ["#000", "#4d4d4d"]; $themes['12']['C']['eyes'] = ["#581b1b", "#FF8B8B", "#000"]; $themes['12']['C']['top'] = ["none", "#9c0092"]; - + // Ateam $themes['13']['A']['env'] = ["#d10084"]; $themes['13']['A']['clo'] = ["#efedee", "#00a1e0", "#00a1e0", "#efedee", "#ffce1c"]; @@ -343,21 +644,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['13']['A']['mouth'] = ["#3a484a", "#000"]; $themes['13']['A']['eyes'] = ["#000"]; $themes['13']['A']['top'] = ["#000", "none", "#000", "none"]; - + $themes['13']['B']['env'] = ["#E6C117"]; $themes['13']['B']['clo'] = ["#efedee", "#ec0033", "#ec0033", "#efedee", "#f2ff05"]; $themes['13']['B']['head'] = ["#ffc016"]; $themes['13']['B']['mouth'] = ["#4a3737", "#000"]; $themes['13']['B']['eyes'] = ["#000"]; $themes['13']['B']['top'] = ["#ffe900", "#ffe900", "none", "#ffe900"]; - + $themes['13']['C']['env'] = ["#1d8c00"]; $themes['13']['C']['clo'] = ["#e000cb", "#fff", "#fff", "#e000cb", "#ffce1c"]; $themes['13']['C']['head'] = ["#b96438"]; $themes['13']['C']['mouth'] = ["#000", "#000"]; $themes['13']['C']['eyes'] = ["#000"]; $themes['13']['C']['top'] = ["#53ffff", "#53ffff", "none", "none"]; - + // Rasta $themes['14']['A']['env'] = ["#fc0065"]; $themes['14']['A']['clo'] = ["#708913", "#fdea14", "#708913", "#fdea14", "#708913"]; @@ -365,21 +666,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['14']['A']['mouth'] = ["#444", "#000"]; $themes['14']['A']['eyes'] = ["#000"]; $themes['14']['A']['top'] = ["#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f" ]; - + $themes['14']['B']['env'] = ["#81f72e"]; $themes['14']['B']['clo'] = ["#ff0000", "#ffc107", "#ff0000", "#ffc107", "#ff0000"]; $themes['14']['B']['head'] = ["#ef9831"]; $themes['14']['B']['mouth'] = ["#6b0000", "#000"]; $themes['14']['B']['eyes'] = ["#000"]; $themes['14']['B']['top'] = ["#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "none", "none", "none", "none"]; - + $themes['14']['C']['env'] = ["#00D872"]; $themes['14']['C']['clo'] = ["#590D00", "#FD1336", "#590D00", "#FD1336", "#590D00"]; $themes['14']['C']['head'] = ["#c36c00"]; $themes['14']['C']['mouth'] = ["#56442b", "#000"]; $themes['14']['C']['eyes'] = ["#000"]; $themes['14']['C']['top'] = ["#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "none", "none", "none", "none", "none", "none", "none", "none"]; - + // Meta $themes['15']['A']['env'] = ["#111"]; $themes['15']['A']['clo'] = ["#000", "#00FFFF"]; @@ -387,269 +688,21 @@ public function generate($avatarId, $sansEnv, $ver) { $themes['15']['A']['mouth'] = ["#fff", "#000"]; $themes['15']['A']['eyes'] = ["black", "#008a", "aqua"]; $themes['15']['A']['top'] = ["#fff", "#fff", "#fff", "#fff", "#fff"]; - + $themes['15']['B']['env'] = ["#00D0D4"]; $themes['15']['B']['clo'] = ["#000", "#fff"]; $themes['15']['B']['head'] = ["#755227"]; $themes['15']['B']['mouth'] = ["#fff", "#000"]; $themes['15']['B']['eyes'] = ["black", "#1df7ffa3", "#fcff2c"]; $themes['15']['B']['top'] = ["#fff539", "none", "#fff539", "none", "#fff539"]; - + $themes['15']['C']['env'] = ["#DC75FF"]; $themes['15']['C']['clo'] = ["#000", "#FFBDEC"]; $themes['15']['C']['head'] = ["#997549"]; $themes['15']['C']['mouth'] = ["#fff", "#000"]; $themes['15']['C']['eyes'] = ["black", "black", "aqua"]; $themes['15']['C']['top'] = ["#00fffd", "none", "none", "none", "none"]; - - - // Optimization - $sP = []; - $svgStart = ''; - $svgEnd = ''; - $env = ''; - $head = ''; - $str = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:'; - - - // SHAPES - - // Robo - $sP['00']['env'] = $env; - $sP['00']['clo'] = ''; - $sP['00']['head'] = $head; - $sP['00']['mouth'] = ''; - $sP['00']['eyes'] = ''; - $sP['00']['top'] = ''; - - // Girl - $sP['01'] = []; - $sP['01']['env'] = $env; - $sP['01']['clo'] = ''; - $sP['01']['head'] = $head; - $sP['01']['mouth'] = ''; - $sP['01']['eyes'] = ''; - $sP['01']['top'] = ''; - - // Blonde - $sP['02'] = []; - $sP['02']['env'] = $env; - $sP['02']['clo'] = ''; - $sP['02']['head'] = $head; - $sP['02']['mouth'] = ''; - $sP['02']['eyes'] = ''; - $sP['02']['top'] = ''; - - // Guy - $sP['03'] = []; - $sP['03']['env'] = $env; - $sP['03']['clo'] = ''; - $sP['03']['head'] = $head; - $sP['03']['mouth'] = ''; - $sP['03']['eyes'] = ''; - $sP['03']['top'] = ''; - - // Country - $sP['04'] = []; - $sP['04']['env'] = $env; - $sP['04']['clo'] = ''; - $sP['04']['head'] = $head; - $sP['04']['mouth'] = ''; - $sP['04']['eyes'] = ''; - $sP['04']['top'] = ''; - - // Geeknot - $sP['05'] = []; - $sP['05']['env'] = $env; - $sP['05']['clo'] = ''; - $sP['05']['head'] = $head; - $sP['05']['mouth'] = ''; - $sP['05']['eyes'] = ''; - $sP['05']['top'] = ''; - - // Asian - $sP['06'] = []; - $sP['06']['env'] = $env; - $sP['06']['clo'] = ''; - $sP['06']['head'] = $head; - $sP['06']['mouth'] = ''; - $sP['06']['eyes'] = ''; - $sP['06']['top'] = ''; - - // Punk - $sP['07'] = []; - $sP['07']['env'] = $env; - $sP['07']['clo'] = ''; - $sP['07']['head'] = $head; - $sP['07']['mouth'] = ''; - $sP['07']['eyes'] = ''; - $sP['07']['top'] = ''; - - // Afrohair - $sP['08'] = []; - $sP['08']['env'] = $env; - $sP['08']['clo'] = ''; - $sP['08']['head'] = $head; - $sP['08']['mouth'] = ''; - $sP['08']['eyes'] = ''; - $sP['08']['top'] = ''; - - // Normie Female - $sP['09'] = []; - $sP['09']['env'] = $env; - $sP['09']['clo'] = ''; - $sP['09']['head'] = $head; - $sP['09']['mouth'] = ''; - $sP['09']['eyes'] = ''; - $sP['09']['top'] = ''; - - // Older - $sP['10'] = []; - $sP['10']['env'] = $env; - $sP['10']['clo'] = ''; - $sP['10']['head'] = $head; - $sP['10']['mouth'] = ''; - $sP['10']['eyes'] = ''; - $sP['10']['top'] = ''; - - // Firehair - $sP['11'] = []; - $sP['11']['env'] = $env; - $sP['11']['clo'] = ''; - $sP['11']['head'] = $head; - $sP['11']['mouth'] = ''; - $sP['11']['eyes'] = ''; - $sP['11']['top'] = ''; - - // Blond - $sP['12'] = []; - $sP['12']['env'] = $env; - $sP['12']['clo'] = ''; - $sP['12']['head'] = $head; - $sP['12']['mouth'] = ''; - $sP['12']['eyes'] = ''; - $sP['12']['top'] = ''; - - // Ateam - $sP['13'] = []; - $sP['13']['env'] = $env; - $sP['13']['clo'] = ''; - $sP['13']['head'] = $head; - $sP['13']['mouth'] = ''; - $sP['13']['eyes'] = ''; - $sP['13']['top'] = ''; - - // Rasta - $sP['14'] = []; - $sP['14']['env'] = $env; - $sP['14']['clo'] = ''; - $sP['14']['head'] = $head; - $sP['14']['mouth'] = ''; - $sP['14']['eyes'] = ''; - $sP['14']['top'] = ''; - - // Meta - $sP['15'] = []; - $sP['15']['env'] = $env; - $sP['15']['clo'] = ''; - $sP['15']['head'] = $head; - $sP['15']['mouth'] = ''; - $sP['15']['eyes'] = ''; - $sP['15']['top'] = ''; - - - // Multiavatar algorithm - $hash = ''; - - if (strlen($avatarId) == 0) return $hash; - - $sha256Hash = hash('sha256', $avatarId); - $sha256Numbers = preg_replace("/[^0-9]/", "", $sha256Hash); - $hash = substr($sha256Numbers, 0, 12); - - - // Parts - $p = []; - - // Get parts (range 0-47) - $p['env'] = $hash[0] . '' . $hash[1]; - $p['env'] = round((47/100)*$p['env']); - - // Freeze a single part - // e.g. '0' = 01A; '16' = 01B; '32' = 01C; '1' = 02A; '17' = 02B; - // p['env'] = '16'; - - $p['clo'] = $hash[2] . '' . $hash[3]; - $p['clo'] = round((47/100)*$p['clo']); - - // p['clo'] = '16'; - - $p['head'] = $hash[4] . '' . $hash[5]; - $p['head'] = round((47/100)*$p['head']); - - // p['head'] = '01'; - - $p['mouth'] = $hash[6] . '' . $hash[7]; - $p['mouth'] = round((47/100)*$p['mouth']); - - // p['mouth'] = '16'; - - $p['eyes'] = $hash[8] . '' . $hash[9]; - $p['eyes'] = round((47/100)*$p['eyes']); - - // p['eyes'] = '16'; - - $p['top'] = $hash[10] . '' . $hash[11]; - $p['top'] = round((47/100)*$p['top']); - - // p['top'] = '25'; - - - // Get parts (range 0-15) + define themes - foreach ($p as $key => $part) { - $nr = $part; - - if ($nr > 31) { - $nr = $nr - 32 . ''; - if ($nr < 10) $nr = '0' . $nr; - $p[$key] = $nr . 'C'; - } - else if ($nr > 15) { - $nr = $nr - 16; - if ($nr < 10) $nr = '0' . $nr; - $p[$key] = $nr . 'B'; - } - else { - if ($nr < 10) $p[$key] = '0' . $nr . 'A'; - else $p[$key] = $nr . 'A'; - } - } - - // Get the SVG code for each part - $final = []; - - foreach ($p as $key => $part) { - $partV = substr($p[$key], 0, 2); - $theme = substr($p[$key], 2, 3); - - if ($ver != null) { - $partV = $ver['part']; - $theme = $ver['theme']; - } - - // Freeze a single base version - // $partV = '00'; $theme = 'A'; - - $final[$key] = $this->getFinal($key, $partV, $theme, $themes, $sP); - } - - - // Without 'env' - if ($sansEnv) { - $final['env'] = ''; - } - - return($svgStart . $final['env'] . $final['head'] . $final['clo'] . $final['top'] . $final['eyes'] . $final['mouth'] . $svgEnd); + return $themes; } } diff --git a/README.md b/README.md index d10aadc..672b33b 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ composer require multiavatar/multiavatar-php ### Usage ### ``` +$multiavatar = new Multiavatar(); $avatarId = "Binx Bond"; -$multiavatar = new Multiavatar($avatarId, null, null); -echo($multiavatar->svgCode); +echo $multiavatar($avatarId); ``` For advanced usage, pass boolean `true` as the second parameter if you wish to generate an avatar without the environment part. @@ -39,9 +39,9 @@ For advanced usage, pass boolean `true` as the second parameter if you wish to g Pass an associative array as the third parameter to generate a specific avatar version. ``` +$multiavatar = new Multiavatar(); $avatarId = "ANY_STRING"; -$multiavatar = new Multiavatar($avatarId, true, array("part" => 11, "theme" => "C")); -echo($multiavatar->svgCode); +echo $multiavatar($avatarId, true, ["part" => "11", "theme" => "C"]); ``` See `index.php` for examples. @@ -88,4 +88,4 @@ For additional information and extended functionality, visit the [multiavatar.co The app is based on static html for the home page, and on Laravel 8 + Vue.js for extended functionality, including the web store. -The product mockup generator for the [Merch Maker](https://multiavatar.com/merch-maker) is based on the ImageMagick PHP library. \ No newline at end of file +The product mockup generator for the [Merch Maker](https://multiavatar.com/merch-maker) is based on the ImageMagick PHP library. diff --git a/index.php b/index.php index 9cc950b..e977388 100644 --- a/index.php +++ b/index.php @@ -1,36 +1,28 @@ svgCode); +$multiAvatar = new Multiavatar(); +$avatarId = "Starcrasher"; +echo $multiAvatar($avatarId); // Without the environment part -$avatarId = "Pandalion"; -$multiavatar = new Multiavatar($avatarId, true, null); -echo($multiavatar->svgCode); - +// $avatarId = "Pandalion"; +// echo $multiAvatar($avatarId, true); // Generate a specific version -$avatarId = "Pandalion"; -$multiavatar = new Multiavatar($avatarId, null, array("part" => 11, "theme" => "C")); -echo($multiavatar->svgCode); - +// $avatarId = "Pandalion"; +// echo $multiAvatar($avatarId, false, ["part" => 11, "theme" => "C"]); // Test with integer -$avatarId = 123456789; -$multiavatar = new Multiavatar($avatarId, null, null); -echo($multiavatar->svgCode); - +// $avatarId = 123456789; +// echo $multiAvatar($avatarId); // $avatarId = "a86f755add37fe0b649c"; -// $multiavatar = new Multiavatar($avatarId, null, null); -// echo($multiavatar->svgCode); - +// echo $multiAvatar($avatarId); // $avatarId = "f7542474d54d2d2d97e4"; -// $multiavatar = new Multiavatar($avatarId, null, null); -// echo($multiavatar->svgCode); \ No newline at end of file +// echo $multiAvatar($avatarId); From 453f19d1ab3c08f1847eb4916312fa75a51624eb Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Thu, 17 Dec 2020 19:36:46 +0100 Subject: [PATCH 04/17] Improve implementation --- Multiavatar.php | 1074 +++++++++++++++++++++++------------------------ index.php | 2 - 2 files changed, 531 insertions(+), 545 deletions(-) diff --git a/Multiavatar.php b/Multiavatar.php index 54ea01b..29ad2cd 100644 --- a/Multiavatar.php +++ b/Multiavatar.php @@ -2,8 +2,6 @@ declare(strict_types=1); -namespace Multiavatar; - // Multiavatar v1.0 (PHP version) // package Multiavatar-PHP @@ -12,22 +10,6 @@ // license https://multiavatar.com/license // homepage https://multiavatar.com -use TypeError; -use function array_map; -use function gettype; -use function hash; -use function is_object; -use function is_scalar; -use function method_exists; -use function preg_match_all; -use function preg_replace; -use function round; -use function strlen; -use function strpos; -use function substr; -use function substr_replace; -use function trim; - class Multiavatar { private const SVG_ROOT_OPEN_TAG = ''; @@ -36,7 +18,7 @@ class Multiavatar private const SVG_ELEMENT_HEAD = ''; private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:'; - private const COEFFICIENT = 47 / 100; + private const THEME_LIST = [0 => 'A', 1 => 'B', 2 => 'C']; /** * @var array>>> @@ -51,144 +33,146 @@ class Multiavatar /** * @param object|int|float|string $avatarId the avatar id the object must implement the __toString method * @param bool $sansEnv If this is true, the returns avatar is without the circle background (environment part). - * @param array $ver options to force a specific initial version, for example ['part' => '01', 'theme' => 'A'] + * @param array $options options to force a specific initial version, for example ['part' => '01', 'theme' => 'A'] */ - public function __invoke($avatarId, bool $sansEnv = false, array $ver = []): string + public function __invoke($avatarId, bool $sansEnv = false, array $options = []): string { - if (is_object($avatarId) && method_exists($avatarId, '__toString')) { - $avatarId = (string) $avatarId; - } - - if (!is_scalar($avatarId)) { - throw new TypeError('Expected a scalar or a Stringable object; got: '.gettype($avatarId)); - } - - $avatarId = (string) $avatarId; - $avatarId = trim($avatarId); + $avatarId = $this->filterAvatar($avatarId); + $options = $this->filterOptions($options); if ('' === $avatarId) { return ''; } - $svgParts = $this->bodyToSvgParts($this->avatarToBodyParts($avatarId), $sansEnv, $ver); + $svgElements = $this->partsToSvgElements($this->avatarToParts($avatarId), $sansEnv, $options); return self::SVG_ROOT_OPEN_TAG - . $svgParts['env'] - . $svgParts['head'] - . $svgParts['clo'] - . $svgParts['top'] - . $svgParts['eyes'] - . $svgParts['mouth'] + . $svgElements['env'] + . $svgElements['head'] + . $svgElements['clo'] + . $svgElements['top'] + . $svgElements['eyes'] + . $svgElements['mouth'] . self::SVG_ROOT_CLOSE_TAG; } /** - * @return array + * @param object|string|float|int $avatarId */ - private function avatarToBodyParts(string $avatarId): array + private function filterAvatar($avatarId): string { - /** @var string $str */ - $str = preg_replace("/\D/", "", hash('sha256', $avatarId)); + if (is_object($avatarId) && method_exists($avatarId, '__toString')) { + $avatarId = (string) $avatarId; + } - $hash = substr($str, 0, 12); + if (!is_scalar($avatarId)) { + throw new TypeError('Expected a scalar or a Stringable object; got: ' . gettype($avatarId)); + } - return array_map([$this, 'mapPart'], [ - 'env' => round(self::COEFFICIENT * ($hash[0] . $hash[1])), - 'clo' => round(self::COEFFICIENT * ($hash[2] . $hash[3])), - 'head' => round(self::COEFFICIENT * ($hash[4] . $hash[5])), - 'mouth' => round(self::COEFFICIENT * ($hash[6] . $hash[7])), - 'eyes' => round(self::COEFFICIENT * ($hash[8] . $hash[9])), - 'top' => round(self::COEFFICIENT * ($hash[10] . $hash[11])), - ]); + return trim((string) $avatarId); } - private function mapPart(float $part): string + /** + * @param array $inputOptions + * + * @return array{part:string|null, theme:string|null} + */ + private function filterOptions(array $inputOptions): array { - if ($part > 31) { - $part = $part - 32; - if ($part < 10) { - $part = '0' . $part; + $options = ['part' => null, 'theme' => null]; + if (isset($inputOptions['part'])) { + $part = sprintf("%'.02d", $inputOptions['part']); + if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) { + throw new InvalidArgumentException('The submitted part does not exists.'); } - return $part . 'C'; + $options['part'] = $part; } - if ($part > 15) { - $part = $part - 16; - if ($part < 10) { - $part = '0' . $part; + if (isset($inputOptions['theme'])) { + if (!in_array($inputOptions['theme'], self::THEME_LIST, true)) { + throw new InvalidArgumentException('The submitted theme does not exists.'); } - - return $part . 'B'; + $options['theme'] = $inputOptions['theme']; } - if ($part < 10) { - return '0' . $part . 'A'; - } + return $options; + } - return $part . 'A'; + /** + * @return array + */ + private function avatarToParts(string $avatarId): array + { + /** @var string $str */ + $str = preg_replace("/\D/", "", hash('sha256', $avatarId)); + + $hash = substr($str, 0, 12); + + return array_map([$this, 'mapPart'], [ + 'env' => $hash[0] . $hash[1], + 'clo' => $hash[2] . $hash[3], + 'head' => $hash[4] . $hash[5], + 'mouth' => $hash[6] . $hash[7], + 'eyes' => $hash[8] . $hash[9], + 'top' => $hash[10] . $hash[11], + ]); } /** - * @param array $bodyParts - * @param bool $sansEnv - * @param array $ver + * @return array{part:string, theme:string} + */ + private function mapPart(string $part): array { + $part = (int) round(47 / 100 * (int) $part); + + return [ + 'part' => sprintf("%'.02d", $part % 16), + 'theme' => self::THEME_LIST[intdiv($part, 16)], + ]; + } + + /** + * @param array $bodyParts + * @param bool $sansEnv + * @param array{part:string|null, theme:string|null} $options * * @return array */ - private function bodyToSvgParts(array $bodyParts, bool $sansEnv, array $ver): array + private function partsToSvgElements(array $bodyParts, bool $sansEnv, array $options): array { - $svgParts = []; + $elements = []; foreach ($bodyParts as $name => $bodyPart) { if ('env' === $name && $sansEnv) { - $svgParts[$name] = ''; - + $elements[$name] = ''; continue; } - $part = $ver['part'] ?? substr($bodyPart, 0, 2); - $theme = $ver['theme'] ?? substr($bodyPart, 2, 3); - $svgParts[$name] = $this->generateSvgPart($part, $name, $theme); + $elements[$name] = $this->generateSvgElement( + $name, + $options['part'] ?? $bodyPart['part'], + $options['theme'] ?? $bodyPart['theme'] + ); } - return $svgParts; + return $elements; } - /** - * @param string|int $part - */ - private function generateSvgPart($part, string $name, string $theme): string + private function generateSvgElement(string $name, string $part, string $theme): string { - self::loadAssets(); - - $svgString = self::$shapes[$part][$name]; - $found = preg_match_all('/#(.*?)+(?=;)/', $svgString, $result); + $svgElement = self::shapes()[$part][$name]; + $found = preg_match_all('/#(.*?)+(?=;)/', $svgElement, $result); if (0 === $found || false === $found) { - return $svgString; + return $svgElement; } - $colors = self::$themes[$part][$theme][$name]; - foreach ($result[0] as $i => $var) { - $pos = strpos($svgString, $var); + $colors = self::themes()[$part][$theme][$name]; + foreach ($result[0] as $index => $var) { + $pos = strpos($svgElement, $var); if ($pos !== false) { - $svgString = substr_replace($svgString, $colors[$i], $pos, strlen($var)); + $svgElement = substr_replace($svgElement, $colors[$index] ?? $colors[0], $pos, strlen($var)); } } - return $svgString; - } - - /** - * Lazy loading assets - */ - private static function loadAssets(): void - { - if ([] === self::$shapes) { - self::$shapes = self::shapes(); - } - - if ([] === self::$themes) { - self::$themes = self::themes(); - } + return $svgElement; } /** @@ -196,152 +180,154 @@ private static function loadAssets(): void */ private static function shapes(): array { - $shapes = []; + if ([] !== self::$shapes) { + return self::$shapes; + } // Robo - $shapes['00']['env'] = self::SVG_ELEMENT_ENV; - $shapes['00']['clo'] = ''; - $shapes['00']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['00']['mouth'] = ''; - $shapes['00']['eyes'] = ''; - $shapes['00']['top'] = ''; + self::$shapes['00']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['00']['clo'] = ''; + self::$shapes['00']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['00']['mouth'] = ''; + self::$shapes['00']['eyes'] = ''; + self::$shapes['00']['top'] = ''; // Girl - $shapes['01'] = []; - $shapes['01']['env'] = self::SVG_ELEMENT_ENV; - $shapes['01']['clo'] = ''; - $shapes['01']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['01']['mouth'] = ''; - $shapes['01']['eyes'] = ''; - $shapes['01']['top'] = ''; + self::$shapes['01'] = []; + self::$shapes['01']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['01']['clo'] = ''; + self::$shapes['01']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['01']['mouth'] = ''; + self::$shapes['01']['eyes'] = ''; + self::$shapes['01']['top'] = ''; // Blonde - $shapes['02'] = []; - $shapes['02']['env'] = self::SVG_ELEMENT_ENV; - $shapes['02']['clo'] = ''; - $shapes['02']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['02']['mouth'] = ''; - $shapes['02']['eyes'] = ''; - $shapes['02']['top'] = ''; + self::$shapes['02'] = []; + self::$shapes['02']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['02']['clo'] = ''; + self::$shapes['02']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['02']['mouth'] = ''; + self::$shapes['02']['eyes'] = ''; + self::$shapes['02']['top'] = ''; // Guy - $shapes['03'] = []; - $shapes['03']['env'] = self::SVG_ELEMENT_ENV; - $shapes['03']['clo'] = ''; - $shapes['03']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['03']['mouth'] = ''; - $shapes['03']['eyes'] = ''; - $shapes['03']['top'] = ''; + self::$shapes['03'] = []; + self::$shapes['03']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['03']['clo'] = ''; + self::$shapes['03']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['03']['mouth'] = ''; + self::$shapes['03']['eyes'] = ''; + self::$shapes['03']['top'] = ''; // Country - $shapes['04'] = []; - $shapes['04']['env'] = self::SVG_ELEMENT_ENV; - $shapes['04']['clo'] = ''; - $shapes['04']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['04']['mouth'] = ''; - $shapes['04']['eyes'] = ''; - $shapes['04']['top'] = ''; + self::$shapes['04'] = []; + self::$shapes['04']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['04']['clo'] = ''; + self::$shapes['04']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['04']['mouth'] = ''; + self::$shapes['04']['eyes'] = ''; + self::$shapes['04']['top'] = ''; // Geeknot - $shapes['05'] = []; - $shapes['05']['env'] = self::SVG_ELEMENT_ENV; - $shapes['05']['clo'] = ''; - $shapes['05']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['05']['mouth'] = ''; - $shapes['05']['eyes'] = ''; - $shapes['05']['top'] = ''; + self::$shapes['05'] = []; + self::$shapes['05']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['05']['clo'] = ''; + self::$shapes['05']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['05']['mouth'] = ''; + self::$shapes['05']['eyes'] = ''; + self::$shapes['05']['top'] = ''; // Asian - $shapes['06'] = []; - $shapes['06']['env'] = self::SVG_ELEMENT_ENV; - $shapes['06']['clo'] = ''; - $shapes['06']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['06']['mouth'] = ''; - $shapes['06']['eyes'] = ''; - $shapes['06']['top'] = ''; + self::$shapes['06'] = []; + self::$shapes['06']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['06']['clo'] = ''; + self::$shapes['06']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['06']['mouth'] = ''; + self::$shapes['06']['eyes'] = ''; + self::$shapes['06']['top'] = ''; // Punk - $shapes['07'] = []; - $shapes['07']['env'] = self::SVG_ELEMENT_ENV; - $shapes['07']['clo'] = ''; - $shapes['07']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['07']['mouth'] = ''; - $shapes['07']['eyes'] = ''; - $shapes['07']['top'] = ''; + self::$shapes['07'] = []; + self::$shapes['07']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['07']['clo'] = ''; + self::$shapes['07']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['07']['mouth'] = ''; + self::$shapes['07']['eyes'] = ''; + self::$shapes['07']['top'] = ''; // Afrohair - $shapes['08'] = []; - $shapes['08']['env'] = self::SVG_ELEMENT_ENV; - $shapes['08']['clo'] = ''; - $shapes['08']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['08']['mouth'] = ''; - $shapes['08']['eyes'] = ''; - $shapes['08']['top'] = ''; + self::$shapes['08'] = []; + self::$shapes['08']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['08']['clo'] = ''; + self::$shapes['08']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['08']['mouth'] = ''; + self::$shapes['08']['eyes'] = ''; + self::$shapes['08']['top'] = ''; // Normie Female - $shapes['09'] = []; - $shapes['09']['env'] = self::SVG_ELEMENT_ENV; - $shapes['09']['clo'] = ''; - $shapes['09']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['09']['mouth'] = ''; - $shapes['09']['eyes'] = ''; - $shapes['09']['top'] = ''; + self::$shapes['09'] = []; + self::$shapes['09']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['09']['clo'] = ''; + self::$shapes['09']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['09']['mouth'] = ''; + self::$shapes['09']['eyes'] = ''; + self::$shapes['09']['top'] = ''; // Older - $shapes['10'] = []; - $shapes['10']['env'] = self::SVG_ELEMENT_ENV; - $shapes['10']['clo'] = ''; - $shapes['10']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['10']['mouth'] = ''; - $shapes['10']['eyes'] = ''; - $shapes['10']['top'] = ''; + self::$shapes['10'] = []; + self::$shapes['10']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['10']['clo'] = ''; + self::$shapes['10']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['10']['mouth'] = ''; + self::$shapes['10']['eyes'] = ''; + self::$shapes['10']['top'] = ''; // Firehair - $shapes['11'] = []; - $shapes['11']['env'] = self::SVG_ELEMENT_ENV; - $shapes['11']['clo'] = ''; - $shapes['11']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['11']['mouth'] = ''; - $shapes['11']['eyes'] = ''; - $shapes['11']['top'] = ''; + self::$shapes['11'] = []; + self::$shapes['11']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['11']['clo'] = ''; + self::$shapes['11']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['11']['mouth'] = ''; + self::$shapes['11']['eyes'] = ''; + self::$shapes['11']['top'] = ''; // Blond - $shapes['12'] = []; - $shapes['12']['env'] = self::SVG_ELEMENT_ENV; - $shapes['12']['clo'] = ''; - $shapes['12']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['12']['mouth'] = ''; - $shapes['12']['eyes'] = ''; - $shapes['12']['top'] = ''; + self::$shapes['12'] = []; + self::$shapes['12']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['12']['clo'] = ''; + self::$shapes['12']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['12']['mouth'] = ''; + self::$shapes['12']['eyes'] = ''; + self::$shapes['12']['top'] = ''; // Ateam - $shapes['13'] = []; - $shapes['13']['env'] = self::SVG_ELEMENT_ENV; - $shapes['13']['clo'] = ''; - $shapes['13']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['13']['mouth'] = ''; - $shapes['13']['eyes'] = ''; - $shapes['13']['top'] = ''; + self::$shapes['13'] = []; + self::$shapes['13']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['13']['clo'] = ''; + self::$shapes['13']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['13']['mouth'] = ''; + self::$shapes['13']['eyes'] = ''; + self::$shapes['13']['top'] = ''; // Rasta - $shapes['14'] = []; - $shapes['14']['env'] = self::SVG_ELEMENT_ENV; - $shapes['14']['clo'] = ''; - $shapes['14']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['14']['mouth'] = ''; - $shapes['14']['eyes'] = ''; - $shapes['14']['top'] = ''; + self::$shapes['14'] = []; + self::$shapes['14']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['14']['clo'] = ''; + self::$shapes['14']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['14']['mouth'] = ''; + self::$shapes['14']['eyes'] = ''; + self::$shapes['14']['top'] = ''; // Meta - $shapes['15'] = []; - $shapes['15']['env'] = self::SVG_ELEMENT_ENV; - $shapes['15']['clo'] = ''; - $shapes['15']['head'] = self::SVG_ELEMENT_HEAD; - $shapes['15']['mouth'] = ''; - $shapes['15']['eyes'] = ''; - $shapes['15']['top'] = ''; - - return $shapes; + self::$shapes['15'] = []; + self::$shapes['15']['env'] = self::SVG_ELEMENT_ENV; + self::$shapes['15']['clo'] = ''; + self::$shapes['15']['head'] = self::SVG_ELEMENT_HEAD; + self::$shapes['15']['mouth'] = ''; + self::$shapes['15']['eyes'] = ''; + self::$shapes['15']['top'] = ''; + + return self::$shapes; } /** @@ -349,360 +335,362 @@ private static function shapes(): array */ private static function themes(): array { - $themes = []; + if ([] !== self::$themes) { + return self::$themes; + } // Robo - $themes['00']['A']['env'] = [ "#ff2f2b" ]; - $themes['00']['A']['clo'] = ["#fff", "#000"]; - $themes['00']['A']['head'] = ["#fff"]; - $themes['00']['A']['mouth'] = ["#fff", "#000", "#000"]; - $themes['00']['A']['eyes'] = ["#000", "none", "#00FFFF"]; - $themes['00']['A']['top'] = ["#fff", "#fff"]; - - $themes['00']['B']['env'] = ["#ff1ec1"]; - $themes['00']['B']['clo'] = ["#000", "#fff" ]; - $themes['00']['B']['head'] = ["#ffc1c1"]; - $themes['00']['B']['mouth'] = ["#fff", "#000", "#000"]; - $themes['00']['B']['eyes'] = ["#FF2D00", "#fff", "none"]; - $themes['00']['B']['top'] = ["#a21d00", "#fff"]; - - $themes['00']['C']['env'] = ["#0079b1"]; - $themes['00']['C']['clo'] = ["#0e00b1", "#d1fffe"]; - $themes['00']['C']['head'] = ["#f5aa77"]; - $themes['00']['C']['mouth'] = ["#fff", "#000", "#000"]; - $themes['00']['C']['eyes'] = ["#0c00de", "#fff", "none"]; - $themes['00']['C']['top'] = ["#acfffd", "#acfffd"]; + self::$themes['00']['A']['env'] = [ "#ff2f2b" ]; + self::$themes['00']['A']['clo'] = ["#fff", "#000"]; + self::$themes['00']['A']['head'] = ["#fff"]; + self::$themes['00']['A']['mouth'] = ["#fff", "#000", "#000"]; + self::$themes['00']['A']['eyes'] = ["#000", "none", "#00FFFF"]; + self::$themes['00']['A']['top'] = ["#fff", "#fff"]; + + self::$themes['00']['B']['env'] = ["#ff1ec1"]; + self::$themes['00']['B']['clo'] = ["#000", "#fff" ]; + self::$themes['00']['B']['head'] = ["#ffc1c1"]; + self::$themes['00']['B']['mouth'] = ["#fff", "#000", "#000"]; + self::$themes['00']['B']['eyes'] = ["#FF2D00", "#fff", "none"]; + self::$themes['00']['B']['top'] = ["#a21d00", "#fff"]; + + self::$themes['00']['C']['env'] = ["#0079b1"]; + self::$themes['00']['C']['clo'] = ["#0e00b1", "#d1fffe"]; + self::$themes['00']['C']['head'] = ["#f5aa77"]; + self::$themes['00']['C']['mouth'] = ["#fff", "#000", "#000"]; + self::$themes['00']['C']['eyes'] = ["#0c00de", "#fff", "none"]; + self::$themes['00']['C']['top'] = ["#acfffd", "#acfffd"]; // Girl - $themes['01']['A']['env'] = ["#a50000"]; - $themes['01']['A']['clo'] = ["#f06", "#8e0039"]; - $themes['01']['A']['head'] = ["#85492C"]; - $themes['01']['A']['mouth'] = ["#000"]; - $themes['01']['A']['eyes'] = ["#000", "#ff9809"]; - $themes['01']['A']['top'] = ["#ff9809", "#ff9809", "none", "none"]; - - $themes['01']['B']['env'] = ["#40E83B"]; - $themes['01']['B']['clo'] = ["#00650b", "#62ce5a"]; - $themes['01']['B']['head'] = ["#f7c1a6"]; - $themes['01']['B']['mouth'] = ["#6e1c1c"]; - $themes['01']['B']['eyes'] = ["#000", "#ff833b"]; - $themes['01']['B']['top'] = ["#67FFCC", "none", "none", "#ecff3b"]; - - $themes['01']['C']['env'] = ["#ff2c2c"]; - $themes['01']['C']['clo'] = ["#fff", "#000"]; - $themes['01']['C']['head'] = ["#ffce8b"]; - $themes['01']['C']['mouth'] = ["#000"]; - $themes['01']['C']['eyes'] = ["#000", "#0072ff"]; - $themes['01']['C']['top'] = ["#ff9809", "none", "#ffc809", "none"]; + self::$themes['01']['A']['env'] = ["#a50000"]; + self::$themes['01']['A']['clo'] = ["#f06", "#8e0039"]; + self::$themes['01']['A']['head'] = ["#85492C"]; + self::$themes['01']['A']['mouth'] = ["#000"]; + self::$themes['01']['A']['eyes'] = ["#000", "#ff9809"]; + self::$themes['01']['A']['top'] = ["#ff9809", "#ff9809", "none", "none"]; + + self::$themes['01']['B']['env'] = ["#40E83B"]; + self::$themes['01']['B']['clo'] = ["#00650b", "#62ce5a"]; + self::$themes['01']['B']['head'] = ["#f7c1a6"]; + self::$themes['01']['B']['mouth'] = ["#6e1c1c"]; + self::$themes['01']['B']['eyes'] = ["#000", "#ff833b"]; + self::$themes['01']['B']['top'] = ["#67FFCC", "none", "none", "#ecff3b"]; + + self::$themes['01']['C']['env'] = ["#ff2c2c"]; + self::$themes['01']['C']['clo'] = ["#fff", "#000"]; + self::$themes['01']['C']['head'] = ["#ffce8b"]; + self::$themes['01']['C']['mouth'] = ["#000"]; + self::$themes['01']['C']['eyes'] = ["#000", "#0072ff"]; + self::$themes['01']['C']['top'] = ["#ff9809", "none", "#ffc809", "none"]; // Blonde - $themes['02']['A']['env'] = ["#ff7520"]; - $themes['02']['A']['clo'] = ["#d12823"]; - $themes['02']['A']['head'] = ["#fee3c5"]; - $themes['02']['A']['mouth'] = ["#d12823"]; - $themes['02']['A']['eyes'] = ["#000", "none"]; - $themes['02']['A']['top'] = ["#000", "none", "none", "#FFCC00", "red"]; - - $themes['02']['B']['env'] = ["#ff9700"]; - $themes['02']['B']['clo'] = ["#000"]; - $themes['02']['B']['head'] = ["#d2ad6d"]; - $themes['02']['B']['mouth'] = ["#000"]; - $themes['02']['B']['eyes'] = ["#000", "#00ffdc"]; - $themes['02']['B']['top'] = ["#fdff00", "#fdff00", "none", "none", "none"]; - - $themes['02']['C']['env'] = ["#26a7ff"]; - $themes['02']['C']['clo'] = ["#d85cd7"]; - $themes['02']['C']['head'] = ["#542e02"]; - $themes['02']['C']['mouth'] = ["#f70014"]; - $themes['02']['C']['eyes'] = ["#000", "magenta"]; - $themes['02']['C']['top'] = ["#FFCC00", "#FFCC00", "#FFCC00", "#ff0000", "yellow"]; + self::$themes['02']['A']['env'] = ["#ff7520"]; + self::$themes['02']['A']['clo'] = ["#d12823"]; + self::$themes['02']['A']['head'] = ["#fee3c5"]; + self::$themes['02']['A']['mouth'] = ["#d12823"]; + self::$themes['02']['A']['eyes'] = ["#000", "none"]; + self::$themes['02']['A']['top'] = ["#000", "none", "none", "#FFCC00", "red"]; + + self::$themes['02']['B']['env'] = ["#ff9700"]; + self::$themes['02']['B']['clo'] = ["#000"]; + self::$themes['02']['B']['head'] = ["#d2ad6d"]; + self::$themes['02']['B']['mouth'] = ["#000"]; + self::$themes['02']['B']['eyes'] = ["#000", "#00ffdc"]; + self::$themes['02']['B']['top'] = ["#fdff00", "#fdff00", "none", "none", "none"]; + + self::$themes['02']['C']['env'] = ["#26a7ff"]; + self::$themes['02']['C']['clo'] = ["#d85cd7"]; + self::$themes['02']['C']['head'] = ["#542e02"]; + self::$themes['02']['C']['mouth'] = ["#f70014"]; + self::$themes['02']['C']['eyes'] = ["#000", "magenta"]; + self::$themes['02']['C']['top'] = ["#FFCC00", "#FFCC00", "#FFCC00", "#ff0000", "yellow"]; // Evilnormie - $themes['03']['A']['env'] = ["#6FC30E"]; - $themes['03']['A']['clo'] = ["#b4e1fa", "#5b5d6e", "#515262", "#a0d2f0", "#a0d2f0"]; - $themes['03']['A']['head'] = ["#fae3b9"]; - $themes['03']['A']['mouth'] = ["#fff", "#000"]; - $themes['03']['A']['eyes'] = ["#000"]; - $themes['03']['A']['top'] = ["#8eff45", "#8eff45", "none", "none"]; - - $themes['03']['B']['env'] = ["#00a58c"]; - $themes['03']['B']['clo'] = ["#000", "#5b00", "#5100", "#a000", "#a000"]; - $themes['03']['B']['head'] = ["#FAD2B9"]; - $themes['03']['B']['mouth'] = ["#fff", "#000"]; - $themes['03']['B']['eyes'] = ["#000"]; - $themes['03']['B']['top'] = ["#FFC600", "none", "#FFC600", "none"]; - - $themes['03']['C']['env'] = ["#ff501f"]; - $themes['03']['C']['clo'] = ["#000", "#ff0000", "#ff0000", "#7d7d7d", "#7d7d7d"]; - $themes['03']['C']['head'] = ["#fff3dc"]; - $themes['03']['C']['mouth'] = ["#d2001b", "none"]; - $themes['03']['C']['eyes'] = ["#000"]; - $themes['03']['C']['top'] = ["#D2001B", "none", "none", "#D2001B"]; + self::$themes['03']['A']['env'] = ["#6FC30E"]; + self::$themes['03']['A']['clo'] = ["#b4e1fa", "#5b5d6e", "#515262", "#a0d2f0", "#a0d2f0"]; + self::$themes['03']['A']['head'] = ["#fae3b9"]; + self::$themes['03']['A']['mouth'] = ["#fff", "#000"]; + self::$themes['03']['A']['eyes'] = ["#000"]; + self::$themes['03']['A']['top'] = ["#8eff45", "#8eff45", "none", "none"]; + + self::$themes['03']['B']['env'] = ["#00a58c"]; + self::$themes['03']['B']['clo'] = ["#000", "#5b00", "#5100", "#a000", "#a000"]; + self::$themes['03']['B']['head'] = ["#FAD2B9"]; + self::$themes['03']['B']['mouth'] = ["#fff", "#000"]; + self::$themes['03']['B']['eyes'] = ["#000"]; + self::$themes['03']['B']['top'] = ["#FFC600", "none", "#FFC600", "none"]; + + self::$themes['03']['C']['env'] = ["#ff501f"]; + self::$themes['03']['C']['clo'] = ["#000", "#ff0000", "#ff0000", "#7d7d7d", "#7d7d7d"]; + self::$themes['03']['C']['head'] = ["#fff3dc"]; + self::$themes['03']['C']['mouth'] = ["#d2001b", "none"]; + self::$themes['03']['C']['eyes'] = ["#000"]; + self::$themes['03']['C']['top'] = ["#D2001B", "none", "none", "#D2001B"]; // Country - $themes['04']['A']['env'] = ["#fc0"]; - $themes['04']['A']['clo'] = ["#901e0e", "#ffbe1e", "#ffbe1e", "#c55f54"]; - $themes['04']['A']['head'] = ["#f8d9ad"]; - $themes['04']['A']['mouth'] = ["#000", "none", "#000", "none"]; - $themes['04']['A']['eyes'] = ["#000"]; - $themes['04']['A']['top'] = ["#583D00", "#AF892E", "#462D00", "#a0a0a0"]; - - $themes['04']['B']['env'] = ["#386465"]; - $themes['04']['B']['clo'] = ["#fff", "#333", "#333", "#333"]; - $themes['04']['B']['head'] = ["#FFD79D"]; - $themes['04']['B']['mouth'] = ["#000", "#000", "#000", "#000"]; - $themes['04']['B']['eyes'] = ["#000"]; - $themes['04']['B']['top'] = ["#27363C", "#5DCAD4", "#314652", "#333"]; - - $themes['04']['C']['env'] = ["#DFFF00"]; - $themes['04']['C']['clo'] = ["#304267", "#aab0b1", "#aab0b1", "#aab0b1"]; - $themes['04']['C']['head'] = ["#e6b876"]; - $themes['04']['C']['mouth'] = ["#50230a", "#50230a", "#50230a", "#50230a"]; - $themes['04']['C']['eyes'] = ["#000"]; - $themes['04']['C']['top'] = ["#333", "#afafaf", "#222", "#6d3a1d"]; + self::$themes['04']['A']['env'] = ["#fc0"]; + self::$themes['04']['A']['clo'] = ["#901e0e", "#ffbe1e", "#ffbe1e", "#c55f54"]; + self::$themes['04']['A']['head'] = ["#f8d9ad"]; + self::$themes['04']['A']['mouth'] = ["#000", "none", "#000", "none"]; + self::$themes['04']['A']['eyes'] = ["#000"]; + self::$themes['04']['A']['top'] = ["#583D00", "#AF892E", "#462D00", "#a0a0a0"]; + + self::$themes['04']['B']['env'] = ["#386465"]; + self::$themes['04']['B']['clo'] = ["#fff", "#333", "#333", "#333"]; + self::$themes['04']['B']['head'] = ["#FFD79D"]; + self::$themes['04']['B']['mouth'] = ["#000", "#000", "#000", "#000"]; + self::$themes['04']['B']['eyes'] = ["#000"]; + self::$themes['04']['B']['top'] = ["#27363C", "#5DCAD4", "#314652", "#333"]; + + self::$themes['04']['C']['env'] = ["#DFFF00"]; + self::$themes['04']['C']['clo'] = ["#304267", "#aab0b1", "#aab0b1", "#aab0b1"]; + self::$themes['04']['C']['head'] = ["#e6b876"]; + self::$themes['04']['C']['mouth'] = ["#50230a", "#50230a", "#50230a", "#50230a"]; + self::$themes['04']['C']['eyes'] = ["#000"]; + self::$themes['04']['C']['top'] = ["#333", "#afafaf", "#222", "#6d3a1d"]; // Johnyold - $themes['05']['A']['env'] = ["#a09300"]; - $themes['05']['A']['clo'] = ["#c7d4e2", "#435363", "#435363", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; - $themes['05']['A']['head'] = ["#f5d4a6"]; - $themes['05']['A']['mouth'] = ["#000", "#cf9f76"]; - $themes['05']['A']['eyes'] = ["#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff", "#000", "#000"]; - $themes['05']['A']['top'] = ["none", "#fdff00"]; - - $themes['05']['B']['env'] = ["#b3003e"]; - $themes['05']['B']['clo'] = ["#000", "#435363", "#435363", "#000", "none", "#e7ecf2", "#e7ecf2"]; - $themes['05']['B']['head'] = ["#f5d4a6"]; - $themes['05']['B']['mouth'] = ["#000", "#af9f94"]; - $themes['05']['B']['eyes'] = ["#9ff3ffdb", "#000", "#9ff3ffdb", "#000", "#2f508a", "#000", "#000", "#000", "none", "none", "none", "none"]; - $themes['05']['B']['top'] = ["#ff9a00", "#ff9a00"]; - - $themes['05']['C']['env'] = ["#884f00"]; - $themes['05']['C']['clo'] = ["#ff0000", "#fff", "#fff", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; - $themes['05']['C']['head'] = ["#c57b14"]; - $themes['05']['C']['mouth'] = ["#000", "#cf9f76"]; - $themes['05']['C']['eyes'] = ["none", "#000", "none", "#000", "#5a0000", "#000", "#000", "#000", "none", "none", "none", "none"]; - $themes['05']['C']['top'] = ["#efefef", "none"]; + self::$themes['05']['A']['env'] = ["#a09300"]; + self::$themes['05']['A']['clo'] = ["#c7d4e2", "#435363", "#435363", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; + self::$themes['05']['A']['head'] = ["#f5d4a6"]; + self::$themes['05']['A']['mouth'] = ["#000", "#cf9f76"]; + self::$themes['05']['A']['eyes'] = ["#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff", "#000", "#000"]; + self::$themes['05']['A']['top'] = ["none", "#fdff00"]; + + self::$themes['05']['B']['env'] = ["#b3003e"]; + self::$themes['05']['B']['clo'] = ["#000", "#435363", "#435363", "#000", "none", "#e7ecf2", "#e7ecf2"]; + self::$themes['05']['B']['head'] = ["#f5d4a6"]; + self::$themes['05']['B']['mouth'] = ["#000", "#af9f94"]; + self::$themes['05']['B']['eyes'] = ["#9ff3ffdb", "#000", "#9ff3ffdb", "#000", "#2f508a", "#000", "#000", "#000", "none", "none", "none", "none"]; + self::$themes['05']['B']['top'] = ["#ff9a00", "#ff9a00"]; + + self::$themes['05']['C']['env'] = ["#884f00"]; + self::$themes['05']['C']['clo'] = ["#ff0000", "#fff", "#fff", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; + self::$themes['05']['C']['head'] = ["#c57b14"]; + self::$themes['05']['C']['mouth'] = ["#000", "#cf9f76"]; + self::$themes['05']['C']['eyes'] = ["none", "#000", "none", "#000", "#5a0000", "#000", "#000", "#000", "none", "none", "none", "none"]; + self::$themes['05']['C']['top'] = ["#efefef", "none"]; // Asian - $themes['06']['A']['env'] = ["#8acf00"]; - $themes['06']['A']['clo'] = ["#ee2829", "#ff0"]; - $themes['06']['A']['head'] = ["#ffce73"]; - $themes['06']['A']['mouth'] = ["#fff", "#000"]; - $themes['06']['A']['eyes'] = ["#000"]; - $themes['06']['A']['top'] = ["#000","#000","none", "#000", "#ff4e4e", "#000"]; - - $themes['06']['B']['env'] = ["#00d2a3"]; - $themes['06']['B']['clo'] = ["#0D0046", "#ffce73"]; - $themes['06']['B']['head'] = ["#ffce73"]; - $themes['06']['B']['mouth'] = ["#000", "none"]; - $themes['06']['B']['eyes'] = ["#000"]; - $themes['06']['B']['top'] = ["#000","#000","#000", "none", "#ffb358", "#000", "none", "none"]; - - $themes['06']['C']['env'] = ["#ff184e"]; - $themes['06']['C']['clo'] = ["#000", "none"]; - $themes['06']['C']['head'] = ["#ffce73"]; - $themes['06']['C']['mouth'] = ["#ff0000", "none"]; - $themes['06']['C']['eyes'] = ["#000"]; - $themes['06']['C']['top'] = ["none","none","none", "none", "none", "#ffc107", "none", "none"]; + self::$themes['06']['A']['env'] = ["#8acf00"]; + self::$themes['06']['A']['clo'] = ["#ee2829", "#ff0"]; + self::$themes['06']['A']['head'] = ["#ffce73"]; + self::$themes['06']['A']['mouth'] = ["#fff", "#000"]; + self::$themes['06']['A']['eyes'] = ["#000"]; + self::$themes['06']['A']['top'] = ["#000","#000","none", "#000", "#ff4e4e", "#000"]; + + self::$themes['06']['B']['env'] = ["#00d2a3"]; + self::$themes['06']['B']['clo'] = ["#0D0046", "#ffce73"]; + self::$themes['06']['B']['head'] = ["#ffce73"]; + self::$themes['06']['B']['mouth'] = ["#000", "none"]; + self::$themes['06']['B']['eyes'] = ["#000"]; + self::$themes['06']['B']['top'] = ["#000","#000","#000", "none", "#ffb358", "#000", "none", "none"]; + + self::$themes['06']['C']['env'] = ["#ff184e"]; + self::$themes['06']['C']['clo'] = ["#000", "none"]; + self::$themes['06']['C']['head'] = ["#ffce73"]; + self::$themes['06']['C']['mouth'] = ["#ff0000", "none"]; + self::$themes['06']['C']['eyes'] = ["#000"]; + self::$themes['06']['C']['top'] = ["none","none","none", "none", "none", "#ffc107", "none", "none"]; // Punk - $themes['07']['A']['env'] = ["#00deae"]; - $themes['07']['A']['clo'] = ["#ff0000"]; - $themes['07']['A']['head'] = ["#ffce94"]; - $themes['07']['A']['mouth'] = ["#f73b6c", "#000"]; - $themes['07']['A']['eyes'] = ["#e91e63", "#000", "#e91e63", "#000", "#000", "#000"]; - $themes['07']['A']['top'] = ["#dd104f", "#dd104f", "#f73b6c", "#dd104f"]; - - $themes['07']['B']['env'] = ["#181284"]; - $themes['07']['B']['clo'] = ["#491f49", "#ff9809", "#491f49"]; - $themes['07']['B']['head'] = ["#f6ba97"]; - $themes['07']['B']['mouth'] = ["#ff9809", "#000"]; - $themes['07']['B']['eyes'] = ["#c4ffe4", "#000", "#c4ffe4", "#000", "#000", "#000"]; - $themes['07']['B']['top'] = [ "none", "none", "#d6f740", "#516303"]; - - $themes['07']['C']['env'] = ["#bcf700"]; - $themes['07']['C']['clo'] = ["#ff14e4", "#000", "#14fffd"]; - $themes['07']['C']['head'] = ["#7b401e"]; - $themes['07']['C']['mouth'] = ["#666", "#000"]; - $themes['07']['C']['eyes'] = ["#00b5b4", "#000", "#00b5b4", "#000", "#000", "#000"]; - $themes['07']['C']['top'] = ["#14fffd", "#14fffd", "#14fffd", "#0d3a62"]; + self::$themes['07']['A']['env'] = ["#00deae"]; + self::$themes['07']['A']['clo'] = ["#ff0000"]; + self::$themes['07']['A']['head'] = ["#ffce94"]; + self::$themes['07']['A']['mouth'] = ["#f73b6c", "#000"]; + self::$themes['07']['A']['eyes'] = ["#e91e63", "#000", "#e91e63", "#000", "#000", "#000"]; + self::$themes['07']['A']['top'] = ["#dd104f", "#dd104f", "#f73b6c", "#dd104f"]; + + self::$themes['07']['B']['env'] = ["#181284"]; + self::$themes['07']['B']['clo'] = ["#491f49", "#ff9809", "#491f49"]; + self::$themes['07']['B']['head'] = ["#f6ba97"]; + self::$themes['07']['B']['mouth'] = ["#ff9809", "#000"]; + self::$themes['07']['B']['eyes'] = ["#c4ffe4", "#000", "#c4ffe4", "#000", "#000", "#000"]; + self::$themes['07']['B']['top'] = [ "none", "none", "#d6f740", "#516303"]; + + self::$themes['07']['C']['env'] = ["#bcf700"]; + self::$themes['07']['C']['clo'] = ["#ff14e4", "#000", "#14fffd"]; + self::$themes['07']['C']['head'] = ["#7b401e"]; + self::$themes['07']['C']['mouth'] = ["#666", "#000"]; + self::$themes['07']['C']['eyes'] = ["#00b5b4", "#000", "#00b5b4", "#000", "#000", "#000"]; + self::$themes['07']['C']['top'] = ["#14fffd", "#14fffd", "#14fffd", "#0d3a62"]; // Afrohair - $themes['08']['A']['env'] = ["#0df"]; - $themes['08']['A']['clo'] = ["#571e57", "#ff0"]; - $themes['08']['A']['head'] = ["#f2c280"]; - $themes['08']['A']['mouth'] = ["#795548", "#000"]; - $themes['08']['A']['eyes'] = ["#ff0000"]; - $themes['08']['A']['top'] = ["#de3b00", "none"]; - - $themes['08']['B']['env'] = ["#B400C2"]; - $themes['08']['B']['clo'] = ["#0D204A", "#00ffdf"]; - $themes['08']['B']['head'] = ["#ca8628"]; - $themes['08']['B']['mouth'] = ["#cbbdaf", "#000"]; - $themes['08']['B']['eyes'] = ["#1a1a1a"]; - $themes['08']['B']['top'] = ["#000", "#000"]; - - $themes['08']['C']['env'] = ["#ffe926"]; - $themes['08']['C']['clo'] = ["#00d6af", "#000"]; - $themes['08']['C']['head'] = ["#8c5100"]; - $themes['08']['C']['mouth'] = ["none", "#000"]; - $themes['08']['C']['eyes'] = ["#7d0000"]; - $themes['08']['C']['top'] = ["#f7f7f7", "none"]; + self::$themes['08']['A']['env'] = ["#0df"]; + self::$themes['08']['A']['clo'] = ["#571e57", "#ff0"]; + self::$themes['08']['A']['head'] = ["#f2c280"]; + self::$themes['08']['A']['mouth'] = ["#795548", "#000"]; + self::$themes['08']['A']['eyes'] = ["#ff0000"]; + self::$themes['08']['A']['top'] = ["#de3b00", "none"]; + + self::$themes['08']['B']['env'] = ["#B400C2"]; + self::$themes['08']['B']['clo'] = ["#0D204A", "#00ffdf"]; + self::$themes['08']['B']['head'] = ["#ca8628"]; + self::$themes['08']['B']['mouth'] = ["#cbbdaf", "#000"]; + self::$themes['08']['B']['eyes'] = ["#1a1a1a"]; + self::$themes['08']['B']['top'] = ["#000", "#000"]; + + self::$themes['08']['C']['env'] = ["#ffe926"]; + self::$themes['08']['C']['clo'] = ["#00d6af", "#000"]; + self::$themes['08']['C']['head'] = ["#8c5100"]; + self::$themes['08']['C']['mouth'] = ["none", "#000"]; + self::$themes['08']['C']['eyes'] = ["#7d0000"]; + self::$themes['08']['C']['top'] = ["#f7f7f7", "none"]; // Normie female - $themes['09']['A']['env'] = ["#4aff0c"]; - $themes['09']['A']['clo'] = ["#101010", "#fff", "#fff"]; - $themes['09']['A']['head'] = ["#dbbc7f"]; - $themes['09']['A']['mouth'] = ["#000"]; - $themes['09']['A']['eyes'] = [ "#000", "none", "none"]; - $themes['09']['A']['top'] = ["#531148", "#531148", "#531148", "none"]; - - $themes['09']['B']['env'] = ["#FFC107"]; - $themes['09']['B']['clo'] = ["#033c58", "#fff", "#fff"]; - $themes['09']['B']['head'] = ["#dbc97f"]; - $themes['09']['B']['mouth'] = ["#000"]; - $themes['09']['B']['eyes'] = ["none", "#fff", "#000"]; - $themes['09']['B']['top'] = ["#FFEB3B", "#FFEB3B", "none", "#FFEB3B"]; - - $themes['09']['C']['env'] = ["#FF9800"]; - $themes['09']['C']['clo'] = ["#b40000", "#fff", "#fff"]; - $themes['09']['C']['head'] = ["#E2AF6B"]; - $themes['09']['C']['mouth'] = ["#000"]; - $themes['09']['C']['eyes'] = ["none", "#fff", "#000"]; - $themes['09']['C']['top'] = ["#ec0000", "#ec0000", "none", "none"]; + self::$themes['09']['A']['env'] = ["#4aff0c"]; + self::$themes['09']['A']['clo'] = ["#101010", "#fff", "#fff"]; + self::$themes['09']['A']['head'] = ["#dbbc7f"]; + self::$themes['09']['A']['mouth'] = ["#000"]; + self::$themes['09']['A']['eyes'] = [ "#000", "none", "none"]; + self::$themes['09']['A']['top'] = ["#531148", "#531148", "#531148", "none"]; + + self::$themes['09']['B']['env'] = ["#FFC107"]; + self::$themes['09']['B']['clo'] = ["#033c58", "#fff", "#fff"]; + self::$themes['09']['B']['head'] = ["#dbc97f"]; + self::$themes['09']['B']['mouth'] = ["#000"]; + self::$themes['09']['B']['eyes'] = ["none", "#fff", "#000"]; + self::$themes['09']['B']['top'] = ["#FFEB3B", "#FFEB3B", "none", "#FFEB3B"]; + + self::$themes['09']['C']['env'] = ["#FF9800"]; + self::$themes['09']['C']['clo'] = ["#b40000", "#fff", "#fff"]; + self::$themes['09']['C']['head'] = ["#E2AF6B"]; + self::$themes['09']['C']['mouth'] = ["#000"]; + self::$themes['09']['C']['eyes'] = ["none", "#fff", "#000"]; + self::$themes['09']['C']['top'] = ["#ec0000", "#ec0000", "none", "none"]; // Older - $themes['10']['A']['env'] = ["#104c8c"]; - $themes['10']['A']['clo'] = ["#354B65", "#3D8EBB", "#89D0DA", "#00FFFD" ]; - $themes['10']['A']['head'] = ["#cc9a5c"]; - $themes['10']['A']['mouth'] = ["#222", "#fff"]; - $themes['10']['A']['eyes'] = ["#000", "#000"]; - $themes['10']['A']['top'] = ["#fff", "#fff", "none"]; - - $themes['10']['B']['env'] = ["#0DC15C"]; - $themes['10']['B']['clo'] = ["#212121", "#fff", "#212121", "#fff", ]; - $themes['10']['B']['head'] = ["#dca45f"]; - $themes['10']['B']['mouth'] = ["#111", "#633b1d"]; - $themes['10']['B']['eyes'] = ["#000", "#000"]; - $themes['10']['B']['top'] = ["none", "#792B74", "#792B74"]; - - $themes['10']['C']['env'] = ["#ffe500"]; - $themes['10']['C']['clo'] = ["#1e5e80", "#fff", "#1e5e80", "#fff"]; - $themes['10']['C']['head'] = ["#e8bc86"]; - $themes['10']['C']['mouth'] = ["#111", "none"]; - $themes['10']['C']['eyes'] = ["#000", "#000"]; - $themes['10']['C']['top'] = ["none", "none", "#633b1d"]; + self::$themes['10']['A']['env'] = ["#104c8c"]; + self::$themes['10']['A']['clo'] = ["#354B65", "#3D8EBB", "#89D0DA", "#00FFFD" ]; + self::$themes['10']['A']['head'] = ["#cc9a5c"]; + self::$themes['10']['A']['mouth'] = ["#222", "#fff"]; + self::$themes['10']['A']['eyes'] = ["#000", "#000"]; + self::$themes['10']['A']['top'] = ["#fff", "#fff", "none"]; + + self::$themes['10']['B']['env'] = ["#0DC15C"]; + self::$themes['10']['B']['clo'] = ["#212121", "#fff", "#212121", "#fff", ]; + self::$themes['10']['B']['head'] = ["#dca45f"]; + self::$themes['10']['B']['mouth'] = ["#111", "#633b1d"]; + self::$themes['10']['B']['eyes'] = ["#000", "#000"]; + self::$themes['10']['B']['top'] = ["none", "#792B74", "#792B74"]; + + self::$themes['10']['C']['env'] = ["#ffe500"]; + self::$themes['10']['C']['clo'] = ["#1e5e80", "#fff", "#1e5e80", "#fff"]; + self::$themes['10']['C']['head'] = ["#e8bc86"]; + self::$themes['10']['C']['mouth'] = ["#111", "none"]; + self::$themes['10']['C']['eyes'] = ["#000", "#000"]; + self::$themes['10']['C']['top'] = ["none", "none", "#633b1d"]; // Firehair - $themes['11']['A']['env'] = ["#4a3f73"]; - $themes['11']['A']['clo'] = ["#e6e9ee", "#f1543f", "#ff7058", "#fff", "#fff"]; - $themes['11']['A']['head'] = ["#b27e5b"]; - $themes['11']['A']['mouth'] = ["#191919", "#191919"]; - $themes['11']['A']['eyes'] = ["#000", "#000", "#57FFFD"]; - $themes['11']['A']['top'] = ["#ffc", "#ffc", "#ffc"]; - - $themes['11']['B']['env'] = ["#00a08d"]; - $themes['11']['B']['clo'] = ["#FFBA32", "#484848", "#4e4e4e", "#fff", "#fff"]; - $themes['11']['B']['head'] = ["#ab5f2c"]; - $themes['11']['B']['mouth'] = ["#191919", "#191919"]; - $themes['11']['B']['eyes'] = ["#000", "#ff23fa63", "#000"]; - $themes['11']['B']['top'] = ["#ff90f4", "#ff90f4", "#ff90f4"]; - - $themes['11']['C']['env'] = ["#22535d"]; - $themes['11']['C']['clo'] = ["#000", "#ff2500", "#ff2500", "#fff", "#fff"]; - $themes['11']['C']['head'] = ["#a76c44"]; - $themes['11']['C']['mouth'] = ["#191919", "#191919"]; - $themes['11']['C']['eyes'] = ["#000", "none", "#000"]; - $themes['11']['C']['top'] = ["none", "#00efff", "none"]; + self::$themes['11']['A']['env'] = ["#4a3f73"]; + self::$themes['11']['A']['clo'] = ["#e6e9ee", "#f1543f", "#ff7058", "#fff", "#fff"]; + self::$themes['11']['A']['head'] = ["#b27e5b"]; + self::$themes['11']['A']['mouth'] = ["#191919", "#191919"]; + self::$themes['11']['A']['eyes'] = ["#000", "#000", "#57FFFD"]; + self::$themes['11']['A']['top'] = ["#ffc", "#ffc", "#ffc"]; + + self::$themes['11']['B']['env'] = ["#00a08d"]; + self::$themes['11']['B']['clo'] = ["#FFBA32", "#484848", "#4e4e4e", "#fff", "#fff"]; + self::$themes['11']['B']['head'] = ["#ab5f2c"]; + self::$themes['11']['B']['mouth'] = ["#191919", "#191919"]; + self::$themes['11']['B']['eyes'] = ["#000", "#ff23fa63", "#000"]; + self::$themes['11']['B']['top'] = ["#ff90f4", "#ff90f4", "#ff90f4"]; + + self::$themes['11']['C']['env'] = ["#22535d"]; + self::$themes['11']['C']['clo'] = ["#000", "#ff2500", "#ff2500", "#fff", "#fff"]; + self::$themes['11']['C']['head'] = ["#a76c44"]; + self::$themes['11']['C']['mouth'] = ["#191919", "#191919"]; + self::$themes['11']['C']['eyes'] = ["#000", "none", "#000"]; + self::$themes['11']['C']['top'] = ["none", "#00efff", "none"]; // Blond - $themes['12']['A']['env'] = ["#2668DC"]; - $themes['12']['A']['clo'] = ["#2385c6", "#b8d0e0", "#b8d0e0"]; - $themes['12']['A']['head'] = ["#ad8a60"]; - $themes['12']['A']['mouth'] = ["#000", "#4d4d4d"]; - $themes['12']['A']['eyes'] = ["#7fb5a2", "#d1eddf", "#301e19"]; - $themes['12']['A']['top'] = ["#fff510", "#fff510"]; - - $themes['12']['B']['env'] = ["#643869"]; - $themes['12']['B']['clo'] = ["#D67D1B", "#b8d0e0", "#b8d0e0"]; - $themes['12']['B']['head'] = ["#CC985A", "none0000"]; - $themes['12']['B']['mouth'] = ["#000", "#ececec"]; - $themes['12']['B']['eyes'] = ["#1f2644", "#9b97ce", "#301e19"]; - $themes['12']['B']['top'] = ["#00eaff", "none"]; - - $themes['12']['C']['env'] = ["#F599FF"]; - $themes['12']['C']['clo'] = ["#2823C6", "#b8d0e0", "#b8d0e0"]; - $themes['12']['C']['head'] = ["#C7873A"]; - $themes['12']['C']['mouth'] = ["#000", "#4d4d4d"]; - $themes['12']['C']['eyes'] = ["#581b1b", "#FF8B8B", "#000"]; - $themes['12']['C']['top'] = ["none", "#9c0092"]; + self::$themes['12']['A']['env'] = ["#2668DC"]; + self::$themes['12']['A']['clo'] = ["#2385c6", "#b8d0e0", "#b8d0e0"]; + self::$themes['12']['A']['head'] = ["#ad8a60"]; + self::$themes['12']['A']['mouth'] = ["#000", "#4d4d4d"]; + self::$themes['12']['A']['eyes'] = ["#7fb5a2", "#d1eddf", "#301e19"]; + self::$themes['12']['A']['top'] = ["#fff510", "#fff510"]; + + self::$themes['12']['B']['env'] = ["#643869"]; + self::$themes['12']['B']['clo'] = ["#D67D1B", "#b8d0e0", "#b8d0e0"]; + self::$themes['12']['B']['head'] = ["#CC985A", "none0000"]; + self::$themes['12']['B']['mouth'] = ["#000", "#ececec"]; + self::$themes['12']['B']['eyes'] = ["#1f2644", "#9b97ce", "#301e19"]; + self::$themes['12']['B']['top'] = ["#00eaff", "none"]; + + self::$themes['12']['C']['env'] = ["#F599FF"]; + self::$themes['12']['C']['clo'] = ["#2823C6", "#b8d0e0", "#b8d0e0"]; + self::$themes['12']['C']['head'] = ["#C7873A"]; + self::$themes['12']['C']['mouth'] = ["#000", "#4d4d4d"]; + self::$themes['12']['C']['eyes'] = ["#581b1b", "#FF8B8B", "#000"]; + self::$themes['12']['C']['top'] = ["none", "#9c0092"]; // Ateam - $themes['13']['A']['env'] = ["#d10084"]; - $themes['13']['A']['clo'] = ["#efedee", "#00a1e0", "#00a1e0", "#efedee", "#ffce1c"]; - $themes['13']['A']['head'] = ["#b35f49"]; - $themes['13']['A']['mouth'] = ["#3a484a", "#000"]; - $themes['13']['A']['eyes'] = ["#000"]; - $themes['13']['A']['top'] = ["#000", "none", "#000", "none"]; - - $themes['13']['B']['env'] = ["#E6C117"]; - $themes['13']['B']['clo'] = ["#efedee", "#ec0033", "#ec0033", "#efedee", "#f2ff05"]; - $themes['13']['B']['head'] = ["#ffc016"]; - $themes['13']['B']['mouth'] = ["#4a3737", "#000"]; - $themes['13']['B']['eyes'] = ["#000"]; - $themes['13']['B']['top'] = ["#ffe900", "#ffe900", "none", "#ffe900"]; - - $themes['13']['C']['env'] = ["#1d8c00"]; - $themes['13']['C']['clo'] = ["#e000cb", "#fff", "#fff", "#e000cb", "#ffce1c"]; - $themes['13']['C']['head'] = ["#b96438"]; - $themes['13']['C']['mouth'] = ["#000", "#000"]; - $themes['13']['C']['eyes'] = ["#000"]; - $themes['13']['C']['top'] = ["#53ffff", "#53ffff", "none", "none"]; + self::$themes['13']['A']['env'] = ["#d10084"]; + self::$themes['13']['A']['clo'] = ["#efedee", "#00a1e0", "#00a1e0", "#efedee", "#ffce1c"]; + self::$themes['13']['A']['head'] = ["#b35f49"]; + self::$themes['13']['A']['mouth'] = ["#3a484a", "#000"]; + self::$themes['13']['A']['eyes'] = ["#000"]; + self::$themes['13']['A']['top'] = ["#000", "none", "#000", "none"]; + + self::$themes['13']['B']['env'] = ["#E6C117"]; + self::$themes['13']['B']['clo'] = ["#efedee", "#ec0033", "#ec0033", "#efedee", "#f2ff05"]; + self::$themes['13']['B']['head'] = ["#ffc016"]; + self::$themes['13']['B']['mouth'] = ["#4a3737", "#000"]; + self::$themes['13']['B']['eyes'] = ["#000"]; + self::$themes['13']['B']['top'] = ["#ffe900", "#ffe900", "none", "#ffe900"]; + + self::$themes['13']['C']['env'] = ["#1d8c00"]; + self::$themes['13']['C']['clo'] = ["#e000cb", "#fff", "#fff", "#e000cb", "#ffce1c"]; + self::$themes['13']['C']['head'] = ["#b96438"]; + self::$themes['13']['C']['mouth'] = ["#000", "#000"]; + self::$themes['13']['C']['eyes'] = ["#000"]; + self::$themes['13']['C']['top'] = ["#53ffff", "#53ffff", "none", "none"]; // Rasta - $themes['14']['A']['env'] = ["#fc0065"]; - $themes['14']['A']['clo'] = ["#708913", "#fdea14", "#708913", "#fdea14", "#708913"]; - $themes['14']['A']['head'] = ["#DEA561"]; - $themes['14']['A']['mouth'] = ["#444", "#000"]; - $themes['14']['A']['eyes'] = ["#000"]; - $themes['14']['A']['top'] = ["#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f" ]; - - $themes['14']['B']['env'] = ["#81f72e"]; - $themes['14']['B']['clo'] = ["#ff0000", "#ffc107", "#ff0000", "#ffc107", "#ff0000"]; - $themes['14']['B']['head'] = ["#ef9831"]; - $themes['14']['B']['mouth'] = ["#6b0000", "#000"]; - $themes['14']['B']['eyes'] = ["#000"]; - $themes['14']['B']['top'] = ["#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "none", "none", "none", "none"]; - - $themes['14']['C']['env'] = ["#00D872"]; - $themes['14']['C']['clo'] = ["#590D00", "#FD1336", "#590D00", "#FD1336", "#590D00"]; - $themes['14']['C']['head'] = ["#c36c00"]; - $themes['14']['C']['mouth'] = ["#56442b", "#000"]; - $themes['14']['C']['eyes'] = ["#000"]; - $themes['14']['C']['top'] = ["#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "none", "none", "none", "none", "none", "none", "none", "none"]; + self::$themes['14']['A']['env'] = ["#fc0065"]; + self::$themes['14']['A']['clo'] = ["#708913", "#fdea14", "#708913", "#fdea14", "#708913"]; + self::$themes['14']['A']['head'] = ["#DEA561"]; + self::$themes['14']['A']['mouth'] = ["#444", "#000"]; + self::$themes['14']['A']['eyes'] = ["#000"]; + self::$themes['14']['A']['top'] = ["#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f" ]; + + self::$themes['14']['B']['env'] = ["#81f72e"]; + self::$themes['14']['B']['clo'] = ["#ff0000", "#ffc107", "#ff0000", "#ffc107", "#ff0000"]; + self::$themes['14']['B']['head'] = ["#ef9831"]; + self::$themes['14']['B']['mouth'] = ["#6b0000", "#000"]; + self::$themes['14']['B']['eyes'] = ["#000"]; + self::$themes['14']['B']['top'] = ["#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "none", "none", "none", "none"]; + + self::$themes['14']['C']['env'] = ["#00D872"]; + self::$themes['14']['C']['clo'] = ["#590D00", "#FD1336", "#590D00", "#FD1336", "#590D00"]; + self::$themes['14']['C']['head'] = ["#c36c00"]; + self::$themes['14']['C']['mouth'] = ["#56442b", "#000"]; + self::$themes['14']['C']['eyes'] = ["#000"]; + self::$themes['14']['C']['top'] = ["#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "none", "none", "none", "none", "none", "none", "none", "none"]; // Meta - $themes['15']['A']['env'] = ["#111"]; - $themes['15']['A']['clo'] = ["#000", "#00FFFF"]; - $themes['15']['A']['head'] = ["#755227"]; - $themes['15']['A']['mouth'] = ["#fff", "#000"]; - $themes['15']['A']['eyes'] = ["black", "#008a", "aqua"]; - $themes['15']['A']['top'] = ["#fff", "#fff", "#fff", "#fff", "#fff"]; - - $themes['15']['B']['env'] = ["#00D0D4"]; - $themes['15']['B']['clo'] = ["#000", "#fff"]; - $themes['15']['B']['head'] = ["#755227"]; - $themes['15']['B']['mouth'] = ["#fff", "#000"]; - $themes['15']['B']['eyes'] = ["black", "#1df7ffa3", "#fcff2c"]; - $themes['15']['B']['top'] = ["#fff539", "none", "#fff539", "none", "#fff539"]; - - $themes['15']['C']['env'] = ["#DC75FF"]; - $themes['15']['C']['clo'] = ["#000", "#FFBDEC"]; - $themes['15']['C']['head'] = ["#997549"]; - $themes['15']['C']['mouth'] = ["#fff", "#000"]; - $themes['15']['C']['eyes'] = ["black", "black", "aqua"]; - $themes['15']['C']['top'] = ["#00fffd", "none", "none", "none", "none"]; - - return $themes; + self::$themes['15']['A']['env'] = ["#111"]; + self::$themes['15']['A']['clo'] = ["#000", "#00FFFF"]; + self::$themes['15']['A']['head'] = ["#755227"]; + self::$themes['15']['A']['mouth'] = ["#fff", "#000"]; + self::$themes['15']['A']['eyes'] = ["black", "#008a", "aqua"]; + self::$themes['15']['A']['top'] = ["#fff", "#fff", "#fff", "#fff", "#fff"]; + + self::$themes['15']['B']['env'] = ["#00D0D4"]; + self::$themes['15']['B']['clo'] = ["#000", "#fff"]; + self::$themes['15']['B']['head'] = ["#755227"]; + self::$themes['15']['B']['mouth'] = ["#fff", "#000"]; + self::$themes['15']['B']['eyes'] = ["black", "#1df7ffa3", "#fcff2c"]; + self::$themes['15']['B']['top'] = ["#fff539", "none", "#fff539", "none", "#fff539"]; + + self::$themes['15']['C']['env'] = ["#DC75FF"]; + self::$themes['15']['C']['clo'] = ["#000", "#FFBDEC"]; + self::$themes['15']['C']['head'] = ["#997549"]; + self::$themes['15']['C']['mouth'] = ["#fff", "#000"]; + self::$themes['15']['C']['eyes'] = ["black", "black", "aqua"]; + self::$themes['15']['C']['top'] = ["#00fffd", "none", "none", "none", "none"]; + + return self::$themes; } } diff --git a/index.php b/index.php index e977388..7be41cc 100644 --- a/index.php +++ b/index.php @@ -1,7 +1,5 @@ Date: Sun, 20 Dec 2020 23:16:46 +0100 Subject: [PATCH 05/17] Adding PHPUnit as a development dependency --- composer.json | 19 ++++-- phpunit.xml.dist | 36 +++++++++++ src/MultiavatarTest.php | 129 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 phpunit.xml.dist create mode 100644 src/MultiavatarTest.php diff --git a/composer.json b/composer.json index 8277571..7cd79d0 100644 --- a/composer.json +++ b/composer.json @@ -20,16 +20,27 @@ } ], "autoload": { - "classmap": ["Multiavatar.php"] + "psr-4": { + "Multiavatar\\": "src/" + } }, "require-dev": { + "php": ">=7.1", + "phpunit/phpunit": "^9.5", "phpstan/phpstan": "^0.12.63", - "phpstan/phpstan-strict-rules": "^0.12.7" + "phpstan/phpstan-strict-rules": "^0.12.7", + "phpstan/phpstan-phpunit": "^0.12.17" }, "scripts": { - "phpstan": "phpstan analyse --level max Multiavatar.php -c phpstan.neon --ansi" + "phpstan": "phpstan analyse src -c phpstan.neon --ansi", + "phpunit": "phpunit --coverage-text", + "test": [ + "@phpunit", + "@phpstan" + ] }, "scripts-descriptions": { - "phpstan": "Static analysis of PHP scripts" + "phpstan": "Static analysis of PHP scripts", + "phpunit": "Runs unit and functional testing" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..eda5739 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + src + + + src + + + + + + + + + + src + + + + + + diff --git a/src/MultiavatarTest.php b/src/MultiavatarTest.php new file mode 100644 index 0000000..d86a5cd --- /dev/null +++ b/src/MultiavatarTest.php @@ -0,0 +1,129 @@ +multiavatar = new Multiavatar(); + } + + /** @test */ + public function it_will_throw_if_the_ver_part_is_not_valid(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The submitted part does not exists.'); + + ($this->multiavatar)('foobar', ['ver' => ['part' => 16]]); + } + + /** @test */ + public function it_will_throw_if_the_ver_theme_is_not_valid(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The submitted theme does not exists.'); + + ($this->multiavatar)('foobar', ['ver' => ['theme' => 'D']]); + } + + /** @test */ + public function it_will_throw_if_the_avatar_type_is_not_valid(): void + { + $this->expectException(TypeError::class); + $this->expectExceptionMessage('Expected a scalar or a Stringable object; got: object'); + + ($this->multiavatar)(new \stdClass()); + } + + /** @test */ + public function it_will_return_the_same_svg_with_a_numeric_or_string_part(): void + { + $svgNumericPart = ($this->multiavatar)('foobar', ['ver' => ['part' => 5]]); + $svgString1Part = ($this->multiavatar)('foobar', ['ver' => ['part' => '05']]); + $svgString2Part = ($this->multiavatar)('foobar', ['ver' => ['part' => '5']]); + + self::assertSame($svgNumericPart, $svgString1Part); + self::assertSame($svgNumericPart, $svgString2Part); + } + + /** @test */ + public function it_will_return_the_same_svg_with_a_case_insensitive_theme(): void + { + $svgLowerTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'a']]); + $svgUpperTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'A']]); + + self::assertSame($svgLowerTheme, $svgUpperTheme); + } + + /** @test */ + public function it_will_return_the_same_svg_with_sans_env_truthy_values(): void + { + $svgSansEnvTrue = ($this->multiavatar)('foobar', ['sansEnv' => true]); + $svgSansEnvOne = ($this->multiavatar)('foobar', ['sansEnv' => 1]); + $svgSansEnvYes = ($this->multiavatar)('foobar', ['sansEnv' => 'yes']); + + self::assertSame($svgSansEnvTrue, $svgSansEnvOne); + self::assertSame($svgSansEnvTrue, $svgSansEnvYes); + } + + /** @test */ + public function it_will_return_the_same_svg_with_sans_env_falsy_values(): void + { + $svgSansEnvFalse = ($this->multiavatar)('foobar', ['sansEnv' => false]); + $svgSansEnvWrongValueIsFalse = ($this->multiavatar)('foobar', ['sansEnv' => 'fdsdf']); + $svgSansEnvNullValue = ($this->multiavatar)('foobar', ['sansEnv' => null]); + + self::assertSame($svgSansEnvFalse, $svgSansEnvNullValue); + self::assertSame($svgSansEnvFalse, $svgSansEnvWrongValueIsFalse); + } + + /** @test */ + public function it_will_return_different_svg_with_sans_env_values(): void + { + $svgSansEnvFalse = ($this->multiavatar)('foobar', ['sansEnv' => false]); + $svgSansEnvTrue = ($this->multiavatar)('foobar', ['sansEnv' => true]); + + self::assertNotSame($svgSansEnvFalse, $svgSansEnvTrue); + } + + /** + * @test + * @dataProvider getEmptySvgProvider + * @param object|string $avatarId + */ + public function it_will_return_an_empty_svg($avatarId): void + { + self::assertSame('', ($this->multiavatar)($avatarId)); + } + + /** + * @return iterable + */ + public function getEmptySvgProvider(): iterable + { + yield 'avatar is an empty string' => ['avatarId' => '',]; + yield 'avatar is an empty string after being trimmed' => ['avatarId' => ' ']; + yield 'avatar can be a stringable object' => [ + 'avatarId' => new class { + public function __toString(): string { + return ''; + } + }, + ]; + } +} From 3f299730153573272cc5d92275a3a572b9baae6a Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 20 Dec 2020 23:17:16 +0100 Subject: [PATCH 06/17] Adding demo page for testing visual rendering --- demo/index.php | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 demo/index.php diff --git a/demo/index.php b/demo/index.php new file mode 100644 index 0000000..4d01963 --- /dev/null +++ b/demo/index.php @@ -0,0 +1,61 @@ + ['part' => $part, 'theme' => $theme]]); + } +} +?> + + + + + + + Multiavatar - All 48 Initial Avatar Designs + + + +
+ $svg): ?> +
+ +
+
+ + From 48bd8c67cc4e1f5bcdae72201e251566de0d29fc Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 20 Dec 2020 23:17:52 +0100 Subject: [PATCH 07/17] Updating package settings --- .gitattributes | 2 ++ .gitignore | 2 ++ phpstan.neon | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.gitattributes b/.gitattributes index 0ae277a..f403e2d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,6 @@ /.gitattributes export-ignore /.gitignore export-ignore /phpstan.neon export-ignore +/phpunit.xml.dist export-ignore /README.md export-ignore +/**/*Test.php export-ignore diff --git a/.gitignore b/.gitignore index 5c272e4..5fa7987 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +build vendor .idea +.phpunit.result.cache composer.lock diff --git a/phpstan.neon b/phpstan.neon index deeb025..6f33fba 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,8 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/rules.neon parameters: + level: max + checkMissingIterableValueType: false ignoreErrors: reportUnmatchedIgnoredErrors: true From 239e66ce237047d2600885989313db36f3259b72 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 20 Dec 2020 23:18:35 +0100 Subject: [PATCH 08/17] Introducing the Multiavatar namespace and the options parameter --- README.md | 9 ++- autoload.php | 16 ++++ index.php | 22 ++--- Multiavatar.php => src/Multiavatar.php | 107 +++++++++++++++---------- 4 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 autoload.php rename Multiavatar.php => src/Multiavatar.php (96%) diff --git a/README.md b/README.md index 672b33b..7672695 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ composer require multiavatar/multiavatar-php ### Usage ### ``` +use Multiavatar\Multiavatar; + $multiavatar = new Multiavatar(); $avatarId = "Binx Bond"; echo $multiavatar($avatarId); @@ -39,14 +41,15 @@ For advanced usage, pass boolean `true` as the second parameter if you wish to g Pass an associative array as the third parameter to generate a specific avatar version. ``` +use Multiavatar\Multiavatar; + $multiavatar = new Multiavatar(); -$avatarId = "ANY_STRING"; -echo $multiavatar($avatarId, true, ["part" => "11", "theme" => "C"]); +$avatarId = "ANY_STRING_OR_INT"; +echo $multiavatar($avatarId, ['ver' => ['part' => '11', 'theme' => 'C'], 'sansEnv' => true]); ``` See `index.php` for examples. - ### API ### This PHP script is powering the [Multiavatar API](https://api.multiavatar.com). Simply pass the avatar's ID as the URL parameter, and the API will return the avatar's SVG code. diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..9b27649 --- /dev/null +++ b/autoload.php @@ -0,0 +1,16 @@ + true]); // Generate a specific version // $avatarId = "Pandalion"; -// echo $multiAvatar($avatarId, false, ["part" => 11, "theme" => "C"]); +// echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['part' => 11, 'theme' => 'C']]); -// Test with integer +// Test with integer and a specific theme from any given version +// version theme are case insensitive // $avatarId = 123456789; -// echo $multiAvatar($avatarId); +// echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['theme' => 'b']]); +// Test with a specific part from any given version // $avatarId = "a86f755add37fe0b649c"; -// echo $multiAvatar($avatarId); +// echo $multiavatar($avatarId, ['ver' => ['part' => '08']]); // $avatarId = "f7542474d54d2d2d97e4"; -// echo $multiAvatar($avatarId); +// echo $multiavatar($avatarId); diff --git a/Multiavatar.php b/src/Multiavatar.php similarity index 96% rename from Multiavatar.php rename to src/Multiavatar.php index 29ad2cd..979557d 100644 --- a/Multiavatar.php +++ b/src/Multiavatar.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace Multiavatar; + // Multiavatar v1.0 (PHP version) // package Multiavatar-PHP @@ -10,16 +12,40 @@ // license https://multiavatar.com/license // homepage https://multiavatar.com +use InvalidArgumentException; +use TypeError; +use function filter_var; +use function hash; +use function intdiv; +use function is_object; +use function is_scalar; +use function method_exists; +use function preg_match; +use function preg_replace; +use function round; +use function sprintf; +use function strpos; +use function strtoupper; +use function substr; +use function trim; + class Multiavatar { private const SVG_ROOT_OPEN_TAG = ''; private const SVG_ROOT_CLOSE_TAG = ''; - private const SVG_ELEMENT_ENV = ''; + private const SVG_ELEMENT_ENV = ''; private const SVG_ELEMENT_HEAD = ''; private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:'; - private const THEME_LIST = [0 => 'A', 1 => 'B', 2 => 'C']; + public const DEFAULT_OPTIONS = [ + 'ver' => [ + 'part' => null, + 'theme' => null, + ], + 'sansEnv' => false, + ]; + /** * @var array>>> */ @@ -32,10 +58,9 @@ class Multiavatar /** * @param object|int|float|string $avatarId the avatar id the object must implement the __toString method - * @param bool $sansEnv If this is true, the returns avatar is without the circle background (environment part). - * @param array $options options to force a specific initial version, for example ['part' => '01', 'theme' => 'A'] + * @param array $options */ - public function __invoke($avatarId, bool $sansEnv = false, array $options = []): string + public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): string { $avatarId = $this->filterAvatar($avatarId); $options = $this->filterOptions($options); @@ -43,7 +68,7 @@ public function __invoke($avatarId, bool $sansEnv = false, array $options = []): return ''; } - $svgElements = $this->partsToSvgElements($this->avatarToParts($avatarId), $sansEnv, $options); + $svgElements = $this->partsToElements($this->avatarToParts($avatarId), $options); return self::SVG_ROOT_OPEN_TAG . $svgElements['env'] @@ -72,27 +97,30 @@ private function filterAvatar($avatarId): string } /** - * @param array $inputOptions - * - * @return array{part:string|null, theme:string|null} + * @return array{ver:array{part:string|null, theme:string|null}, sansEnv:bool} */ private function filterOptions(array $inputOptions): array { - $options = ['part' => null, 'theme' => null]; - if (isset($inputOptions['part'])) { - $part = sprintf("%'.02d", $inputOptions['part']); + $options = [ + 'ver' => ['part' => null, 'theme' => null], + 'sansEnv' => filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOL), + ]; + + if (isset($inputOptions['ver']['part'])) { + $part = sprintf("%'.02d", $inputOptions['ver']['part']); if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) { throw new InvalidArgumentException('The submitted part does not exists.'); } - $options['part'] = $part; + $options['ver']['part'] = $part; } - if (isset($inputOptions['theme'])) { - if (!in_array($inputOptions['theme'], self::THEME_LIST, true)) { + if (isset($inputOptions['ver']['theme'])) { + if (1 !== preg_match('/^([a-c])$/i', $inputOptions['ver']['theme'])) { throw new InvalidArgumentException('The submitted theme does not exists.'); } - $options['theme'] = $inputOptions['theme']; + + $options['ver']['theme'] = strtoupper($inputOptions['ver']['theme']); } return $options; @@ -121,7 +149,8 @@ private function avatarToParts(string $avatarId): array /** * @return array{part:string, theme:string} */ - private function mapPart(string $part): array { + private function mapPart(string $part): array + { $part = (int) round(47 / 100 * (int) $part); return [ @@ -131,25 +160,24 @@ private function mapPart(string $part): array { } /** - * @param array $bodyParts - * @param bool $sansEnv - * @param array{part:string|null, theme:string|null} $options + * @param array $bodyParts + * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options * * @return array */ - private function partsToSvgElements(array $bodyParts, bool $sansEnv, array $options): array + private function partsToElements(array $bodyParts, array $options): array { $elements = []; foreach ($bodyParts as $name => $bodyPart) { - if ('env' === $name && $sansEnv) { + if ('env' === $name && $options['sansEnv']) { $elements[$name] = ''; continue; } $elements[$name] = $this->generateSvgElement( $name, - $options['part'] ?? $bodyPart['part'], - $options['theme'] ?? $bodyPart['theme'] + $options['ver']['part'] ?? $bodyPart['part'], + $options['ver']['theme'] ?? $bodyPart['theme'] ); } @@ -158,21 +186,18 @@ private function partsToSvgElements(array $bodyParts, bool $sansEnv, array $opti private function generateSvgElement(string $name, string $part, string $theme): string { - $svgElement = self::shapes()[$part][$name]; - $found = preg_match_all('/#(.*?)+(?=;)/', $svgElement, $result); - if (0 === $found || false === $found) { - return $svgElement; - } - + $element = self::shapes()[$part][$name]; $colors = self::themes()[$part][$theme][$name]; - foreach ($result[0] as $index => $var) { - $pos = strpos($svgElement, $var); - if ($pos !== false) { - $svgElement = substr_replace($svgElement, $colors[$index] ?? $colors[0], $pos, strlen($var)); + preg_match_all('/#(.*?)+(?=;)/', $element, $result); + + foreach ($result[0] as $index => $initialColor) { + if (false !== ($pos = strpos($element, $initialColor))) { + $selectedColor = $colors[$index] ?? $colors[0]; + $element = substr_replace($element, $selectedColor, $pos, strlen($initialColor)); } } - return $svgElement; + return $element; } /** @@ -519,22 +544,22 @@ private static function themes(): array self::$themes['08']['A']['env'] = ["#0df"]; self::$themes['08']['A']['clo'] = ["#571e57", "#ff0"]; self::$themes['08']['A']['head'] = ["#f2c280"]; - self::$themes['08']['A']['mouth'] = ["#795548", "#000"]; - self::$themes['08']['A']['eyes'] = ["#ff0000"]; + self::$themes['08']['A']['mouth'] = ["#ff0000"]; + self::$themes['08']['A']['eyes'] = ["#795548", "#000"]; self::$themes['08']['A']['top'] = ["#de3b00", "none"]; self::$themes['08']['B']['env'] = ["#B400C2"]; self::$themes['08']['B']['clo'] = ["#0D204A", "#00ffdf"]; self::$themes['08']['B']['head'] = ["#ca8628"]; - self::$themes['08']['B']['mouth'] = ["#cbbdaf", "#000"]; - self::$themes['08']['B']['eyes'] = ["#1a1a1a"]; + self::$themes['08']['B']['mouth'] = ["#1a1a1a"]; + self::$themes['08']['B']['eyes'] = ["#cbbdaf", "#000"]; self::$themes['08']['B']['top'] = ["#000", "#000"]; self::$themes['08']['C']['env'] = ["#ffe926"]; self::$themes['08']['C']['clo'] = ["#00d6af", "#000"]; self::$themes['08']['C']['head'] = ["#8c5100"]; - self::$themes['08']['C']['mouth'] = ["none", "#000"]; - self::$themes['08']['C']['eyes'] = ["#7d0000"]; + self::$themes['08']['C']['mouth'] = ["#7d0000"]; + self::$themes['08']['C']['eyes'] = ["none", "#000"]; self::$themes['08']['C']['top'] = ["#f7f7f7", "none"]; // Normie female From 595943185569f771be1dc4ce79b850c362e1d728 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 20 Dec 2020 23:27:27 +0100 Subject: [PATCH 09/17] Adding PHP7.2 support --- .gitattributes | 2 ++ .github/workflows/build.yml | 26 ++++++++++++++++++++++++++ README.md | 4 ++-- composer.json | 2 +- src/MultiavatarTest.php | 3 --- 5 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.gitattributes b/.gitattributes index f403e2d..65c3bcd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,10 @@ * text=auto +/demo export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore +/.github export-ignore /phpstan.neon export-ignore /phpunit.xml.dist export-ignore /README.md export-ignore diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c213c06 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +--- +name: build +on: + push: ~ + pull_request: ~ + +jobs: + phpunit: + name: PHPUnit tests on ${{ matrix.php }} ${{ matrix.composer-flags }} + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.2', '7.3', '7.4', '8.0' ] + composer-flags: [ '' ] + phpunit-flags: [ '--coverage-text' ] + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + tools: composer:v2 + - run: composer update --no-progress ${{ matrix.composer-flags }} + - run: composer phpstan + if: ${{ matrix.php == '7.4' }} + - run: vendor/bin/phpunit ${{ matrix.phpunit-flags }} diff --git a/README.md b/README.md index 7672695..27850df 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ For more details about the Multiavatar Generator, please refer to the readme ava ### Installation ### -If you don't use composer, just include `Multiavatar.php` in your application. +If you don't use composer, just include the autoloader in the root of the directory in your application. ``` -require_once('Multiavatar.php'); +require_once '/path/to/directory/Multiavatar/autoload.php'; ``` Via Composer: diff --git a/composer.json b/composer.json index 7cd79d0..a01e41a 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require-dev": { "php": ">=7.1", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^8.0 || ^9.5", "phpstan/phpstan": "^0.12.63", "phpstan/phpstan-strict-rules": "^0.12.7", "phpstan/phpstan-phpunit": "^0.12.17" diff --git a/src/MultiavatarTest.php b/src/MultiavatarTest.php index d86a5cd..0c52e8d 100644 --- a/src/MultiavatarTest.php +++ b/src/MultiavatarTest.php @@ -7,9 +7,6 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use TypeError; -use function file_get_contents; -use function range; -use function sprintf; final class MultiavatarTest extends TestCase { From 0c71ae7772fd77d3e2ba268ee76b87331df7ad61 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 20 Dec 2020 23:29:44 +0100 Subject: [PATCH 10/17] bugfix filter name --- src/Multiavatar.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Multiavatar.php b/src/Multiavatar.php index 979557d..cd278c4 100644 --- a/src/Multiavatar.php +++ b/src/Multiavatar.php @@ -28,6 +28,7 @@ use function strtoupper; use function substr; use function trim; +use const FILTER_VALIDATE_BOOLEAN; class Multiavatar { @@ -103,7 +104,7 @@ private function filterOptions(array $inputOptions): array { $options = [ 'ver' => ['part' => null, 'theme' => null], - 'sansEnv' => filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOL), + 'sansEnv' => filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOLEAN), ]; if (isset($inputOptions['ver']['part'])) { From 0e5496454c7fba90dd95a162caae2f2077201b6f Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 21 Dec 2020 06:27:04 +0100 Subject: [PATCH 11/17] BugFix themes colors --- src/Multiavatar.php | 58 ++++++++++++++++++++++------------------- src/MultiavatarTest.php | 32 +++++++++++------------ 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/Multiavatar.php b/src/Multiavatar.php index cd278c4..135a4ea 100644 --- a/src/Multiavatar.php +++ b/src/Multiavatar.php @@ -15,6 +15,8 @@ use InvalidArgumentException; use TypeError; use function filter_var; +use function get_class; +use function gettype; use function hash; use function intdiv; use function is_object; @@ -22,9 +24,9 @@ use function method_exists; use function preg_match; use function preg_replace; +use function preg_replace_callback; use function round; use function sprintf; -use function strpos; use function strtoupper; use function substr; use function trim; @@ -41,10 +43,10 @@ class Multiavatar public const DEFAULT_OPTIONS = [ 'ver' => [ - 'part' => null, - 'theme' => null, + 'part' => null, // string|int - the version part between '00' and '15' + 'theme' => null, // string - the version theme to choose between 'A' and 'C' ], - 'sansEnv' => false, + 'sansEnv' => false, // boolean - Tells whether to display or not the environment (by default the environment is shown) ]; /** @@ -91,7 +93,7 @@ private function filterAvatar($avatarId): string } if (!is_scalar($avatarId)) { - throw new TypeError('Expected a scalar or a Stringable object; got: ' . gettype($avatarId)); + throw new TypeError('Expected a scalar or a Stringable object; got: ' . (is_object($avatarId) ? get_class($avatarId) : gettype($avatarId))); } return trim((string) $avatarId); @@ -102,28 +104,29 @@ private function filterAvatar($avatarId): string */ private function filterOptions(array $inputOptions): array { - $options = [ - 'ver' => ['part' => null, 'theme' => null], - 'sansEnv' => filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOLEAN), - ]; + $inputOptions = $inputOptions + self::DEFAULT_OPTIONS; + $options = self::DEFAULT_OPTIONS; + $options['sansEnv'] = filter_var($inputOptions['sansEnv'], FILTER_VALIDATE_BOOLEAN); - if (isset($inputOptions['ver']['part'])) { + if (null !== $inputOptions['ver']['part']) { $part = sprintf("%'.02d", $inputOptions['ver']['part']); if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) { - throw new InvalidArgumentException('The submitted part does not exists.'); + throw new InvalidArgumentException('The submitted part does not exists; expecting a value between `00` and `15`.'); } $options['ver']['part'] = $part; } - if (isset($inputOptions['ver']['theme'])) { - if (1 !== preg_match('/^([a-c])$/i', $inputOptions['ver']['theme'])) { - throw new InvalidArgumentException('The submitted theme does not exists.'); - } + if (null === $inputOptions['ver']['theme']) { + return $options; + } - $options['ver']['theme'] = strtoupper($inputOptions['ver']['theme']); + if (1 !== preg_match('/^([a-c])$/i', $inputOptions['ver']['theme'])) { + throw new InvalidArgumentException('The submitted theme does not exists; expecting a value between `A`, `B` and `C`.'); } + $options['ver']['theme'] = strtoupper($inputOptions['ver']['theme']); + return $options; } @@ -187,16 +190,17 @@ private function partsToElements(array $bodyParts, array $options): array private function generateSvgElement(string $name, string $part, string $theme): string { - $element = self::shapes()[$part][$name]; $colors = self::themes()[$part][$theme][$name]; - preg_match_all('/#(.*?)+(?=;)/', $element, $result); + $index = 0; + $replace = function (array $result) use ($colors, &$index): string { + $selectedColor = $colors[$index]; + ++$index; - foreach ($result[0] as $index => $initialColor) { - if (false !== ($pos = strpos($element, $initialColor))) { - $selectedColor = $colors[$index] ?? $colors[0]; - $element = substr_replace($element, $selectedColor, $pos, strlen($initialColor)); - } - } + return $selectedColor; + }; + + /** @var string $element */ + $element = preg_replace_callback('/#(.*?)+(?=;)/', $replace, self::shapes()[$part][$name]); return $element; } @@ -480,7 +484,7 @@ private static function themes(): array self::$themes['05']['A']['clo'] = ["#c7d4e2", "#435363", "#435363", "#141720", "#141720", "#e7ecf2", "#e7ecf2"]; self::$themes['05']['A']['head'] = ["#f5d4a6"]; self::$themes['05']['A']['mouth'] = ["#000", "#cf9f76"]; - self::$themes['05']['A']['eyes'] = ["#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff", "#000", "#000"]; + self::$themes['05']['A']['eyes'] = ["#000", "#000", "#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff"]; self::$themes['05']['A']['top'] = ["none", "#fdff00"]; self::$themes['05']['B']['env'] = ["#b3003e"]; @@ -575,14 +579,14 @@ private static function themes(): array self::$themes['09']['B']['clo'] = ["#033c58", "#fff", "#fff"]; self::$themes['09']['B']['head'] = ["#dbc97f"]; self::$themes['09']['B']['mouth'] = ["#000"]; - self::$themes['09']['B']['eyes'] = ["none", "#fff", "#000"]; + self::$themes['09']['B']['eyes'] = ["none", "#000", "#fff"]; self::$themes['09']['B']['top'] = ["#FFEB3B", "#FFEB3B", "none", "#FFEB3B"]; self::$themes['09']['C']['env'] = ["#FF9800"]; self::$themes['09']['C']['clo'] = ["#b40000", "#fff", "#fff"]; self::$themes['09']['C']['head'] = ["#E2AF6B"]; self::$themes['09']['C']['mouth'] = ["#000"]; - self::$themes['09']['C']['eyes'] = ["none", "#fff", "#000"]; + self::$themes['09']['C']['eyes'] = ["none", "#000", "#fff"]; self::$themes['09']['C']['top'] = ["#ec0000", "#ec0000", "none", "none"]; // Older diff --git a/src/MultiavatarTest.php b/src/MultiavatarTest.php index 0c52e8d..49a82dd 100644 --- a/src/MultiavatarTest.php +++ b/src/MultiavatarTest.php @@ -24,7 +24,7 @@ public function setUp(): void public function it_will_throw_if_the_ver_part_is_not_valid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The submitted part does not exists.'); + $this->expectExceptionMessage('The submitted part does not exists; expecting a value between `00` and `15`'); ($this->multiavatar)('foobar', ['ver' => ['part' => 16]]); } @@ -33,7 +33,7 @@ public function it_will_throw_if_the_ver_part_is_not_valid(): void public function it_will_throw_if_the_ver_theme_is_not_valid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The submitted theme does not exists.'); + $this->expectExceptionMessage('The submitted theme does not exists; expecting a value between `A`, `B` and `C`.'); ($this->multiavatar)('foobar', ['ver' => ['theme' => 'D']]); } @@ -42,7 +42,7 @@ public function it_will_throw_if_the_ver_theme_is_not_valid(): void public function it_will_throw_if_the_avatar_type_is_not_valid(): void { $this->expectException(TypeError::class); - $this->expectExceptionMessage('Expected a scalar or a Stringable object; got: object'); + $this->expectExceptionMessage('Expected a scalar or a Stringable object; got: stdClass'); ($this->multiavatar)(new \stdClass()); } @@ -51,11 +51,11 @@ public function it_will_throw_if_the_avatar_type_is_not_valid(): void public function it_will_return_the_same_svg_with_a_numeric_or_string_part(): void { $svgNumericPart = ($this->multiavatar)('foobar', ['ver' => ['part' => 5]]); - $svgString1Part = ($this->multiavatar)('foobar', ['ver' => ['part' => '05']]); - $svgString2Part = ($this->multiavatar)('foobar', ['ver' => ['part' => '5']]); + $svgNumericStringWithLeadingZeroPart = ($this->multiavatar)('foobar', ['ver' => ['part' => '0005']]); + $svgNumericStringPart = ($this->multiavatar)('foobar', ['ver' => ['part' => '5']]); - self::assertSame($svgNumericPart, $svgString1Part); - self::assertSame($svgNumericPart, $svgString2Part); + self::assertSame($svgNumericPart, $svgNumericStringWithLeadingZeroPart); + self::assertSame($svgNumericPart, $svgNumericStringPart); } /** @test */ @@ -70,23 +70,23 @@ public function it_will_return_the_same_svg_with_a_case_insensitive_theme(): voi /** @test */ public function it_will_return_the_same_svg_with_sans_env_truthy_values(): void { - $svgSansEnvTrue = ($this->multiavatar)('foobar', ['sansEnv' => true]); - $svgSansEnvOne = ($this->multiavatar)('foobar', ['sansEnv' => 1]); - $svgSansEnvYes = ($this->multiavatar)('foobar', ['sansEnv' => 'yes']); + $svgSansEnvTrueBoolean = ($this->multiavatar)('foobar', ['sansEnv' => true]); + $svgSansEnvTruthyNumeric = ($this->multiavatar)('foobar', ['sansEnv' => 1]); + $svgSansEnvTruthyString = ($this->multiavatar)('foobar', ['sansEnv' => 'yes']); - self::assertSame($svgSansEnvTrue, $svgSansEnvOne); - self::assertSame($svgSansEnvTrue, $svgSansEnvYes); + self::assertSame($svgSansEnvTrueBoolean, $svgSansEnvTruthyNumeric); + self::assertSame($svgSansEnvTrueBoolean, $svgSansEnvTruthyString); } /** @test */ public function it_will_return_the_same_svg_with_sans_env_falsy_values(): void { - $svgSansEnvFalse = ($this->multiavatar)('foobar', ['sansEnv' => false]); + $svgSansEnvFalseBoolean = ($this->multiavatar)('foobar', ['sansEnv' => false]); $svgSansEnvWrongValueIsFalse = ($this->multiavatar)('foobar', ['sansEnv' => 'fdsdf']); $svgSansEnvNullValue = ($this->multiavatar)('foobar', ['sansEnv' => null]); - self::assertSame($svgSansEnvFalse, $svgSansEnvNullValue); - self::assertSame($svgSansEnvFalse, $svgSansEnvWrongValueIsFalse); + self::assertSame($svgSansEnvFalseBoolean, $svgSansEnvNullValue); + self::assertSame($svgSansEnvFalseBoolean, $svgSansEnvWrongValueIsFalse); } /** @test */ @@ -113,7 +113,7 @@ public function it_will_return_an_empty_svg($avatarId): void */ public function getEmptySvgProvider(): iterable { - yield 'avatar is an empty string' => ['avatarId' => '',]; + yield 'avatar is an empty string' => ['avatarId' => '']; yield 'avatar is an empty string after being trimmed' => ['avatarId' => ' ']; yield 'avatar can be a stringable object' => [ 'avatarId' => new class { From 81aab9a8a5fd644bf1440b575b904cd68a511e22 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 21 Dec 2020 06:27:25 +0100 Subject: [PATCH 12/17] Update documentation --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++------- index.php | 30 ----------------------- 2 files changed, 64 insertions(+), 39 deletions(-) delete mode 100644 index.php diff --git a/README.md b/README.md index 27850df..041d933 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ Initially coded in JavaScript, this version of Multiavatar is re-created in PHP. For more details about the Multiavatar Generator, please refer to the readme available in the JS [repository](https://github.com/multiavatar/Multiavatar). - ### Installation ### If you don't use composer, just include the autoloader in the root of the directory in your application. @@ -25,10 +24,11 @@ Via Composer: composer require multiavatar/multiavatar-php ``` - ### Usage ### -``` +```php + ['part' => '11', 'theme' => 'C'], 'sansEnv' => true]); +$options = [ + 'ver' => [ + 'part' => '11', + 'theme' => 'C', + ], + 'sansEnv' => true, +]; + +echo $multiavatar($avatarId, $options); ``` +The option array contains the following indexes: + +- `sansEnv`: Tells whether the environment should be display or not. By default, the environment is display. + To remove the environment you should set the value to `true. + +- `version`: Indicate which theme and shape should be used to generate the avatar. + There is 3 themes that you specify with the `theme` index named from `A` to `C` + and 16 shapes specified with the `part` index whose named ranged from `00` to `15`. + +Each of those values can be set independently of each other. + +```php + true]); + +// Generate a specific version +// version shape can be express in integer or in string containing only numbers. +$avatarId = "Pandalion"; +echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['part' => 11, 'theme' => 'C']]); + +// The avatarId can be a string or an integer +// version theme are case insensitive. +// The shape will be selected from the $avatarId +$avatarId = 123456789; +echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['theme' => 'b']]); + +// Test with a specific shape +// The theme will be selected from the avatarId +$avatarId = "a86f755add37fe0b649c"; +echo $multiavatar($avatarId, ['ver' => ['part' => '08']]); + +$avatarId = "f7542474d54d2d2d97e4"; +echo $multiavatar($avatarId); +``` ### API ### @@ -68,7 +124,6 @@ fetch('https://api.multiavatar.com/v1/' .then(svg => console.log(svg)) ``` - ### License ### You can use Multiavatar for free, as long as the conditions described in the [LICENSE](https://multiavatar.com/license) are followed. diff --git a/index.php b/index.php deleted file mode 100644 index af2ecbd..0000000 --- a/index.php +++ /dev/null @@ -1,30 +0,0 @@ - true]); - -// Generate a specific version -// $avatarId = "Pandalion"; -// echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['part' => 11, 'theme' => 'C']]); - -// Test with integer and a specific theme from any given version -// version theme are case insensitive -// $avatarId = 123456789; -// echo $multiavatar($avatarId, ['sansEnv' => false, 'ver' => ['theme' => 'b']]); - -// Test with a specific part from any given version -// $avatarId = "a86f755add37fe0b649c"; -// echo $multiavatar($avatarId, ['ver' => ['part' => '08']]); - -// $avatarId = "f7542474d54d2d2d97e4"; -// echo $multiavatar($avatarId); From f2cee289e708514c8c16d5b5409b79d08bcbd367 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 21 Dec 2020 06:33:42 +0100 Subject: [PATCH 13/17] bugfix docblock --- src/Multiavatar.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Multiavatar.php b/src/Multiavatar.php index 135a4ea..8f272d9 100644 --- a/src/Multiavatar.php +++ b/src/Multiavatar.php @@ -60,8 +60,8 @@ class Multiavatar private static $shapes = []; /** - * @param object|int|float|string $avatarId the avatar id the object must implement the __toString method - * @param array $options + * @param mixed $avatarId the avatar id if it is an object it must implement the __toString method + * @param array $options */ public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): string { @@ -84,7 +84,7 @@ public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): str } /** - * @param object|string|float|int $avatarId + * @param mixed $avatarId the avatar Id */ private function filterAvatar($avatarId): string { @@ -104,11 +104,10 @@ private function filterAvatar($avatarId): string */ private function filterOptions(array $inputOptions): array { - $inputOptions = $inputOptions + self::DEFAULT_OPTIONS; $options = self::DEFAULT_OPTIONS; - $options['sansEnv'] = filter_var($inputOptions['sansEnv'], FILTER_VALIDATE_BOOLEAN); + $options['sansEnv'] = filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOLEAN); - if (null !== $inputOptions['ver']['part']) { + if (isset($inputOptions['ver']['part'])) { $part = sprintf("%'.02d", $inputOptions['ver']['part']); if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) { throw new InvalidArgumentException('The submitted part does not exists; expecting a value between `00` and `15`.'); @@ -117,7 +116,7 @@ private function filterOptions(array $inputOptions): array $options['ver']['part'] = $part; } - if (null === $inputOptions['ver']['theme']) { + if (!isset($inputOptions['ver']['theme'])) { return $options; } From 0c0b63630e1c9d983b71a0361ea8479a39b64520 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 21 Dec 2020 07:01:28 +0100 Subject: [PATCH 14/17] Update documentation --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 041d933..8d22f4e 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,19 @@ $avatarId = "f7542474d54d2d2d97e4"; echo $multiavatar($avatarId); ``` +### Testing ### + +The library has a : + +- a [PHPUnit](https://phpunit.de) test suite +- a code analysis compliance test suite using [PHPStan](https://phpstan.org). + +To run the tests, run the following command from the project folder. + +``` bash +$ composer test +``` + ### API ### This PHP script is powering the [Multiavatar API](https://api.multiavatar.com). Simply pass the avatar's ID as the URL parameter, and the API will return the avatar's SVG code. From 684875298f5cb6589a7550637cee8b239379232c Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 21 Dec 2020 08:55:03 +0100 Subject: [PATCH 15/17] Adding missing tests --- src/MultiavatarTest.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/MultiavatarTest.php b/src/MultiavatarTest.php index 49a82dd..676969c 100644 --- a/src/MultiavatarTest.php +++ b/src/MultiavatarTest.php @@ -61,12 +61,23 @@ public function it_will_return_the_same_svg_with_a_numeric_or_string_part(): voi /** @test */ public function it_will_return_the_same_svg_with_a_case_insensitive_theme(): void { - $svgLowerTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'a']]); - $svgUpperTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'A']]); + $svgLowerTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'A']]); + $svgUpperTheme = ($this->multiavatar)('foobar', ['ver' => ['theme' => 'a']]); self::assertSame($svgLowerTheme, $svgUpperTheme); } + /** @test */ + public function it_will_return_the_same_svg_if_the_ver_is_fully_setting_independently_of_the_avatar_id_value(): void + { + $options = ['ver' => ['theme' => 'A', 'part' => 3]]; + + $svgWithFixedVer1 = ($this->multiavatar)('first-avatar', $options); + $svgWithFixedVer2 = ($this->multiavatar)('second-avatar', $options); + + self::assertSame($svgWithFixedVer1, $svgWithFixedVer2); + } + /** @test */ public function it_will_return_the_same_svg_with_sans_env_truthy_values(): void { @@ -98,6 +109,15 @@ public function it_will_return_different_svg_with_sans_env_values(): void self::assertNotSame($svgSansEnvFalse, $svgSansEnvTrue); } + /** @test */ + public function it_will_return_the_same_svg_with_avatar_id_numeric_or_string(): void + { + $svgAvatarIdIsString = ($this->multiavatar)('1234567890'); + $svgAvatarIdIsNumeric = ($this->multiavatar)(1234567890); + + self::assertSame($svgAvatarIdIsNumeric, $svgAvatarIdIsString); + } + /** * @test * @dataProvider getEmptySvgProvider From 84c2b9093332026925bc78a229754282859e7241 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Tue, 22 Dec 2020 08:37:44 +0100 Subject: [PATCH 16/17] Improve internal svg generation method --- src/Multiavatar.php | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Multiavatar.php b/src/Multiavatar.php index 8f272d9..f94540e 100644 --- a/src/Multiavatar.php +++ b/src/Multiavatar.php @@ -171,31 +171,29 @@ private function mapPart(string $part): array private function partsToElements(array $bodyParts, array $options): array { $elements = []; - foreach ($bodyParts as $name => $bodyPart) { - if ('env' === $name && $options['sansEnv']) { - $elements[$name] = ''; - continue; - } - - $elements[$name] = $this->generateSvgElement( - $name, - $options['ver']['part'] ?? $bodyPart['part'], - $options['ver']['theme'] ?? $bodyPart['theme'] - ); + foreach ($bodyParts as $index => $settings) { + $elements[$index] = $this->mapElement($index, $settings, $options); } return $elements; } - private function generateSvgElement(string $name, string $part, string $theme): string + /** + * @param array{part:string, theme:string} $settings + * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options + */ + private function mapElement(string $name, array $settings, array $options): string { + if ('env' === $name && $options['sansEnv']) { + return ''; + } + + $part = $options['ver']['part'] ?? $settings['part']; + $theme = $options['ver']['theme'] ?? $settings['theme']; $colors = self::themes()[$part][$theme][$name]; $index = 0; $replace = function (array $result) use ($colors, &$index): string { - $selectedColor = $colors[$index]; - ++$index; - - return $selectedColor; + return $colors[$index++]; }; /** @var string $element */ From 8ca1c620bab28e81cf994eadbaeccd8e2ef14c49 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Wed, 30 Dec 2020 00:23:30 +0100 Subject: [PATCH 17/17] Refactor the implementation to easily add/expand functionnality --- src/Multiavatar.php | 134 +++++++++++++++++++++++++++------------- src/MultiavatarTest.php | 22 ++++++- 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/src/Multiavatar.php b/src/Multiavatar.php index f94540e..8937067 100644 --- a/src/Multiavatar.php +++ b/src/Multiavatar.php @@ -14,11 +14,16 @@ use InvalidArgumentException; use TypeError; +use function array_combine; +use function array_fill_keys; +use function array_map; +use function array_reduce; use function filter_var; use function get_class; use function gettype; use function hash; use function intdiv; +use function is_numeric; use function is_object; use function is_scalar; use function method_exists; @@ -27,6 +32,7 @@ use function preg_replace_callback; use function round; use function sprintf; +use function str_split; use function strtoupper; use function substr; use function trim; @@ -40,6 +46,7 @@ class Multiavatar private const SVG_ELEMENT_HEAD = ''; private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:'; private const THEME_LIST = [0 => 'A', 1 => 'B', 2 => 'C']; + private const BODY_PARTS = ['env', 'head', 'clo', 'top', 'eyes', 'mouth']; public const DEFAULT_OPTIONS = [ 'ver' => [ @@ -71,15 +78,17 @@ public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): str return ''; } - $svgElements = $this->partsToElements($this->avatarToParts($avatarId), $options); + $parts = $this->setParts($avatarId, $options); + $elements = $this->partsToElements($parts); + $elements = $this->filterElements($elements, $options); return self::SVG_ROOT_OPEN_TAG - . $svgElements['env'] - . $svgElements['head'] - . $svgElements['clo'] - . $svgElements['top'] - . $svgElements['eyes'] - . $svgElements['mouth'] + . $elements['env'] + . $elements['head'] + . $elements['clo'] + . $elements['top'] + . $elements['eyes'] + . $elements['mouth'] . self::SVG_ROOT_CLOSE_TAG; } @@ -108,9 +117,13 @@ private function filterOptions(array $inputOptions): array $options['sansEnv'] = filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOLEAN); if (isset($inputOptions['ver']['part'])) { + if (!is_string($inputOptions['ver']['part']) && !is_numeric($inputOptions['ver']['part'])) { + throw new InvalidArgumentException('The version part is expected to be a scalar; '.gettype($inputOptions['ver']['part']).' was given.'); + } + $part = sprintf("%'.02d", $inputOptions['ver']['part']); if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) { - throw new InvalidArgumentException('The submitted part does not exists; expecting a value between `00` and `15`.'); + throw new InvalidArgumentException('The version part does not exists; expecting a value between `00` and `15`.'); } $options['ver']['part'] = $part; @@ -120,8 +133,12 @@ private function filterOptions(array $inputOptions): array return $options; } - if (1 !== preg_match('/^([a-c])$/i', $inputOptions['ver']['theme'])) { - throw new InvalidArgumentException('The submitted theme does not exists; expecting a value between `A`, `B` and `C`.'); + if (!is_string($inputOptions['ver']['theme'])) { + throw new InvalidArgumentException('The version theme is expected to be a string; '.gettype($inputOptions['ver']['theme']).' was given.'); + } + + if (1 !== preg_match('/^[a-c]$/i', $inputOptions['ver']['theme'])) { + throw new InvalidArgumentException('The version theme does not exists; expecting a value between `A`, `B` and `C`.'); } $options['ver']['theme'] = strtoupper($inputOptions['ver']['theme']); @@ -130,29 +147,54 @@ private function filterOptions(array $inputOptions): array } /** - * @return array + * @param array{ver:array{part:string|null, theme:string|null}, sansEnv:bool} $options + * + * @return array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} */ - private function avatarToParts(string $avatarId): array + private function setParts(string $avatarId, array $options): array { - /** @var string $str */ - $str = preg_replace("/\D/", "", hash('sha256', $avatarId)); + if (isset($options['ver']['theme'], $options['ver']['part'])) { + return array_fill_keys(self::BODY_PARTS, $options['ver']); + } + /** @var string $str */ + $str = preg_replace('/\D/', '', hash('sha256', $avatarId)); $hash = substr($str, 0, 12); - return array_map([$this, 'mapPart'], [ - 'env' => $hash[0] . $hash[1], - 'clo' => $hash[2] . $hash[3], - 'head' => $hash[4] . $hash[5], - 'mouth' => $hash[6] . $hash[7], - 'eyes' => $hash[8] . $hash[9], - 'top' => $hash[10] . $hash[11], - ]); + /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */ + $parts = array_combine(self::BODY_PARTS, array_map([$this, 'stringToVersion'], str_split($hash, 2))); + + if (isset($options['ver']['theme'])) { + $theme = $options['ver']['theme']; + + /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */ + $parts = array_map(function (array $settings) use ($theme): array { + $settings['theme'] = $theme; + + return $settings; + }, $parts); + + return $parts; + } + + if (isset($options['ver']['part'])) { + $part = $options['ver']['part']; + + /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */ + $parts = array_map(function (array $settings) use ($part): array { + $settings['part'] = $part; + + return $settings; + }, $parts); + } + + return $parts; } /** * @return array{part:string, theme:string} */ - private function mapPart(string $part): array + private function stringToVersion(string $part): array { $part = (int) round(47 / 100 * (int) $part); @@ -163,33 +205,26 @@ private function mapPart(string $part): array } /** - * @param array $bodyParts - * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options + * @param array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts * - * @return array + * @return array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} */ - private function partsToElements(array $bodyParts, array $options): array + private function partsToElements(array $parts): array { - $elements = []; - foreach ($bodyParts as $index => $settings) { - $elements[$index] = $this->mapElement($index, $settings, $options); - } + $reducer = function(array $elements, string $name) use ($parts): array { + $elements[$name] = $this->createElement($name, $parts[$name]['part'], $parts[$name]['theme']); + + return $elements; + }; + + /** @var array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} $elements */ + $elements = array_reduce(self::BODY_PARTS, $reducer, []); return $elements; } - /** - * @param array{part:string, theme:string} $settings - * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options - */ - private function mapElement(string $name, array $settings, array $options): string + private function createElement(string $name, string $part, string $theme): string { - if ('env' === $name && $options['sansEnv']) { - return ''; - } - - $part = $options['ver']['part'] ?? $settings['part']; - $theme = $options['ver']['theme'] ?? $settings['theme']; $colors = self::themes()[$part][$theme][$name]; $index = 0; $replace = function (array $result) use ($colors, &$index): string { @@ -202,6 +237,21 @@ private function mapElement(string $name, array $settings, array $options): stri return $element; } + /** + * @param array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} $elements + * @param array{ver:array{part:string|null, theme:string|null}, sansEnv:bool} $options + * + * @return array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} + */ + private function filterElements(array $elements, array $options): array + { + if ($options['sansEnv']) { + $elements['env'] = ''; + } + + return $elements; + } + /** * @return array> */ diff --git a/src/MultiavatarTest.php b/src/MultiavatarTest.php index 676969c..b499977 100644 --- a/src/MultiavatarTest.php +++ b/src/MultiavatarTest.php @@ -24,16 +24,34 @@ public function setUp(): void public function it_will_throw_if_the_ver_part_is_not_valid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The submitted part does not exists; expecting a value between `00` and `15`'); + $this->expectExceptionMessage('The version part does not exists; expecting a value between `00` and `15`'); ($this->multiavatar)('foobar', ['ver' => ['part' => 16]]); } + /** @test */ + public function it_will_throw_if_the_ver_part_is_not_a_valid_type(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The version part is expected to be a scalar; array was given.'); + + ($this->multiavatar)('foobar', ['ver' => ['part' => []]]); + } + + /** @test */ + public function it_will_throw_if_the_ver_theme_is_not_a_supported_type(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The version theme is expected to be a string; object was given.'); + + ($this->multiavatar)('foobar', ['ver' => ['theme' => new \stdClass()]]); + } + /** @test */ public function it_will_throw_if_the_ver_theme_is_not_valid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The submitted theme does not exists; expecting a value between `A`, `B` and `C`.'); + $this->expectExceptionMessage('The version theme does not exists; expecting a value between `A`, `B` and `C`.'); ($this->multiavatar)('foobar', ['ver' => ['theme' => 'D']]); }