Skip to content

Commit

Permalink
Merge pull request #2826 from Leantime/avatarFilenameIssues
Browse files Browse the repository at this point in the history
Improved Avatar Handling
  • Loading branch information
marcelfolaron authored Nov 26, 2024
2 parents 1cbd74c + 29ad2e6 commit 348f82e
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 148 deletions.
6 changes: 3 additions & 3 deletions app/Core/Providers/LoadMacros.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Leantime\Core\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Stringable;
use Leantime\Core\Support\StringableMacros;
use Illuminate\Support\Str;
use Leantime\Core\Support\StrMacros;

class LoadMacros extends ServiceProvider
{
Expand All @@ -21,6 +21,6 @@ public function register(): void
*/
public function boot(): void
{
Stringable::mixin(new StringableMacros);
Str::mixin(new StrMacros);
}
}
131 changes: 131 additions & 0 deletions app/Core/Support/Avatarcreator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace Leantime\Core\Support;

use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use LasseRafn\Initials\Initials;
use Leantime\Core\UI\Theme;
use SVG\SVG;

class Avatarcreator
{
protected $filePrefix = 'user';

protected const MAX_FILENAME_LENGTH = 255;

public function __construct(
protected InitialAvatar $avatarGenerator,
protected Initials $initials,
protected Theme $theme
) {
$this->initials->allowSpecialCharacters(true);

$colorschemes = $theme->getAvailableColorSchemes();
$bgColor = '#00a887';
if (isset($colorschemes['companyColors']) && isset($colorschemes['companyColors']['secondaryColor'])) {
$bgColor = $colorschemes['companyColors']['secondaryColor'];
}

//Set some default values
$this->avatarGenerator->font(APP_ROOT.'/public/dist/fonts/roboto/Roboto-Medium.ttf')
->background('#00a887')
->color('#fff');

}

public function setBackground(string $color)
{
$this->avatarGenerator->background($color);
}

public function setFilePrefix($prefix)
{
$this->filePrefix = Str::sanitizeFilename($prefix);
}

public function getFilePrefix(): string
{
return $this->filePrefix;
}

public function setInitials($name)
{
$cleanString = Str::sanitizeFilename($name);

if (empty($cleanString)) {
$this->initials->name('👻');
} else {
$this->initials->name($cleanString);
}

$this->avatarGenerator->name($cleanString);

}

public function getInitials()
{
return $this->initials->getInitials();
}

public function getAvatar($name): string|SVG
{
$this->setInitials($name);
$filename = $this->getSafeFilename();

if (file_exists($filename)) {
return $filename;
}

return $this->saveAvatar();

}

protected function saveAvatar(): string|SVG
{
if (is_dir(storage_path('framework/cache/avatars')) === false) {
mkdir(storage_path('framework/cache/avatars'));

// Set proper permissions for security
chmod(storage_path('framework/cache/avatars'), 0755);
}

$filename = $this->getSafeFilename();

if (! file_exists($filename)) {
$image = $this->generateAvatar();

if (! is_writable(storage_path('framework/cache/avatars/'))) {

Log::error("Can't write to avatars folder");

return $image;
}

file_put_contents($filename, $image);

}

return $filename;

}

protected function getSafeFilename(): string
{
$baseFilename = $this->filePrefix.'-'.$this->getInitials();

// Ensure filename doesn't exceed maximum length
if (strlen($baseFilename) > self::MAX_FILENAME_LENGTH - 4) { // -4 for .svg
$baseFilename = substr($baseFilename, 0, self::MAX_FILENAME_LENGTH - 4);
}

return storage_path('framework/cache/avatars/'.
Str::sanitizeFilename($baseFilename).'.svg');
}

protected function generateAvatar(): SVG
{
return $this->avatarGenerator->generateSvg();
}
}
98 changes: 98 additions & 0 deletions app/Core/Support/StrMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace Leantime\Core\Support;

use Illuminate\Support\Str;

/**
* @mixin \Illuminate\Support\Stringable
*/
class StrMacros
{
/**
* Cleans a string by removing special characters and optionally spaces.
*
* @param bool $removeSpaces Whether to remove spaces from the string.
* @return callable A function that cleans a string based on the given parameter.
*/
public function alphaNumeric($removeSpaces = false)
{
return function ($value) use ($removeSpaces) {

$cleaned = preg_replace('/[^A-Za-z0-9 ]/', '', (string) $value);

if ($removeSpaces) {
$cleaned = str_replace(' ', '', $cleaned);
} else {
// Step 2: Replace multiple spaces with a single space
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
}

// Step 3: Trim leading and trailing spaces
$cleaned = trim($cleaned);

return $cleaned;
};
}

public function sanitizeFilename($beautify = true)
{
return function ($filename) use ($beautify) {
// sanitize filename
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// avoids ".", ".." or ".hiddenFiles"
$filename = ltrim($filename, '.-');
// optional beautification
if ($beautify) {
$filename = Str::beautifyFilename($filename);
}
// maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$filename = mb_strcut(
pathinfo($filename, PATHINFO_FILENAME),
0,
255 - ($ext ? strlen($ext) + 1 : 0),
mb_detect_encoding($filename)
).($ext ? '.'.$ext : '');

return $filename;
};
}

public function beautifyFilename()
{
return function ($filename) {
// reduce consecutive characters
$filename = preg_replace([
// "file name.zip" becomes "file-name.zip"
'/ +/',
// "file___name.zip" becomes "file-name.zip"
'/_+/',
// "file---name.zip" becomes "file-name.zip"
'/-+/',
], '-', $filename);
$filename = preg_replace([
// "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// "file...name..zip" becomes "file.name.zip"
'/\.{2,}/',
], '.', $filename);
// lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');

return $filename;
};
}
}
36 changes: 0 additions & 36 deletions app/Core/Support/StringableMacros.php

This file was deleted.

Loading

0 comments on commit 348f82e

Please sign in to comment.