Implement users avatars for Collabora

This commit is contained in:
bohwaz 2024-04-08 17:34:09 +02:00
parent 1999fdb4aa
commit b7384648e8
7 changed files with 184 additions and 3 deletions

View file

@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace KD2\Graphics\SVG;
/**
* PHP port of https://github.com/boringdesigners/boring-avatars/
*/
class Avatar
{
static protected function hashCode(string $name): int
{
return hexdec(substr(md5($name), 0, 8));
}
static protected function getRandomColor(int $number, array $colors, int $range): string
{
return $colors[($number) % $range];
}
static protected function getUnit(int $number, int $range, int $index = 0): int
{
$value = $number % $range;
if ($index && ((self::getDigit($number, $index) % 2) === 0)) {
return -($value);
}
return $value;
}
static protected function getDigit(int $number, int $ntn): int
{
$a = intval($number / pow(10, $ntn));
return (int)floor($a % 10);
}
static protected function getBoolean(int $number, int $ntn): bool
{
return (!((self::getDigit($number, $ntn)) % 2));
}
static protected function getAngle(int $x, int $y): float
{
return atan2($y, $x) * 180 / PI;
}
static protected function getContrast(string $hexcolor): string
{
// If a leading # is provided, remove it
if (substr($hexcolor, 0, 1) === '#') {
$hexcolor = substr($hexcolor, 1);
}
if (strlen($hexcolor) === 3) {
$hexcolor = $hexcolor[0] . $hexcolor[0] . $hexcolor[1] . $hexcolor[1] . $hexcolor[2] . $hexcolor[2];
}
// Convert to RGB value
$r = hexdec(substr($hexcolor, 0, 2));
$g = hexdec(substr($hexcolor, 2, 2));
$b = hexdec(substr($hexcolor, 4, 2));
// Get YIQ ratio
$yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
// Check contrast
return ($yiq >= 128) ? '#000000' : '#FFFFFF';
}
static public function beam(string $name, array $options = []): string
{
$colors = $options['colors'] ?? ['#0c9', '#9c0', '#6f0'];
$w = intval($options['size'] ?? 36);
$options['square'] ??= false;
$size = 36;
$numFromName = self::hashCode($name);
$range = count($colors);
$wrapperColor = self::getRandomColor($numFromName, $colors, $range);
$preTranslateX = self::getUnit($numFromName, 10, 1);
$wrapperTranslateX = $preTranslateX < 5 ? $preTranslateX + $size / 9 : $preTranslateX;
$preTranslateY = self::getUnit($numFromName, 10, 2);
$wrapperTranslateY = $preTranslateY < 5 ? $preTranslateY + $size / 9 : $preTranslateY;
$faceColor = self::getContrast($wrapperColor);
$backgroundColor = self::getRandomColor($numFromName + 13, $colors, $range);
$wrapperRotate = self::getUnit($numFromName, 360);
$wrapperScale = 1 + self::getUnit($numFromName, intval($size / 12)) / 10;
$isMouthOpen = self::getBoolean($numFromName, 2);
$isCircle = self::getBoolean($numFromName, 1);
$eyeSpread = self::getUnit($numFromName, 5);
$mouthSpread = self::getUnit($numFromName, 3);
$faceRotate = self::getUnit($numFromName, 10, 3);
$faceTranslateX = $wrapperTranslateX > $size / 6 ? $wrapperTranslateX / 2 : self::getUnit($numFromName, 6, 1);
$faceTranslateY = $wrapperTranslateY > $size / 6 ? $wrapperTranslateY / 2 : self::getUnit($numFromName, 5, 2);
$maskID = 'mask-' . md5(random_bytes(8));
$rx1 = $options['square'] ? 0 : $size * 2;
$rx2 = $isCircle ? $size : $size / 6;
$rx3 = 1 + self::getUnit($numFromName, 6, 2);
$half_size = $size / 2;
$spread = 22 + $mouthSpread;
if (!$isMouthOpen) {
$mouth = "<path d=\"M13 {$spread}c4 2 8 2 12 0\" stroke=\"{$faceColor}\" fill=\"none\" strokeLinecap=\"round\" />";
}
else {
$mouth = "<path d=\"M12,{$spread} a2,1.5 0 0,0 14,0\" fill=\"{$faceColor}\" />";
}
$x1 = 14 - $eyeSpread;
$x2 = 20 + $eyeSpread;
return <<<EOF
<svg
viewBox="0 0 {$size} {$size}"
fill="none"
role="img"
xmlns="http://www.w3.org/2000/svg"
width="{$w}"
height="{$w}"
>
<mask id="{$maskID}" maskUnits="userSpaceOnUse" x="0" y="0" width="{$size}" height="{$size}">
<rect width="{$size}" height="{$size}" rx="{$rx1}" fill="#FFFFFF" />
</mask>
<g mask="url('#{$maskID}')">
<rect width="{$size}" height="{$size}" fill="{$backgroundColor}" />
<rect
x="0"
y="0"
width="{$size}"
height="{$size}"
transform="translate({$wrapperTranslateX} {$wrapperTranslateY}) rotate({$wrapperRotate} {$half_size} {$half_size}) scale({$wrapperScale})"
fill="{$wrapperColor}"
rx="{$rx2}"
/>
<g transform="translate({$faceTranslateY} {$faceTranslateY}) rotate({$faceRotate} $half_size $half_size)">
{$mouth}
<rect
x="{$x1}"
y="14"
width="4"
height="6"
rx="{$rx3}"
stroke="none"
fill="{$faceColor}"
/>
<rect
x="{$x2}"
y="14"
width="4"
height="6"
rx="{$rx3}"
stroke="none"
fill="{$faceColor}"
/>
</g>
</g>
</svg>
EOF;
}
}

