commit 561fd3f1345614f1421b921418e9661acb92cc2f Author: Lukas Winkler Date: Wed May 20 10:41:04 2020 +0200 initial commit diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..091fea9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## Changelog + +### 0.1.0 + +initial release diff --git a/Controller.php b/Controller.php new file mode 100644 index 0000000..9313181 --- /dev/null +++ b/Controller.php @@ -0,0 +1,23 @@ +avatarType->getValue(); + $generator = GeneratorCollection::getGeneratorClasses($chosenGenerator, $hash); + $generator->print(); + exit(); + } +} diff --git a/GeneratorCollection.php b/GeneratorCollection.php new file mode 100644 index 0000000..f72dfc0 --- /dev/null +++ b/GeneratorCollection.php @@ -0,0 +1,45 @@ + "Cat Avatar", + "BirdAvatar" => "Bird Avatar", + "MonsterID" => "MonsterID", + "Identicon" => "Identicon.js", + "Blockies" => "Blockies" + ]; + + static public function getGeneratorClasses(string $name, string $hash): AvatarBase + { + switch ($name) { + case "CatAvatar": + return new CatAvatar($hash); + break; + case "BirdAvatar"; + return new BirdAvatar($hash); + break; + case "MonsterID": + return new MonsterID($hash); + break; + case "Identicon": + return new Identicon($hash); + break; + case "Blockies": + return new Blockies($hash); + break; + default: + throw new \Exception("invalid Generator"); + } + } +} diff --git a/Generators/AvatarBase.php b/Generators/AvatarBase.php new file mode 100644 index 0000000..17f0258 --- /dev/null +++ b/Generators/AvatarBase.php @@ -0,0 +1,13 @@ + [1, 9], + 'hoop' => [1, 9], + 'body' => [1, 9], + 'wing' => [1, 9], + 'eyes' => [1, 9], + 'bec' => [1, 9], + 'accessorie' => [1, 20] + ]; + +} diff --git a/Generators/Blockies.php b/Generators/Blockies.php new file mode 100644 index 0000000..ebee123 --- /dev/null +++ b/Generators/Blockies.php @@ -0,0 +1,66 @@ +seed, -10))); + $colors = [$this->createColor(), $this->createColor(), $this->createColor()]; + $cell = $this->size / $this->rowscols; + for ($j = 0; $j < $this->rowscols; $j++) { + + $values = $this->createRow(); + $values = array_merge($values, array_reverse($values)); + $i = 0; + foreach ($values as $value) { + $this->rectangles[] = [ + "x" => $i * $cell, + "y" => $j * $cell, + "w" => $cell, + "h" => $cell, + "color" => $colors[$value] + ]; + $i += 1; + } + } + srand(); + } + + private function createColor(): array + { + $h = rand(0, 360) / 360; + $s = rand(40, 100) / 100; + $l = ($this->rand() + $this->rand() + $this->rand() + $this->rand()) * 25 / 100; + return [$h, $s, $l]; + } + + private function rand(): float + { + return mt_rand() / mt_getrandmax(); + } + + private function createRow(): array + { + for ($i = 0; $i < $this->rowscols / 2; $i++) { + $row[] = floor($this->rand() * 2.3); + } + return $row; + } + + protected function getForegroundColor(): void + { + return; + } +} diff --git a/Generators/CatAvatar.php b/Generators/CatAvatar.php new file mode 100644 index 0000000..d34445f --- /dev/null +++ b/Generators/CatAvatar.php @@ -0,0 +1,18 @@ + [1, 15], + 'fur' => [1, 10], + 'eyes' => [1, 15], + 'mouth' => [1, 10], + 'accessorie' => [1, 20] + ]; + +} diff --git a/Generators/Identicon.php b/Generators/Identicon.php new file mode 100644 index 0000000..ee19e7f --- /dev/null +++ b/Generators/Identicon.php @@ -0,0 +1,75 @@ +size * $this->margin); + $cell = floor(($this->size - ($baseMargin * 2)) / 5); + $margin = floor(($this->size - $cell * 5) / 2); + + for ($i = 0; $i < 15; $i++) { + $color = hexdec($this->seed[$i]) % 2 ? $this->backgroundColor : $this->getForegroundColor(); + if ($i < 5) { + $this->rectangles[] = [ + "x" => 2 * $cell + $margin, + "y" => $i * $cell + $margin, + "w" => $cell, + "h" => $cell, + "color" => $color + ]; + } elseif ($i < 10) { + $this->rectangles[] = [ + "x" => 1 * $cell + $margin, + "y" => ($i - 5) * $cell + $margin, + "w" => $cell, + "h" => $cell, + "color" => $color + ]; + $this->rectangles[] = [ + "x" => 3 * $cell + $margin, + "y" => ($i - 5) * $cell + $margin, + "w" => $cell, + "h" => $cell, + "color" => $color + ]; + } elseif ($i < 15) { + $this->rectangles[] = [ + "x" => 0 * $cell + $margin, + "y" => ($i - 10) * $cell + $margin, + "w" => $cell, + "h" => $cell, + "color" => $color + ]; + $this->rectangles[] = [ + "x" => 4 * $cell + $margin, + "y" => ($i - 10) * $cell + $margin, + "w" => $cell, + "h" => $cell, + "color" => $color + ]; + } + } + + } + + protected function getForegroundColor(): array + { + $hue = hexdec(substr($this->seed, -7)) / 0xfffffff; + return [$hue * 360, $this->saturation, $this->lightness]; + } + + +} diff --git a/Generators/MonsterID.php b/Generators/MonsterID.php new file mode 100644 index 0000000..b2c9e70 --- /dev/null +++ b/Generators/MonsterID.php @@ -0,0 +1,30 @@ + [1, 5], + 'hair' => [1, 5], + 'arms' => [1, 5], + 'body' => [1, 15], + 'eyes' => [1, 15], + 'mouth' => [1, 10], + ]; + + protected function applyPartToImage($part, $number): void + { + parent::applyPartToImage($part, $number); + if ($part == 'body') { + $color = imagecolorallocate($this->monster, rand(20, 235), rand(20, 235), rand(20, 235)); + imagefill($this->monster, 60, 60, $color); + } + } +} diff --git a/Generators/MonsterIDBase.php b/Generators/MonsterIDBase.php new file mode 100644 index 0000000..e58cfd3 --- /dev/null +++ b/Generators/MonsterIDBase.php @@ -0,0 +1,116 @@ +seed = hexdec(substr($seed, -10)); + } + + public function __destruct() + { + if ($this->monster) { + imagedestroy($this->monster); + } + } + + public function asDataUrl(): string + { + $this->build(); + $output = $this->toBuffer(); + + return $this->dataURL($output); + } + + public function build(): void + { + $this->createImage(); + + srand($this->seed); + + $parts = $this->generateRandomParts(); + + foreach ($parts as $part => $number) { + $this->applyPartToImage($part, $number); + } + + srand(); + } + + private function createImage(): void + { + // create background + $this->monster = imagecreatetruecolor($this->size, $this->size); + if (!$this->monster) { + throw new \Exception('GD image create failed'); + } + $white = imagecolorallocate($this->monster, 255, 255, 255); + imagefill($this->monster, 0, 0, $white); + } + + private function generateRandomParts(): array + { + // throw the dice for body parts + foreach ($this->partTemplate as $name => $template) { + list($min, $max) = $template; + $parts[$name] = rand($min, $max); + } + return $parts; + } + + protected function applyPartToImage($part, $number): void + { + $file = implode(DIRECTORY_SEPARATOR, array(self::getPartsPath(), "{$part}_{$number}.png")); + + $partImage = imagecreatefrompng($file); + if (!$partImage) { + throw new \Exception('Failed to load ' . $file); + } + imagesavealpha($partImage, true); + imagecopy($this->monster, $partImage, 0, 0, 0, 0, $this->size, $this->size); + imagedestroy($partImage); + + } + + private function getPartsPath(): string + { + return realpath(__DIR__ . '/../images/' . $this->path); + } + + public function toBuffer(): string + { + ob_start(); + imagepng($this->monster, null, 9); + $buffer = ob_get_clean(); + imagedestroy($this->monster); + return $buffer; + } + + private function dataURL(string $output): string + { + $base64 = base64_encode($output); + return "data:image/png;base64," . $base64; + } + + public function print(): void + { + $this->build(); + header('Content-type: image/png'); + imagepng($this->monster, null, 9); + imagedestroy($this->monster); + } +} diff --git a/Generators/PixelBased.php b/Generators/PixelBased.php new file mode 100644 index 0000000..88b7ecb --- /dev/null +++ b/Generators/PixelBased.php @@ -0,0 +1,92 @@ +seed = $seed; + } + + public function asDataURL() + { + $svg = $this->build(); + + return "data:image/svg+xml," . rawurlencode($svg); + } + + public function build() + { + $this->createPatterns(); + return $this->createSVG(); + } + + abstract protected function createPatterns(); + + protected function createSVG() + { + $bg = $this->toSVGColor($this->backgroundColor); + $stroke = $this->size * 0.005; + $svg = ""; + if ($this->singleColor) { + $fg = $this->toSVGColor($this->getForegroundColor()); + $svg .= ""; + } else { + $svg .= ""; + } + + foreach ($this->rectangles as $rectangle) { + $x = $rectangle["x"]; + $y = $rectangle["y"]; + $w = $rectangle["w"]; + $h = $rectangle["h"]; + if ($rectangle["color"] == $this->backgroundColor) { + continue; + } + if ($this->singleColor) { + $svg .= "\n"; + } else { + $color = $this->toSVGColor($rectangle["color"]); + $svg .= "\n"; + } + } + $svg .= ""; + return $svg; + } + + protected static function toSVGColor(array $color) + { + list($h, $s, $l) = $color; + $h *= 360; + $s *= 100; + $l *= 100; + $h=round($h); + $s=round($s); + $l=round($l); + return "hsl($h, $s%, $l%)"; + } + + abstract protected function getForegroundColor(); + + public function print() + { + header('Content-type:image/svg+xml;charset=utf-8'); + echo $this->build(); + } + +} diff --git a/ProfileAvatar.php b/ProfileAvatar.php new file mode 100644 index 0000000..c057d8c --- /dev/null +++ b/ProfileAvatar.php @@ -0,0 +1,31 @@ + 'getExtraVisitorDetails' + ]; + } + + public function getExtraVisitorDetails(&$result): void + { + $settings = new UserSettings(); + $visitorID = $result["visitorId"]; + $hash = hash("sha256", $visitorID); + + if ($settings->dataURLs->getValue()) { + $chosenGenerator = $settings->avatarType->getValue(); + $generator = GeneratorCollection::getGeneratorClasses($chosenGenerator,$hash); + $result['visitorAvatar'] = $generator->asDataUrl(); + } else { + $result['visitorAvatar'] = "?module=ProfileAvatar&action=getProfileAvatar&hash=$hash"; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..575f026 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Matomo ProfileAvatar Plugin + +## Description + +Are you tired of always seeing the same placeholder profile picture in the Vistitor Profile? + +This plugin creates randomly generated user avatars based on the visitorID inspired by classical identicons: + +- Cat Avatar (based on [cat-avatar-generator](https://framagit.org/Deevad/cat-avatar-generator/) by [David Revoy](https://www.davidrevoy.com/)) +- Bird Avatar (the same with birds) +- MonsterID (based on [MonsterID](https://www.splitbrain.org/projects/monsterid) by Andreas Gohr) +- Identicon (based on [indenticon.js](https://github.com/stewartlord/identicon.js) by Stewart Lord) +- Blockies (based on [blockies](https://github.com/download13/blockies) by Erin Dachtler) + +In the Personal Settings every use can choose which avatar type they want to see. diff --git a/UserSettings.php b/UserSettings.php new file mode 100644 index 0000000..c2e3d96 --- /dev/null +++ b/UserSettings.php @@ -0,0 +1,50 @@ +avatarType = $this->createAvatarTypeSetting(); + + $this->dataURLs = $this->createDataURLsSetting(); + } + + private function createAvatarTypeSetting():Setting + { + return $this->makeSetting("avatarType", "CatAvatar", FieldConfig::TYPE_STRING, function (FieldConfig $field) { + $field->title = Piwik::translate('ProfileAvatar_AvatarTypeTitle');; + $field->uiControl = FieldConfig::UI_CONTROL_RADIO; + $field->description = Piwik::translate('ProfileAvatar_AvatarTypeDescription'); + $field->availableValues = GeneratorCollection::$generatorNames; + }); + } + + private function createDataURLsSetting():Setting + { + return $this->makeSetting("dataURLs", FALSE, FieldConfig::TYPE_BOOL, function (FieldConfig $field) { + $field->title = Piwik::translate('ProfileAvatar_DataURLsTitle'); + $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX; + $field->description = Piwik::translate('ProfileAvatar_DataURLsDescription'); + }); + } +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..d266508 --- /dev/null +++ b/config/config.php @@ -0,0 +1,2 @@ +3.13.0,<5.0.0-b1", + "piwik": ">=3.13.0,<5.0.0-b1" + }, + "authors": [ + { + "name": "Lukas Winkler", + "email": "lukas@matomo.org", + "homepage": "https://lw1.at" + } + ], + "support": { + "email": "lukas@matomo.org", + "issues": "https://github.com/Findus23/plugin-ProfileAvatar/issues", + "forum": "https://forum.matomo.org", + "source": "https://github.com/Findus23/plugin-ProfileAvatar" + }, + "homepage": "https://lw1.at", + "license": "GPL v3+", + "keywords": [ + "Visitor Profile", + "profile picture" + ] +} diff --git a/screenshots/.gitkeep b/screenshots/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/screenshots/Visitor_Profile.png b/screenshots/Visitor_Profile.png new file mode 100644 index 0000000..b789a0c Binary files /dev/null and b/screenshots/Visitor_Profile.png differ diff --git a/screenshots/avatars.png b/screenshots/avatars.png new file mode 100644 index 0000000..eadf2bb Binary files /dev/null and b/screenshots/avatars.png differ diff --git a/screenshots/avatars.svg b/screenshots/avatars.svg new file mode 100644 index 0000000..c12b16d --- /dev/null +++ b/screenshots/avatars.svg @@ -0,0 +1,2110 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +