From d93fc98469ffe93913b65e539dec396891708c70 Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Thu, 16 Mar 2023 13:00:05 +0000 Subject: [PATCH] feat(media): add s3 to manage media files Users may choose between filesystem (FS) or S3 to store and manage their media files --- .gitignore | 1 + app/Config/App.php | 20 - app/Config/Autoload.php | 1 + app/Config/Mimes.php | 2 +- .../Seeds/FakeSinglePodcastApiSeeder.php | 8 +- app/Entities/Clip/BaseClip.php | 18 +- app/Entities/Clip/VideoClip.php | 19 +- app/Entities/Episode.php | 31 +- app/Entities/Media/Image.php | 106 -- app/Entities/Media/Transcript.php | 78 -- app/Entities/Person.php | 13 +- app/Entities/Podcast.php | 22 +- app/Helpers/id3_helper.php | 8 +- app/Libraries/MediaClipper/VideoClipper.php | 35 +- .../ViewComponents/ComponentRenderer.php | 20 +- app/Models/PodcastModel.php | 8 +- app/Views/errors/cli/error_exception.php | 4 +- composer.json | 15 +- composer.lock | 1010 ++++++++++++++--- docker-compose.yml | 31 +- docs/src/contributing/setup-development.md | 15 +- .../Admin/Controllers/DashboardController.php | 2 +- .../Admin/Controllers/EpisodeController.php | 4 +- .../Admin/Controllers/PodcastController.php | 2 +- .../Controllers/PodcastImportController.php | 1 + .../Admin/Controllers/SchedulerController.php | 3 +- .../Admin/Controllers/SettingsController.php | 147 +-- .../Admin/Controllers/SoundbiteController.php | 2 +- .../Controllers/VideoClipsController.php | 2 +- modules/Admin/Language/ar/Episode.php | 2 +- modules/Admin/Language/br/Episode.php | 2 +- modules/Admin/Language/ca/Episode.php | 2 +- modules/Admin/Language/de/Episode.php | 2 +- modules/Admin/Language/el/Episode.php | 2 +- modules/Admin/Language/en/Episode.php | 2 +- modules/Admin/Language/es/Episode.php | 2 +- modules/Admin/Language/fa/Episode.php | 2 +- modules/Admin/Language/fr/Episode.php | 2 +- modules/Admin/Language/gd/Episode.php | 2 +- modules/Admin/Language/gl/Episode.php | 2 +- modules/Admin/Language/id/Episode.php | 2 +- modules/Admin/Language/it/Episode.php | 2 +- modules/Admin/Language/ko/Episode.php | 2 +- modules/Admin/Language/nl/Episode.php | 2 +- modules/Admin/Language/nn-NO/Episode.php | 2 +- modules/Admin/Language/oc/Episode.php | 2 +- modules/Admin/Language/pl/Episode.php | 2 +- modules/Admin/Language/pt-BR/Episode.php | 2 +- modules/Admin/Language/pt/Episode.php | 2 +- modules/Admin/Language/ro/Episode.php | 2 +- modules/Admin/Language/ru/Episode.php | 2 +- modules/Admin/Language/sk/Episode.php | 2 +- modules/Admin/Language/sr_Latn/Episode.php | 2 +- modules/Admin/Language/sv/Episode.php | 2 +- modules/Admin/Language/zh-Hans/Episode.php | 2 +- modules/Analytics/Config/Analytics.php | 4 +- .../Models/AnalyticsPodcastModel.php | 2 +- modules/Auth/Commands/RolesDoc.php | 4 +- .../Fediverse/Helpers/fediverse_helper.php | 4 +- modules/Media/Config/Media.php | 64 ++ modules/Media/Config/Services.php | 42 + .../2021-05-29-120000_add_media.php | 6 +- ...22-30-12-180000_rename_media_file_path.php | 40 + .../Media/Entities}/Audio.php | 4 +- .../Media/Entities}/BaseMedia.php | 73 +- .../Media/Entities}/Chapters.php | 2 +- .../Media/Entities}/Document.php | 2 +- modules/Media/Entities/Image.php | 169 +++ modules/Media/Entities/Transcript.php | 104 ++ .../Media/Entities}/Video.php | 2 +- modules/Media/FileManagers/FS.php | 135 +++ .../FileManagers/FileManagerInterface.php | 26 + modules/Media/FileManagers/S3.php | 174 +++ modules/Media/Helpers/filesystem_helper.php | 27 + .../Media}/Helpers/media_helper.php | 63 +- {app => modules/Media}/Models/MediaModel.php | 65 +- .../Media}/TranscriptParser.php | 2 +- package.json | 26 +- phpstan.neon | 3 +- pnpm-lock.yaml | 413 ++++--- public/media/podcasts/index.html | 0 public/media/site/index.html | 0 rector.php | 5 + themes/cp_admin/episode/video_clips_new.php | 2 +- themes/cp_admin/settings/general.php | 1 - 85 files changed, 2168 insertions(+), 976 deletions(-) delete mode 100644 app/Entities/Media/Image.php delete mode 100644 app/Entities/Media/Transcript.php create mode 100644 modules/Media/Config/Media.php create mode 100644 modules/Media/Config/Services.php rename {app => modules/Media}/Database/Migrations/2021-05-29-120000_add_media.php (95%) create mode 100644 modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php rename {app/Entities/Media => modules/Media/Entities}/Audio.php (93%) rename {app/Entities/Media => modules/Media/Entities}/BaseMedia.php (60%) rename {app/Entities/Media => modules/Media/Entities}/Chapters.php (88%) rename {app/Entities/Media => modules/Media/Entities}/Document.php (88%) create mode 100644 modules/Media/Entities/Image.php create mode 100644 modules/Media/Entities/Transcript.php rename {app/Entities/Media => modules/Media/Entities}/Video.php (87%) create mode 100644 modules/Media/FileManagers/FS.php create mode 100644 modules/Media/FileManagers/FileManagerInterface.php create mode 100644 modules/Media/FileManagers/S3.php create mode 100644 modules/Media/Helpers/filesystem_helper.php rename {app => modules/Media}/Helpers/media_helper.php (55%) rename {app => modules/Media}/Models/MediaModel.php (77%) rename {app/Libraries => modules/Media}/TranscriptParser.php (99%) create mode 100644 public/media/podcasts/index.html create mode 100644 public/media/site/index.html diff --git a/.gitignore b/.gitignore index 370e1c2c..45fa5204 100644 --- a/.gitignore +++ b/.gitignore @@ -177,6 +177,7 @@ modules/Admin/Language/*/PersonsTaxonomy.php mariadb phpmyadmin sessions +data # Castopod bundle & packages castopod/ diff --git a/app/Config/App.php b/app/Config/App.php index d574e3b6..50dafa80 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -26,18 +26,6 @@ class App extends BaseConfig */ public string $baseURL = 'http://localhost:8080/'; - /** - * -------------------------------------------------------------------------- - * Media Base URL - * -------------------------------------------------------------------------- - * - * URL to your media root. Typically this will be your base URL, - * WITH a trailing slash: - * - * http://cdn.example.com/ - */ - public string $mediaBaseURL = 'http://localhost:8080/'; - /** * -------------------------------------------------------------------------- * Index File @@ -420,14 +408,6 @@ class App extends BaseConfig */ public bool $CSPEnabled = false; - /** - * -------------------------------------------------------------------------- - * Media root folder - * -------------------------------------------------------------------------- - * Defines the root folder for media files storage - */ - public string $mediaRoot = 'media'; - /** * -------------------------------------------------------------------------- * Instance / Site Config diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 1b4bab55..dd769704 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -50,6 +50,7 @@ class Autoload extends AutoloadConfig 'Modules\Install' => ROOTPATH . 'modules/Install/', 'Modules\Update' => ROOTPATH . 'modules/Update/', 'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/', + 'Modules\Media' => ROOTPATH . 'modules/Media/', 'Modules\WebSub' => ROOTPATH . 'modules/WebSub/', 'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1', 'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/', diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index d2374aa7..017792fb 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -211,7 +211,7 @@ class Mimes 'word' => ['application/msword', 'application/octet-stream'], 'xl' => 'application/excel', 'eml' => 'message/rfc822', - 'json' => ['application/json', 'text/json'], + 'json' => ['application/json', 'text/json', 'text/plain'], 'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'], 'p10' => ['application/x-pkcs10', 'application/pkcs10'], 'p12' => 'application/x-pkcs12', diff --git a/app/Database/Seeds/FakeSinglePodcastApiSeeder.php b/app/Database/Seeds/FakeSinglePodcastApiSeeder.php index c3cebb79..0d28ecc2 100644 --- a/app/Database/Seeds/FakeSinglePodcastApiSeeder.php +++ b/app/Database/Seeds/FakeSinglePodcastApiSeeder.php @@ -9,13 +9,13 @@ use CodeIgniter\Database\Seeder; class FakeSinglePodcastApiSeeder extends Seeder { /** - * @return array{id: int, file_path: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} + * @return array{id: int, file_key: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} */ public static function cover(): array { return [ 'id' => 1, - 'file_path' => 'podcasts/Handle/cover.jpg', + 'file_key' => 'podcasts/Handle/cover.jpg', 'file_size' => 400000, 'file_mimetype' => 'image/jpeg', 'file_metadata' => '{"FILE":{"FileName":"cover.jpg","FileDateTime":1654861723,"FileSize":468541,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":"COMMENT"},"COMPUTED":{"html":"width=\"1400\" height=\"1400\"","Height":1400,"Width":1400,"IsColor":1},"COMMENT":["CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90\n"],"sizes":{"tiny":{"width":40,"height":40,"mimetype":"image\/webp","extension":"webp"},"thumbnail":{"width":150,"height":150,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":320,"height":320,"mimetype":"image\/webp","extension":"webp"},"large":{"width":1024,"height":1024,"mimetype":"image\/webp","extension":"webp"},"feed":{"width":1400,"height":1400},"id3":{"width":500,"height":500},"og":{"width":1200,"height":1200},"federation":{"width":400,"height":400},"webmanifest192":{"width":192,"height":192,"mimetype":"image\/png","extension":"png"},"webmanifest512":{"width":512,"height":512,"mimetype":"image\/png","extension":"png"}}}', @@ -30,13 +30,13 @@ class FakeSinglePodcastApiSeeder extends Seeder } /** - * @return array{id: int, file_path: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} + * @return array{id: int, file_key: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string} */ public static function banner(): array { return [ 'id' => 2, - 'file_path' => 'podcasts/Handle/banner.jpg', + 'file_key' => 'podcasts/Handle/banner.jpg', 'file_size' => 400000, 'file_mimetype' => 'image/jpeg', 'file_metadata' => '{"FILE":{"FileName":"banner.jpg","FileDateTime":1654861724,"FileSize":98209,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":""},"COMPUTED":{"html":"width=\"1500\" height=\"500\"","Height":500,"Width":1500,"IsColor":1},"sizes":{"small":{"width":320,"height":128,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":960,"height":320,"mimetype":"image\/webp","extension":"webp"},"federation":{"width":1500,"height":500}}}', diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php index 51ccc0a5..5cb7eda5 100644 --- a/app/Entities/Clip/BaseClip.php +++ b/app/Entities/Clip/BaseClip.php @@ -11,17 +11,17 @@ declare(strict_types=1); namespace App\Entities\Clip; use App\Entities\Episode; -use App\Entities\Media\Audio; -use App\Entities\Media\Video; use App\Entities\Podcast; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use CodeIgniter\I18n\Time; use CodeIgniter\Shield\Entities\User; use Modules\Auth\Models\UserModel; +use Modules\Media\Entities\Audio; +use Modules\Media\Entities\Video; +use Modules\Media\Models\MediaModel; /** * @property int $id @@ -122,14 +122,8 @@ class BaseClip extends Entity return (new UserModel())->find($this->created_by); } - public function setMedia(string $filePath = null): static + public function setMedia(File $file, string $fileKey): static { - if ($filePath === null) { - return $this; - } - - $file = new File($filePath); - if ($this->media_id !== null) { $this->getMedia() ->setFile($file); @@ -138,9 +132,9 @@ class BaseClip extends Entity (new MediaModel('audio'))->updateMedia($this->getMedia()); } else { $media = new Audio([ - 'file_path' => $filePath, + 'file_key' => $fileKey, 'language_code' => $this->getPodcast() - ->language_code, +->language_code, 'uploaded_by' => $this->attributes['created_by'], 'updated_by' => $this->attributes['created_by'], ]); diff --git a/app/Entities/Clip/VideoClip.php b/app/Entities/Clip/VideoClip.php index 960296f9..86af79b2 100644 --- a/app/Entities/Clip/VideoClip.php +++ b/app/Entities/Clip/VideoClip.php @@ -10,9 +10,9 @@ declare(strict_types=1); namespace App\Entities\Clip; -use App\Entities\Media\Video; -use App\Models\MediaModel; use CodeIgniter\Files\File; +use Modules\Media\Entities\Video; +use Modules\Media\Models\MediaModel; /** * @property array $theme @@ -63,30 +63,23 @@ class VideoClip extends BaseClip return $this; } - public function setMedia(string $filePath = null): static + public function setMedia(File $file, string $fileKey): static { - if ($filePath === null) { - return $this; - } - if ($this->attributes['media_id'] !== null) { // media is already set, do nothing return $this; } - helper('media'); - $file = new File(media_path($filePath)); - $video = new Video([ - 'file_path' => $filePath, + 'file_key' => $fileKey, 'language_code' => $this->getPodcast() - ->language_code, +->language_code, 'uploaded_by' => $this->attributes['created_by'], 'updated_by' => $this->attributes['created_by'], ]); $video->setFile($file); - $this->attributes['media_id'] = (new MediaModel())->saveMedia($video); + $this->attributes['media_id'] = (new MediaModel('video'))->saveMedia($video); return $this; } diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 4850b801..55934260 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -11,14 +11,9 @@ declare(strict_types=1); namespace App\Entities; use App\Entities\Clip\Soundbite; -use App\Entities\Media\Audio; -use App\Entities\Media\Chapters; -use App\Entities\Media\Image; -use App\Entities\Media\Transcript; use App\Libraries\SimpleRSSElement; use App\Models\ClipModel; use App\Models\EpisodeCommentModel; -use App\Models\MediaModel; use App\Models\PersonModel; use App\Models\PodcastModel; use App\Models\PostModel; @@ -32,6 +27,11 @@ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension; use League\CommonMark\Extension\SmartPunct\SmartPunctExtension; use League\CommonMark\MarkdownConverter; +use Modules\Media\Entities\Audio; +use Modules\Media\Entities\Chapters; +use Modules\Media\Entities\Image; +use Modules\Media\Entities\Transcript; +use Modules\Media\Models\MediaModel; use RuntimeException; /** @@ -191,10 +191,9 @@ class Episode extends Entity (new MediaModel('image'))->updateMedia($this->getCover()); } else { $cover = new Image([ - 'file_name' => $this->attributes['slug'], - 'file_directory' => 'podcasts/' . $this->getPodcast()->handle, + 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(), 'sizes' => config('Images') - ->podcastCoverSizes, +->podcastCoverSizes, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); @@ -238,8 +237,10 @@ class Episode extends Entity (new MediaModel('audio'))->updateMedia($this->getAudio()); } else { $audio = new Audio([ - 'file_name' => pathinfo($file->getRandomName(), PATHINFO_FILENAME), - 'file_directory' => 'podcasts/' . $this->getPodcast()->handle, + 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . pathinfo( + $file->getRandomName(), + PATHINFO_FILENAME + ) . '.' . $file->getExtension(), 'language_code' => $this->getPodcast() ->language_code, 'uploaded_by' => user_id(), @@ -276,10 +277,9 @@ class Episode extends Entity (new MediaModel('transcript'))->updateMedia($this->getTranscript()); } else { $transcript = new Transcript([ - 'file_name' => $this->attributes['slug'] . '-transcript', - 'file_directory' => 'podcasts/' . $this->getPodcast()->handle, + 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(), 'language_code' => $this->getPodcast() - ->language_code, +->language_code, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); @@ -314,10 +314,9 @@ class Episode extends Entity (new MediaModel('chapters'))->updateMedia($this->getChapters()); } else { $chapters = new Chapters([ - 'file_name' => $this->attributes['slug'] . '-chapters', - 'file_directory' => 'podcasts/' . $this->getPodcast()->handle, + 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(), 'language_code' => $this->getPodcast() - ->language_code, +->language_code, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); diff --git a/app/Entities/Media/Image.php b/app/Entities/Media/Image.php deleted file mode 100644 index c1524806..00000000 --- a/app/Entities/Media/Image.php +++ /dev/null @@ -1,106 +0,0 @@ -file_path && $this->file_metadata) { - $this->sizes = $this->file_metadata['sizes']; - $this->initSizeProperties(); - } - } - - public function initSizeProperties(): bool - { - helper('media'); - - 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 . '_path'} = $this->file_directory . '/' . $this->file_name . '_' . $name . '.' . $extension; - $this->{$name . '_url'} = media_base_url($this->{$name . '_path'}); - $this->{$name . '_mimetype'} = $mimetype; - } - - return true; - } - - public function setFile(File $file): self - { - parent::setFile($file); - - if ($this->file_mimetype === 'image/jpeg' && $metadata = @exif_read_data( - media_path($this->file_path), - null, - true - )) { - $metadata['sizes'] = $this->sizes; - $this->attributes['file_size'] = $metadata['FILE']['FileSize']; - } else { - $metadata = [ - 'sizes' => $this->sizes, - ]; - } - - $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); - - $this->initFileProperties(); - $this->saveSizes(); - - return $this; - } - - public function deleteFile(): bool - { - if (parent::deleteFile()) { - return $this->deleteSizes(); - } - - return false; - } - - public function saveSizes(): void - { - // save derived sizes - $imageService = service('image'); - foreach ($this->sizes as $name => $size) { - $pathProperty = $name . '_path'; - $imageService - ->withFile(media_path($this->file_path)) - ->resize($size['width'], $size['height']); - $imageService->save(media_path($this->{$pathProperty})); - } - } - - private function deleteSizes(): bool - { - // delete all derived sizes - foreach (array_keys($this->sizes) as $name) { - $pathProperty = $name . '_path'; - if (! unlink(media_path($this->{$pathProperty}))) { - return false; - } - } - - return true; - } -} diff --git a/app/Entities/Media/Transcript.php b/app/Entities/Media/Transcript.php deleted file mode 100644 index 988e2488..00000000 --- a/app/Entities/Media/Transcript.php +++ /dev/null @@ -1,78 +0,0 @@ -file_path && $this->file_metadata && array_key_exists('json_path', $this->file_metadata)) { - helper('media'); - - $this->json_path = media_path($this->file_metadata['json_path']); - $this->json_url = media_base_url($this->file_metadata['json_path']); - } - } - - public function setFile(File $file): self - { - parent::setFile($file); - - $content = file_get_contents(media_path($this->attributes['file_path'])); - - if ($content === false) { - return $this; - } - - $metadata = []; - if ($fileMetadata = lstat((string) $file)) { - $metadata = $fileMetadata; - } - - $transcriptParser = new TranscriptParser(); - $jsonFilePath = $this->attributes['file_directory'] . '/' . $this->attributes['file_name'] . '.json'; - if (($transcriptJson = $transcriptParser->loadString($content)->parseSrt()) && file_put_contents( - media_path($jsonFilePath), - $transcriptJson - )) { - // set metadata (generated json file path) - $metadata['json_path'] = $jsonFilePath; - } - - $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); - - return $this; - } - - public function deleteFile(): bool - { - if (! parent::deleteFile()) { - return false; - } - - if ($this->json_path) { - return unlink($this->json_path); - } - - return true; - } -} diff --git a/app/Entities/Person.php b/app/Entities/Person.php index 75e3edbf..f0f27721 100644 --- a/app/Entities/Person.php +++ b/app/Entities/Person.php @@ -10,12 +10,12 @@ declare(strict_types=1); namespace App\Entities; -use App\Entities\Media\Image; -use App\Models\MediaModel; use App\Models\PersonModel; use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use CodeIgniter\HTTP\Files\UploadedFile; +use Modules\Media\Entities\Image; +use Modules\Media\Models\MediaModel; use RuntimeException; /** @@ -70,10 +70,9 @@ class Person extends Entity (new MediaModel('image'))->updateMedia($this->getAvatar()); } else { $avatar = new Image([ - 'file_name' => $this->attributes['unique_name'], - 'file_directory' => 'persons', + 'file_key' => 'persons/' . $this->attributes['unique_name'] . '.' . $file->getExtension(), 'sizes' => config('Images') - ->personAvatarSizes, +->personAvatarSizes, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); @@ -90,7 +89,7 @@ class Person extends Entity if ($this->attributes['avatar_id'] === null) { helper('media'); return new Image([ - 'file_path' => config('Images') + 'file_key' => config('Images') ->avatarDefaultPath, 'file_mimetype' => config('Images') ->avatarDefaultMimeType, @@ -99,7 +98,7 @@ class Person extends Entity 'sizes' => config('Images') ->personAvatarSizes, ], - ]); + ], 'fs'); } if ($this->avatar === null) { diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 47c62e4d..313fa085 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -10,12 +10,10 @@ declare(strict_types=1); namespace App\Entities; -use App\Entities\Media\Image; use App\Libraries\SimpleRSSElement; use App\Models\ActorModel; use App\Models\CategoryModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PersonModel; use App\Models\PlatformModel; use CodeIgniter\Entity\Entity; @@ -30,6 +28,8 @@ use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension; use League\CommonMark\Extension\SmartPunct\SmartPunctExtension; use League\CommonMark\MarkdownConverter; use Modules\Auth\Models\UserModel; +use Modules\Media\Entities\Image; +use Modules\Media\Models\MediaModel; use Modules\PremiumPodcasts\Entities\Subscription; use Modules\PremiumPodcasts\Models\SubscriptionModel; use RuntimeException; @@ -49,7 +49,7 @@ use RuntimeException; * @property int $cover_id * @property Image $cover * @property int|null $banner_id - * @property Image|null $banner + * @property Image $banner * @property string $language_code * @property int $category_id * @property Category|null $category @@ -243,10 +243,9 @@ class Podcast extends Entity (new MediaModel('image'))->updateMedia($this->getCover()); } else { $cover = new Image([ - 'file_name' => 'cover', - 'file_directory' => 'podcasts/' . $this->attributes['handle'], + 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/cover.' . $file->getExtension(), 'sizes' => config('Images') - ->podcastCoverSizes, +->podcastCoverSizes, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); @@ -281,10 +280,9 @@ class Podcast extends Entity (new MediaModel('image'))->updateMedia($this->getBanner()); } else { $banner = new Image([ - 'file_name' => 'banner', - 'file_directory' => 'podcasts/' . $this->attributes['handle'], + 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/banner.' . $file->getExtension(), 'sizes' => config('Images') - ->podcastBannerSizes, +->podcastBannerSizes, 'uploaded_by' => user_id(), 'updated_by' => user_id(), ]); @@ -304,14 +302,14 @@ class Podcast extends Entity 'Images' )->podcastBannerDefaultPaths['default']; return new Image([ - 'file_path' => $defaultBanner['path'], + 'file_key' => $defaultBanner['path'], 'file_mimetype' => $defaultBanner['mimetype'], 'file_size' => 0, 'file_metadata' => [ 'sizes' => config('Images') - ->podcastBannerSizes, +->podcastBannerSizes, ], - ]); + ], 'fs'); } if (! $this->banner instanceof Image) { diff --git a/app/Helpers/id3_helper.php b/app/Helpers/id3_helper.php index e8051c9b..584a07d5 100644 --- a/app/Helpers/id3_helper.php +++ b/app/Helpers/id3_helper.php @@ -10,6 +10,7 @@ declare(strict_types=1); use App\Entities\Episode; use JamesHeinrich\GetID3\WriteTags; +use Modules\Media\FileManagers\FileManagerInterface; if (! function_exists('write_audio_file_tags')) { /** @@ -23,13 +24,16 @@ if (! function_exists('write_audio_file_tags')) { // Initialize getID3 tag-writing module $tagwriter = new WriteTags(); - $tagwriter->filename = media_path($episode->audio->file_path); + $tagwriter->filename = $episode->audio->file_name; // set various options (optional) $tagwriter->tagformats = ['id3v2.4']; $tagwriter->tag_encoding = $TextEncoding; - $APICdata = file_get_contents(media_path($episode->cover->id3_path)); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + $APICdata = $fileManager->getFileContents($episode->cover->id3_key); // TODO: variables used for podcast specific tags // $podcastUrl = $episode->podcast->link; diff --git a/app/Libraries/MediaClipper/VideoClipper.php b/app/Libraries/MediaClipper/VideoClipper.php index c057415d..86ebb758 100644 --- a/app/Libraries/MediaClipper/VideoClipper.php +++ b/app/Libraries/MediaClipper/VideoClipper.php @@ -13,6 +13,7 @@ namespace MediaClipper; use App\Entities\Episode; use Exception; use GdImage; +use Modules\Media\FileManagers\FileManagerInterface; /** * TODO: refactor this by splitting process modules into different classes (image generation, subtitles clip, video @@ -35,9 +36,9 @@ class VideoClipper public bool $error = false; - public string $videoClipFilePath; + public string $videoClipFileKey; - protected string $videoClipOutput; + public string $videoClipOutput; protected float $duration; @@ -83,15 +84,11 @@ class VideoClipper $this->colors = config('MediaClipper') ->themes[$theme]; - helper(['media']); + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); - $this->audioInput = media_path($this->episode->audio->file_path); - $this->episodeCoverPath = media_path($this->episode->cover->file_path); - - $podcastFolder = media_path("podcasts/{$this->episode->podcast->handle}"); - - $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; - $this->videoClipFilePath = "podcasts/{$this->episode->podcast->handle}/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; + $this->audioInput = $fileManager->getFileInput($this->episode->audio->file_key); + $this->episodeCoverPath = $fileManager->getFileInput($this->episode->cover->file_key); // Temporary files to generate clip $tempFile = tempnam(WRITEPATH . 'temp', "{$this->episode->slug}-{$this->start}-{$this->end}"); @@ -102,7 +99,10 @@ class VideoClipper ); } + $this->videoClipFileKey = "podcasts/{$this->episode->podcast->handle}/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; + $this->tempFileOutput = $tempFile; + $this->videoClipOutput = $tempFile . '-video-clip.mp4'; $this->soundbiteOutput = $tempFile . '-soundbite.mp3'; $this->subtitlesClipOutput = $tempFile . '-subtitle.srt'; $this->videoClipBgOutput = $tempFile . '-bg.png'; @@ -120,19 +120,22 @@ class VideoClipper throw new Exception('Episode does not have a transcript!'); } - if ($this->episode->transcript->json_path) { - $this->generateSubtitlesClipFromJson($this->episode->transcript->json_path); + if ($this->episode->transcript->json_url) { + $this->generateSubtitlesClipFromJson($this->episode->transcript->json_key); } else { - $subtitlesInput = media_path($this->episode->transcript->file_path); + $subtitlesInput = $this->episode->transcript->file_url; $subtitleClipCmd = "ffmpeg -y -i {$subtitlesInput} -ss {$this->start} -t {$this->duration} {$this->subtitlesClipOutput}"; exec($subtitleClipCmd); } } - public function generateSubtitlesClipFromJson(string $jsonFileInput): void + public function generateSubtitlesClipFromJson(string $jsonFileKey): void { - $jsonTranscriptString = file_get_contents($jsonFileInput); - if ($jsonTranscriptString === false) { + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); + + $jsonTranscriptString = $fileManager->getFileContents($jsonFileKey); + if ($jsonTranscriptString === '') { throw new Exception('Cannot get transcript json contents.'); } diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php index 09712615..76495398 100644 --- a/app/Libraries/ViewComponents/ComponentRenderer.php +++ b/app/Libraries/ViewComponents/ComponentRenderer.php @@ -134,17 +134,17 @@ class ComponentRenderer foreach ($pathsToDiscover as $basePath) { // Look for a class component first - $filePath = $basePath . $this->config->componentsDirectory . '/' . $namePath . '.php'; + $fileKey = $basePath . $this->config->componentsDirectory . '/' . $namePath . '.php'; - if (is_file($filePath)) { - return $filePath; + if (is_file($fileKey)) { + return $fileKey; } $snakeCaseName = strtolower(preg_replace('~(?config->componentsDirectory . '/' . $snakeCaseName . '.php'; + $fileKey = $basePath . $this->config->componentsDirectory . '/' . $snakeCaseName . '.php'; - if (is_file($filePath)) { - return $filePath; + if (is_file($fileKey)) { + return $fileKey; } } @@ -204,18 +204,18 @@ class ComponentRenderer { // Locate the class in the same folder as the view $class = $name . '.php'; - $filePath = str_replace($name . '.php', $class, $view); + $fileKey = str_replace($name . '.php', $class, $view); - if ($filePath === '') { + if ($fileKey === '') { return null; } - if (! file_exists($filePath)) { + if (! file_exists($fileKey)) { return null; } $className = service('locator') - ->getClassname($filePath); + ->getClassname($fileKey); if (! class_exists($className)) { return null; diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 300ffdf3..0590b434 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -440,8 +440,8 @@ class PodcastModel extends Model $podcastActor = (new ActorModel())->find($podcast->actor_id); if ($podcastActor) { - $podcastActor->avatar_image_url = $podcast->cover->thumbnail_url; - $podcastActor->avatar_image_mimetype = $podcast->cover->thumbnail_mimetype; + $podcastActor->avatar_image_url = $podcast->cover->federation_url; + $podcastActor->avatar_image_mimetype = $podcast->cover->federation_mimetype; (new ActorModel())->update($podcast->actor_id, $podcastActor); } @@ -468,9 +468,9 @@ class PodcastModel extends Model $actor->display_name = $podcast->title; $actor->summary = $podcast->description_html; $actor->avatar_image_url = $podcast->cover->federation_url; - $actor->avatar_image_mimetype = $podcast->cover->file_mimetype; + $actor->avatar_image_mimetype = $podcast->cover->federation_mimetype; $actor->cover_image_url = $podcast->banner->federation_url; - $actor->cover_image_mimetype = $podcast->banner->file_mimetype; + $actor->cover_image_mimetype = $podcast->banner->federation_mimetype; if ($actor->hasChanged()) { $actorModel->update($actor->id, $actor); diff --git a/app/Views/errors/cli/error_exception.php b/app/Views/errors/cli/error_exception.php index b9c4941c..aa946812 100644 --- a/app/Views/errors/cli/error_exception.php +++ b/app/Views/errors/cli/error_exception.php @@ -27,9 +27,9 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { $c = str_pad((string) ($i + 1), 3, ' ', STR_PAD_LEFT); if (isset($error['file'])) { - $filepath = clean_path($error['file']) . ':' . $error['line']; + $fileKey = clean_path($error['file']) . ':' . $error['line']; - CLI::write($c . $padFile . CLI::color($filepath, 'yellow')); + CLI::write($c . $padFile . CLI::color($fileKey, 'yellow')); } else { CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow')); } diff --git a/composer.json b/composer.json index d9a61018..a04b3e3d 100644 --- a/composer.json +++ b/composer.json @@ -17,21 +17,22 @@ "opawg/user-agents-php": "^v1.0", "adaures/ipcat-php": "^v1.0.0", "adaures/podcast-persons-taxonomy": "^v1.0.0", - "phpseclib/phpseclib": "~2.0.41", + "phpseclib/phpseclib": "~2.0.42", "michalsn/codeigniter4-uuid": "dev-develop", "essence/essence": "^3.5.4", "codeigniter4/settings": "^v2.1.0", "chrisjean/php-ico": "^1.0.4", "melbahja/seo": "^v2.1.1", - "codeigniter4/shield": "v1.0.0-beta.3" + "codeigniter4/shield": "v1.0.0-beta.3", + "aws/aws-sdk-php": "^3.261.10" }, "require-dev": { "mikey179/vfsstream": "^v1.6.11", - "phpunit/phpunit": "^10.0.11", - "captainhook/captainhook": "^5.14.4", - "symplify/easy-coding-standard": "^11.2.9", - "phpstan/phpstan": "^1.10.0", - "rector/rector": "^0.15.17", + "phpunit/phpunit": "^10.0.16", + "captainhook/captainhook": "^5.15.2", + "symplify/easy-coding-standard": "^11.2.10", + "phpstan/phpstan": "^1.10.6", + "rector/rector": "^0.15.21", "symplify/coding-standard": "^11.3.0" }, "autoload": { diff --git a/composer.lock b/composer.lock index fdc011b5..21ca5638 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "86928335baed20f4228d148b0b4f77e0", + "content-hash": "84568da2a8ddda6d9bcd8eb63ee83682", "packages": [ { "name": "adaures/ipcat-php", @@ -73,6 +73,137 @@ "homepage": "https://code.castopod.org/adaures/podcast-persons-taxonomy", "time": "2022-02-20T14:09:25+00:00" }, + { + "name": "aws/aws-crt-php", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "f5c64ee7c5fce196e2519b3d9b7138649efe032d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/f5c64ee7c5fce196e2519b3d9b7138649efe032d", + "reference": "f5c64ee7c5fce196e2519b3d9b7138649efe032d", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.6.3" + }, + "type": "library", + "autoload": { + "classmap": ["src/"] + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["Apache-2.0"], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": ["amazon", "aws", "crt", "sdk"], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.4" + }, + "time": "2023-01-31T23:08:25+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.261.10", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "4889eff2b3fe35e878fbcaf8374d73f043609170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4889eff2b3fe35e878fbcaf8374d73f043609170", + "reference": "4889eff2b3fe35e878fbcaf8374d73f043609170", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.4", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.8.5 || ^2.3", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": ["src/functions.php"], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["Apache-2.0"], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.261.10" + }, + "time": "2023-03-13T18:19:14+00:00" + }, { "name": "brick/math", "version": "0.10.2", @@ -707,24 +838,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.1" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "autoload": { @@ -751,7 +882,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" }, "funding": [ { @@ -763,7 +894,326 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2023-02-25T20:23:15+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "files": ["src/functions_include.php"], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-08-28T15:39:27+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": ["src/functions_include.php"], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": ["promise"], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:55:35+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-03-09T13:19:02+00:00" }, { "name": "james-heinrich/getid3", @@ -1430,6 +1880,58 @@ }, "time": "2021-05-10T16:28:01+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": ["bin/jp.php"], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "files": ["src/JmesPath.php"], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": ["json", "jsonpath"], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + }, + "time": "2021-06-14T00:11:39+00:00" + }, { "name": "nette/schema", "version": "v1.2.3", @@ -1601,24 +2103,24 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "extra": { @@ -1653,7 +2155,7 @@ "keywords": ["language", "option", "php", "type"], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" }, "funding": [ { @@ -1665,20 +2167,20 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2023-02-25T19:38:58+00:00" }, { "name": "phpseclib/phpseclib", - "version": "2.0.41", + "version": "2.0.42", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "7e763c6f97ec1fcb37c46aa8ecfc20a2c71d9c1b" + "reference": "665d289f59e646a259ebf13f29be7f6f54cab24b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7e763c6f97ec1fcb37c46aa8ecfc20a2c71d9c1b", - "reference": "7e763c6f97ec1fcb37c46aa8ecfc20a2c71d9c1b", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/665d289f59e646a259ebf13f29be7f6f54cab24b", + "reference": "665d289f59e646a259ebf13f29be7f6f54cab24b", "shasum": "" }, "require": { @@ -1755,7 +2257,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.41" + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.42" }, "funding": [ { @@ -1771,7 +2273,7 @@ "type": "tidelift" } ], - "time": "2022-12-23T16:44:18+00:00" + "time": "2023-03-06T12:45:53+00:00" }, { "name": "psr/cache", @@ -1860,6 +2362,155 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": ["http", "http-client", "psr", "psr-18"], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.1.4", @@ -1904,6 +2555,46 @@ }, "time": "2021-05-03T11:20:27+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": ["src/getallheaders.php"] + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "ramsey/collection", "version": "2.0.0", @@ -2070,16 +2761,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "shasum": "" }, "require": { @@ -2113,7 +2804,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" }, "funding": [ { @@ -2129,7 +2820,7 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:25:55+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2485,26 +3176,26 @@ "packages-dev": [ { "name": "captainhook/captainhook", - "version": "5.14.4", + "version": "5.15.2", "source": { "type": "git", "url": "https://github.com/captainhookphp/captainhook.git", - "reference": "8309f6e16097754469c485e604900c573bf2c5d8" + "reference": "728fe3847c57f2d9cd4c53a8c26bc5522b42a1d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/captainhookphp/captainhook/zipball/8309f6e16097754469c485e604900c573bf2c5d8", - "reference": "8309f6e16097754469c485e604900c573bf2c5d8", + "url": "https://api.github.com/repos/captainhookphp/captainhook/zipball/728fe3847c57f2d9cd4c53a8c26bc5522b42a1d0", + "reference": "728fe3847c57f2d9cd4c53a8c26bc5522b42a1d0", "shasum": "" }, "require": { "ext-json": "*", "ext-spl": "*", "ext-xml": "*", - "php": ">=7.2", + "php": ">=7.4", "sebastianfeldmann/camino": "^0.9.2", "sebastianfeldmann/cli": "^3.3", - "sebastianfeldmann/git": "^3.8.5", + "sebastianfeldmann/git": "^3.8.6", "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0", "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0", "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" @@ -2552,7 +3243,7 @@ ], "support": { "issues": "https://github.com/captainhookphp/captainhook/issues", - "source": "https://github.com/captainhookphp/captainhook/tree/5.14.4" + "source": "https://github.com/captainhookphp/captainhook/tree/5.15.2" }, "funding": [ { @@ -2560,7 +3251,7 @@ "type": "github" } ], - "time": "2023-02-05T15:14:48+00:00" + "time": "2023-03-03T11:11:58+00:00" }, { "name": "composer/pcre", @@ -2902,16 +3593,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.14.4", + "version": "v3.15.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b" + "reference": "d48755372a113bddb99f749e34805d83f3acfe04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1b3d9dba63d93b8a202c31e824748218781eae6b", - "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d48755372a113bddb99f749e34805d83f3acfe04", + "reference": "d48755372a113bddb99f749e34805d83f3acfe04", "shasum": "" }, "require": { @@ -2974,9 +3665,15 @@ } ], "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.4" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.15.1" }, "funding": [ { @@ -2984,7 +3681,7 @@ "type": "github" } ], - "time": "2023-02-09T21:49:13+00:00" + "time": "2023-03-13T23:26:30+00:00" }, { "name": "mikey179/vfsstream", @@ -3037,16 +3734,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -3074,7 +3771,7 @@ "keywords": ["clone", "copy", "duplicate", "object", "object graph"], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -3082,20 +3779,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -3129,9 +3826,9 @@ "keywords": ["parser", "php"], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "phar-io/manifest", @@ -3238,16 +3935,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.0", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "71b28a67f01ac231f9a8f1ce242270bf4ec1f99c" + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/71b28a67f01ac231f9a8f1ce242270bf4ec1f99c", - "reference": "71b28a67f01ac231f9a8f1ce242270bf4ec1f99c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", "shasum": "" }, "require": { @@ -3267,7 +3964,7 @@ "keywords": ["dev", "static analysis"], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.0" + "source": "https://github.com/phpstan/phpstan/tree/1.10.6" }, "funding": [ { @@ -3283,27 +3980,27 @@ "type": "tidelift" } ], - "time": "2023-02-21T13:50:49+00:00" + "time": "2023-03-09T16:55:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.0.0", + "version": "10.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "bf4fbc9c13af7da12b3ea807574fb460f255daba" + "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bf4fbc9c13af7da12b3ea807574fb460f255daba", - "reference": "bf4fbc9c13af7da12b3ea807574fb460f255daba", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/20800e84296ea4732f9a125e08ce86b4004ae3e4", + "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=8.1", "phpunit/php-file-iterator": "^4.0", "phpunit/php-text-template": "^3.0", @@ -3318,8 +4015,8 @@ "phpunit/phpunit": "^10.0" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -3344,7 +4041,7 @@ "keywords": ["coverage", "testing", "xunit"], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.2" }, "funding": [ { @@ -3352,7 +4049,7 @@ "type": "github" } ], - "time": "2023-02-03T07:14:34+00:00" + "time": "2023-03-06T13:00:19+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3572,16 +4269,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.0.11", + "version": "10.0.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d18a18b07e7a9ad52d994b1785f9e301fc84b616" + "reference": "07d386a11ac7094032900f07cada1c8975d16607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d18a18b07e7a9ad52d994b1785f9e301fc84b616", - "reference": "d18a18b07e7a9ad52d994b1785f9e301fc84b616", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/07d386a11ac7094032900f07cada1c8975d16607", + "reference": "07d386a11ac7094032900f07cada1c8975d16607", "shasum": "" }, "require": { @@ -3613,7 +4310,7 @@ "sebastian/version": "^4.0" }, "suggest": { - "ext-soap": "*" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": ["phpunit"], "type": "library", @@ -3640,7 +4337,7 @@ "keywords": ["phpunit", "testing", "xunit"], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.16" }, "funding": [ { @@ -3656,7 +4353,7 @@ "type": "tidelift" } ], - "time": "2023-02-20T16:39:36+00:00" + "time": "2023-03-13T09:02:40+00:00" }, { "name": "psr/container", @@ -3711,21 +4408,21 @@ }, { "name": "rector/rector", - "version": "0.15.17", + "version": "0.15.21", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "7f6ee7974175138864d3b50c28ea73a7b0fd4e2d" + "reference": "1cee8cc5d6d836e1bf9a3006d7b062adde3a6022" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/7f6ee7974175138864d3b50c28ea73a7b0fd4e2d", - "reference": "7f6ee7974175138864d3b50c28ea73a7b0fd4e2d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/1cee8cc5d6d836e1bf9a3006d7b062adde3a6022", + "reference": "1cee8cc5d6d836e1bf9a3006d7b062adde3a6022", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.9.14" + "phpstan/phpstan": "^1.10.1" }, "conflict": { "rector/rector-doctrine": "*", @@ -3746,9 +4443,10 @@ "notification-url": "https://packagist.org/downloads/", "license": ["MIT"], "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": ["automation", "dev", "migration", "refactoring"], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.15.17" + "source": "https://github.com/rectorphp/rector/tree/0.15.21" }, "funding": [ { @@ -3756,7 +4454,7 @@ "type": "github" } ], - "time": "2023-02-17T20:34:07+00:00" + "time": "2023-03-06T11:44:29+00:00" }, { "name": "sebastian/cli-parser", @@ -4695,16 +5393,16 @@ }, { "name": "sebastianfeldmann/git", - "version": "3.8.5", + "version": "3.8.6", "source": { "type": "git", "url": "https://github.com/sebastianfeldmann/git.git", - "reference": "14de823cba82e30a3759e9eab1ebbd0d6f5d542c" + "reference": "d4ce674cf5104a162f8401033d88ea7f47134c5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/14de823cba82e30a3759e9eab1ebbd0d6f5d542c", - "reference": "14de823cba82e30a3759e9eab1ebbd0d6f5d542c", + "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/d4ce674cf5104a162f8401033d88ea7f47134c5d", + "reference": "d4ce674cf5104a162f8401033d88ea7f47134c5d", "shasum": "" }, "require": { @@ -4740,7 +5438,7 @@ "keywords": ["git"], "support": { "issues": "https://github.com/sebastianfeldmann/git/issues", - "source": "https://github.com/sebastianfeldmann/git/tree/3.8.5" + "source": "https://github.com/sebastianfeldmann/git/tree/3.8.6" }, "funding": [ { @@ -4748,20 +5446,20 @@ "type": "github" } ], - "time": "2022-10-23T11:29:41+00:00" + "time": "2023-02-24T21:21:52+00:00" }, { "name": "symfony/console", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3e294254f2191762c1d137aed4b94e966965e985" + "reference": "cbad09eb8925b6ad4fb721c7a179344dc4a19d45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3e294254f2191762c1d137aed4b94e966965e985", - "reference": "3e294254f2191762c1d137aed4b94e966965e985", + "url": "https://api.github.com/repos/symfony/console/zipball/cbad09eb8925b6ad4fb721c7a179344dc4a19d45", + "reference": "cbad09eb8925b6ad4fb721c7a179344dc4a19d45", "shasum": "" }, "require": { @@ -4819,7 +5517,7 @@ "homepage": "https://symfony.com", "keywords": ["cli", "command line", "console", "terminal"], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.5" + "source": "https://github.com/symfony/console/tree/v6.2.7" }, "funding": [ { @@ -4835,20 +5533,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-02-25T17:00:03+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68" + "reference": "404b307de426c1c488e5afad64403e5f145e82a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68", - "reference": "f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/404b307de426c1c488e5afad64403e5f145e82a5", + "reference": "404b307de426c1c488e5afad64403e5f145e82a5", "shasum": "" }, "require": { @@ -4898,7 +5596,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.5" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.7" }, "funding": [ { @@ -4914,20 +5612,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae" + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0782b0b52a737a05b4383d0df35a474303cabdae", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", "shasum": "" }, "require": { @@ -4975,7 +5673,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" }, "funding": [ { @@ -4991,20 +5689,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:32:47+00:00" }, { "name": "symfony/filesystem", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e59e8a4006afd7f5654786a83b4fcb8da98f4593" + "reference": "82b6c62b959f642d000456f08c6d219d749215b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e59e8a4006afd7f5654786a83b4fcb8da98f4593", - "reference": "e59e8a4006afd7f5654786a83b4fcb8da98f4593", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3", + "reference": "82b6c62b959f642d000456f08c6d219d749215b3", "shasum": "" }, "require": { @@ -5034,7 +5732,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.2.5" + "source": "https://github.com/symfony/filesystem/tree/v6.2.7" }, "funding": [ { @@ -5050,20 +5748,20 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/finder", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "c90dc446976a612e3312a97a6ec0069ab0c2099c" + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/c90dc446976a612e3312a97a6ec0069ab0c2099c", - "reference": "c90dc446976a612e3312a97a6ec0069ab0c2099c", + "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", "shasum": "" }, "require": { @@ -5094,7 +5792,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.5" + "source": "https://github.com/symfony/finder/tree/v6.2.7" }, "funding": [ { @@ -5110,20 +5808,20 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-02-16T09:57:23+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "e8324d44f5af99ec2ccec849934a242f64458f86" + "reference": "aa0e85b53bbb2b4951960efd61d295907eacd629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/e8324d44f5af99ec2ccec849934a242f64458f86", - "reference": "e8324d44f5af99ec2ccec849934a242f64458f86", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/aa0e85b53bbb2b4951960efd61d295907eacd629", + "reference": "aa0e85b53bbb2b4951960efd61d295907eacd629", "shasum": "" }, "require": { @@ -5153,7 +5851,7 @@ "homepage": "https://symfony.com", "keywords": ["config", "configuration", "options"], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.2.5" + "source": "https://github.com/symfony/options-resolver/tree/v6.2.7" }, "funding": [ { @@ -5169,7 +5867,7 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5396,16 +6094,16 @@ }, { "name": "symfony/process", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "9ead139f63dfa38c4e4a9049cc64a8b2748c83b7" + "reference": "680e8a2ea6b3f87aecc07a6a65a203ae573d1902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/9ead139f63dfa38c4e4a9049cc64a8b2748c83b7", - "reference": "9ead139f63dfa38c4e4a9049cc64a8b2748c83b7", + "url": "https://api.github.com/repos/symfony/process/zipball/680e8a2ea6b3f87aecc07a6a65a203ae573d1902", + "reference": "680e8a2ea6b3f87aecc07a6a65a203ae573d1902", "shasum": "" }, "require": { @@ -5433,7 +6131,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.5" + "source": "https://github.com/symfony/process/tree/v6.2.7" }, "funding": [ { @@ -5449,20 +6147,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-02-24T10:42:00+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" + "reference": "a8c9cedf55f314f3a186041d19537303766df09a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", + "reference": "a8c9cedf55f314f3a186041d19537303766df09a", "shasum": "" }, "require": { @@ -5514,7 +6212,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" }, "funding": [ { @@ -5530,20 +6228,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:32:47+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "00b6ac156aacffc53487c930e0ab14587a6607f6" + "reference": "f3adc98c1061875dd2edcd45e5b04e63d0e29f8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/00b6ac156aacffc53487c930e0ab14587a6607f6", - "reference": "00b6ac156aacffc53487c930e0ab14587a6607f6", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f3adc98c1061875dd2edcd45e5b04e63d0e29f8f", + "reference": "f3adc98c1061875dd2edcd45e5b04e63d0e29f8f", "shasum": "" }, "require": { @@ -5572,7 +6270,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.2.5" + "source": "https://github.com/symfony/stopwatch/tree/v6.2.7" }, "funding": [ { @@ -5588,20 +6286,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:36:55+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/string", - "version": "v6.2.5", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" + "reference": "67b8c1eec78296b85dc1c7d9743830160218993d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "url": "https://api.github.com/repos/symfony/string/zipball/67b8c1eec78296b85dc1c7d9743830160218993d", + "reference": "67b8c1eec78296b85dc1c7d9743830160218993d", "shasum": "" }, "require": { @@ -5645,7 +6343,7 @@ "homepage": "https://symfony.com", "keywords": ["grapheme", "i18n", "string", "unicode", "utf-8", "utf8"], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.5" + "source": "https://github.com/symfony/string/tree/v6.2.7" }, "funding": [ { @@ -5661,7 +6359,7 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-02-24T10:42:00+00:00" }, { "name": "symplify/coding-standard", @@ -5732,16 +6430,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "11.2.9", + "version": "11.2.10", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "947267668efb055bf78c7b68416cb206285f21d4" + "reference": "5fc343df9b86e154516ddf506f7fe0b421d7d5ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/947267668efb055bf78c7b68416cb206285f21d4", - "reference": "947267668efb055bf78c7b68416cb206285f21d4", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/5fc343df9b86e154516ddf506f7fe0b421d7d5ef", + "reference": "5fc343df9b86e154516ddf506f7fe0b421d7d5ef", "shasum": "" }, "require": { @@ -5761,7 +6459,7 @@ "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/11.2.9" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/11.2.10" }, "funding": [ { @@ -5773,7 +6471,7 @@ "type": "github" } ], - "time": "2023-02-21T11:31:15+00:00" + "time": "2023-02-27T10:00:30+00:00" }, { "name": "symplify/rule-doc-generator-contracts", diff --git a/docker-compose.yml b/docker-compose.yml index 8b735730..a1f5f79c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,10 @@ -version: "3" +version: "3.8" networks: castopod: + ipam: + config: + - subnet: 172.20.0.0/24 services: app: @@ -18,7 +21,8 @@ services: - redis - mariadb networks: - - castopod + castopod: + ipv4_address: 172.20.0.2 redis: image: redis:alpine @@ -28,7 +32,8 @@ services: volumes: - redis:/data networks: - - castopod + castopod: + ipv4_address: 172.20.0.3 mariadb: image: mariadb:10.2 @@ -44,7 +49,8 @@ services: MYSQL_USER: castopod MYSQL_PASSWORD: castopod networks: - - castopod + castopod: + ipv4_address: 172.20.0.4 phpmyadmin: image: phpmyadmin/phpmyadmin:latest @@ -59,7 +65,22 @@ services: depends_on: - mariadb networks: - - castopod + castopod: + ipv4_address: 172.20.0.5 + + s3: + image: adobe/s3mock:latest + container_name: castopod_s3 + environment: + - debug=true + - root=/data + ports: + - 9090:9090 + volumes: + - ./data/s3:/data:cached + networks: + castopod: + ipv4_address: 172.20.0.6 volumes: redis: diff --git a/docs/src/contributing/setup-development.md b/docs/src/contributing/setup-development.md index 5cbecd1c..7de2e408 100644 --- a/docs/src/contributing/setup-development.md +++ b/docs/src/contributing/setup-development.md @@ -43,7 +43,6 @@ to help you kickstart your contribution. app.forceGlobalSecureRequests=false app.baseURL="http://localhost:8080/" - app.mediaBaseURL="http://localhost:8080/" admin.gateway="cp-admin" auth.gateway="cp-auth" @@ -62,7 +61,21 @@ to help you kickstart your contribution. # You may not want to use redis as your cache handler # Comment/remove the two lines above and uncomment # the next line for file caching. + # ----------------------- #cache.handler="file" + + ###################################### + # Media config + ###################################### + media.baseURL="http://localhost:8080/" + + # S3 + # Uncomment to store s3 objects using adobe/s3mock service + # ----------------------- + #media.fileManager="s3" + #media.s3.bucket="castopod" + #media.s3.endpoint="http://172.20.0.6:9090/" + #media.s3.path_style_endpoint=true ``` > _NB._ You can tweak your environment by setting more environment variables diff --git a/modules/Admin/Controllers/DashboardController.php b/modules/Admin/Controllers/DashboardController.php index ffef90d9..4d71fc20 100644 --- a/modules/Admin/Controllers/DashboardController.php +++ b/modules/Admin/Controllers/DashboardController.php @@ -11,9 +11,9 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\I18n\Time; +use Modules\Media\Models\MediaModel; class DashboardController extends BaseController { diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 73b382d4..3e861715 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -17,12 +17,12 @@ use App\Entities\Podcast; use App\Entities\Post; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\I18n\Time; +use Modules\Media\Models\MediaModel; class EpisodeController extends BaseController { @@ -933,7 +933,7 @@ class EpisodeController extends BaseController if ($episodeMedia !== null && ! $episodeMedia->deleteFile()) { $warnings[] = lang('Episode.messages.deleteFileError', [ 'type' => $episodeMedia->type, - 'file_path' => $episodeMedia->file_path, + 'file_key' => $episodeMedia->file_key, ]); } } diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php index e8bcf742..2f57a233 100644 --- a/modules/Admin/Controllers/PodcastController.php +++ b/modules/Admin/Controllers/PodcastController.php @@ -17,7 +17,6 @@ use App\Models\ActorModel; use App\Models\CategoryModel; use App\Models\EpisodeModel; use App\Models\LanguageModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Exceptions\PageNotFoundException; @@ -32,6 +31,7 @@ use Modules\Analytics\Models\AnalyticsPodcastModel; use Modules\Analytics\Models\AnalyticsWebsiteByBrowserModel; use Modules\Analytics\Models\AnalyticsWebsiteByEntryPageModel; use Modules\Analytics\Models\AnalyticsWebsiteByRefererModel; +use Modules\Media\Models\MediaModel; class PodcastController extends BaseController { diff --git a/modules/Admin/Controllers/PodcastImportController.php b/modules/Admin/Controllers/PodcastImportController.php index 08ccd067..e0df6ed7 100644 --- a/modules/Admin/Controllers/PodcastImportController.php +++ b/modules/Admin/Controllers/PodcastImportController.php @@ -306,6 +306,7 @@ class PodcastImportController extends BaseController $slugs = []; for ($itemNumber = 1; $itemNumber <= $lastItem; ++$itemNumber) { $item = $feed->channel[0]->item[$itemsCount - $itemNumber]; + log_message('critical', (string) $item->title); $nsItunes = $item->children('http://www.itunes.com/dtds/podcast-1.0.dtd'); $nsPodcast = $item->children( diff --git a/modules/Admin/Controllers/SchedulerController.php b/modules/Admin/Controllers/SchedulerController.php index 9246557b..d5e60235 100644 --- a/modules/Admin/Controllers/SchedulerController.php +++ b/modules/Admin/Controllers/SchedulerController.php @@ -12,6 +12,7 @@ namespace Modules\Admin\Controllers; use App\Models\ClipModel; use CodeIgniter\Controller; +use CodeIgniter\Files\File; use CodeIgniter\I18n\Time; use Exception; use MediaClipper\VideoClipper; @@ -65,7 +66,7 @@ class SchedulerController extends Controller $clipModel = new ClipModel(); if ($exitCode === 0) { // success, video was generated - $scheduledClip->setMedia($clipper->videoClipFilePath); + $scheduledClip->setMedia(new File($clipper->videoClipOutput), $clipper->videoClipFileKey); $clipModel->update($scheduledClip->id, [ 'media_id' => $scheduledClip->media_id, 'status' => 'passed', diff --git a/modules/Admin/Controllers/SettingsController.php b/modules/Admin/Controllers/SettingsController.php index abd4413a..f089a4d2 100644 --- a/modules/Admin/Controllers/SettingsController.php +++ b/modules/Admin/Controllers/SettingsController.php @@ -10,15 +10,18 @@ declare(strict_types=1); namespace Modules\Admin\Controllers; +use App\Entities\Podcast; use App\Models\ActorModel; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; 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; class SettingsController extends BaseController @@ -61,7 +64,10 @@ class SettingsController extends BaseController delete_files(media_path('/site')); // save original in disk - $originalFilename = save_media($siteIconFile, 'site', 'icon'); + $originalFilename = (new FS(config('Media')))->save( + $siteIconFile, + 'site/icon.' . $siteIconFile->getExtension() + ); // convert jpeg image to png if not if ($siteIconFile->getClientMimeType() !== 'image/png') { @@ -113,23 +119,14 @@ class SettingsController extends BaseController public function regenerateImages(): RedirectResponse { - helper('media'); - + /** @var Podcast[] $allPodcasts */ $allPodcasts = (new PodcastModel())->findAll(); - $imageExt = ['jpg', 'png', 'webp']; + + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager'); foreach ($allPodcasts as $podcast) { - foreach ($imageExt as $ext) { - $podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}")); - - if ($podcastImages) { - foreach ($podcastImages as $podcastImage) { - if (is_file($podcastImage)) { - unlink($podcastImage); - } - } - } - } + $fileManager->deletePodcastImageSizes($podcast->handle); $podcast->cover->saveSizes(); if ($podcast->banner_id !== null) { @@ -143,16 +140,7 @@ class SettingsController extends BaseController } } - foreach ($imageExt as $ext) { - $personsImages = glob(media_path("/persons/*_*{$ext}")); - if ($personsImages) { - foreach ($personsImages as $personsImage) { - if (is_file($personsImage)) { - unlink($personsImage); - } - } - } - } + $fileManager->deletePersonImagesSizes(); $persons = (new PersonModel())->findAll(); foreach ($persons as $person) { @@ -180,115 +168,12 @@ class SettingsController extends BaseController (new EpisodeCommentModel())->resetRepliesCount(); } - helper('media'); - - if ($this->request->getPost('rewrite_media') === 'yes') { - $imageExt = ['jpg', 'png', 'webp']; - // Delete all podcast image sizes to recreate them - $allPodcasts = (new PodcastModel())->findAll(); - foreach ($allPodcasts as $podcast) { - foreach ($imageExt as $ext) { - $podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}")); - - if ($podcastImages) { - foreach ($podcastImages as $podcastImage) { - if (is_file($podcastImage)) { - unlink($podcastImage); - } - } - } - } - } - - // Delete all person image sizes to recreate them - foreach ($imageExt as $ext) { - $personsImages = glob(media_path("/persons/*_*{$ext}")); - if ($personsImages) { - foreach ($personsImages as $personsImage) { - if (is_file($personsImage)) { - unlink($personsImage); - } - } - } - } - - $allImages = (new MediaModel('image'))->getAllOfType(); - foreach ($allImages as $image) { - if (str_starts_with((string) $image->file_path, 'podcasts')) { - if (str_ends_with((string) $image->file_path, 'banner.jpg') || str_ends_with( - (string) $image->file_path, - 'banner.png' - ) || str_ends_with((string) $image->file_path, 'banner.jpeg')) { - $image->sizes = config('Images') - ->podcastBannerSizes; - } else { - $image->sizes = config('Images') - ->podcastCoverSizes; - } - } elseif (str_starts_with((string) $image->file_path, 'persons')) { - $image->sizes = config('Images') - ->personAvatarSizes; - } else { - $image->sizes = []; - } - - $image->setFile(new File(media_path($image->file_path))); - - (new MediaModel('image'))->updateMedia($image); - } - - $allAudio = (new MediaModel('audio'))->getAllOfType(); - foreach ($allAudio as $audio) { - $audio->setFile(new File(media_path($audio->file_path))); - - (new MediaModel('audio'))->updateMedia($audio); - } - - $allTranscripts = (new MediaModel('transcript'))->getAllOfType(); - foreach ($allTranscripts as $transcript) { - $transcript->setFile(new File(media_path($transcript->file_path))); - - (new MediaModel('transcript'))->updateMedia($transcript); - } - - $allChapters = (new MediaModel('chapters'))->getAllOfType(); - foreach ($allChapters as $chapters) { - $chapters->setFile(new File(media_path($chapters->file_path))); - - (new MediaModel('chapters'))->updateMedia($chapters); - } - - $allVideos = (new MediaModel('video'))->getAllOfType(); - foreach ($allVideos as $video) { - $video->setFile(new File(media_path($video->file_path))); - - (new MediaModel('video'))->updateMedia($video); - } - - // reset avatar and banner image urls for each podcast actor - foreach ($allPodcasts as $podcast) { - $actorModel = new ActorModel(); - $actor = $actorModel->getActorById($podcast->actor_id); - - if ($actor !== null) { - // update values - $actor->avatar_image_url = $podcast->cover->federation_url; - $actor->avatar_image_mimetype = $podcast->cover->file_mimetype; - $actor->cover_image_url = $podcast->banner->federation_url; - $actor->cover_image_mimetype = $podcast->banner->file_mimetype; - - if ($actor->hasChanged()) { - $actorModel->update($actor->id, $actor); - } - } - } - } - if ($this->request->getPost('clear_cache') === 'yes') { cache()->clean(); } if ($this->request->getPost('rename_episodes_files') === 'yes') { + /** @var Audio[] $allAudio */ $allAudio = (new MediaModel('audio'))->getAllOfType(); foreach ($allAudio as $audio) { diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php index 020785b7..fe049e1c 100644 --- a/modules/Admin/Controllers/SoundbiteController.php +++ b/modules/Admin/Controllers/SoundbiteController.php @@ -15,10 +15,10 @@ use App\Entities\Episode; use App\Entities\Podcast; use App\Models\ClipModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Models\MediaModel; class SoundbiteController extends BaseController { diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php index cac087e5..074221eb 100644 --- a/modules/Admin/Controllers/VideoClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -15,10 +15,10 @@ use App\Entities\Episode; use App\Entities\Podcast; use App\Models\ClipModel; use App\Models\EpisodeModel; -use App\Models\MediaModel; use App\Models\PodcastModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; +use Modules\Media\Models\MediaModel; class VideoClipsController extends BaseController { diff --git a/modules/Admin/Language/ar/Episode.php b/modules/Admin/Language/ar/Episode.php index 99eed5ba..088c1355 100644 --- a/modules/Admin/Language/ar/Episode.php +++ b/modules/Admin/Language/ar/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/br/Episode.php b/modules/Admin/Language/br/Episode.php index 56e1c93d..8558fc6f 100644 --- a/modules/Admin/Language/br/Episode.php +++ b/modules/Admin/Language/br/Episode.php @@ -87,7 +87,7 @@ return [ image {ar golo} audio {an aodio} other {ar media} - } ({file_path}). Gallout a rit lemel kuit ar restr-mañ diouzh ar gantenn dre zorn.', + } ({file_key}). Gallout a rit lemel kuit ar restr-mañ diouzh ar gantenn dre zorn.', 'sameSlugError' => 'Bez ez eus eus ur rann gant ar berradur-mañ (slug) dija.', ], 'form' => [ diff --git a/modules/Admin/Language/ca/Episode.php b/modules/Admin/Language/ca/Episode.php index ed05a8d7..f8cde303 100644 --- a/modules/Admin/Language/ca/Episode.php +++ b/modules/Admin/Language/ca/Episode.php @@ -79,7 +79,7 @@ return [ audio {l\'àudio} other {el material} } de l\'episodi.', - 'deleteFileError' => 'No s\'ha pogut esborrar el fitxer {file_path} {type, select, + 'deleteFileError' => 'No s\'ha pogut esborrar el fitxer {file_key} {type, select, transcript {de la transcripció} chapters {dels episodis} image {de la portada} diff --git a/modules/Admin/Language/de/Episode.php b/modules/Admin/Language/de/Episode.php index dde86cdb..d98870ab 100644 --- a/modules/Admin/Language/de/Episode.php +++ b/modules/Admin/Language/de/Episode.php @@ -85,7 +85,7 @@ return [ image {Cover} audio {Audio} other {Medien} - }-Datei {file_path}. Sie können es manuell von der Festplatte entfernen.', + }-Datei {file_key}. Sie können es manuell von der Festplatte entfernen.', 'sameSlugError' => 'Eine Folge mit dem ausgewählten Slug existiert bereits.', ], 'form' => [ diff --git a/modules/Admin/Language/el/Episode.php b/modules/Admin/Language/el/Episode.php index 6bd5c6a1..b7e2bbc6 100644 --- a/modules/Admin/Language/el/Episode.php +++ b/modules/Admin/Language/el/Episode.php @@ -85,7 +85,7 @@ return [ image {καλύψτε} audio {ήχος} other {πολυμέσα} - } αρχείο {file_path}. Μπορείτε να το αφαιρέσετε χειροκίνητα από το δίσκο σας.', + } αρχείο {file_key}. Μπορείτε να το αφαιρέσετε χειροκίνητα από το δίσκο σας.', 'sameSlugError' => 'Ένα επεισόδιο με το επιλεγμένο slug υπάρχει ήδη.', ], 'form' => [ diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 98498bee..5ed5e3ac 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -86,7 +86,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/es/Episode.php b/modules/Admin/Language/es/Episode.php index 574759e9..d586f420 100644 --- a/modules/Admin/Language/es/Episode.php +++ b/modules/Admin/Language/es/Episode.php @@ -79,7 +79,7 @@ return [ audio {audio} other {media} }.', - 'deleteFileError' => 'Hubo un problema al tratar de eliminar el archivo {file_path} {type, select, + 'deleteFileError' => 'Hubo un problema al tratar de eliminar el archivo {file_key} {type, select, transcript {de la transcripción} chapters {de los episodios} image {de la portada} diff --git a/modules/Admin/Language/fa/Episode.php b/modules/Admin/Language/fa/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/fa/Episode.php +++ b/modules/Admin/Language/fa/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php index 51b8d4d7..5cf925c0 100644 --- a/modules/Admin/Language/fr/Episode.php +++ b/modules/Admin/Language/fr/Episode.php @@ -85,7 +85,7 @@ return [ image {couverture} audio {audio} other {média} - } fichier {file_path}. Vous pouvez le supprimer manuellement de votre disque.', + } fichier {file_key}. Vous pouvez le supprimer manuellement de votre disque.', 'sameSlugError' => 'Il existe déjà un épisode avec le slug choisi.', ], 'form' => [ diff --git a/modules/Admin/Language/gd/Episode.php b/modules/Admin/Language/gd/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/gd/Episode.php +++ b/modules/Admin/Language/gd/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/gl/Episode.php b/modules/Admin/Language/gl/Episode.php index aa6e2a88..7e28539c 100644 --- a/modules/Admin/Language/gl/Episode.php +++ b/modules/Admin/Language/gl/Episode.php @@ -85,7 +85,7 @@ return [ image {da imaxe} audio {do audio} other {do multimedia} - } {file_path}. Deberías eliminala manualmente do disco.', + } {file_key}. Deberías eliminala manualmente do disco.', 'sameSlugError' => 'Xa existe un episodio co id de url elexido.', ], 'form' => [ diff --git a/modules/Admin/Language/id/Episode.php b/modules/Admin/Language/id/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/id/Episode.php +++ b/modules/Admin/Language/id/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/it/Episode.php b/modules/Admin/Language/it/Episode.php index 6cb572a3..2e5a2673 100644 --- a/modules/Admin/Language/it/Episode.php +++ b/modules/Admin/Language/it/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/ko/Episode.php b/modules/Admin/Language/ko/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/ko/Episode.php +++ b/modules/Admin/Language/ko/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/nl/Episode.php b/modules/Admin/Language/nl/Episode.php index 60845744..7b3f428c 100644 --- a/modules/Admin/Language/nl/Episode.php +++ b/modules/Admin/Language/nl/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/nn-NO/Episode.php b/modules/Admin/Language/nn-NO/Episode.php index 12a3672a..ab6ebe91 100644 --- a/modules/Admin/Language/nn-NO/Episode.php +++ b/modules/Admin/Language/nn-NO/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/oc/Episode.php b/modules/Admin/Language/oc/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/oc/Episode.php +++ b/modules/Admin/Language/oc/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/pl/Episode.php b/modules/Admin/Language/pl/Episode.php index ee45c805..4cb167b3 100644 --- a/modules/Admin/Language/pl/Episode.php +++ b/modules/Admin/Language/pl/Episode.php @@ -86,7 +86,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/pt-BR/Episode.php b/modules/Admin/Language/pt-BR/Episode.php index 0f54fe01..fbd6d601 100644 --- a/modules/Admin/Language/pt-BR/Episode.php +++ b/modules/Admin/Language/pt-BR/Episode.php @@ -85,7 +85,7 @@ return [ image {capa} audio {áudio} other {mídia} - } {file_path}. Você pode removê-lo manualmente do seu disco.', + } {file_key}. Você pode removê-lo manualmente do seu disco.', 'sameSlugError' => 'Um episódio com o slug escolhido já existe.', ], 'form' => [ diff --git a/modules/Admin/Language/pt/Episode.php b/modules/Admin/Language/pt/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/pt/Episode.php +++ b/modules/Admin/Language/pt/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/ro/Episode.php b/modules/Admin/Language/ro/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/ro/Episode.php +++ b/modules/Admin/Language/ro/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/ru/Episode.php b/modules/Admin/Language/ru/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/ru/Episode.php +++ b/modules/Admin/Language/ru/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/sk/Episode.php b/modules/Admin/Language/sk/Episode.php index 69edd601..f44c663a 100644 --- a/modules/Admin/Language/sk/Episode.php +++ b/modules/Admin/Language/sk/Episode.php @@ -89,7 +89,7 @@ return [ image {obrázok} audio {zvuk} other {médiá} - } súbor {file_path}. Môžete ho z disku odstrániť ručne.', + } súbor {file_key}. Môžete ho z disku odstrániť ručne.', 'sameSlugError' => 'Epizóda s takýmto trvalým odkazom už existuje.', ], 'form' => [ diff --git a/modules/Admin/Language/sr_Latn/Episode.php b/modules/Admin/Language/sr_Latn/Episode.php index 91313a7c..713b517b 100644 --- a/modules/Admin/Language/sr_Latn/Episode.php +++ b/modules/Admin/Language/sr_Latn/Episode.php @@ -85,7 +85,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You may manually remove it from your disk.', + } file {file_key}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/sv/Episode.php b/modules/Admin/Language/sv/Episode.php index 4daf1a0b..98280f8d 100644 --- a/modules/Admin/Language/sv/Episode.php +++ b/modules/Admin/Language/sv/Episode.php @@ -85,7 +85,7 @@ return [ image {omslag} audio {ljud} other {media} - } fil {file_path}. Du kan manuellt ta bort den från disken.', + } fil {file_key}. Du kan manuellt ta bort den från disken.', 'sameSlugError' => 'Ett avsnitt med den valda slug finns redan.', ], 'form' => [ diff --git a/modules/Admin/Language/zh-Hans/Episode.php b/modules/Admin/Language/zh-Hans/Episode.php index 736faa96..6495f1a3 100644 --- a/modules/Admin/Language/zh-Hans/Episode.php +++ b/modules/Admin/Language/zh-Hans/Episode.php @@ -85,7 +85,7 @@ return [ image {封面} audio {音频} other {媒体} - } 文件 {file_path}。您可以手动将其从磁盘删除。', + } 文件 {file_key}。您可以手动将其从磁盘删除。', 'sameSlugError' => '选中的剧集已存在。', ], 'form' => [ diff --git a/modules/Analytics/Config/Analytics.php b/modules/Analytics/Config/Analytics.php index 2d08ad04..c4f5ca59 100644 --- a/modules/Analytics/Config/Analytics.php +++ b/modules/Analytics/Config/Analytics.php @@ -59,9 +59,7 @@ class Analytics extends BaseConfig */ public function getAudioUrl(Episode $episode, array $params): string { - helper(['media', 'setting']); - - $audioFileURI = new URI(media_base_url($episode->audio->file_path)); + $audioFileURI = new URI(service('file_manager')->getUrl($episode->audio->file_key)); $audioFileURI->setQueryArray($params); // Wrap episode url with OP3 if episode is public and OP3 is enabled on this podcast diff --git a/modules/Analytics/Models/AnalyticsPodcastModel.php b/modules/Analytics/Models/AnalyticsPodcastModel.php index fee1ae44..f433bdb5 100644 --- a/modules/Analytics/Models/AnalyticsPodcastModel.php +++ b/modules/Analytics/Models/AnalyticsPodcastModel.php @@ -13,9 +13,9 @@ declare(strict_types=1); namespace Modules\Analytics\Models; use App\Entities\Media\BaseMedia; -use App\Models\MediaModel; use CodeIgniter\Model; use Modules\Analytics\Entities\AnalyticsPodcasts; +use Modules\Media\Models\MediaModel; class AnalyticsPodcastModel extends Model { diff --git a/modules/Auth/Commands/RolesDoc.php b/modules/Auth/Commands/RolesDoc.php index fdd8833e..35475783 100644 --- a/modules/Auth/Commands/RolesDoc.php +++ b/modules/Auth/Commands/RolesDoc.php @@ -171,11 +171,11 @@ class RolesDoc extends BaseCommand return $newFileContents; } - private function detectLocaleFromPath($filePath): string + private function detectLocaleFromPath($fileKey): string { preg_match( '~docs\/src\/(?:([a-z]{2}(?:-[A-Za-z]{2,})?)\/)getting-started\/auth\.md~', - (string) $filePath, + (string) $fileKey, $match ); diff --git a/modules/Fediverse/Helpers/fediverse_helper.php b/modules/Fediverse/Helpers/fediverse_helper.php index f4500585..8a86486d 100644 --- a/modules/Fediverse/Helpers/fediverse_helper.php +++ b/modules/Fediverse/Helpers/fediverse_helper.php @@ -362,7 +362,7 @@ if (! function_exists('linkify')) { $text = match ($protocol) { 'http', 'https' => preg_replace_callback( '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(? + */ + public array $fileManagers = [ + 'fs' => FS::class, + 's3' => S3::class, + ]; + + /** + * @var array + */ + public array $s3 = [ + 'bucket' => 'castopod', + 'key' => '', + 'secret' => '', + 'region' => '', + 'protocol' => '', + 'endpoint' => '', + 'debug' => false, + 'path_style_endpoint' => false, + ]; + + /** + * -------------------------------------------------------------------------- + * Media Base URL + * -------------------------------------------------------------------------- + * + * URL to your media root. Typically this will be your base URL, + * WITH a trailing slash: + * + * http://cdn.example.com/ + */ + public string $baseURL = 'http://localhost:8080/'; + + /** + * -------------------------------------------------------------------------- + * Media root folder + * -------------------------------------------------------------------------- + * Defines the root folder for media files storage + */ + public string $root = 'media'; + + /** + * @var array + */ + public array $folders = [ + 'podcasts' => 'podcasts', + 'persons' => 'persons', + ]; +} diff --git a/modules/Media/Config/Services.php b/modules/Media/Config/Services.php new file mode 100644 index 00000000..bb66d217 --- /dev/null +++ b/modules/Media/Config/Services.php @@ -0,0 +1,42 @@ +fileManagers[$config->fileManager]; + + $fileManager = new $fileManagerClass($config); + + if ($fileManager instanceof FileManagerInterface) { + return $fileManager; + } + + throw new Exception('File Manager service must extend FileManagerInterface'); + } +} diff --git a/app/Database/Migrations/2021-05-29-120000_add_media.php b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php similarity index 95% rename from app/Database/Migrations/2021-05-29-120000_add_media.php rename to modules/Media/Database/Migrations/2021-05-29-120000_add_media.php index 732f51e2..9ddef693 100644 --- a/app/Database/Migrations/2021-05-29-120000_add_media.php +++ b/modules/Media/Database/Migrations/2021-05-29-120000_add_media.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Database\Migrations; +namespace Media\Database\Migrations; use CodeIgniter\Database\Migration; @@ -22,7 +22,7 @@ class AddMedia extends Migration 'unsigned' => true, 'auto_increment' => true, ], - 'file_path' => [ + 'file_key' => [ 'type' => 'VARCHAR', 'constraint' => 255, ], @@ -72,7 +72,7 @@ class AddMedia extends Migration ]); $this->forge->addKey('id', true); - $this->forge->addUniqueKey('file_path'); + $this->forge->addUniqueKey('file_key'); $this->forge->addForeignKey('uploaded_by', 'users', 'id'); $this->forge->addForeignKey('updated_by', 'users', 'id'); $this->forge->createTable('media'); diff --git a/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php new file mode 100644 index 00000000..840b851f --- /dev/null +++ b/modules/Media/Database/Migrations/2022-30-12-180000_rename_media_file_path.php @@ -0,0 +1,40 @@ + [ + 'name' => 'file_key', + 'type' => 'VARCHAR', + 'constraint' => 255, + ], + ]; + $this->forge->modifyColumn('media', $fields); + } + + public function down(): void + { + $fields = [ + 'file_key' => [ + 'name' => 'file_key', + 'type' => 'VARCHAR', + 'constraint' => 255, + ], + ]; + $this->forge->modifyColumn('media', $fields); + } +} diff --git a/app/Entities/Media/Audio.php b/modules/Media/Entities/Audio.php similarity index 93% rename from app/Entities/Media/Audio.php rename to modules/Media/Entities/Audio.php index 29b304b3..3f494e69 100644 --- a/app/Entities/Media/Audio.php +++ b/modules/Media/Entities/Audio.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; use CodeIgniter\Files\File; use JamesHeinrich\GetID3\GetID3; @@ -39,7 +39,7 @@ class Audio extends BaseMedia parent::setFile($file); $getID3 = new GetID3(); - $audioMetadata = $getID3->analyze(media_path($this->file_path)); + $audioMetadata = $getID3->analyze($file->getRealPath()); // remove heavy image data from metadata unset($audioMetadata['comments']['picture']); diff --git a/app/Entities/Media/BaseMedia.php b/modules/Media/Entities/BaseMedia.php similarity index 60% rename from app/Entities/Media/BaseMedia.php rename to modules/Media/Entities/BaseMedia.php index 4bb9b0a1..d293ef6b 100644 --- a/app/Entities/Media/BaseMedia.php +++ b/modules/Media/Entities/BaseMedia.php @@ -8,20 +8,22 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; -use App\Models\MediaModel; -use CodeIgniter\Database\BaseResult; 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; /** * @property int $id - * @property string $file_path + * @property string $file_key * @property string $file_url + * @property string $file_name * @property string $file_directory * @property string $file_extension - * @property string $file_name * @property int $file_size * @property string $file_mimetype * @property array|null $file_metadata @@ -45,8 +47,7 @@ class BaseMedia extends Entity */ protected $casts = [ 'id' => 'integer', - 'file_extension' => 'string', - 'file_path' => 'string', + 'file_key' => 'string', 'file_size' => 'int', 'file_mimetype' => 'string', 'file_metadata' => '?json-array', @@ -57,27 +58,41 @@ class BaseMedia extends Entity 'updated_by' => 'integer', ]; + protected FileManagerInterface $fileManager; + /** * @param array|null $data + * @param 'fs'|'s3'|null $fileManager */ - public function __construct(array $data = null) + public function __construct(array $data = null, string $fileManager = 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(); } public function initFileProperties(): void { - if ($this->file_path !== '') { - helper('media'); + if ($this->file_key !== '') { [ 'filename' => $filename, 'dirname' => $dirname, 'extension' => $extension, - ] = pathinfo($this->file_path); + ] = pathinfo($this->file_key); - $this->attributes['file_url'] = media_base_url($this->file_path); + $this->attributes['file_url'] = $this->fileManager->getUrl($this->file_key); $this->attributes['file_name'] = $filename; $this->attributes['file_directory'] = $dirname; $this->attributes['file_extension'] = $extension; @@ -86,49 +101,49 @@ class BaseMedia extends Entity public function setFile(File $file): self { - helper('media'); - $this->attributes['type'] = $this->type; $this->attributes['file_mimetype'] = $file->getMimeType(); $this->attributes['file_metadata'] = json_encode(lstat((string) $file), JSON_INVALID_UTF8_IGNORE); - $this->attributes['file_path'] = save_media( - $file, - $this->attributes['file_directory'], - $this->attributes['file_name'] - ); - if ($filesize = filesize(media_path($this->file_path))) { + + if ($filesize = $file->getSize()) { $this->attributes['file_size'] = $filesize; } + $this->attributes['file'] = $file; + return $this; } - public function deleteFile(): bool + public function saveFile(): bool { - helper('media'); - return unlink(media_path($this->file_path)); + if (! $this->attributes['file'] || ! $this->file_key) { + return false; + } + + $this->attributes['file_key'] = $this->fileManager->save($this->attributes['file'], $this->file_key); + + return true; } - public function delete(): bool|BaseResult + public function deleteFile(): bool { - $mediaModel = new MediaModel(); - return $mediaModel->delete($this->id); + return $this->fileManager->delete($this->file_key); } public function rename(): bool { - $newFilePath = $this->file_directory . '/' . (new File(''))->getRandomName() . '.' . $this->file_extension; + $newFileKey = $this->file_directory . '/' . (new File(''))->getRandomName() . '.' . $this->file_extension; $db = db_connect(); $db->transStart(); if (! (new MediaModel())->update($this->id, [ - 'file_path' => $newFilePath, + 'file_key' => $newFileKey, ])) { return false; } - if (! rename(media_path($this->file_path), media_path($newFilePath))) { + if (! $this->fileManager->rename($this->file_key, $newFileKey)) { $db->transRollback(); return false; } diff --git a/app/Entities/Media/Chapters.php b/modules/Media/Entities/Chapters.php similarity index 88% rename from app/Entities/Media/Chapters.php rename to modules/Media/Entities/Chapters.php index 227cb5af..fe66c59e 100644 --- a/app/Entities/Media/Chapters.php +++ b/modules/Media/Entities/Chapters.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; class Chapters extends BaseMedia { diff --git a/app/Entities/Media/Document.php b/modules/Media/Entities/Document.php similarity index 88% rename from app/Entities/Media/Document.php rename to modules/Media/Entities/Document.php index 705f2a3a..7a540862 100644 --- a/app/Entities/Media/Document.php +++ b/modules/Media/Entities/Document.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; class Document extends BaseMedia { diff --git a/modules/Media/Entities/Image.php b/modules/Media/Entities/Image.php new file mode 100644 index 00000000..9f45bacc --- /dev/null +++ b/modules/Media/Entities/Image.php @@ -0,0 +1,169 @@ +> + */ + protected array $sizes = []; + + public function initFileProperties(): void + { + parent::initFileProperties(); + + if ($this->file_key !== '' && $this->file_metadata !== null && array_key_exists( + 'sizes', + $this->file_metadata + )) { + $this->sizes = $this->file_metadata['sizes']; + $this->initSizeProperties(); + } + } + + public function initSizeProperties(): bool + { + 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 . '_mimetype'} = $mimetype; + } + + return true; + } + + /** + * @param array $data + */ + public function setAttributes(array $data): self + { + parent::setAttributes($data); + + if ($this->attributes === []) { + return $this; + } + + if ($this->file_metadata !== [] && array_key_exists('sizes', $this->file_metadata)) { + $this->sizes = $this->file_metadata['sizes']; + $this->attributes['sizes'] = $this->file_metadata['sizes']; + $this->initFileProperties(); + $this->initSizeProperties(); + } + + return $this; + } + + public function setFile(File $file): self + { + parent::setFile($file); + + if ($this->file_mimetype === 'image/jpeg' && $metadata = @exif_read_data( + $file->getRealPath(), + null, + true + )) { + $metadata['sizes'] = $this->attributes['sizes']; + $this->attributes['file_size'] = $metadata['FILE']['FileSize']; + } else { + $metadata = [ + 'sizes' => $this->attributes['sizes'], + ]; + } + + $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); + + return $this; + } + + public function saveFile(): bool + { + if ($this->attributes['sizes'] !== []) { + $this->initFileProperties(); + $this->saveSizes(); + } + + return parent::saveFile(); + } + + public function deleteFile(): bool + { + if (parent::deleteFile()) { + return $this->deleteSizes(); + } + + return false; + } + + public function saveSizes(): void + { + $tempImagePath = ''; + if (! array_key_exists('file', $this->attributes) && $this->file_key) { + // no original file instance set to save sizes from + + // download image temporarily to generate sizes from + $tempImagePath = (string) tempnam(WRITEPATH . 'temp', 'img_'); + $imageContent = $this->fileManager->getFileContents($this->file_key); + file_put_contents($tempImagePath, $imageContent); + + $this->attributes['file'] = new File($tempImagePath, true); + } + + // save derived sizes + $imageService = Services::image(); + + foreach ($this->sizes as $name => $size) { + $tempFilePath = tempnam(WRITEPATH . 'temp', 'img_'); + $imageService + ->withFile($this->attributes['file']->getRealPath()) + ->resize($size['width'], $size['height']) + ->save($tempFilePath); + + $newImage = new File($tempFilePath, true); + + $this->fileManager + ->save($newImage, $this->{$name . '_key'}); + } + + if ($tempImagePath !== '') { + unlink($tempImagePath); + } + } + + private function deleteSizes(): bool + { + // delete all derived sizes + foreach (array_keys($this->sizes) as $name) { + $pathProperty = $name . '_key'; + + if (! $this->fileManager->delete($this->{$pathProperty})) { + return false; + } + } + + return true; + } +} diff --git a/modules/Media/Entities/Transcript.php b/modules/Media/Entities/Transcript.php new file mode 100644 index 00000000..4ad7cdcb --- /dev/null +++ b/modules/Media/Entities/Transcript.php @@ -0,0 +1,104 @@ +file_key && $this->file_metadata && array_key_exists('json_key', $this->file_metadata)) { + helper('media'); + + $this->json_key = $this->file_metadata['json_key']; + $this->json_url = $this->fileManager + ->getUrl($this->json_key); + } + } + + public function setFile(File $file): self + { + parent::setFile($file); + + $metadata = lstat((string) $file) ?? []; + + helper('filesystem'); + + $fileKeyWithoutExt = path_without_ext($this->file_key); + + $jsonfileKey = $fileKeyWithoutExt . '.json'; + + // set metadata (generated json file path) + $this->json_key = $jsonfileKey; + $metadata['json_key'] = $this->json_key; + + $this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE); + + $this->file = $file; + + return $this; + } + + public function saveFile(): bool + { + $this->saveJsonTranscript(); + + return parent::saveFile(); + } + + public function deleteFile(): bool + { + if (! parent::deleteFile()) { + return false; + } + + if ($this->json_key) { + return $this->fileManager->delete($this->json_key); + } + + return true; + } + + private function saveJsonTranscript(): bool + { + $srtContent = file_get_contents($this->file->getRealPath()); + + $transcriptParser = new TranscriptParser(); + + if ($srtContent === false) { + return false; + } + + if (! $transcriptJson = $transcriptParser->loadString($srtContent)->parseSrt()) { + return false; + } + + $tempFilePath = WRITEPATH . 'uploads/' . $this->file->getRandomName(); + file_put_contents($tempFilePath, $transcriptJson); + + $newTranscriptJson = new File($tempFilePath, true); + + $this->fileManager + ->save($newTranscriptJson, $this->json_key); + + return true; + } +} diff --git a/app/Entities/Media/Video.php b/modules/Media/Entities/Video.php similarity index 87% rename from app/Entities/Media/Video.php rename to modules/Media/Entities/Video.php index dd7f8658..5c2d692b 100644 --- a/app/Entities/Media/Video.php +++ b/modules/Media/Entities/Video.php @@ -8,7 +8,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Entities\Media; +namespace Modules\Media\Entities; class Video extends BaseMedia { diff --git a/modules/Media/FileManagers/FS.php b/modules/Media/FileManagers/FS.php new file mode 100644 index 00000000..80c03547 --- /dev/null +++ b/modules/Media/FileManagers/FS.php @@ -0,0 +1,135 @@ +config = $config; + } + + /** + * Saves a file to the corresponding folder in `public/media` + */ + public function save(File $file, string $path): string | false + { + if ((pathinfo($path, PATHINFO_EXTENSION) === '') && (($extension = $file->getExtension()) !== '')) { + $path = $path . '.' . $extension; + } + + $mediaRoot = $this->config->root; + + if (! file_exists(dirname($mediaRoot . '/' . $path))) { + mkdir(dirname($mediaRoot . '/' . $path), 0777, true); + } + + if (! file_exists(dirname($mediaRoot . '/' . $path) . '/index.html')) { + touch(dirname($mediaRoot . '/' . $path) . '/index.html'); + } + + try { + // move to media folder, overwrite file if already existing + $file->move($mediaRoot . '/', $path, true); + } catch (Exception) { + return false; + } + + return $path; + } + + public function delete(string $key): bool + { + helper('media'); + + return unlink(media_path($key)); + } + + public function getUrl(string $key): string + { + $appConfig = config('App'); + $mediaBaseUrl = $this->config->baseURL === '' ? $appConfig->baseURL : $this->config->baseURL; + + return rtrim((string) $mediaBaseUrl, '/') . + '/' . + $this->config->root . + '/' . + $key; + } + + public function rename(string $oldKey, string $newKey): bool + { + helper('media'); + + return rename(media_path($oldKey), media_path($newKey)); + } + + public function getFileContents(string $key): string + { + helper('media'); + + return (string) file_get_contents(media_path($key)); + } + + public function getFileInput(string $key): string + { + helper('media'); + + return media_path($key); + } + + public function deletePodcastImageSizes(string $podcastHandle): bool + { + helper('media'); + + $allPodcastImagesPaths = []; + foreach (['jpg', 'png', 'webp'] as $ext) { + $images = glob(media_path("/podcasts/{$podcastHandle}/*_*{$ext}")); + + if (! $images) { + return false; + } + + array_push($allPodcastImagesPaths, ...$images); + } + + foreach ($allPodcastImagesPaths as $podcastImagePath) { + if (is_file($podcastImagePath)) { + unlink($podcastImagePath); + } + } + + return true; + } + + public function deletePersonImagesSizes(): bool + { + helper('media'); + + $allPersonsImagesPaths = []; + foreach (['jpg', 'png', 'webp'] as $ext) { + $images = glob(media_path("/persons/*_*{$ext}")); + + if (! $images) { + return false; + } + + array_push($allPersonsImagesPaths, ...$images); + } + + foreach ($allPersonsImagesPaths as $personImagePath) { + if (is_file($personImagePath)) { + unlink($personImagePath); + } + } + + return true; + } +} diff --git a/modules/Media/FileManagers/FileManagerInterface.php b/modules/Media/FileManagers/FileManagerInterface.php new file mode 100644 index 00000000..3b53afa7 --- /dev/null +++ b/modules/Media/FileManagers/FileManagerInterface.php @@ -0,0 +1,26 @@ +s3 = new S3Client([ + 'version' => 'latest', + 'region' => $config->s3['region'], + 'endpoint' => $config->s3['endpoint'], + 'credentials' => new Credentials((string) $config->s3['key'], (string) $config->s3['secret']), + 'debug' => $config->s3['debug'], + 'use_path_style_endpoint' => $config->s3['path_style_endpoint'], + ]); + + // create bucket if it does not already exist + if (! $this->s3->doesBucketExist((string) $this->config->s3['bucket'])) { + try { + $this->s3->createBucket([ + 'Bucket' => $this->config->s3['bucket'], + ]); + } catch (Exception $exception) { + log_message('critical', $exception->getMessage()); + } + } + } + + public function save(File $file, string $key): string|false + { + try { + $this->s3->putObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $key, + 'SourceFile' => $file, + ]); + } catch (Exception) { + return false; + } + + // delete file after storage in s3 + unlink($file->getRealPath()); + + return $key; + } + + public function delete(string $key): bool + { + try { + $this->s3->deleteObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $key, + ]); + } catch (Exception) { + return false; + } + + return true; + } + + public function getUrl(string $key): string + { + $url = new URI((string) $this->config->s3['endpoint']); + + if ($this->config->s3['path_style_endpoint'] === true) { + $url->setPath($this->config->s3['bucket'] . '/' . $key); + return (string) $url; + } + + $url->setHost($this->config->s3['bucket'] . '.' . $url->getHost()); + $url->setPath($key); + return (string) $url; + } + + public function rename(string $oldKey, string $newKey): bool + { + try { + // copy old object with new key + $this->s3->copyObject([ + 'Bucket' => $this->config->s3['bucket'], + 'CopySource' => $this->config->s3['bucket'] . '/' . $oldKey, + 'Key' => $newKey, + ]); + } catch (Exception) { + return false; + } + + // delete old object + return $this->delete($oldKey); + } + + public function getFileContents(string $key): string + { + $result = $this->s3->getObject([ + 'Bucket' => $this->config->s3['bucket'], + 'Key' => $key, + ]); + + return (string) $result->get('Body'); + } + + public function getFileInput(string $key): string + { + return $this->getUrl($key); + } + + public function deletePodcastImageSizes(string $podcastHandle): bool + { + $results = $this->s3->getPaginator('ListObjectsV2', [ + 'Bucket' => $this->config->s3['bucket'], + 'Prefix' => 'podcasts/' . $podcastHandle . '/', + ]); + + $keys = []; + foreach ($results as $result) { + $key = array_map(static function ($object) { + return $object['Key']; + }, $result['Contents']); + + array_push($keys, ...preg_grep("~^podcasts\/{$podcastHandle}\/.*_.*.\.(jpg|png|webp)$~", $key)); + } + + $objectsToDelete = array_map(static function ($key): array { + return [ + 'Key' => $key, + ]; + }, $keys); + + if ($objectsToDelete === []) { + return true; + } + + try { + $this->s3->deleteObjects([ + 'Bucket' => $this->config->s3['bucket'], + 'Delete' => [ + 'Objects' => $objectsToDelete, + ], + ]); + } catch (Exception) { + return false; + } + + return true; + } + + public function deletePersonImagesSizes(): bool + { + $objects = $this->s3->getIterator('ListObjectsV2', [ + 'Bucket' => $this->config->s3['bucket'], + 'prefix' => 'persons/', + ]); + + $objectsKeys = array_map(static function ($object) { + return $object['Key']; + }, iterator_to_array($objects)); + + $podcastImageKeys = preg_grep("~^persons\/.*_.*.\.(jpg|png|webp)$~", $objectsKeys); + return (bool) $podcastImageKeys; + } +} diff --git a/modules/Media/Helpers/filesystem_helper.php b/modules/Media/Helpers/filesystem_helper.php new file mode 100644 index 00000000..0cff140c --- /dev/null +++ b/modules/Media/Helpers/filesystem_helper.php @@ -0,0 +1,27 @@ +getExtension()) !== '') { - $filename = $filename . '.' . $extension; - } - - $mediaRoot = config('App') - ->mediaRoot . '/' . $folder; - - if (! file_exists($mediaRoot)) { - mkdir($mediaRoot, 0777, true); - } - - if (! file_exists($mediaRoot . '/index.html')) { - touch($mediaRoot . '/index.html'); - } - - // move to media folder, overwrite file if already existing - $file->move($mediaRoot . '/', $filename, true); - - return $folder . '/' . $filename; - } -} - if (! function_exists('download_file')) { function download_file(string $fileUrl, string $mimetype = ''): File { @@ -86,10 +57,10 @@ if (! function_exists('download_file')) { bin2hex(random_bytes(10)) . '.' . $extension; - $tmpFilePath = WRITEPATH . 'uploads/' . $tmpFilename; - file_put_contents($tmpFilePath, $response->getBody()); + $tmpfileKey = WRITEPATH . 'uploads/' . $tmpFilename; + file_put_contents($tmpfileKey, $response->getBody()); - return new File($tmpFilePath); + return new File($tmpfileKey); } } @@ -108,32 +79,6 @@ if (! function_exists('media_path')) { $uri = trim($uri, '/'); - return config('App')->mediaRoot . '/' . $uri; - } -} - -if (! function_exists('media_base_url')) { - /** - * Return the media base URL to use in views - * - * @param string|string[] $uri URI string or array of URI segments - */ - function media_base_url(string | array $uri = ''): string - { - // convert segment array to string - if (is_array($uri)) { - $uri = implode('/', $uri); - } - - $uri = trim($uri, '/'); - - $appConfig = config('App'); - $mediaBaseUrl = $appConfig->mediaBaseURL === '' ? $appConfig->baseURL : $appConfig->mediaBaseURL; - - return rtrim((string) $mediaBaseUrl, '/') . - '/' . - $appConfig->mediaRoot . - '/' . - $uri; + return config('Media')->root . '/' . $uri; } } diff --git a/app/Models/MediaModel.php b/modules/Media/Models/MediaModel.php similarity index 77% rename from app/Models/MediaModel.php rename to modules/Media/Models/MediaModel.php index c35263ba..38bdb1f4 100644 --- a/app/Models/MediaModel.php +++ b/modules/Media/Models/MediaModel.php @@ -8,18 +8,18 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Models; +namespace Modules\Media\Models; -use App\Entities\Media\Audio; -use App\Entities\Media\Chapters; -use App\Entities\Media\Document; -use App\Entities\Media\Image; -use App\Entities\Media\Transcript; -use App\Entities\Media\Video; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Model; use CodeIgniter\Validation\ValidationInterface; +use Modules\Media\Entities\Audio; +use Modules\Media\Entities\Chapters; +use Modules\Media\Entities\Document; +use Modules\Media\Entities\Image; +use Modules\Media\Entities\Transcript; +use Modules\Media\Entities\Video; class MediaModel extends Model { @@ -52,7 +52,7 @@ class MediaModel extends Model */ protected $allowedFields = [ 'id', - 'file_path', + 'file_key', 'file_size', 'file_mimetype', 'file_metadata', @@ -86,26 +86,14 @@ class MediaModel extends Model ConnectionInterface &$db = null, ValidationInterface $validation = null ) { - switch ($fileType) { - case 'audio': - $this->returnType = Audio::class; - break; - case 'video': - $this->returnType = Video::class; - break; - case 'image': - $this->returnType = Image::class; - break; - case 'transcript': - $this->returnType = Transcript::class; - break; - case 'chapters': - $this->returnType = Chapters::class; - break; - default: - // do nothing, keep Document class as default - break; - } + $this->returnType = match ($fileType) { + 'audio' => Audio::class, + 'video' => Video::class, + 'image' => Image::class, + 'transcript' => Transcript::class, + 'chapters' => Chapters::class, + default => Document::class + }; parent::__construct($db, $validation); } @@ -135,8 +123,15 @@ class MediaModel extends Model */ public function saveMedia(object $media): int | false { + // save file first + if (! $media->saveFile()) { + return false; + } + // insert record in database if (! $mediaId = $this->insert($media, true)) { + $this->db->transRollback(); + return false; } @@ -148,6 +143,11 @@ class MediaModel extends Model */ public function updateMedia(object $media): bool { + // save file first + if (! $media->saveFile()) { + return false; + } + return $this->update($media->id, $media); } @@ -166,9 +166,14 @@ class MediaModel extends Model return $result; } - public function deleteMedia(object $media): bool|BaseResult + /** + * @param Document|Audio|Video|Image|Transcript|Chapters $media + */ + public function deleteMedia($media): bool|BaseResult { - $media->deleteFile(); + if (! $media->deleteFile()) { + return false; + } return $this->delete($media->id); } diff --git a/app/Libraries/TranscriptParser.php b/modules/Media/TranscriptParser.php similarity index 99% rename from app/Libraries/TranscriptParser.php rename to modules/Media/TranscriptParser.php index b0f7f364..3034e388 100644 --- a/app/Libraries/TranscriptParser.php +++ b/modules/Media/TranscriptParser.php @@ -10,7 +10,7 @@ declare(strict_types=1); * @link https://castopod.org/ */ -namespace App\Libraries; +namespace Modules\Media; use stdClass; diff --git a/package.json b/package.json index 1b8cdff8..5490d8c4 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ "dependencies": { "@amcharts/amcharts4": "^4.10.34", "@amcharts/amcharts4-geodata": "^4.1.26", - "@codemirror/commands": "^6.2.1", + "@codemirror/commands": "^6.2.2", "@codemirror/lang-xml": "^6.0.2", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.2.0", - "@codemirror/view": "^6.9.1", - "@floating-ui/dom": "^1.2.1", + "@codemirror/view": "^6.9.2", + "@floating-ui/dom": "^1.2.4", "@github/clipboard-copy-element": "^1.1.2", "@github/hotkey": "^2.0.1", "@github/markdown-toolbar-element": "^2.1.1", @@ -48,8 +48,8 @@ "leaflet.markercluster": "^1.5.3", "lit": "^2.6.1", "marked": "^4.2.12", - "wavesurfer.js": "^6.4.0", - "xml-formatter": "^3.2.0" + "wavesurfer.js": "^6.5.2", + "xml-formatter": "^3.3.2" }, "devDependencies": { "@commitlint/cli": "^17.4.4", @@ -61,22 +61,22 @@ "@tailwindcss/forms": "^0.5.3", "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.9", - "@types/leaflet": "^1.9.1", + "@types/leaflet": "^1.9.2", "@types/marked": "^4.0.8", "@types/wavesurfer.js": "^6.0.3", - "@typescript-eslint/eslint-plugin": "^5.53.0", - "@typescript-eslint/parser": "^5.53.0", + "@typescript-eslint/eslint-plugin": "^5.55.0", + "@typescript-eslint/parser": "^5.55.0", "all-contributors-cli": "^6.24.0", "commitizen": "^4.3.0", "cross-env": "^7.0.3", "cssnano": "^5.1.15", "cz-conventional-changelog": "^3.3.0", - "eslint": "^8.34.0", - "eslint-config-prettier": "^8.6.0", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.7.0", "eslint-plugin-prettier": "^4.2.1", "husky": "^8.0.3", "is-ci": "^3.0.1", - "lint-staged": "^13.1.2", + "lint-staged": "^13.2.0", "postcss": "^8.4.21", "postcss-import": "^15.1.0", "postcss-nesting": "^11.2.1", @@ -84,13 +84,13 @@ "postcss-reporter": "^7.0.5", "prettier": "2.8.4", "prettier-plugin-organize-imports": "^3.2.2", - "semantic-release": "^20.1.0", + "semantic-release": "^20.1.1", "stylelint": "^15.2.0", "stylelint-config-standard": "^30.0.1", "svgo": "^3.0.2", "tailwindcss": "^3.2.7", "typescript": "^4.9.5", - "vite": "4.1.3", + "vite": "^4.1.4", "vite-plugin-pwa": "^0.14.4", "workbox-build": "^6.5.4", "workbox-core": "^6.5.4", diff --git a/phpstan.neon b/phpstan.neon index 4c46c3fa..8b88ce7b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,6 +11,7 @@ parameters: - modules/Analytics/Helpers - modules/Auth/Helpers - modules/Fediverse/Helpers + - modules/Media/Helpers - modules/PremiumPodcasts/Helpers - vendor/codeigniter4/framework/system/Helpers - vendor/codeigniter4/settings/src/Helpers @@ -28,5 +29,5 @@ parameters: ignoreErrors: - '#Cannot access property [\$a-z_]+ on ((array\|)?object)#' - '#^Call to an undefined method CodeIgniter\\Database\\ConnectionInterface#' - - '#^Access to an undefined property App\\Entities\\Media\\Image#' + - '#^Access to an undefined property Modules\\Media\\Entities\\Image#' - '#^Call to an undefined method CodeIgniter\\HTTP\\RequestInterface#' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad6a2cca..abd13945 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,14 +3,14 @@ lockfileVersion: 5.4 specifiers: "@amcharts/amcharts4": ^4.10.34 "@amcharts/amcharts4-geodata": ^4.1.26 - "@codemirror/commands": ^6.2.1 + "@codemirror/commands": ^6.2.2 "@codemirror/lang-xml": ^6.0.2 "@codemirror/language": ^6.6.0 "@codemirror/state": ^6.2.0 - "@codemirror/view": ^6.9.1 + "@codemirror/view": ^6.9.2 "@commitlint/cli": ^17.4.4 "@commitlint/config-conventional": ^17.4.4 - "@floating-ui/dom": ^1.2.1 + "@floating-ui/dom": ^1.2.4 "@github/clipboard-copy-element": ^1.1.2 "@github/hotkey": ^2.0.1 "@github/markdown-toolbar-element": ^2.1.1 @@ -23,11 +23,11 @@ specifiers: "@tailwindcss/line-clamp": ^0.4.2 "@tailwindcss/nesting": 0.0.0-insiders.565cd3e "@tailwindcss/typography": ^0.5.9 - "@types/leaflet": ^1.9.1 + "@types/leaflet": ^1.9.2 "@types/marked": ^4.0.8 "@types/wavesurfer.js": ^6.0.3 - "@typescript-eslint/eslint-plugin": ^5.53.0 - "@typescript-eslint/parser": ^5.53.0 + "@typescript-eslint/eslint-plugin": ^5.55.0 + "@typescript-eslint/parser": ^5.55.0 "@vime/core": ^5.4.0 all-contributors-cli: ^6.24.0 choices.js: ^10.2.0 @@ -36,15 +36,15 @@ specifiers: cross-env: ^7.0.3 cssnano: ^5.1.15 cz-conventional-changelog: ^3.3.0 - eslint: ^8.34.0 - eslint-config-prettier: ^8.6.0 + eslint: ^8.36.0 + eslint-config-prettier: ^8.7.0 eslint-plugin-prettier: ^4.2.1 flatpickr: ^4.6.13 husky: ^8.0.3 is-ci: ^3.0.1 leaflet: ^1.9.3 leaflet.markercluster: ^1.5.3 - lint-staged: ^13.1.2 + lint-staged: ^13.2.0 lit: ^2.6.1 marked: ^4.2.12 postcss: ^8.4.21 @@ -54,30 +54,30 @@ specifiers: postcss-reporter: ^7.0.5 prettier: 2.8.4 prettier-plugin-organize-imports: ^3.2.2 - semantic-release: ^20.1.0 + semantic-release: ^20.1.1 stylelint: ^15.2.0 stylelint-config-standard: ^30.0.1 svgo: ^3.0.2 tailwindcss: ^3.2.7 typescript: ^4.9.5 - vite: 4.1.3 + vite: ^4.1.4 vite-plugin-pwa: ^0.14.4 - wavesurfer.js: ^6.4.0 + wavesurfer.js: ^6.5.2 workbox-build: ^6.5.4 workbox-core: ^6.5.4 workbox-routing: ^6.5.4 workbox-strategies: ^6.5.4 - xml-formatter: ^3.2.0 + xml-formatter: ^3.3.2 dependencies: "@amcharts/amcharts4": 4.10.34 "@amcharts/amcharts4-geodata": 4.1.26 - "@codemirror/commands": 6.2.1 - "@codemirror/lang-xml": 6.0.2_@codemirror+view@6.9.1 + "@codemirror/commands": 6.2.2 + "@codemirror/lang-xml": 6.0.2_@codemirror+view@6.9.2 "@codemirror/language": 6.6.0 "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 - "@floating-ui/dom": 1.2.1 + "@codemirror/view": 6.9.2 + "@floating-ui/dom": 1.2.4 "@github/clipboard-copy-element": 1.1.2 "@github/hotkey": 2.0.1 "@github/markdown-toolbar-element": 2.1.1 @@ -91,35 +91,35 @@ dependencies: leaflet.markercluster: 1.5.3_leaflet@1.9.3 lit: 2.6.1 marked: 4.2.12 - wavesurfer.js: 6.4.0 - xml-formatter: 3.2.0 + wavesurfer.js: 6.5.2 + xml-formatter: 3.3.2 devDependencies: "@commitlint/cli": 17.4.4 "@commitlint/config-conventional": 17.4.4 - "@semantic-release/changelog": 6.0.2_semantic-release@20.1.0 - "@semantic-release/exec": 6.0.3_semantic-release@20.1.0 - "@semantic-release/git": 10.0.1_semantic-release@20.1.0 - "@semantic-release/gitlab": 11.0.1_semantic-release@20.1.0 + "@semantic-release/changelog": 6.0.2_semantic-release@20.1.1 + "@semantic-release/exec": 6.0.3_semantic-release@20.1.1 + "@semantic-release/git": 10.0.1_semantic-release@20.1.1 + "@semantic-release/gitlab": 11.0.1_semantic-release@20.1.1 "@tailwindcss/forms": 0.5.3_tailwindcss@3.2.7 "@tailwindcss/line-clamp": 0.4.2_tailwindcss@3.2.7 "@tailwindcss/typography": 0.5.9_tailwindcss@3.2.7 - "@types/leaflet": 1.9.1 + "@types/leaflet": 1.9.2 "@types/marked": 4.0.8 "@types/wavesurfer.js": 6.0.3 - "@typescript-eslint/eslint-plugin": 5.53.0_ny4s7qc6yg74faf3d6xty2ofzy - "@typescript-eslint/parser": 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + "@typescript-eslint/eslint-plugin": 5.55.0_342y7v4tc7ytrrysmit6jo4wri + "@typescript-eslint/parser": 5.55.0_vgl77cfdswitgr47lm5swmv43m all-contributors-cli: 6.24.0 commitizen: 4.3.0 cross-env: 7.0.3 cssnano: 5.1.15_postcss@8.4.21 cz-conventional-changelog: 3.3.0 - eslint: 8.34.0 - eslint-config-prettier: 8.6.0_eslint@8.34.0 - eslint-plugin-prettier: 4.2.1_u5wnrdwibbfomslmnramz52buy + eslint: 8.36.0 + eslint-config-prettier: 8.7.0_eslint@8.36.0 + eslint-plugin-prettier: 4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm husky: 8.0.3 is-ci: 3.0.1 - lint-staged: 13.1.2 + lint-staged: 13.2.0 postcss: 8.4.21 postcss-import: 15.1.0_postcss@8.4.21 postcss-nesting: 11.2.1_postcss@8.4.21 @@ -127,14 +127,14 @@ devDependencies: postcss-reporter: 7.0.5_postcss@8.4.21 prettier: 2.8.4 prettier-plugin-organize-imports: 3.2.2_silln3pw57har7jydmecgzoypa - semantic-release: 20.1.0 + semantic-release: 20.1.1 stylelint: 15.2.0 stylelint-config-standard: 30.0.1_stylelint@15.2.0 svgo: 3.0.2 tailwindcss: 3.2.7_postcss@8.4.21 typescript: 4.9.5 - vite: 4.1.3 - vite-plugin-pwa: 0.14.4_rcpzravakhu7gk56p6427hsr2y + vite: 4.1.4 + vite-plugin-pwa: 0.14.4_vizhyq4kcdharmiplw7eejneda workbox-build: 6.5.4 workbox-core: 6.5.4 workbox-routing: 6.5.4 @@ -1648,7 +1648,7 @@ packages: to-fast-properties: 2.0.0 dev: true - /@codemirror/autocomplete/6.4.2_dtwlkgx6567fllxi7sgvnep6hy: + /@codemirror/autocomplete/6.4.2_m2g2fjrvetqbsl7zxwctz5ljh4: resolution: { integrity: sha512-8WE2xp+D0MpWEv5lZ6zPW1/tf4AGb358T5GWYiKEuCP8MvFfT3tH2mIF9Y2yr2e3KbHuSvsVhosiEyqCpiJhZQ==, @@ -1660,29 +1660,29 @@ packages: dependencies: "@codemirror/language": 6.6.0 "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 "@lezer/common": 1.0.2 dev: false - /@codemirror/commands/6.2.1: + /@codemirror/commands/6.2.2: resolution: { - integrity: sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==, + integrity: sha512-s9lPVW7TxXrI/7voZ+HmD/yiAlwAYn9PH5SUVSUhsxXHhv4yl5eZ3KLntSoTynfdgVYM0oIpccQEWRBQgmNZyw==, } dependencies: "@codemirror/language": 6.6.0 "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 "@lezer/common": 1.0.2 dev: false - /@codemirror/lang-xml/6.0.2_@codemirror+view@6.9.1: + /@codemirror/lang-xml/6.0.2_@codemirror+view@6.9.2: resolution: { integrity: sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==, } dependencies: - "@codemirror/autocomplete": 6.4.2_dtwlkgx6567fllxi7sgvnep6hy + "@codemirror/autocomplete": 6.4.2_m2g2fjrvetqbsl7zxwctz5ljh4 "@codemirror/language": 6.6.0 "@codemirror/state": 6.2.0 "@lezer/common": 1.0.2 @@ -1698,7 +1698,7 @@ packages: } dependencies: "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 "@lezer/common": 1.0.2 "@lezer/highlight": 1.1.3 "@lezer/lr": 1.3.3 @@ -1712,7 +1712,7 @@ packages: } dependencies: "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 crelt: 1.0.5 dev: false @@ -1723,7 +1723,7 @@ packages: } dependencies: "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 crelt: 1.0.5 dev: false @@ -1734,10 +1734,10 @@ packages: } dev: false - /@codemirror/view/6.9.1: + /@codemirror/view/6.9.2: resolution: { - integrity: sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==, + integrity: sha512-ci0r/v6aKOSlzOs7/STMTYP3jX/+YMq2dAfAJcLIB6uom4ThtrUlzeuS7GTRGNqJJ+qAJR1vGWfXgu4CO/0myQ==, } dependencies: "@codemirror/state": 6.2.0 @@ -2574,16 +2574,37 @@ packages: dev: true optional: true - /@eslint/eslintrc/1.4.1: + /@eslint-community/eslint-utils/4.2.0_eslint@8.36.0: resolution: { - integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==, + integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.36.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /@eslint-community/regexpp/4.4.0: + resolution: + { + integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + dev: true + + /@eslint/eslintrc/2.0.1: + resolution: + { + integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.4.1 + espree: 9.5.0 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -2594,20 +2615,28 @@ packages: - supports-color dev: true - /@floating-ui/core/1.2.1: + /@eslint/js/8.36.0: resolution: { - integrity: sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==, + integrity: sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dev: true + + /@floating-ui/core/1.2.4: + resolution: + { + integrity: sha512-SQOeVbMwb1di+mVWWJLpsUTToKfqVNioXys011beCAhyOIFtS+GQoW4EQSneuxzmQKddExDwQ+X0hLl4lJJaSQ==, } dev: false - /@floating-ui/dom/1.2.1: + /@floating-ui/dom/1.2.4: resolution: { - integrity: sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==, + integrity: sha512-4+k+BLhtWj+peCU60gp0+rHeR8+Ohqx6kjJf/lHMnJ8JD5Qj6jytcq1+SZzRwD7rvHKRhR7TDiWWddrNrfwQLg==, } dependencies: - "@floating-ui/core": 1.2.1 + "@floating-ui/core": 1.2.4 dev: false /@foliojs-fork/fontkit/1.9.1: @@ -3147,7 +3176,7 @@ packages: rollup: 3.17.2 dev: true - /@semantic-release/changelog/6.0.2_semantic-release@20.1.0: + /@semantic-release/changelog/6.0.2_semantic-release@20.1.1: resolution: { integrity: sha512-jHqfTkoPbDEOAgAP18mGP53IxeMwxTISN+GwTRy9uLu58UjARoZU8ScCgWGeO2WPkEsm57H8AkyY02W2ntIlIw==, @@ -3160,10 +3189,10 @@ packages: aggregate-error: 3.1.0 fs-extra: 11.1.0 lodash: 4.17.21 - semantic-release: 20.1.0 + semantic-release: 20.1.1 dev: true - /@semantic-release/commit-analyzer/9.0.2_semantic-release@20.1.0: + /@semantic-release/commit-analyzer/9.0.2_semantic-release@20.1.1: resolution: { integrity: sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==, @@ -3179,7 +3208,7 @@ packages: import-from: 4.0.0 lodash: 4.17.21 micromatch: 4.0.5 - semantic-release: 20.1.0 + semantic-release: 20.1.1 transitivePeerDependencies: - supports-color dev: true @@ -3192,7 +3221,7 @@ packages: engines: { node: ">=14.17" } dev: true - /@semantic-release/exec/6.0.3_semantic-release@20.1.0: + /@semantic-release/exec/6.0.3_semantic-release@20.1.1: resolution: { integrity: sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ==, @@ -3207,12 +3236,12 @@ packages: execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 - semantic-release: 20.1.0 + semantic-release: 20.1.1 transitivePeerDependencies: - supports-color dev: true - /@semantic-release/git/10.0.1_semantic-release@20.1.0: + /@semantic-release/git/10.0.1_semantic-release@20.1.1: resolution: { integrity: sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==, @@ -3229,12 +3258,12 @@ packages: lodash: 4.17.21 micromatch: 4.0.5 p-reduce: 2.1.0 - semantic-release: 20.1.0 + semantic-release: 20.1.1 transitivePeerDependencies: - supports-color dev: true - /@semantic-release/github/8.0.7_semantic-release@20.1.0: + /@semantic-release/github/8.0.7_semantic-release@20.1.1: resolution: { integrity: sha512-VtgicRIKGvmTHwm//iqTh/5NGQwsncOMR5vQK9pMT92Aem7dv37JFKKRuulUsAnUOIlO4G8wH3gPiBAA0iW0ww==, @@ -3258,14 +3287,14 @@ packages: mime: 3.0.0 p-filter: 2.1.0 p-retry: 4.6.2 - semantic-release: 20.1.0 + semantic-release: 20.1.1 url-join: 4.0.1 transitivePeerDependencies: - encoding - supports-color dev: true - /@semantic-release/gitlab/11.0.1_semantic-release@20.1.0: + /@semantic-release/gitlab/11.0.1_semantic-release@20.1.1: resolution: { integrity: sha512-CWXHlLZonwrUR2pbYaoERVu1cDVsi5W4H0WXDDCcDwicMmizsTkKJlpP9CQowoluqHKIJYrLkr2b+lYXCnBJZw==, @@ -3286,13 +3315,13 @@ packages: hpagent: 1.2.0 lodash-es: 4.17.21 parse-url: 8.1.0 - semantic-release: 20.1.0 + semantic-release: 20.1.1 url-join: 4.0.1 transitivePeerDependencies: - supports-color dev: true - /@semantic-release/npm/9.0.2_semantic-release@20.1.0: + /@semantic-release/npm/9.0.2_semantic-release@20.1.1: resolution: { integrity: sha512-zgsynF6McdzxPnFet+a4iO9HpAlARXOM5adz7VGVCvj0ne8wtL2ZOQoDV2wZPDmdEotDIbVeJjafhelZjs9j6g==, @@ -3312,12 +3341,12 @@ packages: rc: 1.2.8 read-pkg: 5.2.0 registry-auth-token: 5.0.1 - semantic-release: 20.1.0 + semantic-release: 20.1.1 semver: 7.3.8 tempy: 1.0.1 dev: true - /@semantic-release/release-notes-generator/10.0.3_semantic-release@20.1.0: + /@semantic-release/release-notes-generator/10.0.3_semantic-release@20.1.1: resolution: { integrity: sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==, @@ -3336,7 +3365,7 @@ packages: into-stream: 6.0.0 lodash: 4.17.21 read-pkg-up: 7.0.1 - semantic-release: 20.1.0 + semantic-release: 20.1.1 transitivePeerDependencies: - supports-color dev: true @@ -3523,10 +3552,10 @@ packages: } dev: true - /@types/leaflet/1.9.1: + /@types/leaflet/1.9.2: resolution: { - integrity: sha512-lYawM3I3lLO6rmBASaqdGgY6zUL4YHr3H79/axx7FNYyPXuj0P1DZHbkNo8Itbv0i7Y9EryLWtDXXROMygXhRA==, + integrity: sha512-vrokGIGVO8RSNXQBcWdEJ4Xy6E9kLQHZfpxIkFjSD1OhqTKOjYLFJDG6JCoAWYm/n755fdNCyrpna6/00kVajw==, } dependencies: "@types/geojson": 7946.0.10 @@ -3598,10 +3627,10 @@ packages: "@types/debounce": 1.2.1 dev: true - /@typescript-eslint/eslint-plugin/5.53.0_ny4s7qc6yg74faf3d6xty2ofzy: + /@typescript-eslint/eslint-plugin/5.55.0_342y7v4tc7ytrrysmit6jo4wri: resolution: { - integrity: sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==, + integrity: sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -3612,16 +3641,16 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/parser": 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm - "@typescript-eslint/scope-manager": 5.53.0 - "@typescript-eslint/type-utils": 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm - "@typescript-eslint/utils": 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + "@eslint-community/regexpp": 4.4.0 + "@typescript-eslint/parser": 5.55.0_vgl77cfdswitgr47lm5swmv43m + "@typescript-eslint/scope-manager": 5.55.0 + "@typescript-eslint/type-utils": 5.55.0_vgl77cfdswitgr47lm5swmv43m + "@typescript-eslint/utils": 5.55.0_vgl77cfdswitgr47lm5swmv43m debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.36.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - regexpp: 3.2.0 semver: 7.3.8 tsutils: 3.21.0_typescript@4.9.5 typescript: 4.9.5 @@ -3629,10 +3658,10 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: + /@typescript-eslint/parser/5.55.0_vgl77cfdswitgr47lm5swmv43m: resolution: { - integrity: sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==, + integrity: sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -3642,31 +3671,31 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/scope-manager": 5.53.0 - "@typescript-eslint/types": 5.53.0 - "@typescript-eslint/typescript-estree": 5.53.0_typescript@4.9.5 + "@typescript-eslint/scope-manager": 5.55.0 + "@typescript-eslint/types": 5.55.0 + "@typescript-eslint/typescript-estree": 5.55.0_typescript@4.9.5 debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.36.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.53.0: + /@typescript-eslint/scope-manager/5.55.0: resolution: { - integrity: sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==, + integrity: sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - "@typescript-eslint/types": 5.53.0 - "@typescript-eslint/visitor-keys": 5.53.0 + "@typescript-eslint/types": 5.55.0 + "@typescript-eslint/visitor-keys": 5.55.0 dev: true - /@typescript-eslint/type-utils/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: + /@typescript-eslint/type-utils/5.55.0_vgl77cfdswitgr47lm5swmv43m: resolution: { - integrity: sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==, + integrity: sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -3676,28 +3705,28 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/typescript-estree": 5.53.0_typescript@4.9.5 - "@typescript-eslint/utils": 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + "@typescript-eslint/typescript-estree": 5.55.0_typescript@4.9.5 + "@typescript-eslint/utils": 5.55.0_vgl77cfdswitgr47lm5swmv43m debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.36.0 tsutils: 3.21.0_typescript@4.9.5 typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.53.0: + /@typescript-eslint/types/5.55.0: resolution: { - integrity: sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==, + integrity: sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /@typescript-eslint/typescript-estree/5.53.0_typescript@4.9.5: + /@typescript-eslint/typescript-estree/5.55.0_typescript@4.9.5: resolution: { - integrity: sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==, + integrity: sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: @@ -3706,8 +3735,8 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/types": 5.53.0 - "@typescript-eslint/visitor-keys": 5.53.0 + "@typescript-eslint/types": 5.55.0 + "@typescript-eslint/visitor-keys": 5.55.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -3718,37 +3747,37 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: + /@typescript-eslint/utils/5.55.0_vgl77cfdswitgr47lm5swmv43m: resolution: { - integrity: sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==, + integrity: sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: + "@eslint-community/eslint-utils": 4.2.0_eslint@8.36.0 "@types/json-schema": 7.0.11 "@types/semver": 7.3.13 - "@typescript-eslint/scope-manager": 5.53.0 - "@typescript-eslint/types": 5.53.0 - "@typescript-eslint/typescript-estree": 5.53.0_typescript@4.9.5 - eslint: 8.34.0 + "@typescript-eslint/scope-manager": 5.55.0 + "@typescript-eslint/types": 5.55.0 + "@typescript-eslint/typescript-estree": 5.55.0_typescript@4.9.5 + eslint: 8.36.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.34.0 semver: 7.3.8 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys/5.53.0: + /@typescript-eslint/visitor-keys/5.55.0: resolution: { - integrity: sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==, + integrity: sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - "@typescript-eslint/types": 5.53.0 + "@typescript-eslint/types": 5.55.0 eslint-visitor-keys: 3.3.0 dev: true @@ -4678,13 +4707,13 @@ packages: integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==, } dependencies: - "@codemirror/autocomplete": 6.4.2_dtwlkgx6567fllxi7sgvnep6hy - "@codemirror/commands": 6.2.1 + "@codemirror/autocomplete": 6.4.2_m2g2fjrvetqbsl7zxwctz5ljh4 + "@codemirror/commands": 6.2.2 "@codemirror/language": 6.6.0 "@codemirror/lint": 6.1.1 "@codemirror/search": 6.2.3 "@codemirror/state": 6.2.0 - "@codemirror/view": 6.9.1 + "@codemirror/view": 6.9.2 dev: false /codepage/1.15.0: @@ -4752,6 +4781,14 @@ packages: delayed-stream: 1.0.0 dev: true + /commander/10.0.0: + resolution: + { + integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==, + } + engines: { node: ">=14" } + dev: true + /commander/2.20.3: resolution: { @@ -4766,14 +4803,6 @@ packages: } engines: { node: ">= 10" } - /commander/9.5.0: - resolution: - { - integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==, - } - engines: { node: ^12.20.0 || >=14 } - dev: true - /commitizen/4.3.0: resolution: { @@ -6066,19 +6095,19 @@ packages: source-map: 0.1.43 dev: false - /eslint-config-prettier/8.6.0_eslint@8.34.0: + /eslint-config-prettier/8.7.0_eslint@8.36.0: resolution: { - integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==, + integrity: sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==, } hasBin: true peerDependencies: eslint: ">=7.0.0" dependencies: - eslint: 8.34.0 + eslint: 8.36.0 dev: true - /eslint-plugin-prettier/4.2.1_u5wnrdwibbfomslmnramz52buy: + /eslint-plugin-prettier/4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm: resolution: { integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==, @@ -6092,8 +6121,8 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.34.0 - eslint-config-prettier: 8.6.0_eslint@8.34.0 + eslint: 8.36.0 + eslint-config-prettier: 8.7.0_eslint@8.36.0 prettier: 2.8.4 prettier-linter-helpers: 1.0.0 dev: true @@ -6120,27 +6149,6 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.34.0: - resolution: - { - integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==, - } - engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } - peerDependencies: - eslint: ">=5" - dependencies: - eslint: 8.34.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys/2.1.0: - resolution: - { - integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==, - } - engines: { node: ">=10" } - dev: true - /eslint-visitor-keys/3.3.0: resolution: { @@ -6149,15 +6157,18 @@ packages: engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /eslint/8.34.0: + /eslint/8.36.0: resolution: { - integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==, + integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } hasBin: true dependencies: - "@eslint/eslintrc": 1.4.1 + "@eslint-community/eslint-utils": 4.2.0_eslint@8.36.0 + "@eslint-community/regexpp": 4.4.0 + "@eslint/eslintrc": 2.0.1 + "@eslint/js": 8.36.0 "@humanwhocodes/config-array": 0.11.8 "@humanwhocodes/module-importer": 1.0.1 "@nodelib/fs.walk": 1.2.8 @@ -6168,9 +6179,8 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.34.0 eslint-visitor-keys: 3.3.0 - espree: 9.4.1 + espree: 9.5.0 esquery: 1.4.2 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -6192,7 +6202,6 @@ packages: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - regexpp: 3.2.0 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -6200,10 +6209,10 @@ packages: - supports-color dev: true - /espree/9.4.1: + /espree/9.5.0: resolution: { - integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==, + integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: @@ -6354,6 +6363,24 @@ packages: strip-final-newline: 3.0.0 dev: true + /execa/7.1.0: + resolution: + { + integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==, + } + engines: { node: ^14.18.0 || ^16.14.0 || >=18.0.0 } + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /expand-tilde/2.0.2: resolution: { @@ -7231,6 +7258,14 @@ packages: engines: { node: ">=12.20.0" } dev: true + /human-signals/4.3.0: + resolution: + { + integrity: sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==, + } + engines: { node: ">=14.18.0" } + dev: true + /husky/8.0.3: resolution: { @@ -8063,6 +8098,14 @@ packages: engines: { node: ">=10" } dev: true + /lilconfig/2.1.0: + resolution: + { + integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==, + } + engines: { node: ">=10" } + dev: true + /lines-and-columns/1.2.4: resolution: { @@ -8070,20 +8113,20 @@ packages: } dev: true - /lint-staged/13.1.2: + /lint-staged/13.2.0: resolution: { - integrity: sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==, + integrity: sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==, } engines: { node: ^14.13.1 || >=16.0.0 } hasBin: true dependencies: + chalk: 5.2.0 cli-truncate: 3.1.0 - colorette: 2.0.19 - commander: 9.5.0 + commander: 10.0.0 debug: 4.3.4 - execa: 6.1.0 - lilconfig: 2.0.6 + execa: 7.1.0 + lilconfig: 2.1.0 listr2: 5.0.7 micromatch: 4.0.5 normalize-path: 3.0.0 @@ -9923,7 +9966,7 @@ packages: ts-node: optional: true dependencies: - lilconfig: 2.0.6 + lilconfig: 2.1.0 postcss: 8.4.21 yaml: 1.10.2 dev: true @@ -10835,14 +10878,6 @@ packages: define-properties: 1.2.0 functions-have-names: 1.2.3 - /regexpp/3.2.0: - resolution: - { - integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==, - } - engines: { node: ">=8" } - dev: true - /regexpu-core/5.3.1: resolution: { @@ -11158,24 +11193,24 @@ packages: get-assigned-identifiers: 1.2.0 dev: false - /semantic-release/20.1.0: + /semantic-release/20.1.1: resolution: { - integrity: sha512-+9+n6RIr0Fz0F53cXrjpawxWlUg3O7/qr1jF9lrE+/v6WqwBrSWnavVHTPaf2WLerET2EngoqI0M4pahkKl6XQ==, + integrity: sha512-jXDr8y7ozo42N4+G9m/P5Qyx5oQO4aOS66a+Up8XECzEOFIpEoo3ngnr4R5lSix/sVJW69/fgNkOUZhsGFiQ5g==, } engines: { node: ">=18" } hasBin: true dependencies: - "@semantic-release/commit-analyzer": 9.0.2_semantic-release@20.1.0 + "@semantic-release/commit-analyzer": 9.0.2_semantic-release@20.1.1 "@semantic-release/error": 3.0.0 - "@semantic-release/github": 8.0.7_semantic-release@20.1.0 - "@semantic-release/npm": 9.0.2_semantic-release@20.1.0 - "@semantic-release/release-notes-generator": 10.0.3_semantic-release@20.1.0 + "@semantic-release/github": 8.0.7_semantic-release@20.1.1 + "@semantic-release/npm": 9.0.2_semantic-release@20.1.1 + "@semantic-release/release-notes-generator": 10.0.3_semantic-release@20.1.1 aggregate-error: 4.0.1 cosmiconfig: 8.0.0 debug: 4.3.4 env-ci: 8.0.0 - execa: 6.1.0 + execa: 7.1.0 figures: 5.0.0 find-versions: 5.1.0 get-stream: 6.0.1 @@ -12504,7 +12539,7 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-plugin-pwa/0.14.4_rcpzravakhu7gk56p6427hsr2y: + /vite-plugin-pwa/0.14.4_vizhyq4kcdharmiplw7eejneda: resolution: { integrity: sha512-M7Ct0so8OlouMkTWgXnl8W1xU95glITSKIe7qswZf1tniAstO2idElGCnsrTJ5NPNSx1XqfTCOUj8j94S6FD7Q==, @@ -12518,17 +12553,17 @@ packages: fast-glob: 3.2.12 pretty-bytes: 6.1.0 rollup: 3.17.2 - vite: 4.1.3 + vite: 4.1.4 workbox-build: 6.5.4 workbox-window: 6.5.4 transitivePeerDependencies: - supports-color dev: true - /vite/4.1.3: + /vite/4.1.4: resolution: { - integrity: sha512-0Zqo4/Fr/swSOBmbl+HAAhOjrqNwju+yTtoe4hQX9UsARdcuc9njyOdr6xU0DDnV7YP0RT6mgTTOiRtZgxfCxA==, + integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==, } engines: { node: ^14.18.0 || >=16.0.0 } hasBin: true @@ -12568,10 +12603,10 @@ packages: } dev: false - /wavesurfer.js/6.4.0: + /wavesurfer.js/6.5.2: resolution: { - integrity: sha512-+i9GkYdMTm998goOWvycWqCgxfbolnuGTPgrdGss7ZbQDuu+n6AHdnJwYnqxW8RZo5A2ck6ztzXbrXGuCi0m5g==, + integrity: sha512-1GfjeFlaaYnlOcwZ3M0MjYgmAVzL4dKARfJIlM9L/NVECFRwMsV7wtOWA1ZBukjFABt+DL+JiZOEIAtomqSMJg==, } dev: false @@ -12958,20 +12993,20 @@ packages: word: 0.3.0 dev: false - /xml-formatter/3.2.0: + /xml-formatter/3.3.2: resolution: { - integrity: sha512-PYROODIUDHz1SDFePg2VThajPOuSmvo/PrYRKARcSc9xxKKs62EN9uar60IIxxknzmOSNDAxlylpw34bQp0g/Q==, + integrity: sha512-ld34F1b7+2UQGNkfsAV4MN3/b7cdUstyMj3XJhzKFasOPtMToVCkqmrNcmrRuSlPxgH1K9tXPkqr75gAT3ix2g==, } engines: { node: ">= 14" } dependencies: - xml-parser-xo: 4.0.2 + xml-parser-xo: 4.0.5 dev: false - /xml-parser-xo/4.0.2: + /xml-parser-xo/4.0.5: resolution: { - integrity: sha512-tM9LyyGumFAf7VD3GLlcN7eIbpvgzmt7PAseAMO6thgEq6VIEHPxlcWVIzMmn6pqGD1NTZS8mSPfhePo6AETVw==, + integrity: sha512-UWXOHMQ4ySxpUiU3S/9KzPOhninlL8SN1xFfWgX9WjgoZWoLKtEeJIEz4jhKtdFsoZBCYjg9rDEP3qfnpiHagQ==, } engines: { node: ">= 14" } dev: false diff --git a/public/media/podcasts/index.html b/public/media/podcasts/index.html new file mode 100644 index 00000000..e69de29b diff --git a/public/media/site/index.html b/public/media/site/index.html new file mode 100644 index 00000000..e69de29b diff --git a/rector.php b/rector.php index afb6f1bb..bd0125b9 100644 --- a/rector.php +++ b/rector.php @@ -11,6 +11,7 @@ use Rector\CodingStyle\Rector\String_\SymplifyQuoteEscapeRector; use Rector\Config\RectorConfig; use Rector\Core\ValueObject\PhpVersion; use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector; +use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector; use Rector\EarlyReturn\Rector\If_\ChangeAndIfToEarlyReturnRector; use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector; use Rector\EarlyReturn\Rector\If_\ChangeOrIfReturnToEarlyReturnRector; @@ -66,6 +67,10 @@ return static function (RectorConfig $rectorConfig): void { NewlineAfterStatementRector::class => [__DIR__ . '/app/Views'], + RemoveUnreachableStatementRector::class => [ + __DIR__ . '/modules/Install/Controllers/InstallController.php', + ], + ChangeAndIfToEarlyReturnRector::class => [__DIR__ . '/modules/Install/Controllers/InstallController.php'], ]); diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php index a2b60bd6..3f3ca52a 100644 --- a/themes/cp_admin/episode/video_clips_new.php +++ b/themes/cp_admin/episode/video_clips_new.php @@ -18,7 +18,7 @@ <?= $episode->cover->description ?> -