-
-
Notifications
You must be signed in to change notification settings - Fork 610
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2826 from Leantime/avatarFilenameIssues
Improved Avatar Handling
- Loading branch information
Showing
8 changed files
with
425 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.