132c37733Swikidesign<?php 232c37733Swikidesign/** 323415f0dSnerun * Avatar Plugin for DokuWiki 423415f0dSnerun * 523415f0dSnerun * Displays avatar images with syntax {{avatar>email@domain.com}} 623415f0dSnerun * Supports local avatars, Gravatar.com, and MonsterID fallback 723415f0dSnerun * 832c37733Swikidesign * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 932c37733Swikidesign * @author Esther Brunner <wikidesign@gmail.com> 1023415f0dSnerun * @author Daniel Dias Rodrigues <danieldiasr@gmail.com> (modernization) 1132c37733Swikidesign */ 1232c37733Swikidesign 1323415f0dSnerundeclare(strict_types=1); 1423415f0dSnerun 1532c37733Swikidesignif (!defined('DOKU_INC')) die(); 1632c37733Swikidesign 1723415f0dSnerunclass helper_plugin_avatar extends DokuWiki_Plugin 1823415f0dSnerun{ 1923415f0dSnerun private const DEFAULT_SIZES = [ 2023415f0dSnerun 'small' => 20, 2123415f0dSnerun 'medium' => 40, 2223415f0dSnerun 'large' => 80, 2323415f0dSnerun 'xlarge' => 120 2423415f0dSnerun ]; 2532c37733Swikidesign 2623415f0dSnerun private const ALLOWED_FORMATS = ['.png', '.jpg', '.gif']; 2723415f0dSnerun private const GRAVATAR_BASE = 'https://secure.gravatar.com/avatar/'; 2823415f0dSnerun 2923415f0dSnerun private array $avatarCache = []; 3023415f0dSnerun 3123415f0dSnerun public function getMethods(): array 3223415f0dSnerun { 3323415f0dSnerun return [ 3423415f0dSnerun [ 3523415f0dSnerun 'name' => 'renderXhtml', 3623415f0dSnerun 'desc' => 'Returns the XHTML to display an avatar', 3723415f0dSnerun 'params' => [ 3823415f0dSnerun 'user' => 'string|array', 3923415f0dSnerun 'title' => 'string', 4023415f0dSnerun 'align' => 'string', 4123415f0dSnerun 'size' => 'int' 4223415f0dSnerun ], 4323415f0dSnerun 'return' => ['xhtml' => 'string'] 4423415f0dSnerun ] 4523415f0dSnerun ]; 4632c37733Swikidesign } 4732c37733Swikidesign 481bc20383SMichael Klier /** 4923415f0dSnerun * Renders the avatar as XHTML <img> 501bc20383SMichael Klier */ 51986bb475Snerun public function renderXhtml(string|array $user, string $title = '', ?string $align = '', ?int $size = null): string 5223415f0dSnerun { 5323415f0dSnerun $src = $this->resolveAvatarUrl($user, $title, $size); 5432c37733Swikidesign 55*f994dce8Snerun $title = hsc($title); 56*f994dce8Snerun 5754eb7e18Snerun return '<img src="' . $src . '" ' . 5854eb7e18Snerun 'class="media' . $align . ' photo fn" ' . 5954eb7e18Snerun 'title="' . $title . '" ' . 6054eb7e18Snerun 'alt="' . $title . '" ' . 6154eb7e18Snerun 'width="' . (string) $size . '" ' . 6254eb7e18Snerun 'height="' . (string) $size . '" />'; 6332c37733Swikidesign } 6432c37733Swikidesign 6532c37733Swikidesign /** 6623415f0dSnerun * Gets or generates the avatar URL for a user/email 6732c37733Swikidesign */ 6823415f0dSnerun public function resolveAvatarUrl(string|array $user, ?string &$title = null, ?int &$size = null): string 6923415f0dSnerun { 7032c37733Swikidesign global $auth; 7132c37733Swikidesign 7223415f0dSnerun $size = $this->normalizeSize($size); 7323415f0dSnerun $cacheKey = $this->getCacheKey($user, $title, $size); 7432c37733Swikidesign 7523415f0dSnerun if (isset($this->avatarCache[$cacheKey])) { 7623415f0dSnerun return $this->avatarCache[$cacheKey]; 771bc20383SMichael Klier } 781bc20383SMichael Klier 7923415f0dSnerun $mail = $this->extractUserData($user, $title); 804c5c7bddSnerun $isEmail = mail_isvalid($mail) && (!is_array($user) || !isset($user['user'])); 814c5c7bddSnerun 824c5c7bddSnerun // For emails (Gravatar) 834c5c7bddSnerun if ($isEmail) { 844c5c7bddSnerun $src = $this->getGravatarUrl($mail, $size); 854c5c7bddSnerun } 864c5c7bddSnerun // For local users 874c5c7bddSnerun else { 8823415f0dSnerun $src = $this->tryLocalAvatar($user, $title, $size); 8923415f0dSnerun 9023415f0dSnerun if (!$src) { 914c5c7bddSnerun // Apply fallback configured for local users only 924c5c7bddSnerun if ($this->getConf('local_default') === 'monsterid' && function_exists('imagecreatetruecolor')) { 934c5c7bddSnerun $seed = md5(dokuwiki\Utf8\PhpString::strtolower(is_array($user) ? ($user['user'] ?? '') : $user)); 944c5c7bddSnerun $src = $this->getMonsterIdUrl($seed, $size); 954c5c7bddSnerun } else { 964c5c7bddSnerun $src = $this->getDefaultImageUrl($size); 974c5c7bddSnerun } 984c5c7bddSnerun } 9932c37733Swikidesign } 10032c37733Swikidesign 10123415f0dSnerun if (empty($title)) { 10223415f0dSnerun $title = obfuscate($mail); 10332c37733Swikidesign } 10432c37733Swikidesign 10523415f0dSnerun $this->avatarCache[$cacheKey] = $src; 10632c37733Swikidesign return $src; 10732c37733Swikidesign } 10823415f0dSnerun 10923415f0dSnerun private function normalizeSize(?int $size): int 11023415f0dSnerun { 111986bb475Snerun if ($size && $size > 0) { 112986bb475Snerun return $size; 113986bb475Snerun } 114986bb475Snerun 115986bb475Snerun $confSize = (int) $this->getConf('size'); 116986bb475Snerun return $confSize > 0 ? $confSize : self::DEFAULT_SIZES['medium']; 11732c37733Swikidesign } 11823415f0dSnerun 11923415f0dSnerun private function getCacheKey(string|array $user, ?string $title, int $size): string 12023415f0dSnerun { 12123415f0dSnerun $userKey = is_array($user) ? ($user['mail'] ?? '') : $user; 12223415f0dSnerun return md5($userKey . $title . $size); 12323415f0dSnerun } 12423415f0dSnerun 12523415f0dSnerun private function extractUserData(string|array $user, ?string &$title): string 12623415f0dSnerun { 12723415f0dSnerun if (is_array($user)) { 12823415f0dSnerun if (empty($title) && !empty($user['name'])) { 12923415f0dSnerun $title = hsc($user['name']); 13023415f0dSnerun } 13123415f0dSnerun return $user['mail'] ?? ''; 13223415f0dSnerun } 13323415f0dSnerun return $user; 13423415f0dSnerun } 13523415f0dSnerun 13623415f0dSnerun private function tryLocalAvatar(string|array $user, ?string &$title, int $size): ?string 13723415f0dSnerun { 13823415f0dSnerun global $auth; 13923415f0dSnerun 14023415f0dSnerun $username = is_array($user) ? ($user['user'] ?? '') : $user; 14123415f0dSnerun $userinfo = $auth->getUserData($username); 14223415f0dSnerun 14323415f0dSnerun if (!$userinfo) { 14423415f0dSnerun return null; 14523415f0dSnerun } 14623415f0dSnerun 14723415f0dSnerun if (empty($title) && !empty($userinfo['name'])) { 14823415f0dSnerun $title = hsc($userinfo['name']); 14923415f0dSnerun } 15023415f0dSnerun 15123415f0dSnerun $ns = $this->getConf('namespace'); 15223415f0dSnerun foreach (self::ALLOWED_FORMATS as $format) { 15323415f0dSnerun $imagePath = $ns . ':' . $username . $format; 15423415f0dSnerun $imageFile = mediaFN($imagePath); 15523415f0dSnerun 15623415f0dSnerun if (file_exists($imageFile)) { 157535581fcSnerun return ml($imagePath, ['w' => $size, 'h' => $size], true, '&', false); 15823415f0dSnerun } 15923415f0dSnerun } 16023415f0dSnerun 1614c5c7bddSnerun // Only generate MonsterID if it's the selected fallback 1624c5c7bddSnerun if ($this->getConf('local_default') === 'monsterid') { 163f443324cSnerun if (is_string($user) && $user === $username) { 1644c5c7bddSnerun if ($this->saveMonsterIdAvatar($username, 120)) { 165f443324cSnerun $imagePath = $ns . ':' . $username . '.png'; 166f443324cSnerun if (file_exists(mediaFN($imagePath))) { 167f443324cSnerun return ml($imagePath, ['w' => $size, 'h' => $size], true, '&', false); 168f443324cSnerun } 169f443324cSnerun } 170f443324cSnerun } 1714c5c7bddSnerun } 172f443324cSnerun 17323415f0dSnerun return null; 17423415f0dSnerun } 17523415f0dSnerun 176f443324cSnerun private function saveMonsterIdAvatar(string $username, int $size): bool 177f443324cSnerun { 178f443324cSnerun $ns = $this->getConf('namespace'); 179f443324cSnerun $filename = $ns . ':' . $username . '.png'; 180f443324cSnerun $filepath = mediaFN($filename); 181f443324cSnerun 182f443324cSnerun // Monsterid URL for the user 183f443324cSnerun $seed = md5(dokuwiki\Utf8\PhpString::strtolower($username)); 184f443324cSnerun $monsterUrl = DOKU_URL . 'lib/plugins/avatar/monsterid.php?seed=' . $seed . '&size=' . $size; 185f443324cSnerun 186f443324cSnerun // Download the image using file_get_contents 187f443324cSnerun $imageData = @file_get_contents($monsterUrl); 188f443324cSnerun if ($imageData === false) { 189f443324cSnerun return false; 190f443324cSnerun } 191f443324cSnerun 192f443324cSnerun // creates the directory if it does not exist 193f443324cSnerun io_makeFileDir($filepath); 194f443324cSnerun 195f443324cSnerun // Save the image 196f443324cSnerun return file_put_contents($filepath, $imageData) !== false; 197f443324cSnerun } 198f443324cSnerun 19923415f0dSnerun private function getGravatarUrl(string $mail, int $size): string 20023415f0dSnerun { 20123415f0dSnerun $seed = md5(dokuwiki\Utf8\PhpString::strtolower($mail)); 20223415f0dSnerun 20323415f0dSnerun $default = function_exists('imagecreatetruecolor') 20423415f0dSnerun ? $this->getMonsterIdUrl($seed, $size) 20523415f0dSnerun : $this->getDefaultImageUrl($size); 20623415f0dSnerun 20723415f0dSnerun if (!mail_isvalid($mail)) { 20823415f0dSnerun return $default; 20923415f0dSnerun } 21023415f0dSnerun 21123415f0dSnerun $params = [ 21223415f0dSnerun 's' => $size, 2134c5c7bddSnerun 'd' => $this->getConf('gravatar_default'), 21423415f0dSnerun 'r' => $this->getConf('rating') 21523415f0dSnerun ]; 21623415f0dSnerun 21754eb7e18Snerun return self::GRAVATAR_BASE . $seed . '?' . http_build_query($params); 21823415f0dSnerun } 21923415f0dSnerun 22023415f0dSnerun private function getMonsterIdUrl(string $seed, int $size): string 22123415f0dSnerun { 22223415f0dSnerun $file = 'monsterid.php?seed=' . $seed . '&size=' . $size . '&.png'; 22323415f0dSnerun return ml(DOKU_URL . 'lib/plugins/avatar/' . $file, 'cache=recache', true, '&', true); 22423415f0dSnerun } 22523415f0dSnerun 22623415f0dSnerun private function getDefaultImageUrl(int $size): string 22723415f0dSnerun { 22823415f0dSnerun $validSizes = array_values(self::DEFAULT_SIZES); 22923415f0dSnerun $realSize = in_array($size, $validSizes, true) ? $size : self::DEFAULT_SIZES['xlarge']; 23023415f0dSnerun $file = 'images/default_' . $realSize . '.png'; 23123415f0dSnerun return ml(DOKU_URL . 'lib/plugins/avatar/' . $file, 'cache=recache', true, '&', true); 23223415f0dSnerun } 23323415f0dSnerun} 234