Implement users avatars for Collabora
This commit is contained in:
parent
1999fdb4aa
commit
b7384648e8
166
lib/KD2/Graphics/SVG/Avatar.php
Normal file
166
lib/KD2/Graphics/SVG/Avatar.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ namespace KaraDAV;
|
||||||
|
|
||||||
use KD2\WebDAV\NextCloud as WebDAV_NextCloud;
|
use KD2\WebDAV\NextCloud as WebDAV_NextCloud;
|
||||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||||
|
use KD2\Graphics\SVG\Avatar;
|
||||||
|
|
||||||
class NextCloud extends WebDAV_NextCloud
|
class NextCloud extends WebDAV_NextCloud
|
||||||
{
|
{
|
||||||
|
@ -187,4 +188,9 @@ class NextCloud extends WebDAV_NextCloud
|
||||||
return ['created' => !$exists, 'etag' => md5(filemtime($target) . filesize($target))];
|
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']]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -649,7 +649,7 @@ class Storage extends AbstractStorage implements TrashInterface
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $this->users->current()->path . $uri;
|
$path = $user->path . $uri;
|
||||||
|
|
||||||
if (!file_exists($path)) {
|
if (!file_exists($path)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -662,7 +662,7 @@ class Storage extends AbstractStorage implements TrashInterface
|
||||||
WOPI::PROP_READ_ONLY => $readonly,
|
WOPI::PROP_READ_ONLY => $readonly,
|
||||||
WOPI::PROP_USER_NAME => $user->login,
|
WOPI::PROP_USER_NAME => $user->login,
|
||||||
WOPI::PROP_USER_ID => md5($user->login),
|
WOPI::PROP_USER_ID => md5($user->login),
|
||||||
WOPI::PROP_USER_AVATAR => null,
|
WOPI::PROP_USER_AVATAR => $user->avatar_url,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ class Users
|
||||||
$user->path = rtrim(realpath($user->path), '/') . '/';
|
$user->path = rtrim(realpath($user->path), '/') . '/';
|
||||||
|
|
||||||
$user->dav_url = WWW_URL . 'files/' . $user->login . '/';
|
$user->dav_url = WWW_URL . 'files/' . $user->login . '/';
|
||||||
|
$user->avatar_url = WWW_URL . 'avatars/' . substr(md5($user->login), 0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
|
|
@ -46,7 +46,7 @@ html_head('My files');
|
||||||
|
|
||||||
echo <<<EOF
|
echo <<<EOF
|
||||||
<h2 class="myfiles"><a class="btn" href="{$user->dav_url}">Manage my files</a></h2>
|
<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>
|
<dl>
|
||||||
<dd><h3>{$percent} used, {$free} free</h3></dd>
|
<dd><h3>{$percent} used, {$free} free</h3></dd>
|
||||||
<dd><progress max="{$quota->total}" value="{$quota->used}"></progress>
|
<dd><progress max="{$quota->total}" value="{$quota->used}"></progress>
|
||||||
|
|
|
@ -180,6 +180,11 @@ p.info tt {
|
||||||
padding: .2em;
|
padding: .2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 img {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
main {
|
main {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
|
@ -142,6 +142,7 @@ else {
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td></td>
|
||||||
<th>User</th>
|
<th>User</th>
|
||||||
<td>Quota</td>
|
<td>Quota</td>
|
||||||
<td>Admin</td>
|
<td>Admin</td>
|
||||||
|
@ -154,11 +155,13 @@ else {
|
||||||
$quota = $users->quota($user);
|
$quota = $users->quota($user);
|
||||||
|
|
||||||
printf('<tr>
|
printf('<tr>
|
||||||
|
<td><img src="%s" alt="" /></td>
|
||||||
<th>%s</th>
|
<th>%s</th>
|
||||||
<td>%s used out of %s<br /><progress max="%d" value="%d"></progress></td>
|
<td>%s used out of %s<br /><progress max="%d" value="%d"></progress></td>
|
||||||
<td>%s</td>
|
<td>%s</td>
|
||||||
<td><a href="?edit=%d" class="btn sm">Edit</a> <a href="?delete=%d" class="btn sm">Delete</a></td>
|
<td><a href="?edit=%d" class="btn sm">Edit</a> <a href="?delete=%d" class="btn sm">Delete</a></td>
|
||||||
</tr>',
|
</tr>',
|
||||||
|
$user->avatar_url,
|
||||||
htmlspecialchars($user->login),
|
htmlspecialchars($user->login),
|
||||||
format_bytes($quota->used),
|
format_bytes($quota->used),
|
||||||
format_bytes($quota->total),
|
format_bytes($quota->total),
|
||||||
|
|
Loading…
Reference in a new issue