diff --git a/.gitignore b/.gitignore index 45fa520426b9cf00a7b273a4d2d91b234df5618b..75d7aa6e9445d22490bf1cf1da8ac06d1e9d795d 100644 --- a/.gitignore +++ b/.gitignore @@ -143,16 +143,20 @@ node_modules # public folder public/* -public/media/site !public/media !public/.htaccess !public/favicon.ico !public/icon* +!public/castopod-banner* +!public/castopod-avatar* !public/index.php !public/robots.txt !public/.well-known !public/.well-known/GDPR.yml +public/assets/* +!public/assets/index.html + # public media folder !public/media/podcasts !public/media/persons diff --git a/app/Config/Fediverse.php b/app/Config/Fediverse.php index cb10bc1afc1e17541ea174befd2996c2a00fb256..f42d45585ed81060c795ce90fde99d515b0739a5 100644 --- a/app/Config/Fediverse.php +++ b/app/Config/Fediverse.php @@ -23,7 +23,7 @@ class Fediverse extends FediverseBaseConfig */ public string $noteObject = NoteObject::class; - public string $defaultAvatarImagePath = 'media/castopod-avatar_thumbnail.webp'; + public string $defaultAvatarImagePath = 'castopod-avatar_thumbnail.webp'; public string $defaultAvatarImageMimetype = 'image/webp'; @@ -52,7 +52,7 @@ class Fediverse extends FediverseBaseConfig helper('media'); - $this->defaultCoverImagePath = media_path($defaultBannerPath . '_federation.' . $extension); + $this->defaultCoverImagePath = $defaultBannerPath . '_federation.' . $extension; $this->defaultCoverImageMimetype = $defaultBanner['mimetype']; } } diff --git a/app/Controllers/CreditsController.php b/app/Controllers/CreditsController.php index 1d41afd23f1d2442b35192a6b9af90cd9f433163..ca243a954960a5b8d89158dac6e61cbc668a51e7 100644 --- a/app/Controllers/CreditsController.php +++ b/app/Controllers/CreditsController.php @@ -52,7 +52,7 @@ class CreditsController extends BaseController $personId => [ 'full_name' => $credit->person->full_name, 'thumbnail_url' => - $credit->person->avatar->thumbnail_url, + get_avatar_url($credit->person, 'thumbnail'), 'information_url' => $credit->person->information_url, 'roles' => [ @@ -90,7 +90,7 @@ class CreditsController extends BaseController $credits[$personGroup]['persons'][$personId] = [ 'full_name' => $credit->person->full_name, 'thumbnail_url' => - $credit->person->avatar->thumbnail_url, + get_avatar_url($credit->person, 'thumbnail'), 'information_url' => $credit->person->information_url, 'roles' => [ $personRole => [ diff --git a/app/Controllers/FeedController.php b/app/Controllers/FeedController.php index 290de8c476515f3176090a01f98be2cb0fd5892e..ee0ad37e50afb88795ca012aa0cdb40501449a0b 100644 --- a/app/Controllers/FeedController.php +++ b/app/Controllers/FeedController.php @@ -23,7 +23,7 @@ class FeedController extends Controller { public function index(string $podcastHandle): ResponseInterface { - helper(['rss', 'premium_podcasts']); + helper(['rss', 'premium_podcasts', 'misc']); $podcast = (new PodcastModel())->where('handle', $podcastHandle) ->first(); diff --git a/app/Controllers/WebmanifestController.php b/app/Controllers/WebmanifestController.php index 653bcaae6cb4b57da66eae14d804b15b51d4c17b..f1e6449b43900ea3d4f2dad9034012d2dc13df2e 100644 --- a/app/Controllers/WebmanifestController.php +++ b/app/Controllers/WebmanifestController.php @@ -61,14 +61,12 @@ class WebmanifestController extends Controller 'background_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['background'], 'icons' => [ [ - 'src' => service('settings') - ->get('App.siteIcon')['192'], + 'src' => get_site_icon_url('192'), 'type' => 'image/png', 'sizes' => '192x192', ], [ - 'src' => service('settings') - ->get('App.siteIcon')['512'], + 'src' => get_site_icon_url('512'), 'type' => 'image/png', 'sizes' => '512x512', ], diff --git a/app/Entities/Person.php b/app/Entities/Person.php index f0f27721027547f71c4e26bf6f4e7043dd830567..5ce4d8f0debdd62ab608b7da83d8bdd765804f0b 100644 --- a/app/Entities/Person.php +++ b/app/Entities/Person.php @@ -84,21 +84,10 @@ class Person extends Entity return $this; } - public function getAvatar(): Image + public function getAvatar(): ?Image { - if ($this->attributes['avatar_id'] === null) { - helper('media'); - return new Image([ - 'file_key' => config('Images') - ->avatarDefaultPath, - 'file_mimetype' => config('Images') - ->avatarDefaultMimeType, - 'file_size' => 0, - 'file_metadata' => [ - 'sizes' => config('Images') - ->personAvatarSizes, - ], - ], 'fs'); + if ($this->avatar_id === null) { + return null; } if ($this->avatar === null) { diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 313fa0853c8a4bbab315901cdb9028a747dde6cf..3894b102c8a679316eac4895a3201c243c7241af 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -294,22 +294,10 @@ class Podcast extends Entity return $this; } - public function getBanner(): Image + public function getBanner(): ?Image { if ($this->banner_id === null) { - $defaultBanner = config('Images') - ->podcastBannerDefaultPaths[service('settings')->get('App.theme')] ?? config( - 'Images' - )->podcastBannerDefaultPaths['default']; - return new Image([ - 'file_key' => $defaultBanner['path'], - 'file_mimetype' => $defaultBanner['mimetype'], - 'file_size' => 0, - 'file_metadata' => [ - 'sizes' => config('Images') -->podcastBannerSizes, - ], - ], 'fs'); + return null; } if (! $this->banner instanceof Image) { diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index 584a07d550c37c091b53e799cabbc440e9c45106..e0564f291f5ac4ba472c82a25c1d7c9e1d264f30 100644 --- a/app/Helpers/id3_helper.php +++ b/app/Helpers/id3_helper.php @@ -33,7 +33,7 @@ if (! function_exists('write_audio_file_tags')) { /** @var FileManagerInterface $fileManager */ $fileManager = service('file_manager'); - $APICdata = $fileManager->getFileContents($episode->cover->id3_key); + $APICdata = (string) $fileManager->getFileContents($episode->cover->id3_key); // TODO: variables used for podcast specific tags // $podcastUrl = $episode->podcast->link; diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php index e4a5dc931e0618c6079b5f622e4087f9a8f85b89..06b0aff3c0edba7cdaf1d46da5c06c9e96022d40 100644 --- a/app/Helpers/misc_helper.php +++ b/app/Helpers/misc_helper.php @@ -2,6 +2,9 @@ declare(strict_types=1); +use App\Entities\Person; +use App\Entities\Podcast; + /** * @copyright 2020 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 @@ -297,3 +300,89 @@ if (! function_exists('format_bytes')) { return round($bytes, $precision) . $units[$pow]; } } + + +if (! function_exists('get_site_icon_url')) { + function get_site_icon_url(string $size): string + { + if (config('App')->siteIcon['ico'] === service('settings')->get('App.siteIcon')['ico']) { + // return default site icon url + return base_url(service('settings')->get('App.siteIcon')[$size]); + } + + return service('file_manager')->getUrl(service('settings')->get('App.siteIcon')[$size]); + } +} + + +if (! function_exists('get_podcast_banner')) { + function get_podcast_banner_url(Podcast $podcast, string $size): string + { + if ($podcast->banner === null) { + $defaultBanner = config('Images') + ->podcastBannerDefaultPaths[service('settings')->get('App.theme')] ?? config( + 'Images' + )->podcastBannerDefaultPaths['default']; + + $sizes = config('Images') +->podcastBannerSizes; + + $sizeConfig = $sizes[$size]; + helper('filesystem'); + + // return default site icon url + return base_url( + change_file_path($defaultBanner['path'], '_' . $size, $sizeConfig['extension'] ?? null) + ); + } + + $sizeKey = $size . '_url'; + return $podcast->banner->{$sizeKey}; + } +} + +if (! function_exists('get_podcast_banner_mimetype')) { + function get_podcast_banner_mimetype(Podcast $podcast, string $size): string + { + if ($podcast->banner === null) { + $sizes = config('Images') +->podcastBannerSizes; + + $sizeConfig = $sizes[$size]; + helper('filesystem'); + + // return default site icon url + return array_key_exists('mimetype', $sizeConfig) ? $sizeConfig['mimetype'] : config( + 'Images' + )->podcastBannerDefaultMimeType; + } + + $mimetype = $size . '_mimetype'; + return $podcast->banner->{$mimetype}; + } +} + +if (! function_exists('get_avatar_url')) { + function get_avatar_url(Person $person, string $size): string + { + if ($person->avatar === null) { + $defaultAvatar = config('Images') +->avatarDefaultPath; + + $sizes = config('Images') +->personAvatarSizes; + + $sizeConfig = $sizes[$size]; + + helper('filesystem'); + + // return default site icon url + return base_url( + change_file_path($defaultAvatar['path'], '_' . $size, $sizeConfig['extension'] ?? null) + ); + } + + $sizeKey = $size . '_url'; + return $person->avatar->{$sizeKey}; + } +} diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index c4329f7783218512aac18ad01e1c2521bee9f637..7da6baa294952933e4031512f70c466d7b93aeae 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -204,7 +204,7 @@ if (! function_exists('get_rss_feed')) { foreach ($person->roles as $role) { $personElement = $channel->addChild('person', $person->full_name, $podcastNamespace); - $personElement->addAttribute('img', $person->avatar->medium_url); + $personElement->addAttribute('img', get_avatar_url($person, 'medium')); if ($person->information_url !== null) { $personElement->addAttribute('href', $person->information_url); @@ -388,7 +388,7 @@ if (! function_exists('get_rss_feed')) { esc(lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en')), ); - $personElement->addAttribute('img', $person->avatar->medium_url); + $personElement->addAttribute('img', get_avatar_url($person, 'medium')); if ($person->information_url !== null) { $personElement->addAttribute('href', $person->information_url); diff --git a/app/Helpers/seo_helper.php b/app/Helpers/seo_helper.php index 6d535739c63db7f4f2b28bda3331146f0b9d8fde..3099965d622381eac01ae18ab45031770617e793 100644 --- a/app/Helpers/seo_helper.php +++ b/app/Helpers/seo_helper.php @@ -273,7 +273,7 @@ if (! function_exists('get_home_metatags')) { $metatags ->title(service('settings')->get('App.siteName')) ->description(esc(service('settings')->get('App.siteDescription'))) - ->image(service('settings')->get('App.siteIcon')['512']) + ->image(get_site_icon_url('512')) ->canonical((string) current_url()) ->og('site_name', esc(service('settings')->get('App.siteName'))); @@ -292,7 +292,7 @@ if (! function_exists('get_page_metatags')) { )->get('App.siteName') ) ->description(esc(service('settings')->get('App.siteDescription'))) - ->image(service('settings')->get('App.siteIcon')['512']) + ->image(get_site_icon_url('512')) ->canonical((string) current_url()) ->og('site_name', esc(service('settings')->get('App.siteName'))); diff --git a/app/Libraries/MediaClipper/VideoClipper.php b/app/Libraries/MediaClipper/VideoClipper.php index 86ebb7587c4eb38e8803c6ad693e38b1a653452a..f3315fd2eff9ed28b1b0d8e951d383e54456ae49 100644 --- a/app/Libraries/MediaClipper/VideoClipper.php +++ b/app/Libraries/MediaClipper/VideoClipper.php @@ -134,7 +134,7 @@ class VideoClipper /** @var FileManagerInterface $fileManager */ $fileManager = service('file_manager'); - $jsonTranscriptString = $fileManager->getFileContents($jsonFileKey); + $jsonTranscriptString = (string) $fileManager->getFileContents($jsonFileKey); if ($jsonTranscriptString === '') { throw new Exception('Cannot get transcript json contents.'); } diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 0590b4340506d73e204bd478ea7e1bda6e4962f0..8fd51be4e12032d02fae9c8b940768b1f6a11a7d 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -469,8 +469,8 @@ class PodcastModel extends Model $actor->summary = $podcast->description_html; $actor->avatar_image_url = $podcast->cover->federation_url; $actor->avatar_image_mimetype = $podcast->cover->federation_mimetype; - $actor->cover_image_url = $podcast->banner->federation_url; - $actor->cover_image_mimetype = $podcast->banner->federation_mimetype; + $actor->cover_image_url = get_podcast_banner_url($podcast, 'federation'); + $actor->cover_image_mimetype = get_podcast_banner_mimetype($podcast, 'federation'); if ($actor->hasChanged()) { $actorModel->update($actor->id, $actor); diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php index d68e339467654ae6d3a9de446954c5fa519a4e40..e6215e17809f34637886a5b1bfa3b36c86b4f71b 100644 --- a/modules/Admin/Controllers/SettingsController.php +++ b/modules/Admin/Controllers/SettingsController.php @@ -17,10 +17,10 @@ use App\Models\EpisodeModel; use App\Models\PersonModel; use App\Models\PodcastModel; use App\Models\PostModel; +use CodeIgniter\Files\File; use CodeIgniter\HTTP\RedirectResponse; use Modules\Media\Entities\Audio; use Modules\Media\FileManagers\FileManagerInterface; -use Modules\Media\FileManagers\FS; use Modules\Media\Models\MediaModel; use PHP_ICO; @@ -58,47 +58,60 @@ class SettingsController extends BaseController $siteIconFile = $this->request->getFile('site_icon'); if ($siteIconFile !== null && $siteIconFile->isValid()) { - helper(['filesystem', 'media']); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); // delete site folder in media before repopulating it - delete_files(media_path_absolute('/site')); - - // save original in disk - $originalFilename = (new FS(config('Media')))->save( - $siteIconFile, - 'site/icon.' . $siteIconFile->getExtension() - ); + $fileManager->deleteAll('site'); // convert jpeg image to png if not if ($siteIconFile->getClientMimeType() !== 'image/png') { - service('image')->withFile(media_path_absolute($originalFilename)) + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); + service('image') + ->withFile($siteIconFile->getRealPath()) ->convert(IMAGETYPE_JPEG) - ->save(media_path_absolute('/site/icon.png')); + ->save($tempFilePath); + + @unlink($siteIconFile->getRealPath()); + + $siteIconFile = new File($tempFilePath, true); } - // generate random hash to use as a suffix to renew browser cache - $randomHash = substr(bin2hex(random_bytes(18)), 0, 8); + $icoTempFilePath = WRITEPATH . 'temp/img_favicon.ico'; // generate ico $ico_lib = new PHP_ICO(); - $ico_lib->add_image(media_path_absolute('/site/icon.png'), [[32, 32], [64, 64]]); - $ico_lib->save_ico(media_path_absolute("/site/favicon.{$randomHash}.ico")); + $ico_lib->add_image($siteIconFile->getRealPath(), [[32, 32], [64, 64]]); + $ico_lib->save_ico($icoTempFilePath); + + // generate random hash to use as a suffix to renew browser cache + $randomHash = substr(bin2hex(random_bytes(18)), 0, 8); + + // save ico + $fileManager->save(new File($icoTempFilePath, true), "site/favicon.{$randomHash}.ico"); // resize original to needed sizes foreach ([64, 180, 192, 512] as $size) { + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); service('image') - ->withFile(media_path_absolute('/site/icon.png')) + ->withFile($siteIconFile->getRealPath()) ->resize($size, $size) - ->save(media_path_absolute("/site/icon-{$size}.{$randomHash}.png")); + ->save($tempFilePath); + + // save sizes to + $fileManager->save(new File($tempFilePath), "site/icon-{$size}.{$randomHash}.png"); } + // save original as png + $fileManager->save($siteIconFile, 'site/icon.png'); + service('settings') ->set('App.siteIcon', [ - 'ico' => '/' . media_path("/site/favicon.{$randomHash}.ico"), - '64' => '/' . media_path("/site/icon-64.{$randomHash}.png"), - '180' => '/' . media_path("/site/icon-180.{$randomHash}.png"), - '192' => '/' . media_path("/site/icon-192.{$randomHash}.png"), - '512' => '/' . media_path("/site/icon-512.{$randomHash}.png"), + 'ico' => "site/favicon.{$randomHash}.ico", + '64' => "site/icon-64.{$randomHash}.png", + '180' => "site/icon-180.{$randomHash}.png", + '192' => "site/icon-192.{$randomHash}.png", + '512' => "site/icon-512.{$randomHash}.png", ]); } @@ -107,9 +120,11 @@ class SettingsController extends BaseController public function deleteIcon(): RedirectResponse { - helper(['filesystem', 'media']); - // delete site folder in media - delete_files(media_path_absolute('/site')); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + // delete site folder + $fileManager->deleteAll('site'); service('settings') ->forget('App.siteIcon'); diff --git a/modules/Fediverse/Config/Fediverse.php b/modules/Fediverse/Config/Fediverse.php index 382153a871a6f6c374c7bab48669cfea29aa79d8..f40fec3757bd145d9393a1fd18343c7f3e59321a 100644 --- a/modules/Fediverse/Config/Fediverse.php +++ b/modules/Fediverse/Config/Fediverse.php @@ -30,11 +30,11 @@ class Fediverse extends BaseConfig * Default avatar and cover images * -------------------------------------------------------------------- */ - public string $defaultAvatarImagePath = 'media/avatar-default.jpg'; + public string $defaultAvatarImagePath = 'avatar-default.jpg'; public string $defaultAvatarImageMimetype = 'image/jpeg'; - public string $defaultCoverImagePath = 'media/banner-default.jpg'; + public string $defaultCoverImagePath = 'banner-default.jpg'; public string $defaultCoverImageMimetype = 'image/jpeg'; diff --git a/modules/Media/Config/Routes.php b/modules/Media/Config/Routes.php new file mode 100644 index 0000000000000000000000000000000000000000..19766ba1c2385b2534efcbc623bd23051354612b --- /dev/null +++ b/modules/Media/Config/Routes.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Ad Aures + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +$routes = service('routes'); + +$routes->get('static/(:any)', 'MediaController::serve/$1', [ + 'as' => 'media-serve', + 'namespace' => 'Modules\Media\Controllers', +]); diff --git a/modules/Media/Controllers/MediaController.php b/modules/Media/Controllers/MediaController.php new file mode 100644 index 0000000000000000000000000000000000000000..65a8f5dbe647ec454e72931b62c5f1a69dbf3f62 --- /dev/null +++ b/modules/Media/Controllers/MediaController.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2020 Ad Aures + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace Modules\Media\Controllers; + +use CodeIgniter\Controller; +use CodeIgniter\HTTP\Response; +use Modules\Media\FileManagers\FileManagerInterface; + +class MediaController extends Controller +{ + public function serve(string ...$key): Response + { + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + return $fileManager->serve(implode('/', $key)); + } +} diff --git a/modules/Media/Entities/BaseMedia.php b/modules/Media/Entities/BaseMedia.php index d293ef6b0ee79eb04722a4db53338cb8fdcf0bc3..905a0c21f6b7f122456cd3c388be30d66d3d6a68 100644 --- a/modules/Media/Entities/BaseMedia.php +++ b/modules/Media/Entities/BaseMedia.php @@ -12,9 +12,6 @@ namespace Modules\Media\Entities; use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; -use Modules\Media\FileManagers\FileManagerInterface; -use Modules\Media\FileManagers\FS; -use Modules\Media\FileManagers\S3; use Modules\Media\Models\MediaModel; /** @@ -58,28 +55,13 @@ class BaseMedia extends Entity 'updated_by' => 'integer', ]; - protected FileManagerInterface $fileManager; - /** * @param array<string, mixed>|null $data - * @param 'fs'|'s3'|null $fileManager */ - public function __construct(array $data = null, string $fileManager = null) + public function __construct(array $data = null) { parent::__construct($data); - if ($fileManager !== null) { - $this->fileManager = match ($fileManager) { - 'fs' => new FS(config('Media')), - 's3' => new S3(config('Media')) - }; - } else { - /** @var FileManagerInterface $fileManagerService */ - $fileManagerService = service('file_manager'); - - $this->fileManager = $fileManagerService; - } - $this->initFileProperties(); } @@ -92,7 +74,7 @@ class BaseMedia extends Entity 'extension' => $extension, ] = pathinfo($this->file_key); - $this->attributes['file_url'] = $this->fileManager->getUrl($this->file_key); + $this->attributes['file_url'] = service('file_manager')->getUrl($this->file_key); $this->attributes['file_name'] = $filename; $this->attributes['file_directory'] = $dirname; $this->attributes['file_extension'] = $extension; @@ -120,14 +102,14 @@ class BaseMedia extends Entity return false; } - $this->attributes['file_key'] = $this->fileManager->save($this->attributes['file'], $this->file_key); + $this->attributes['file_key'] = service('file_manager')->save($this->attributes['file'], $this->file_key); return true; } public function deleteFile(): bool { - return $this->fileManager->delete($this->file_key); + return service('file_manager')->delete($this->file_key); } public function rename(): bool @@ -143,7 +125,7 @@ class BaseMedia extends Entity return false; } - if (! $this->fileManager->rename($this->file_key, $newFileKey)) { + if (! service('file_manager')->rename($this->file_key, $newFileKey)) { $db->transRollback(); return false; } diff --git a/modules/Media/Entities/Image.php b/modules/Media/Entities/Image.php index 9f45bacc7fc0f571349798a25ed9afe7cac9ebd2..123cc203edfb0167311da8736cfae91a0b0ae3ea 100644 --- a/modules/Media/Entities/Image.php +++ b/modules/Media/Entities/Image.php @@ -42,14 +42,12 @@ class Image extends BaseMedia { helper('filesystem'); - $fileKeyWithoutExt = path_without_ext($this->file_key); - foreach ($this->sizes as $name => $size) { $extension = array_key_exists('extension', $size) ? $size['extension'] : $this->file_extension; $mimetype = array_key_exists('mimetype', $size) ? $size['mimetype'] : $this->file_mimetype; - $this->{$name . '_key'} = $fileKeyWithoutExt . '_' . $name . '.' . $extension; - $this->{$name . '_url'} = $this->fileManager->getUrl($this->{$name . '_key'}); + $this->{$name . '_key'} = change_file_path($this->file_key, '_' . $name, $extension); + $this->{$name . '_url'} = service('file_manager')->getUrl($this->{$name . '_key'}); $this->{$name . '_mimetype'} = $mimetype; } @@ -126,7 +124,8 @@ class Image extends BaseMedia // download image temporarily to generate sizes from $tempImagePath = (string) tempnam(WRITEPATH . 'temp', 'img_'); - $imageContent = $this->fileManager->getFileContents($this->file_key); + $imageContent = (string) service('file_manager') + ->getFileContents($this->file_key); file_put_contents($tempImagePath, $imageContent); $this->attributes['file'] = new File($tempImagePath, true); @@ -144,7 +143,7 @@ class Image extends BaseMedia $newImage = new File($tempFilePath, true); - $this->fileManager + service('file_manager') ->save($newImage, $this->{$name . '_key'}); } @@ -159,7 +158,7 @@ class Image extends BaseMedia foreach (array_keys($this->sizes) as $name) { $pathProperty = $name . '_key'; - if (! $this->fileManager->delete($this->{$pathProperty})) { + if (! service('file_manager')->delete($this->{$pathProperty})) { return false; } } diff --git a/modules/Media/Entities/Transcript.php b/modules/Media/Entities/Transcript.php index 4ad7cdcb7a83da1bb7bbcf30dc45ce6a4ea5b595..a969cff1f22c9693f6ac849384dae68b201da881 100644 --- a/modules/Media/Entities/Transcript.php +++ b/modules/Media/Entities/Transcript.php @@ -29,7 +29,7 @@ class Transcript extends BaseMedia helper('media'); $this->json_key = $this->file_metadata['json_key']; - $this->json_url = $this->fileManager + $this->json_url = service('file_manager') ->getUrl($this->json_key); } } @@ -42,12 +42,8 @@ class Transcript extends BaseMedia helper('filesystem'); - $fileKeyWithoutExt = path_without_ext($this->file_key); - - $jsonfileKey = $fileKeyWithoutExt . '.json'; - // set metadata (generated json file path) - $this->json_key = $jsonfileKey; + $this->json_key = change_file_path($this->file_key, '', 'json'); $metadata['json_key'] = $this->json_key; $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); @@ -71,7 +67,7 @@ class Transcript extends BaseMedia } if ($this->json_key) { - return $this->fileManager->delete($this->json_key); + return service('file_manager')->delete($this->json_key); } return true; @@ -96,7 +92,7 @@ class Transcript extends BaseMedia $newTranscriptJson = new File($tempFilePath, true); - $this->fileManager + service('file_manager') ->save($newTranscriptJson, $this->json_key); return true; diff --git a/modules/Media/FileManagers/FS.php b/modules/Media/FileManagers/FS.php index 141f690d60fb619097cddeb2d5ad94ec40c2f8ad..63c1f707321f94fbef6c8b339bd1afd090459487 100644 --- a/modules/Media/FileManagers/FS.php +++ b/modules/Media/FileManagers/FS.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Modules\Media\FileManagers; use CodeIgniter\Files\File; +use CodeIgniter\HTTP\Response; use Exception; use Modules\Media\Config\Media as MediaConfig; @@ -27,7 +28,7 @@ class FS implements FileManagerInterface $path = $path . '.' . $extension; } - $mediaRoot = media_path_absolute(); + $mediaRoot = $this->media_path_absolute(); if (! file_exists(dirname($mediaRoot . '/' . $path))) { mkdir(dirname($mediaRoot . '/' . $path), 0777, true); @@ -51,7 +52,7 @@ class FS implements FileManagerInterface { helper('media'); - return @unlink(media_path_absolute($key)); + return @unlink($this->media_path_absolute($key)); } public function getUrl(string $key): string @@ -70,21 +71,21 @@ class FS implements FileManagerInterface { helper('media'); - return rename(media_path_absolute($oldKey), media_path_absolute($newKey)); + return rename($this->media_path_absolute($oldKey), $this->media_path_absolute($newKey)); } - public function getFileContents(string $key): string + public function getFileContents(string $key): string|false { helper('media'); - return (string) file_get_contents(media_path_absolute($key)); + return file_get_contents($this->media_path_absolute($key)); } public function getFileInput(string $key): string { helper('media'); - return media_path_absolute($key); + return $this->media_path_absolute($key); } public function deletePodcastImageSizes(string $podcastHandle): bool @@ -113,12 +114,12 @@ class FS implements FileManagerInterface if ($pattern === '*') { helper('filesystem'); - return delete_files(media_path_absolute($prefix), true); + return delete_files($this->media_path_absolute($prefix), true); } $prefix = rtrim($prefix, '/') . '/'; - $imagePaths = glob(media_path_absolute($prefix . $pattern)); + $imagePaths = glob($this->media_path_absolute($prefix . $pattern)); if (! $imagePaths) { return true; @@ -135,6 +136,28 @@ class FS implements FileManagerInterface { helper('media'); - return is_really_writable(media_path_absolute()); + return is_really_writable($this->media_path_absolute()); + } + + public function serve(string $key): Response + { + return redirect()->to($this->getUrl($key)); + } + + /** + * Prefixes the absolute storage directory to the media path of a given uri + * + * @param string|string[] $uri URI string or array of URI segments + */ + private function media_path_absolute(string | array $uri = ''): string + { + // convert segment array to string + if (is_array($uri)) { + $uri = implode('/', $uri); + } + + $uri = trim($uri, '/'); + + return config('Media')->storage . '/' . config('Media')->root . '/' . $uri; } } diff --git a/modules/Media/FileManagers/FileManagerInterface.php b/modules/Media/FileManagers/FileManagerInterface.php index 3d1d2ac499d463b229129c2282c4e64fe2469f18..5b2dd07881513fb3a60fade041941325d133b1ae 100644 --- a/modules/Media/FileManagers/FileManagerInterface.php +++ b/modules/Media/FileManagers/FileManagerInterface.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Modules\Media\FileManagers; use CodeIgniter\Files\File; +use CodeIgniter\HTTP\Response; interface FileManagerInterface { @@ -16,7 +17,7 @@ interface FileManagerInterface public function rename(string $oldKey, string $newKey): bool; - public function getFileContents(string $key): string; + public function getFileContents(string $key): string|false; public function getFileInput(string $key): string; @@ -27,4 +28,6 @@ interface FileManagerInterface public function deleteAll(string $prefix, string $pattern = '*'): bool; public function isHealthy(): bool; + + public function serve(string $key): Response; } diff --git a/modules/Media/FileManagers/S3.php b/modules/Media/FileManagers/S3.php index b5856faf7ab2965846636f260412a4d274e698b3..fe5b5e3d47e296566087ee0ef8727762f4a3fce7 100644 --- a/modules/Media/FileManagers/S3.php +++ b/modules/Media/FileManagers/S3.php @@ -6,8 +6,9 @@ namespace Modules\Media\FileManagers; use Aws\Credentials\Credentials; use Aws\S3\S3Client; +use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Files\File; -use CodeIgniter\HTTP\URI; +use CodeIgniter\HTTP\Response; use Exception; use Modules\Media\Config\Media as MediaConfig; @@ -35,6 +36,7 @@ class S3 implements FileManagerInterface 'Bucket' => $this->config->s3['bucket'], 'Key' => $this->prefixKey($key), 'SourceFile' => $file, + 'ContentType' => $file->getMimeType(), ]); } catch (Exception) { return false; @@ -62,16 +64,7 @@ class S3 implements FileManagerInterface public function getUrl(string $key): string { - $url = new URI((string) $this->config->s3['endpoint']); - - if ($this->config->s3['pathStyleEndpoint'] === true) { - $url->setPath($this->config->s3['bucket'] . '/' . $this->prefixKey($key)); - return (string) $url; - } - - $url->setHost($this->config->s3['bucket'] . '.' . $url->getHost()); - $url->setPath($this->prefixKey($key)); - return (string) $url; + return url_to('media-serve', $key); } public function rename(string $oldKey, string $newKey): bool @@ -91,12 +84,16 @@ class S3 implements FileManagerInterface return $this->delete($oldKey); } - public function getFileContents(string $key): string + public function getFileContents(string $key): string|false { - $result = $this->s3->getObject([ - 'Bucket' => $this->config->s3['bucket'], - 'Key' => $this->prefixKey($key), - ]); + try { + $result = $this->s3->getObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $this->prefixKey($key), + ]); + } catch (Exception) { + return false; + } return (string) $result->get('Body'); } @@ -186,6 +183,31 @@ class S3 implements FileManagerInterface return true; } + public function serve(string $key): Response + { + $response = service('response'); + + try { + $result = $this->s3->getObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $this->prefixKey($key), + ]); + } catch (Exception) { + throw new PageNotFoundException(); + } + + // Remove Cache-Control header before redefining it + header_remove('Cache-Control'); + + return $response->setCache([ + 'max-age' => DECADE, + 'last-modified' => $result->get('LastModified'), + 'etag' => $result->get('ETag'), + 'public' => true, + ])->setContentType($result->get('ContentType')) + ->setBody((string) $result->get('Body')->getContents()); + } + private function prefixKey(string $key): string { if ($this->config->s3['keyPrefix'] === '') { diff --git a/modules/Media/Helpers/filesystem_helper.php b/modules/Media/Helpers/filesystem_helper.php index 0cff140c7bdd58c02ca7395d2bf9a4aea5dd9a9b..9a6d00c8a58073e3a3e579279f41f53bc7857fe7 100644 --- a/modules/Media/Helpers/filesystem_helper.php +++ b/modules/Media/Helpers/filesystem_helper.php @@ -8,20 +8,13 @@ declare(strict_types=1); * @link https://castopod.org/ */ - -if (! function_exists('path_without_ext')) { - function path_without_ext(string $path): string +if (! function_exists('add_suffix_to_path')) { + function change_file_path(string $path, string $suffix = '', ?string $newExtension = null): string { - $fileKeyInfo = pathinfo($path); - - if ($fileKeyInfo['dirname'] === '.' && ! str_starts_with($path, '.')) { - return $fileKeyInfo['filename']; - } - - if ($fileKeyInfo['dirname'] === '/') { - return '/' . $fileKeyInfo['filename']; + if ($newExtension === null) { + $newExtension = pathinfo($path, PATHINFO_EXTENSION); } - return implode('/', [$fileKeyInfo['dirname'], $fileKeyInfo['filename']]); + return preg_replace('~\.[^.]+$~', '', $path) . $suffix . '.' . $newExtension; } } diff --git a/modules/Media/Helpers/media_helper.php b/modules/Media/Helpers/media_helper.php index fab6123dba4bd72b5af01443bb93fc84c0b4e237..68eb3aa999938ce901d920f1d4776835bd4924b8 100644 --- a/modules/Media/Helpers/media_helper.php +++ b/modules/Media/Helpers/media_helper.php @@ -63,34 +63,3 @@ if (! function_exists('download_file')) { return new File($tmpfileKey); } } - -if (! function_exists('media_path')) { - /** - * Prefixes the root media path to a given uri - * - * @param string|string[] $uri URI string or array of URI segments - */ - function media_path(string | array $uri = ''): string - { - // convert segment array to string - if (is_array($uri)) { - $uri = implode('/', $uri); - } - - $uri = trim($uri, '/'); - - return config('Media')->root . '/' . $uri; - } -} - -if (! function_exists('media_path_absolute')) { - /** - * Prefixes the absolute storage directory to the media path of a given uri - * - * @param string|string[] $uri URI string or array of URI segments - */ - function media_path_absolute(string | array $uri = ''): string - { - return config('Media')->storage . '/' . media_path($uri); - } -} diff --git a/public/media/castopod-avatar.jpg b/public/castopod-avatar.jpg similarity index 100% rename from public/media/castopod-avatar.jpg rename to public/castopod-avatar.jpg diff --git a/public/media/castopod-avatar_medium.webp b/public/castopod-avatar_medium.webp similarity index 100% rename from public/media/castopod-avatar_medium.webp rename to public/castopod-avatar_medium.webp diff --git a/public/media/castopod-avatar_thumbnail.webp b/public/castopod-avatar_thumbnail.webp similarity index 100% rename from public/media/castopod-avatar_thumbnail.webp rename to public/castopod-avatar_thumbnail.webp diff --git a/public/media/castopod-avatar_tiny.webp b/public/castopod-avatar_tiny.webp similarity index 100% rename from public/media/castopod-avatar_tiny.webp rename to public/castopod-avatar_tiny.webp diff --git a/public/media/castopod-banner-amber.jpg b/public/castopod-banner-amber.jpg similarity index 100% rename from public/media/castopod-banner-amber.jpg rename to public/castopod-banner-amber.jpg diff --git a/public/media/castopod-banner-amber_federation.jpg b/public/castopod-banner-amber_federation.jpg similarity index 100% rename from public/media/castopod-banner-amber_federation.jpg rename to public/castopod-banner-amber_federation.jpg diff --git a/public/media/castopod-banner-amber_medium.webp b/public/castopod-banner-amber_medium.webp similarity index 100% rename from public/media/castopod-banner-amber_medium.webp rename to public/castopod-banner-amber_medium.webp diff --git a/public/media/castopod-banner-amber_small.webp b/public/castopod-banner-amber_small.webp similarity index 100% rename from public/media/castopod-banner-amber_small.webp rename to public/castopod-banner-amber_small.webp diff --git a/public/media/castopod-banner-crimson.jpg b/public/castopod-banner-crimson.jpg similarity index 100% rename from public/media/castopod-banner-crimson.jpg rename to public/castopod-banner-crimson.jpg diff --git a/public/media/castopod-banner-crimson_federation.jpg b/public/castopod-banner-crimson_federation.jpg similarity index 100% rename from public/media/castopod-banner-crimson_federation.jpg rename to public/castopod-banner-crimson_federation.jpg diff --git a/public/media/castopod-banner-crimson_medium.webp b/public/castopod-banner-crimson_medium.webp similarity index 100% rename from public/media/castopod-banner-crimson_medium.webp rename to public/castopod-banner-crimson_medium.webp diff --git a/public/media/castopod-banner-crimson_small.webp b/public/castopod-banner-crimson_small.webp similarity index 100% rename from public/media/castopod-banner-crimson_small.webp rename to public/castopod-banner-crimson_small.webp diff --git a/public/media/castopod-banner-jacaranda.jpg b/public/castopod-banner-jacaranda.jpg similarity index 100% rename from public/media/castopod-banner-jacaranda.jpg rename to public/castopod-banner-jacaranda.jpg diff --git a/public/media/castopod-banner-jacaranda_federation.jpg b/public/castopod-banner-jacaranda_federation.jpg similarity index 100% rename from public/media/castopod-banner-jacaranda_federation.jpg rename to public/castopod-banner-jacaranda_federation.jpg diff --git a/public/media/castopod-banner-jacaranda_medium.webp b/public/castopod-banner-jacaranda_medium.webp similarity index 100% rename from public/media/castopod-banner-jacaranda_medium.webp rename to public/castopod-banner-jacaranda_medium.webp diff --git a/public/media/castopod-banner-jacaranda_small.webp b/public/castopod-banner-jacaranda_small.webp similarity index 100% rename from public/media/castopod-banner-jacaranda_small.webp rename to public/castopod-banner-jacaranda_small.webp diff --git a/public/media/castopod-banner-lake.jpg b/public/castopod-banner-lake.jpg similarity index 100% rename from public/media/castopod-banner-lake.jpg rename to public/castopod-banner-lake.jpg diff --git a/public/media/castopod-banner-lake_federation.jpg b/public/castopod-banner-lake_federation.jpg similarity index 100% rename from public/media/castopod-banner-lake_federation.jpg rename to public/castopod-banner-lake_federation.jpg diff --git a/public/media/castopod-banner-lake_medium.webp b/public/castopod-banner-lake_medium.webp similarity index 100% rename from public/media/castopod-banner-lake_medium.webp rename to public/castopod-banner-lake_medium.webp diff --git a/public/media/castopod-banner-lake_small.webp b/public/castopod-banner-lake_small.webp similarity index 100% rename from public/media/castopod-banner-lake_small.webp rename to public/castopod-banner-lake_small.webp diff --git a/public/media/castopod-banner-onyx.jpg b/public/castopod-banner-onyx.jpg similarity index 100% rename from public/media/castopod-banner-onyx.jpg rename to public/castopod-banner-onyx.jpg diff --git a/public/media/castopod-banner-onyx_federation.jpg b/public/castopod-banner-onyx_federation.jpg similarity index 100% rename from public/media/castopod-banner-onyx_federation.jpg rename to public/castopod-banner-onyx_federation.jpg diff --git a/public/media/castopod-banner-onyx_medium.webp b/public/castopod-banner-onyx_medium.webp similarity index 100% rename from public/media/castopod-banner-onyx_medium.webp rename to public/castopod-banner-onyx_medium.webp diff --git a/public/media/castopod-banner-onyx_small.webp b/public/castopod-banner-onyx_small.webp similarity index 100% rename from public/media/castopod-banner-onyx_small.webp rename to public/castopod-banner-onyx_small.webp diff --git a/public/media/castopod-banner-pine.jpg b/public/castopod-banner-pine.jpg similarity index 100% rename from public/media/castopod-banner-pine.jpg rename to public/castopod-banner-pine.jpg diff --git a/public/media/castopod-banner-pine_federation.jpg b/public/castopod-banner-pine_federation.jpg similarity index 100% rename from public/media/castopod-banner-pine_federation.jpg rename to public/castopod-banner-pine_federation.jpg diff --git a/public/media/castopod-banner-pine_medium.webp b/public/castopod-banner-pine_medium.webp similarity index 100% rename from public/media/castopod-banner-pine_medium.webp rename to public/castopod-banner-pine_medium.webp diff --git a/public/media/castopod-banner-pine_small.webp b/public/castopod-banner-pine_small.webp similarity index 100% rename from public/media/castopod-banner-pine_small.webp rename to public/castopod-banner-pine_small.webp diff --git a/public/media/persons/index.html b/public/media/persons/index.html index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eebf8ecb2b2bdf794e1a23e04bc129e3aaacaeb4 100644 --- a/public/media/persons/index.html +++ b/public/media/persons/index.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>403 Forbidden</title> + </head> + <body> + <p>Directory access is forbidden.</p> + </body> +</html> diff --git a/public/media/podcasts/index.html b/public/media/podcasts/index.html index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eebf8ecb2b2bdf794e1a23e04bc129e3aaacaeb4 100644 --- a/public/media/podcasts/index.html +++ b/public/media/podcasts/index.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>403 Forbidden</title> + </head> + <body> + <p>Directory access is forbidden.</p> + </body> +</html> diff --git a/public/media/site/index.html b/public/media/site/index.html index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eebf8ecb2b2bdf794e1a23e04bc129e3aaacaeb4 100644 --- a/public/media/site/index.html +++ b/public/media/site/index.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>403 Forbidden</title> + </head> + <body> + <p>Directory access is forbidden.</p> + </body> +</html> diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index d23a48a463dcb6daa7f9bfbcfad0ed2d04f00e00..2cca6f23fb86c2a0f3554c7244220bce734f09b6 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -15,9 +15,8 @@ $isEpisodeArea = isset($podcast) && isset($episode); <title><?= $this->renderSection('title') ?> | Castopod Admin</title> <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' /> diff --git a/themes/cp_admin/episode/persons.php b/themes/cp_admin/episode/persons.php index 05e0a619cabb71f3cafde59a8ffbc5e0dc266129..d5deb5ba1183756e4c4dc1be910a2532155416b5 100644 --- a/themes/cp_admin/episode/persons.php +++ b/themes/cp_admin/episode/persons.php @@ -58,7 +58,7 @@ return '<div class="flex">' . '<a href="' . route_to('person-view', $person->id) . - '"><img src="' . $person->avatar->thumbnail_url . '" alt="' . esc($person->full_name) . '" class="object-cover w-16 rounded-full aspect-square" loading="lazy" /></a>' . + '"><img src="' . get_avatar_url($person, 'thumbnail') . '" alt="' . esc($person->full_name) . '" class="object-cover w-16 rounded-full aspect-square" loading="lazy" /></a>' . '<div class="flex flex-col ml-3">' . esc($person->full_name) . implode( diff --git a/themes/cp_admin/person/_card.php b/themes/cp_admin/person/_card.php index 20408bb517906addfdf4fc9455ba94de1a6a9b3d..afd7e23f79c296f4d2517602eb15da73078c1fa1 100644 --- a/themes/cp_admin/person/_card.php +++ b/themes/cp_admin/person/_card.php @@ -2,7 +2,7 @@ <a href="<?= route_to('person-view', $person->id) ?>" class="flex flex-col justify-end w-full h-full text-white group"> <div class="absolute bottom-0 left-0 z-10 w-full h-full backdrop-gradient mix-blend-multiply"></div> <div class="w-full h-full overflow-hidden bg-header"> - <img alt="<?= esc($person->full_name) ?>" src="<?= $person->avatar->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform aspect-square group-focus:scale-105 group-hover:scale-105" loading="lazy" /> + <img alt="<?= esc($person->full_name) ?>" src="<?= get_avatar_url($person, 'medium') ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform aspect-square group-focus:scale-105 group-hover:scale-105" loading="lazy" /> </div> <div class="absolute z-20"> <h2 class="px-4 py-2 font-semibold leading-tight"><?= esc($person->full_name) ?></h2> diff --git a/themes/cp_admin/person/view.php b/themes/cp_admin/person/view.php index 4956b3cca3c24260d8a970acb7380e604278f7eb..e07efd7719b6b1ace2da418cd8e6e523e12ea31c 100644 --- a/themes/cp_admin/person/view.php +++ b/themes/cp_admin/person/view.php @@ -17,7 +17,7 @@ <div class="flex flex-wrap gap-2"> <img - src="<?= $person->avatar->medium_url ?>" + src="<?= get_avatar_url($person, 'medium') ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-full max-w-xs rounded aspect-square" loading="lazy" diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index 794e446c507c0e6c6c8c369a1ca2d47b878704ea..3b05c882366d2377e898fe01ff4ed4d329da9ee6 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -25,7 +25,7 @@ <?php if ($podcast->banner_id !== null): ?> <a href="<?= route_to('podcast-banner-delete', $podcast->id) ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast focus:ring-accent top-2 right-2" title="<?= lang('Podcast.form.banner_delete') ?>" data-tooltip="bottom"><?= icon('delete-bin') ?></a> <?php endif; ?> - <img src="<?= $podcast->banner->small_url ?>" alt="" class="w-full aspect-[3/1] bg-header" loading="lazy" /> + <img src="<?= get_podcast_banner_url($podcast, 'small') ?>" alt="" class="w-full aspect-[3/1] bg-header" loading="lazy" /> <div class="flex px-4 py-2"> <img src="<?= $podcast->cover->thumbnail_url ?>" alt="<?= esc($podcast->title) ?>" class="w-16 h-16 mr-4 -mt-8 rounded-full ring-2 ring-background-elevated aspect-square" loading="lazy" /> diff --git a/themes/cp_admin/podcast/persons.php b/themes/cp_admin/podcast/persons.php index 7b127d630bed9e3896765cab1c62eb4bad535231..c19f061efd07522da86368ff8a328454db112fc5 100644 --- a/themes/cp_admin/podcast/persons.php +++ b/themes/cp_admin/podcast/persons.php @@ -55,7 +55,7 @@ return '<div class="flex">' . '<a href="' . route_to('person-view', $person->id) . - '"><img src="' . $person->avatar->thumbnail_url . '" alt="' . esc($person->full_name) . '" class="object-cover w-16 h-16 rounded-full aspect-square" loading="lazy" /></a>' . + '"><img src="' . get_avatar_url($person, 'thumbnail') . '" alt="' . esc($person->full_name) . '" class="object-cover w-16 h-16 rounded-full aspect-square" loading="lazy" /></a>' . '<div class="flex flex-col ml-3">' . esc($person->full_name) . implode( diff --git a/themes/cp_admin/settings/general.php b/themes/cp_admin/settings/general.php index dd2ce565f577f9053b4fc5518927b683db58a914..182d59a74e2f7cf953320d9c05efa64f0e34b021 100644 --- a/themes/cp_admin/settings/general.php +++ b/themes/cp_admin/settings/general.php @@ -46,7 +46,7 @@ <?php if (config('App')->siteIcon['ico'] !== service('settings')->get('App.siteIcon')['ico']): ?> <div class="relative ml-2"> <a href="<?= route_to('settings-instance-delete-icon') ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast -top-3 -right-3 focus:ring-accent" title="<?= lang('Settings.instance.site_icon_delete') ?>" data-tooltip="top"><?= icon('delete-bin') ?></a> - <img src="<?= service('settings')->get('App.siteIcon')['64'] ?>" alt="<?= esc(service('settings')->get('App.siteName')) ?> Favicon" class="w-10 h-10 aspect-square" loading="lazy" /> + <img src="<?= get_site_icon_url('64') ?>" alt="<?= esc(service('settings')->get('App.siteName')) ?> Favicon" class="w-10 h-10 aspect-square" loading="lazy" /> </div> <?php endif; ?> </div> @@ -62,7 +62,7 @@ <Forms.Section title="<?= lang('Settings.images.title') ?>" - subtitle="<?= lang('Settings.images.subtitle') ?>" > + subtitle="<?= lang('Settings.images.subtitle') ?>"> <Button variant="primary" type="submit" iconLeft="refresh"><?= lang('Settings.images.regenerate') ?></Button> diff --git a/themes/cp_app/_persons_modal.php b/themes/cp_app/_persons_modal.php index 237fefeeee0d6bcedbaa46054a1adac3f0024741..847f46d2b1b47d12fd2fc688afff925eebb18970 100644 --- a/themes/cp_app/_persons_modal.php +++ b/themes/cp_app/_persons_modal.php @@ -17,7 +17,7 @@ <div class="flex flex-col items-start p-4 gap-y-4"> <?php foreach ($persons as $person): ?> <div class="flex gap-x-2"> - <img src="<?= $person->avatar->thumbnail_url ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-10 rounded-full bg-header aspect-square" loading="lazy" /> + <img src="<?= get_avatar_url($person, 'thumbnail') ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-10 rounded-full bg-header aspect-square" loading="lazy" /> <div class="flex flex-col"> <h4 class="text-sm font-semibold"> <?php if ($person->information_url): ?> diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php index afc1c05de3a7ce85666e6c58f7746df9576a8a8e..f18322d6f8f411356c2ed6bb4a853bd0b269b1bf 100644 --- a/themes/cp_app/embed.php +++ b/themes/cp_app/embed.php @@ -9,9 +9,8 @@ <meta name="description" content="<?= esc( $episode->description, ) ?>" /> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel='stylesheet' type='text/css' href='<?= route_to('themes-colors-css') ?>' /> <?= service('vite') ->asset('styles/index.css', 'css') ?> diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php index 8bf65fd319fe412811e2098eb692a1a09a43b79c..730ec143dabf6882eab7e0463c8a0bf80d0aa9c6 100644 --- a/themes/cp_app/episode/_layout.php +++ b/themes/cp_app/episode/_layout.php @@ -7,9 +7,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> @@ -78,7 +77,7 @@ </div> </nav> <header class="relative z-50 flex flex-col col-start-2 px-8 pt-8 pb-4 overflow-hidden bg-accent-base/75 gap-y-4"> - <div class="absolute top-0 left-0 w-full h-full bg-center bg-no-repeat bg-cover blur-lg mix-blend-overlay filter grayscale" style="background-image: url('<?= $episode->podcast->banner->small_url ?>');"></div> + <div class="absolute top-0 left-0 w-full h-full bg-center bg-no-repeat bg-cover blur-lg mix-blend-overlay filter grayscale" style="background-image: url('<?= get_podcast_banner_url($episode->podcast, 'small') ?>');"></div> <div class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-background-header to-transparent"></div> <div class="z-10 flex flex-col items-start gap-y-2 gap-x-4 sm:flex-row"> <div class="relative flex-shrink-0"> @@ -97,7 +96,7 @@ <span class="inline-flex flex-row-reverse"> <?php $i = 0; ?> <?php foreach ($episode->persons as $person): ?> - <img src="<?= $person->avatar->thumbnail_url ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-8 h-8 -ml-4 border-2 rounded-full aspect-square border-background-header last:ml-0" loading="lazy" /> + <img src="<?= get_avatar_url($person, 'thumbnail') ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-8 h-8 -ml-4 border-2 rounded-full aspect-square border-background-header last:ml-0" loading="lazy" /> <?php $i++; if ($i === 3) { break; diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php index aa6eedda52cd6ef09724e76744424dbfb664cc60..73dc8cff4a7b088955bbe89c8959f9ab96995bb5 100644 --- a/themes/cp_app/home.php +++ b/themes/cp_app/home.php @@ -6,9 +6,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> diff --git a/themes/cp_app/pages/_layout.php b/themes/cp_app/pages/_layout.php index 466d9a94073af708d73a7a7e2feb479cbf0aa6f4..a87db84ffe217b985fefc5771901aa7b08b82c41 100644 --- a/themes/cp_app/pages/_layout.php +++ b/themes/cp_app/pages/_layout.php @@ -6,9 +6,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> diff --git a/themes/cp_app/pages/map.php b/themes/cp_app/pages/map.php index a0629f7bd85c6345a58f4583bb44b947361dc3ea..a7b5bbddd09a7bafc86412e960259c2c7f07bbb8 100644 --- a/themes/cp_app/pages/map.php +++ b/themes/cp_app/pages/map.php @@ -11,9 +11,8 @@ ->get('App.siteName')), ]) ?>"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php index 372002ab8e76b1ea6630253d883ddd456b91ebb6..c420f5cf2c0c8d51ce047404e4c6225cef4e372d 100644 --- a/themes/cp_app/podcast/_layout.php +++ b/themes/cp_app/podcast/_layout.php @@ -7,9 +7,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> @@ -43,7 +42,7 @@ </div> <?php endif; ?> - <header class="min-h-[200px] relative z-50 flex flex-col-reverse justify-between w-full gap-x-2 col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-header aspect-[3/1]" style="background-image: url('<?= $podcast->banner->medium_url ?>');"> + <header class="min-h-[200px] relative z-50 flex flex-col-reverse justify-between w-full gap-x-2 col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-header aspect-[3/1]" style="background-image: url('<?= get_podcast_banner_url($podcast, 'medium') ?>');"> <div class="absolute bottom-0 left-0 w-full h-full backdrop-gradient mix-blend-multiply"></div> <div class="z-10 flex items-center pl-4 -mb-6 md:pl-8 md:-mb-8 gap-x-4"> <img src="<?= $podcast->cover->thumbnail_url ?>" alt="<?= esc($podcast->title) ?>" class="h-24 rounded-full sm:h-28 md:h-36 ring-3 ring-background-elevated aspect-square" loading="lazy" /> diff --git a/themes/cp_app/podcast/about.php b/themes/cp_app/podcast/about.php index 7909f3ba7e0ac1d153c19ced8604498a8d78d276..b42507fb7dedc9cf688c6cf78ef9f2679e8f691f 100644 --- a/themes/cp_app/podcast/about.php +++ b/themes/cp_app/podcast/about.php @@ -21,7 +21,7 @@ <span class="inline-flex flex-row-reverse"> <?php $i = 0; ?> <?php foreach ($podcast->persons as $person): ?> - <img src="<?= $person->avatar->thumbnail_url ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-8 -ml-4 border-2 rounded-full aspect-square bg-header border-background-base last:ml-0" loading="lazy" /> + <img src="<?= get_avatar_url($person, 'thumbnail') ?>" alt="<?= esc($person->full_name) ?>" class="object-cover w-8 -ml-4 border-2 rounded-full aspect-square bg-header border-background-base last:ml-0" loading="lazy" /> <?php $i++; if ($i === 3) { break; diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php index 142f20f2968df73c138f7c1b2f1f4e87a1f32f1c..7438a1e2e2804e4a6ea23a0471e38227c7d5741a 100644 --- a/themes/cp_app/podcast/follow.php +++ b/themes/cp_app/podcast/follow.php @@ -7,9 +7,8 @@ <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($actor->podcast->handle)) ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> @@ -38,7 +37,7 @@ 'Fediverse.follow.subtitle', ) ?></h1> <div class="flex flex-col w-full max-w-xs -mt-24 overflow-hidden shadow bg-elevated rounded-xl"> - <img src="<?= $actor->podcast->banner->small_url ?>" alt="" class="w-full aspect-[3/1] bg-header" loading="lazy" /> + <img src="<?= get_podcast_banner_url($actor->podcast, 'small') ?>" alt="" class="w-full aspect-[3/1] bg-header" loading="lazy" /> <div class="flex px-4 py-2"> <img src="<?= $actor->avatar_image_url ?>" alt="<?= esc($actor->display_name) ?>" class="w-16 h-16 mr-4 -mt-8 rounded-full ring-2 ring-background-elevated aspect-square" loading="lazy" /> diff --git a/themes/cp_app/podcast/unlock.php b/themes/cp_app/podcast/unlock.php index aaa3d0ec61315b7cd13d967ad27e2b3600093d0a..9a5ec691aacd8292a49656ed60dcd929482b866a 100644 --- a/themes/cp_app/podcast/unlock.php +++ b/themes/cp_app/podcast/unlock.php @@ -7,9 +7,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($podcast->handle)) ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script> @@ -70,7 +69,7 @@ </form> </div> - <header class="relative flex flex-col-reverse justify-between w-full col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-header aspect-[3/1]" style="background-image: url('<?= $podcast->banner->medium_url ?>');"> + <header class="relative flex flex-col-reverse justify-between w-full col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-header aspect-[3/1]" style="background-image: url('<?= get_podcast_banner_url($podcast, 'medium') ?>');"> <div class="absolute bottom-0 left-0 w-full h-full backdrop-gradient mix-blend-multiply"></div> <div class="flex items-center pl-4 -mb-6 md:pl-8 md:-mb-8 gap-x-4"> <img src="<?= $podcast->cover->thumbnail_url ?>" alt="<?= esc($podcast->title) ?>" class="z-[45] h-24 rounded-full sm:h-28 md:h-36 ring-3 ring-background-elevated aspect-square" loading="lazy" /> diff --git a/themes/cp_app/post/remote_action.php b/themes/cp_app/post/remote_action.php index bcc84db61e352babd59ffa6e1af2808276db19fc..ec8665e3ed5eba94a4b8d25e1e3dc61ea08606aa 100644 --- a/themes/cp_app/post/remote_action.php +++ b/themes/cp_app/post/remote_action.php @@ -5,9 +5,8 @@ <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> + <link rel="icon" type="image/x-icon" href="<?= get_site_icon_url('ico') ?>" /> + <link rel="apple-touch-icon" href="<?= get_site_icon_url('180') ?>"> <link rel="manifest" href="<?= route_to('podcast-webmanifest', esc($post->actor->podcast->handle)) ?>"> <meta name="theme-color" content="<?= \App\Controllers\WebmanifestController::THEME_COLORS[service('settings')->get('App.theme')]['theme'] ?>"> <script>