From fcdacc5e00dddbfa6bc800f179a299be5c2ea8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?= Date: Wed, 25 Mar 2026 08:36:56 +0100 Subject: [PATCH] Refactor and cleanup of the Movim Image class Check for the required directories when the daemon is launched Introduce the public/images/ path for upcoming persistent images storage --- app/MujiCallParticipant.php | 2 +- app/Post.php | 14 ++-- app/Presence.php | 2 +- app/Widgets/Avatar/Avatar.php | 14 ++-- .../CommunityConfig/CommunityConfig.php | 14 ++-- app/Widgets/RoomsUtils/RoomsUtils.php | 12 ++-- app/Widgets/SpaceInfo/SpaceInfo.php | 4 +- src/Movim/Bootstrap.php | 26 ++----- src/Movim/Console/DaemonCommand.php | 33 ++++++++- src/Movim/Image.php | 67 ++++++++++--------- 10 files changed, 102 insertions(+), 86 deletions(-) diff --git a/app/MujiCallParticipant.php b/app/MujiCallParticipant.php index 2de1f0411..723c89482 100644 --- a/app/MujiCallParticipant.php +++ b/app/MujiCallParticipant.php @@ -43,6 +43,6 @@ class MujiCallParticipant extends Model public function getConferencePictureAttribute(): string { - return Image::getOrCreate($this->jid, 120) ?? avatarPlaceholder($this->jid); + return Image::getOrCreate($this->jid, width: 120) ?? avatarPlaceholder($this->jid); } } diff --git a/app/Post.php b/app/Post.php index d65f5bb2d..a81b778fb 100644 --- a/app/Post.php +++ b/app/Post.php @@ -764,13 +764,13 @@ class Post extends Model new Promise(function () use ($url) { \requestResolverWorker($url)->then(function ($extractor) { try { - $atte = new Attachment; - $atte->rel = 'enclosure'; - $atte->href = $extractor->image; - $atte->type = 'media/jpeg'; - $atte->category = 'picture'; - $atte->post_id = $this->id; - $atte->save(); + $attachment = new Attachment; + $attachment->rel = 'enclosure'; + $attachment->href = $extractor->image; + $attachment->type = 'media/jpeg'; + $attachment->category = 'picture'; + $attachment->post_id = $this->id; + $attachment->save(); } catch (\Throwable $th) { // } diff --git a/app/Presence.php b/app/Presence.php index d83927c24..9f3e3aa8a 100644 --- a/app/Presence.php +++ b/app/Presence.php @@ -90,7 +90,7 @@ class Presence extends Model public function getConferencePictureAttribute(): string { - return Image::getOrCreate($this->mucjid, 120) ?? avatarPlaceholder($this->resource); + return Image::getOrCreate($this->mucjid, width: 120) ?? avatarPlaceholder($this->resource); } public function getAffiliationTxtAttribute(): ?string diff --git a/app/Widgets/Avatar/Avatar.php b/app/Widgets/Avatar/Avatar.php index c38750b75..d4798c054 100644 --- a/app/Widgets/Avatar/Avatar.php +++ b/app/Widgets/Avatar/Avatar.php @@ -95,20 +95,20 @@ class Avatar extends \Movim\Widget\Base $key = $this->me->id . 'banner'; - $p = new Image; - $p->fromBase64($banner->photobin->value); - $p->setKey($key); - $p->save(false, false, 'jpeg', 60); + $image = new Image; + $image->fromBase64($banner->photobin->value); + $image->setKey($key); + $image->save(format: 'jpeg', quality: 60); // Reload - $p->load('jpeg'); + $image->load('jpeg'); $r = $this->xmpp(new Set); $r->setNode('urn:xmpp:movim-banner:0') - ->setUrl(Image::getOrCreate($key, false, false, 'jpeg', true)) + ->setUrl(Image::getOrCreate($key, format: 'jpeg', noTime: true)) ->setWidthMetadata(1280) ->setHeightMetadata(320) - ->setData($p->toBase()) + ->setData($image->toBase()) ->request(); } } diff --git a/app/Widgets/CommunityConfig/CommunityConfig.php b/app/Widgets/CommunityConfig/CommunityConfig.php index ec65143eb..dde0bf265 100644 --- a/app/Widgets/CommunityConfig/CommunityConfig.php +++ b/app/Widgets/CommunityConfig/CommunityConfig.php @@ -80,19 +80,19 @@ class CommunityConfig extends Base $key = $origin . $node . 'avatar'; - $p = new Image; - $p->fromBase64($form->photobin->value); - $p->setKey($key); - $p->save(false, false, 'jpeg', 60); + $image = new Image; + $image->fromBase64($form->photobin->value); + $image->setKey($key); + $image->save(format: 'jpeg', quality: 60); // Reload the freshly compressed picture - $p->load('jpeg'); + $image->load('jpeg'); $r = $this->xmpp(new AvatarSet); $r->setTo($origin) ->setNode($node) - ->setUrl(Image::getOrCreate($key, false, false, 'jpeg', true)) - ->setData($p->toBase()) + ->setUrl(Image::getOrCreate($key, format: 'jpeg', noTime: true)) + ->setData($image->toBase()) ->request(); } diff --git a/app/Widgets/RoomsUtils/RoomsUtils.php b/app/Widgets/RoomsUtils/RoomsUtils.php index 4568f2bf2..2a3a85324 100644 --- a/app/Widgets/RoomsUtils/RoomsUtils.php +++ b/app/Widgets/RoomsUtils/RoomsUtils.php @@ -270,18 +270,18 @@ class RoomsUtils extends Base $tempKey = \generateKey(6); - $p = new Image; - $p->fromBase64($form->photobin->value); - $p->setKey($tempKey); - $p->save(false, false, 'jpeg', 60); + $image = new Image; + $image->fromBase64($form->photobin->value); + $image->setKey($tempKey); + $image->save(format: 'jpeg', quality: 60); // Reload - $p->load('jpeg'); + $image->load('jpeg'); $vcard = new \stdClass; $vcard->photobin = new \stdClass; $vcard->phototype = new \stdClass; - $vcard->photobin->value = $p->toBase(); + $vcard->photobin->value = $image->toBase(); $vcard->phototype->value = 'image/jpeg'; $r = $this->xmpp(new VcardSet); diff --git a/app/Widgets/SpaceInfo/SpaceInfo.php b/app/Widgets/SpaceInfo/SpaceInfo.php index 8a2377d8f..2eba762ff 100644 --- a/app/Widgets/SpaceInfo/SpaceInfo.php +++ b/app/Widgets/SpaceInfo/SpaceInfo.php @@ -273,7 +273,7 @@ class SpaceInfo extends Base $p = new Image; $p->fromBase64($form->photobin->value); $p->setKey($key); - $p->save(false, false, 'jpeg', 60); + $p->save(format: 'jpeg', quality: 60); // Reload the freshly compressed picture $p->load('jpeg'); @@ -281,7 +281,7 @@ class SpaceInfo extends Base $r = $this->xmpp(new AvatarSet); $r->setTo($server) ->setNode($node) - ->setUrl(Image::getOrCreate($key, false, false, 'jpeg', true)) + ->setUrl(Image::getOrCreate($key, format: 'jpeg', noTime: true)) ->setData($p->toBase()) ->request(); } diff --git a/src/Movim/Bootstrap.php b/src/Movim/Bootstrap.php index 47de22ed9..b5b449e4c 100644 --- a/src/Movim/Bootstrap.php +++ b/src/Movim/Bootstrap.php @@ -40,8 +40,6 @@ class Bootstrap return null; } - //Check if vital system need is OK - $this->checkSystem(); $session = $this->checkSession(); $this->loadTimezone($session); $this->loadLanguage($session); @@ -49,21 +47,6 @@ class Bootstrap return $session; } - private function checkSystem() - { - if (!file_exists(CACHE_PATH) && !@mkdir(CACHE_PATH)) { - throw new \Exception('Couldn’t create cache directory'); - } - - if (!file_exists(PUBLIC_CACHE_PATH) && !@mkdir(PUBLIC_CACHE_PATH)) { - throw new \Exception('Couldn’t create public cache directory'); - } - - if (!file_exists(config('paths.log')) && !@mkdir(config('paths.log'))) { - throw new \Exception('Couldn’t create log directory'); - } - } - private function setConstants() { if (file_exists(DOCUMENT_ROOT . '/.env')) { @@ -87,10 +70,13 @@ class Bootstrap define('APP_PATH', DOCUMENT_ROOT . '/app/'); define('CONFIG_PATH', DOCUMENT_ROOT . '/config/'); define('LOCALES_PATH', DOCUMENT_ROOT . '/locales/'); - define('PUBLIC_CACHE_PATH', DOCUMENT_ROOT . '/public/cache/'); - define('PUBLIC_EMOJIS_PATH', DOCUMENT_ROOT . '/public/emojis/'); define('PUBLIC_PATH', DOCUMENT_ROOT . '/public/'); - define('PUBLIC_STICKERS_PATH', DOCUMENT_ROOT . '/public/stickers/'); + define('CACHE_DIR', 'cache/'); + define('IMAGES_DIR', 'images/'); + define('PUBLIC_CACHE_PATH', PUBLIC_PATH . CACHE_DIR); + define('PUBLIC_IMAGES_PATH', PUBLIC_PATH . IMAGES_DIR); + define('PUBLIC_EMOJIS_PATH', PUBLIC_PATH . 'emojis/'); + define('PUBLIC_STICKERS_PATH', PUBLIC_PATH . 'stickers/'); define('VIEWS_PATH', DOCUMENT_ROOT . '/app/Views/'); define('WIDGETS_PATH', DOCUMENT_ROOT . '/app/Widgets/'); define('WORKERS_PATH', DOCUMENT_ROOT . '/workers/'); diff --git a/src/Movim/Console/DaemonCommand.php b/src/Movim/Console/DaemonCommand.php index d0740fa28..4f42f7369 100644 --- a/src/Movim/Console/DaemonCommand.php +++ b/src/Movim/Console/DaemonCommand.php @@ -11,8 +11,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Respect\Validation\Validator; - use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; @@ -85,6 +83,16 @@ class DaemonCommand extends Command exit; } + if ($paths = $this->checkDirectories()) { + foreach ($paths as $path) { + $output->writeln('Can’t create ' . $path . ' directory'); + } + + $output->writeln('You can create them yourself or let the daemon create them for you.'); + $output->writeln('In both case they should be writable by the same system user that is running the daemon.'); + exit; + } + if (User::where('admin', true)->count() == 0) { $output->writeln('Please set at least one user as an admin once its account is logged in'); @@ -160,7 +168,7 @@ class DaemonCommand extends Command 'baseuri' => $baseuri, 'DAEMON_DEBUG' => config('daemon.debug'), 'DAEMON_PORT' => config('daemon.port'), - 'DAEMON_VERBOSE'=> config('daemon.verbose'), + 'DAEMON_VERBOSE' => config('daemon.verbose'), 'DB_DATABASE' => config('database.database'), 'DB_DRIVER' => config('database.driver'), 'DB_HOST' => config('database.host'), @@ -178,4 +186,23 @@ class DaemonCommand extends Command return Command::SUCCESS; } + + private function checkDirectories(): ?array + { + $paths = [ + CACHE_PATH, + PUBLIC_CACHE_PATH, + PUBLIC_IMAGES_PATH, + config('paths.log') + ]; + $errors = []; + + foreach ($paths as $path) { + if (!file_exists($path) && !@mkdir($path)) { + array_push($errors, $path); + } + } + + return !empty($errors) ? $errors : null; + } } diff --git a/src/Movim/Image.php b/src/Movim/Image.php index 97fecdb5e..ad79e64ba 100644 --- a/src/Movim/Image.php +++ b/src/Movim/Image.php @@ -12,7 +12,6 @@ class Image private $_im; private $_inMemory = false; - public static $folder = 'cache/'; public static $formats = ['jpeg' => '.jpg', 'png' => '.png', 'webp' => '.webp', 'gif' => '.gif']; public static $hash = 'sha256'; // Cache need to be cleared in a migration if changed private static $originalType = '_o'; @@ -50,11 +49,11 @@ class Image /** * @desc Load a bin picture from a path */ - public function load(string $format = DEFAULT_PICTURE_FORMAT): bool + public function load(string $format = DEFAULT_PICTURE_FORMAT, ?string $directory = CACHE_DIR): bool { if (!empty($this->_key)) { return $this->fromPath( - PUBLIC_CACHE_PATH . + PUBLIC_PATH . $directory . hash(Image::$hash, $this->_key) . self::$originalType . self::$formats[$format] @@ -129,44 +128,43 @@ class Image /** * @desc Convert to a base64 */ - public function toBase(): string + public function toBase(): ?string { if ($this->_im) { - return base64_encode($this->toBin()); + return base64_encode($this->_im->getImageBlob()); } - } - /** - * @desc Convert to a base64 - */ - public function toBin(): string - { - if ($this->_im) { - return $this->_im->getImageBlob(); - } + return null; } /** * @desc Return the picture URL or create it if possible */ - public static function getOrCreate(string $key, $width = false, $height = false, $format = DEFAULT_PICTURE_FORMAT, bool $noTime = false): ?string - { + public static function getOrCreate( + string $key, + ?int $width = null, + ?int $height = null, + ?string $format = DEFAULT_PICTURE_FORMAT, + ?bool $noTime = false, + ?string $directory = CACHE_DIR, + ): ?string { if (!in_array($format, array_keys(self::$formats))) { $format = DEFAULT_PICTURE_FORMAT; } - $type = $width ? '_' . $width + $type = $width != null + ? '_' . $width : self::$originalType; /** * The file is in the cache and we can directly return it */ if (file_exists( - PUBLIC_CACHE_PATH . hash(Image::$hash, $key) . + PUBLIC_PATH . $directory . hash(Image::$hash, $key) . $type . self::$formats[$format] )) { return urilize( - self::$folder . hash(Image::$hash, $key) . $type . self::$formats[$format], + $directory . hash(Image::$hash, $key) . $type . self::$formats[$format], $noTime ); } @@ -175,9 +173,9 @@ class Image * The file is not in the cache but we do have the original to build the requested size */ elseif ( - $width + $width != null && file_exists( - PUBLIC_CACHE_PATH . hash(Image::$hash, $key) . + PUBLIC_PATH . $directory . hash(Image::$hash, $key) . self::$originalType . self::$formats[$format] ) ) { @@ -189,7 +187,7 @@ class Image $im->save($width, $height, $format); return urilize( - self::$folder . hash(Image::$hash, $key) . $type . self::$formats[$format], + $directory . hash(Image::$hash, $key) . $type . self::$formats[$format], $noTime ); } @@ -197,16 +195,21 @@ class Image return null; } - public function save($width = false, $height = false, $format = DEFAULT_PICTURE_FORMAT, $quality = DEFAULT_PICTURE_QUALITY) - { + public function save( + ?int $width = null, + ?int $height = null, + ?string $format = DEFAULT_PICTURE_FORMAT, + ?int $quality = DEFAULT_PICTURE_QUALITY, + ?string $directory = CACHE_DIR + ) { if (!$this->_key && !$this->_inMemory) return; - $type = $width ? '_' . $width + $type = $width != null ? '_' . $width : self::$originalType; if (!$this->_inMemory) { // Cleanup the existing files - $path = PUBLIC_CACHE_PATH . hash(Image::$hash, $this->_key) . $type . self::$formats[$format]; + $path = PUBLIC_PATH . $directory . hash(Image::$hash, $this->_key) . $type . self::$formats[$format]; // If the file exists we replace it if (file_exists($path)) { @@ -216,7 +219,7 @@ class Image if ($width == false) { foreach ( glob( - PUBLIC_CACHE_PATH . + PUBLIC_PATH . $directory . hash(Image::$hash, $this->_key) . '*' . self::$formats[$format], GLOB_NOSORT @@ -279,11 +282,11 @@ class Image $this->_im->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT); // Resize - if (!$height) { + if ($height == null) { $height = $width; } - if ($width && $height) { + if ($width != null && $height != null) { $geo = $this->_im->getImageGeometry(); $this->_im->cropThumbnailImage($width, $height); @@ -300,7 +303,7 @@ class Image $this->_im->clear(); } } catch (\ImagickException $e) { - logError($this->_key . ' '. $e->getMessage()); + logError($this->_key . ' ' . $e->getMessage()); } } @@ -315,12 +318,12 @@ class Image /** * Remove the original */ - public function remove(string $format = DEFAULT_PICTURE_FORMAT) + /*public function remove(string $format = DEFAULT_PICTURE_FORMAT) { $path = PUBLIC_CACHE_PATH . hash(Image::$hash, $this->_key) . self::$originalType . self::$formats[$format]; if (file_exists($path)) { @unlink($path); } - } + }*/ }