xref: /plugin/avatar/helper.php (revision f994dce80d391ff97d0eea9e085f823e00439d60) !
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