Skip to content

Commit

Permalink
Merge pull request #2796 from Leantime/fixAvatarFontFileIssues
Browse files Browse the repository at this point in the history
Resolving open basedir restrictions with font files in avatar generat…
  • Loading branch information
marcelfolaron authored Nov 18, 2024
2 parents c4032e0 + 9285883 commit bc17610
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 31 deletions.
1 change: 1 addition & 0 deletions app/Core/Configuration/laravelConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Application Service Providers...
*/
\Leantime\Core\Providers\AppServiceProvider::class,
\Leantime\Core\Providers\LoadMacros::class,

\Leantime\Core\Providers\Cache::class, //\Illuminate\Cache\CacheServiceProvider::class,
\Leantime\Core\Providers\Redis::class,
Expand Down
13 changes: 11 additions & 2 deletions app/Core/Fileupload.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ private function uploadLocal(): bool
/**
* displayImageFile - display image file
*/
public function displayImageFile(string $imageName, string $fullPath = ''): Response
public function displayImageFile(string $imageName, string $fullPath = '', $allowSvg = false): Response
{
$mimes = [
'jpg' => 'image/jpg',
Expand All @@ -339,6 +339,10 @@ public function displayImageFile(string $imageName, string $fullPath = ''): Resp
'png' => 'image/png',
];

if ($allowSvg) {
$mimes['svg'] = 'image/svg+xml';
}

$responseFailure = new Response(file_get_contents(ROOT.'/dist/images/doc.png'));
$sLastModified = filemtime(ROOT.'/dist/images/doc.png');
$responseFailure->headers->set('Content-Type', 'image/png');
Expand Down Expand Up @@ -387,7 +391,12 @@ public function displayImageFile(string $imageName, string $fullPath = ''): Resp
$path_parts = pathinfo($fullPath);
$ext = $path_parts['extension'];

if (! in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) {
$fileExtensions = ['jpg', 'jpeg', 'gif', 'png'];
if ($allowSvg) {
$fileExtensions[] = 'svg';
}

if (! in_array($ext, $fileExtensions)) {
throw new HttpResponseException($responseFailure);
}

Expand Down
26 changes: 26 additions & 0 deletions app/Core/Providers/LoadMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Leantime\Core\Providers;

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

class LoadMacros extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Stringable::mixin(new StringableMacros);
}
}
36 changes: 36 additions & 0 deletions app/Core/Support/StringableMacros.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Leantime\Core\Support;

/**
* @mixin \Illuminate\Support\Stringable
*/
class StringableMacros
{
/**
* 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 () use ($removeSpaces) {
/** @var \Illuminate\Support\Stringable $this */
$cleaned = preg_replace('/[^A-Za-z0-9 ]/', '', (string) $this);

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;
};

}
}
2 changes: 1 addition & 1 deletion app/Domain/Api/Controllers/Projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function get(array $params): Response

return match ($svg['type']) {
'uploaded' => $file->displayImageFile($svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename'], true),
};
}

Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Api/Controllers/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function get(array $params): Response

return match ($svg['type']) {
'uploaded' => $file->displayImageFile($svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename']),
'generated' => $file->displayImageFile('avatar', $svg['filename'], true),
};
}

