feat(media): add s3 to manage media files
Users may choose between filesystem (FS) or S3 to store and manage their media files
This commit is contained in:
parent
9fc49a7430
commit
d93fc98469
|
@ -177,6 +177,7 @@ modules/Admin/Language/*/PersonsTaxonomy.php
|
|||
mariadb
|
||||
phpmyadmin
|
||||
sessions
|
||||
data
|
||||
|
||||
# Castopod bundle & packages
|
||||
castopod/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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}}}',
|
||||
|
|
|
@ -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'],
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
]);
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
/**
|
||||
* @property array $sizes
|
||||
*/
|
||||
class Image extends BaseMedia
|
||||
{
|
||||
protected string $type = 'image';
|
||||
|
||||
public function initFileProperties(): void
|
||||
{
|
||||
parent::initFileProperties();
|
||||
|
||||
if ($this->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;
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use App\Libraries\TranscriptParser;
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
class Transcript extends BaseMedia
|
||||
{
|
||||
public ?string $json_path = null;
|
||||
|
||||
public ?string $json_url = null;
|
||||
|
||||
protected string $type = 'transcript';
|
||||
|
||||
public function initFileProperties(): void
|
||||
{
|
||||
parent::initFileProperties();
|
||||
|
||||
if ($this->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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
|
|
|
@ -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('~(?<!^)(?<!\/)[A-Z]~', '_$0', $namePath) ?? '');
|
||||
$filePath = $basePath . $this->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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -85,7 +85,7 @@ return [
|
|||
image {καλύψτε}
|
||||
audio {ήχος}
|
||||
other {πολυμέσα}
|
||||
} αρχείο {file_path}. Μπορείτε να το αφαιρέσετε χειροκίνητα από το δίσκο σας.',
|
||||
} αρχείο {file_key}. Μπορείτε να το αφαιρέσετε χειροκίνητα από το δίσκο σας.',
|
||||
'sameSlugError' => 'Ένα επεισόδιο με το επιλεγμένο slug υπάρχει ήδη.',
|
||||
],
|
||||
'form' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -85,7 +85,7 @@ return [
|
|||
image {封面}
|
||||
audio {音频}
|
||||
other {媒体}
|
||||
} 文件 {file_path}。您可以手动将其从磁盘删除。',
|
||||
} 文件 {file_key}。您可以手动将其从磁盘删除。',
|
||||
'sameSlugError' => '选中的剧集已存在。',
|
||||
],
|
||||
'form' => [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -362,7 +362,7 @@ if (! function_exists('linkify')) {
|
|||
$text = match ($protocol) {
|
||||
'http', 'https' => preg_replace_callback(
|
||||
'~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i',
|
||||
static function (array $match) use ($protocol, &$links) {
|
||||
static function (array $match) use ($protocol, &$links): string {
|
||||
if ($match[1] !== '' && $match[1] !== '0') {
|
||||
$protocol = $match[1];
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ if (! function_exists('linkify')) {
|
|||
'~' .
|
||||
preg_quote($protocol, '~') .
|
||||
'://([^\s<]+?)(?<![\.,:])~i',
|
||||
static function (array $match) use ($protocol, &$links) {
|
||||
static function (array $match) use ($protocol, &$links): string {
|
||||
return '<' .
|
||||
array_push(
|
||||
$links,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Media\Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Modules\Media\FileManagers\FS;
|
||||
use Modules\Media\FileManagers\S3;
|
||||
|
||||
class Media extends BaseConfig
|
||||
{
|
||||
public string $fileManager = 'fs';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public array $fileManagers = [
|
||||
'fs' => FS::class,
|
||||
's3' => S3::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, null|string|bool>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
public array $folders = [
|
||||
'podcasts' => 'podcasts',
|
||||
'persons' => 'persons',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Media\Config;
|
||||
|
||||
use CodeIgniter\Config\BaseService;
|
||||
use Exception;
|
||||
use Modules\Media\Config\Media as MediaConfig;
|
||||
use Modules\Media\FileManagers\FileManagerInterface;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
*
|
||||
* Services are simply other classes/libraries that the system uses to do its job. This is used by CodeIgniter to allow
|
||||
* the core of the framework to be swapped out easily without affecting the usage within the rest of your application.
|
||||
*
|
||||
* This file holds any application-specific services, or service overrides that you might need. An example has been
|
||||
* included with the general method format you should use for your service methods. For more examples, see the core
|
||||
* Services file at system/Config/Services.php.
|
||||
*/
|
||||
class Services extends BaseService
|
||||
{
|
||||
public static function file_manager(bool $getShared = true): FileManagerInterface
|
||||
{
|
||||
if ($getShared) {
|
||||
return self::getSharedInstance('file_manager');
|
||||
}
|
||||
|
||||
/** @var MediaConfig $config * */
|
||||
$config = config('Media');
|
||||
$fileManagerClass = $config->fileManagers[$config->fileManager];
|
||||
|
||||
$fileManager = new $fileManagerClass($config);
|
||||
|
||||
if ($fileManager instanceof FileManagerInterface) {
|
||||
return $fileManager;
|
||||
}
|
||||
|
||||
throw new Exception('File Manager service must extend FileManagerInterface');
|
||||
}
|
||||
}
|
|
@ -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');
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2022 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Media\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class RenameMediafileKey extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'file_key' => [
|
||||
'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);
|
||||
}
|
||||
}
|
|
@ -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']);
|
|
@ -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<string, mixed>|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;
|
||||
}
|
|
@ -8,7 +8,7 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
class Chapters extends BaseMedia
|
||||
{
|
|
@ -8,7 +8,7 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
class Document extends BaseMedia
|
||||
{
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
* @property array $sizes
|
||||
*/
|
||||
class Image extends BaseMedia
|
||||
{
|
||||
protected string $type = 'image';
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, int|string>>
|
||||
*/
|
||||
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<string, string> $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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
use Modules\Media\TranscriptParser;
|
||||
|
||||
class Transcript extends BaseMedia
|
||||
{
|
||||
public ?string $json_key = null;
|
||||
|
||||
public ?string $json_url = null;
|
||||
|
||||
protected string $type = 'transcript';
|
||||
|
||||
public function __construct(?array $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
|
||||
if ($this->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;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
namespace Modules\Media\Entities;
|
||||
|
||||
class Video extends BaseMedia
|
||||
{
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Media\FileManagers;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
use Exception;
|
||||
use Modules\Media\Config\Media as MediaConfig;
|
||||
|
||||
class FS implements FileManagerInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected MediaConfig $config
|
||||
) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Media\FileManagers;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
interface FileManagerInterface
|
||||
{
|
||||
public function save(File $file, string $key): string | false;
|
||||
|
||||
public function delete(string $key): bool;
|
||||
|
||||
public function getUrl(string $key): string;
|
||||
|
||||
public function rename(string $oldKey, string $newKey): bool;
|
||||
|
||||
public function getFileContents(string $key): string;
|
||||
|
||||
public function getFileInput(string $key): string;
|
||||
|
||||
public function deletePodcastImageSizes(string $podcastHandle): bool;
|
||||
|
||||
public function deletePersonImagesSizes(): bool;
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Media\FileManagers;
|
||||
|
||||
use Aws\Credentials\Credentials;
|
||||
use Aws\S3\S3Client;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use Exception;
|
||||
use Modules\Media\Config\Media as MediaConfig;
|
||||
|
||||
class S3 implements FileManagerInterface
|
||||
{
|
||||
public S3Client $s3;
|
||||
|
||||
public function __construct(
|
||||
protected MediaConfig $config
|
||||
) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2023 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
|
||||
if (! function_exists('path_without_ext')) {
|
||||
function path_without_ext(string $path): string
|
||||
{
|
||||
$fileKeyInfo = pathinfo($path);
|
||||
|
||||
if ($fileKeyInfo['dirname'] === '.' && ! str_starts_with($path, '.')) {
|
||||
return $fileKeyInfo['filename'];
|
||||
}
|
||||
|
||||
if ($fileKeyInfo['dirname'] === '/') {
|
||||
return '/' . $fileKeyInfo['filename'];
|
||||
}
|
||||
|
||||
return implode('/', [$fileKeyInfo['dirname'], $fileKeyInfo['filename']]);
|
||||
}
|
||||
}
|
|
@ -9,39 +9,10 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Mimes;
|
||||
use Config\Services;
|
||||
|
||||
if (! function_exists('save_media')) {
|
||||
/**
|
||||
* Saves a file to the corresponding podcast folder in `public/media`
|
||||
*/
|
||||
function save_media(File | UploadedFile $file, string $folder = '', string $filename = null): string
|
||||
{
|
||||
if (($extension = $file->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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Libraries;
|
||||
namespace Modules\Media;
|
||||
|
||||
use stdClass;
|
||||
|
26
package.json
26
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",
|
||||
|
|
|
@ -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#'
|
||||
|
|
413
pnpm-lock.yaml
413
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
|
||||
|
|
|
@ -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'],
|
||||
]);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<img slot="preview_image" src="<?= $episode->cover->thumbnail_url ?>" alt="<?= $episode->cover->description ?>" loading="lazy" />
|
||||
</video-clip-previewer>
|
||||
<audio-clipper start-time="<?= old('start_time', 0) ?>" duration="<?= old('duration', 30) ?>" min-duration="10" volume=".5" height="50" trim-start-label="<?= lang('VideoClip.form.trim_start') ?>" trim-end-label="<?= lang('VideoClip.form.trim_end') ?>">
|
||||
<audio slot="audio" src="<?= $episode->audio_url ?>" preload="auto">
|
||||
<audio slot="audio" src="<?= $episode->audio->file_url ?>" preload="auto">
|
||||
Your browser does not support the <code>audio</code> element.
|
||||
</audio>
|
||||
<input slot="start_time" type="number" name="start_time" placeholder="<?= lang('VideoClip.form.start_time') ?>" step="0.001" />
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
subtitle="<?= lang('Settings.housekeeping.subtitle') ?>" >
|
||||
|
||||
<Forms.Toggler name="reset_counts" value="yes" size="small" checked="false" hint="<?= lang('Settings.housekeeping.reset_counts_helper') ?>"><?= lang('Settings.housekeeping.reset_counts') ?></Forms.Toggler>
|
||||
<Forms.Toggler name="rewrite_media" value="yes" size="small" checked="false" hint="<?= lang('Settings.housekeeping.rewrite_media_helper') ?>"><?= lang('Settings.housekeeping.rewrite_media') ?></Forms.Toggler>
|
||||
<Forms.Toggler name="rename_episodes_files" value="yes" size="small" checked="false" hint="<?= lang('Settings.housekeeping.rename_episodes_files_hint') ?>"><?= lang('Settings.housekeeping.rename_episodes_files') ?></Forms.Toggler>
|
||||
<Forms.Toggler name="clear_cache" value="yes" size="small" checked="false" hint="<?= lang('Settings.housekeeping.clear_cache_helper') ?>"><?= lang('Settings.housekeeping.clear_cache') ?></Forms.Toggler>
|
||||
|
||||
|
|
Loading…
Reference in New Issue