View file

@ -4,6 +4,7 @@ namespace KaraDAV;
use KD2\WebDAV\NextCloud as WebDAV_NextCloud;
use KD2\WebDAV\Exception as WebDAV_Exception;
use KD2\Graphics\SVG\Avatar;
class NextCloud extends WebDAV_NextCloud
{
@ -187,4 +188,9 @@ class NextCloud extends WebDAV_NextCloud
return ['created' => !$exists, 'etag' => md5(filemtime($target) . filesize($target))];
}
protected function nc_avatar(): void
{
header('Content-Type: image/svg+xml; charset=utf-8');
echo Avatar::beam($_SERVER['REQUEST_URI'] ?? '', ['colors' => ['#009', '#ccf', '#9cf']]);
}
}

View file

@ -649,7 +649,7 @@ class Storage extends AbstractStorage implements TrashInterface
return null;
}
$path = $this->users->current()->path . $uri;
$path = $user->path . $uri;
if (!file_exists($path)) {
return null;
@ -662,7 +662,7 @@ class Storage extends AbstractStorage implements TrashInterface
WOPI::PROP_READ_ONLY => $readonly,
WOPI::PROP_USER_NAME => $user->login,
WOPI::PROP_USER_ID => md5($user->login),
WOPI::PROP_USER_AVATAR => null,
WOPI::PROP_USER_AVATAR => $user->avatar_url,
];
}

View file

@ -80,6 +80,7 @@ class Users
$user->path = rtrim(realpath($user->path), '/') . '/';
$user->dav_url = WWW_URL . 'files/' . $user->login . '/';
$user->avatar_url = WWW_URL . 'avatars/' . substr(md5($user->login), 0, 16);
}
return $user;

View file

@ -46,7 +46,7 @@ html_head('My files');
echo <<<EOF
<h2 class="myfiles"><a class="btn" href="{$user->dav_url}">Manage my files</a></h2>
<h3>Hello, {$username} !</h3>
<h3>Hello, {$username} ! <img src="{$user->avatar_url}" alt="" /></h3>
<dl>
<dd><h3>{$percent} used, {$free} free</h3></dd>
<dd><progress max="{$quota->total}" value="{$quota->used}"></progress>

View file

@ -180,6 +180,11 @@ p.info tt {
padding: .2em;
}
h3 img {
vertical-align: middle;
margin: 0 1em;
}
@media screen and (max-width: 900px) {
main {
border-radius: 0;

View file

@ -142,6 +142,7 @@ else {
<table>
<thead>
<tr>
<td></td>
<th>User</th>
<td>Quota</td>
<td>Admin</td>
@ -154,11 +155,13 @@ else {
$quota = $users->quota($user);
printf('<tr>
<td><img src="%s" alt="" /></td>
<th>%s</th>
<td>%s used out of %s<br /><progress max="%d" value="%d"></progress></td>
<td>%s</td>
<td><a href="?edit=%d" class="btn sm">Edit</a> <a href="?delete=%d" class="btn sm">Delete</a></td>
</tr>',
$user->avatar_url,
htmlspecialchars($user->login),
format_bytes($quota->used),
format_bytes($quota->total),