Expand Down
17 changes: 13 additions & 4 deletions app/Domain/Projects/Repositories/Projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DatePeriod;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use LasseRafn\Initials\Initials;
use Leantime\Core\Configuration\Environment;
Expand Down Expand Up @@ -1134,8 +1135,8 @@ public function getProjectAvatar($id): array|SVG
$stmn->closeCursor();
}
try {
$avatar = (new InitialAvatar)
->fontName('Verdana')
$avatar = app()->make(InitialAvatar::class)
->font(APP_ROOT.'/public/dist/fonts/roboto/Roboto-Medium.ttf')
->background('#555555')
->color('#fff');

Expand All @@ -1152,13 +1153,21 @@ public function getProjectAvatar($id): array|SVG

/** @var Initials $initialsClass */
$initialsClass = app()->make(Initials::class);
$initialsClass->allowSpecialCharacters(false);
$initialsClass->name($value['name']);
$imagename = $initialsClass->getInitials();
$imagename = Str::of($imagename)->alphaNumeric(true);

if (! file_exists($filename = APP_ROOT.'/cache/avatars/'.$imagename.'.svg')) {
if (is_dir(storage_path('framework/cache/avatars')) === false) {
mkdir(storage_path('framework/cache/avatars'));
}

if (! file_exists($filename = storage_path('framework/cache/avatars/user-'.$imagename.'.svg'))) {
$image = $avatar->name($value['name'])->generateSvg();

if (! is_writable(APP_ROOT.'/cache/avatars/')) {
if (! is_writable(storage_path('framework/cache/avatars/'))) {
Log::warning("Can't write to avatars folders");

return $image;
}

Expand Down
18 changes: 14 additions & 4 deletions app/Domain/Users/Repositories/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
use LasseRafn\Initials\Initials;
use Leantime\Core\Configuration\Environment;
Expand Down Expand Up @@ -612,8 +613,8 @@ public function getProfilePicture($id): array|SVG

try {

$avatar = (new InitialAvatar)
->fontName('Verdana')
$avatar = app()->make(InitialAvatar::class)
->font(APP_ROOT.'/public/dist/fonts/roboto/Roboto-Medium.ttf')
->background('#00a887')
->color('#fff');

Expand All @@ -634,13 +635,22 @@ public function getProfilePicture($id): array|SVG

/** @var Initials $initialsClass */
$initialsClass = app()->make(Initials::class);
$initialsClass->allowSpecialCharacters(false);
$initialsClass->name($name);

$imagename = $initialsClass->getInitials();
$imagename = Str::of($imagename)->alphaNumeric(true);

if (is_dir(storage_path('framework/cache/avatars')) === false) {
mkdir(storage_path('framework/cache/avatars'));
}

if (! file_exists($filename = APP_ROOT.'/cache/avatars/user-'.$imagename.'.svg')) {
if (! file_exists($filename = storage_path('framework/cache/avatars/user-'.$imagename.'.svg'))) {
$image = $avatar->name($name)->generateSvg();

if (! is_writable(APP_ROOT.'/cache/avatars/')) {
if (! is_writable(storage_path('framework/cache/avatars/'))) {
Log::warning("Can't write to avatars folders");

return $image;
}

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "leantime/leantime",
"description": "Open source project management system",
"version": "3.3",
"version": "3.3.1",
"type": "project",
"license": "AGPL-3.0",
"config": {
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ get-version:
@echo $(VERSION)

phpstan:
./vendor/bin/phpstan analyse -c .phpstan/phpstan.neon -v --debug --memory-limit 2G
./vendor/bin/phpstan analyse -c .phpstan/phpstan.neon --memory-limit 2G

update-carbon-macros:
./vendor/bin/carbon macro Leantime\\Core\\Support\\CarbonMacros app/Core/Support/CarbonMacros.php
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "leantime",
"version": "3.3.0",
"version": "3.3.1",
"description": "Open source innovation management system",
"dependencies": {
"@assuradeurengilde/fontawesome-iconpicker": "^3.2.3",
Expand Down
30 changes: 15 additions & 15 deletions public/dist/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"/js/compiled-htmx.3.3.0.min.js": "/js/compiled-htmx.3.3.0.min.js",
"/js/compiled-htmx-headSupport.3.3.0.min.js": "/js/compiled-htmx-headSupport.3.3.0.min.js",
"/css/main.3.3.0.min.css": "/css/main.3.3.0.min.css",
"/css/editor.3.3.0.min.css": "/css/editor.3.3.0.min.css",
"/css/app.3.3.0.min.css": "/css/app.3.3.0.min.css",
"/js/compiled-footer.3.3.0.min.js": "/js/compiled-footer.3.3.0.min.js",
"/js/compiled-app.3.3.0.min.js": "/js/compiled-app.3.3.0.min.js",
"/js/compiled-frameworks.3.3.0.min.js": "/js/compiled-frameworks.3.3.0.min.js",
"/js/compiled-framework-plugins.3.3.0.min.js": "/js/compiled-framework-plugins.3.3.0.min.js",
"/js/compiled-global-component.3.3.0.min.js": "/js/compiled-global-component.3.3.0.min.js",
"/js/compiled-calendar-component.3.3.0.min.js": "/js/compiled-calendar-component.3.3.0.min.js",
"/js/compiled-table-component.3.3.0.min.js": "/js/compiled-table-component.3.3.0.min.js",
"/js/compiled-editor-component.3.3.0.min.js": "/js/compiled-editor-component.3.3.0.min.js",
"/js/compiled-gantt-component.3.3.0.min.js": "/js/compiled-gantt-component.3.3.0.min.js",
"/js/compiled-chart-component.3.3.0.min.js": "/js/compiled-chart-component.3.3.0.min.js",
"/js/compiled-htmx.3.3.1.min.js": "/js/compiled-htmx.3.3.1.min.js",
"/js/compiled-htmx-headSupport.3.3.1.min.js": "/js/compiled-htmx-headSupport.3.3.1.min.js",
"/css/main.3.3.1.min.css": "/css/main.3.3.1.min.css",
"/css/editor.3.3.1.min.css": "/css/editor.3.3.1.min.css",
"/css/app.3.3.1.min.css": "/css/app.3.3.1.min.css",
"/js/compiled-footer.3.3.1.min.js": "/js/compiled-footer.3.3.1.min.js",
"/js/compiled-app.3.3.1.min.js": "/js/compiled-app.3.3.1.min.js",
"/js/compiled-frameworks.3.3.1.min.js": "/js/compiled-frameworks.3.3.1.min.js",
"/js/compiled-framework-plugins.3.3.1.min.js": "/js/compiled-framework-plugins.3.3.1.min.js",
"/js/compiled-global-component.3.3.1.min.js": "/js/compiled-global-component.3.3.1.min.js",
"/js/compiled-calendar-component.3.3.1.min.js": "/js/compiled-calendar-component.3.3.1.min.js",
"/js/compiled-table-component.3.3.1.min.js": "/js/compiled-table-component.3.3.1.min.js",
"/js/compiled-editor-component.3.3.1.min.js": "/js/compiled-editor-component.3.3.1.min.js",
"/js/compiled-gantt-component.3.3.1.min.js": "/js/compiled-gantt-component.3.3.1.min.js",
"/js/compiled-chart-component.3.3.1.min.js": "/js/compiled-chart-component.3.3.1.min.js",
"/images/03-1.png": "/images/03-1.png",
"/images/32px.png": "/images/32px.png",
"/images/40px.png": "/images/40px.png",
Expand Down

0 comments on commit bc17610

Please sign in to comment.