Compare commits

..

1 Commits

Author SHA1 Message Date
Yassine Doghri 644a47fb94 refactor(icons): use php-icons library to load icons 2024-04-12 09:54:51 +00:00
319 changed files with 9311 additions and 17261 deletions

View File

@ -4,7 +4,7 @@
# ⚠️ NOT optimized for production
# should be used only for development purposes
#---------------------------------------------------
FROM php:8.2-fpm
FROM php:8.1-fpm
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"

View File

@ -12,7 +12,7 @@ services:
environment:
CI_ENVIRONMENT: development
vite_environment: development
app_forceGlobalSecureRequests: 0 #false
app_forceGlobalSecureRequests: false
app_baseURL: http://localhost:8080/
media_baseURL: http://localhost:8080/
admin_gateway: cp-admin
@ -23,7 +23,7 @@ services:
database_default_username: castopod
database_default_password: castopod
database_default_DBPrefix: cp_
restapi_enabled: 1 #true
restapi_enabled: true
email_fromEmail: hello@castopod.local
email_SMTPCrypto: ""
email_SMTPHost: mailpit

1
.gitignore vendored
View File

@ -132,6 +132,7 @@ tmp/
/results/
/phpunit*.xml
/.phpunit.*.cache
# js package manager
yarn.lock

View File

@ -1,27 +1,3 @@
# [1.11.0](https://code.castopod.org/adaures/castopod/compare/v1.10.5...v1.11.0) (4/17/2024)
### Bug Fixes
- **premium:** set itunes:block on premium feeds to prevent indexing
([88851b0](https://code.castopod.org/adaures/castopod/commit/88851b022663d575a816f0e2f33f0353767dd52d))
- **rss:** generate podcast guid if empty
([a5aef2a](https://code.castopod.org/adaures/castopod/commit/a5aef2a63e464632f3941649d455672835989e6c)),
closes [#450](https://code.castopod.org/adaures/castopod/issues/450)
### Features
- add trailer tags to rss if trailer episodes are present
([80fdd9c](https://code.castopod.org/adaures/castopod/commit/80fdd9cfb4a95feac6ed0000435a013fc83e6892))
- add transcript display to episode page
([4d141fc](https://code.castopod.org/adaures/castopod/commit/4d141fceae56fa9e666b42c32a830ff9c68989db)),
closes [#411](https://code.castopod.org/adaures/castopod/issues/411)
- **platforms:** add telegram to socials
([004f804](https://code.castopod.org/adaures/castopod/commit/004f804045cd8e884361bb4318109fbdd7afc9a8))
- **platforms:** add truefans.fm and episodes.fm
([d046ecc](https://code.castopod.org/adaures/castopod/commit/d046ecc52f6ccd41d09f6de48e00d2c61d25d7f0)),
closes [#458](https://code.castopod.org/adaures/castopod/issues/458)
[#459](https://code.castopod.org/adaures/castopod/issues/459)
## [1.10.5](https://code.castopod.org/adaures/castopod/compare/v1.10.4...v1.10.5) (3/12/2024)
### Bug Fixes

View File

@ -1,2 +1,6 @@
<IfModule authz_core_module> Require all denied </IfModule>
<IfModule !authz_core_module> Deny from all </IfModule>
<IfModule authz_core_module>
Require all denied
</IfModule>
<IfModule !authz_core_module>
Deny from all
</IfModule>

View File

@ -61,30 +61,6 @@ class App extends BaseConfig
*/
public string $uriProtocol = 'REQUEST_URI';
/*
*--------------------------------------------------------------------------
* Allowed URL Characters
*--------------------------------------------------------------------------
*
* This lets you specify which characters are permitted within your URLs.
* When someone tries to submit a URL with disallowed characters they will
* get a warning message.
*
* As a security measure you are STRONGLY encouraged to restrict URLs to
* as few characters as possible.
*
* By default, only these are allowed: `a-z 0-9~%.:_-`
*
* Set an empty string to allow all characters -- but only if you are insane.
*
* The configured value is actually a regular expression character group
* and it will be used as: '/\A[<permittedURIChars>]+\z/iu'
*
* DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
*
*/
public string $permittedURIChars = 'a-z 0-9~%.:_\-@';
/**
* --------------------------------------------------------------------------
* Default Locale

View File

@ -29,17 +29,23 @@ class Autoload extends AutoloadConfig
* their location on the file system. These are used by the autoloader
* to locate files the first time they have been instantiated.
*
* The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are
* already mapped for you.
*
* You may change the name of the 'App' namespace if you wish,
* The '/app' and '/system' directories are already mapped for you.
* you may change the name of the 'App' namespace if you wish,
* but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work.
*
* Prototype:
*
* $psr4 = [
* 'CodeIgniter' => SYSTEMPATH,
* 'App' => APPPATH
* ];
*
* @var array<string, list<string>|string>
*/
public $psr4 = [
APP_NAMESPACE => APPPATH,
'Config' => APPPATH . 'Config/',
'Modules' => ROOTPATH . 'modules/',
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
@ -49,7 +55,6 @@ class Autoload extends AutoloadConfig
'Modules\Install' => ROOTPATH . 'modules/Install/',
'Modules\Media' => ROOTPATH . 'modules/Media/',
'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/',
'Modules\Platforms' => ROOTPATH . 'modules/Platforms/',
'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/',
'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/',
'Modules\Update' => ROOTPATH . 'modules/Update/',
@ -62,6 +67,7 @@ class Autoload extends AutoloadConfig
/**
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have

View File

@ -11,10 +11,8 @@ declare(strict_types=1);
*
* If you set 'display_errors' to '1', CI4's detailed error report will show.
*/
error_reporting(E_ALL & ~E_DEPRECATED);
// If you want to suppress more types of errors.
// error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
ini_set('display_errors', '0');
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
/**
* --------------------------------------------------------------------------

View File

@ -48,6 +48,25 @@ class Cache extends BaseConfig
*/
public string $storePath = WRITEPATH . 'cache/';
/**
* --------------------------------------------------------------------------
* Cache Include Query String
* --------------------------------------------------------------------------
*
* Whether to take the URL query string into consideration when generating
* output cache files. Valid options are:
*
* false = Disabled
* true = Enabled, take all query parameters into account.
* Please be aware that this may result in numerous cache
* files generated for the same page over and over again.
* ['q'] = Enabled, but only take into account the specified list
* of query parameters.
*
* @var boolean|string[]
*/
public bool | array $cacheQueryString = false;
/**
* --------------------------------------------------------------------------
* Key Prefix
@ -151,23 +170,4 @@ class Cache extends BaseConfig
'redis' => RedisHandler::class,
'wincache' => WincacheHandler::class,
];
/**
* --------------------------------------------------------------------------
* Web Page Caching: Cache Include Query String
* --------------------------------------------------------------------------
*
* Whether to take the URL query string into consideration when generating
* output cache files. Valid options are:
*
* false = Disabled
* true = Enabled, take all query parameters into account.
* Please be aware that this may result in numerous cache
* files generated for the same page over and over again.
* ['q'] = Enabled, but only take into account the specified list
* of query parameters.
*
* @var bool|list<string>
*/
public $cacheQueryString = false;
}

View File

@ -11,7 +11,7 @@ declare(strict_types=1);
|
| NOTE: this constant is updated upon release with Continuous Integration.
*/
defined('CP_VERSION') || define('CP_VERSION', '1.11.0');
defined('CP_VERSION') || define('CP_VERSION', '1.10.5');
/*
| --------------------------------------------------------------------

View File

@ -35,28 +35,28 @@ class ContentSecurityPolicy extends BaseConfig
/**
* Will default to self if not overridden
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $defaultSrc = null;
/**
* Lists allowed scripts' URLs.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $scriptSrc = 'self';
/**
* Lists allowed stylesheets' URLs.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $styleSrc = 'self';
/**
* Defines the origins from which images can be loaded.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $imageSrc = 'self';
@ -65,35 +65,35 @@ class ContentSecurityPolicy extends BaseConfig
*
* Will default to self if not overridden
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $baseURI = null;
/**
* Lists the URLs for workers and embedded frame contents
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $childSrc = 'self';
/**
* Limits the origins that you can connect to (via XHR, WebSockets, and EventSource).
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $connectSrc = 'self';
/**
* Specifies the origins that can serve web fonts.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $fontSrc;
/**
* Lists valid endpoints for submission from `<form>` tags.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $formAction = 'self';
@ -102,47 +102,47 @@ class ContentSecurityPolicy extends BaseConfig
* `<embed>`, and `<applet>` tags. This directive can't be used in `<meta>` tags and applies only to non-HTML
* resources.
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $frameAncestors = null;
/**
* The frame-src directive restricts the URLs which may be loaded into nested browsing contexts.
*
* @var list<string>|string|null
* @var string[]|string|null
*/
public string | array | null $frameSrc = null;
/**
* Restricts the origins allowed to deliver video and audio.
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $mediaSrc = null;
/**
* Allows control over Flash and other plugins.
*
* @var list<string>|string
* @var string|string[]
*/
public string | array $objectSrc = 'self';
/**
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $manifestSrc = null;
/**
* Limits the kinds of plugins a page may invoke.
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $pluginTypes = null;
/**
* List of actions allowed.
*
* @var list<string>|string|null
* @var string|string[]|null
*/
public string | array | null $sandbox = null;

View File

@ -1,107 +0,0 @@
<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Config\BaseConfig;
/**
* Cross-Origin Resource Sharing (CORS) Configuration
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
*/
class Cors extends BaseConfig
{
/**
* The default CORS configuration.
*
* @var array{
* allowedOrigins: list<string>,
* allowedOriginsPatterns: list<string>,
* supportsCredentials: bool,
* allowedHeaders: list<string>,
* exposedHeaders: list<string>,
* allowedMethods: list<string>,
* maxAge: int,
* }
*/
public array $default = [
/**
* Origins for the `Access-Control-Allow-Origin` header.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
*
* E.g.:
* - ['http://localhost:8080']
* - ['https://www.example.com']
*/
'allowedOrigins' => [],
/**
* Origin regex patterns for the `Access-Control-Allow-Origin` header.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
*
* NOTE: A pattern specified here is part of a regular expression. It will
* be actually `#\A<pattern>\z#`.
*
* E.g.:
* - ['https://\w+\.example\.com']
*/
'allowedOriginsPatterns' => [],
/**
* Weather to send the `Access-Control-Allow-Credentials` header.
*
* The Access-Control-Allow-Credentials response header tells browsers whether
* the server allows cross-origin HTTP requests to include credentials.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
*/
'supportsCredentials' => false,
/**
* Set headers to allow.
*
* The Access-Control-Allow-Headers response header is used in response to
* a preflight request which includes the Access-Control-Request-Headers to
* indicate which HTTP headers can be used during the actual request.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
*/
'allowedHeaders' => [],
/**
* Set headers to expose.
*
* The Access-Control-Expose-Headers response header allows a server to
* indicate which response headers should be made available to scripts running
* in the browser, in response to a cross-origin request.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
*/
'exposedHeaders' => [],
/**
* Set methods to allow.
*
* The Access-Control-Allow-Methods response header specifies one or more
* methods allowed when accessing a resource in response to a preflight
* request.
*
* E.g.:
* - ['GET', 'POST', 'PUT', 'DELETE']
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
*/
'allowedMethods' => [],
/**
* Set how many seconds the results of a preflight request can be cached.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
*/
'maxAge' => 7200,
];
}

View File

@ -45,11 +45,6 @@ class Database extends Config
'failover' => [],
'port' => 3306,
'numberNative' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
/**
@ -69,7 +64,7 @@ class Database extends Config
'pConnect' => false,
'DBDebug' => true,
'charset' => 'utf8',
'DBCollat' => '',
'DBCollat' => 'utf8_general_ci',
'swapPre' => '',
'encrypt' => false,
'compress' => false,
@ -78,11 +73,6 @@ class Database extends Config
'port' => 3306,
'foreignKeys' => true,
'busyTimeout' => 1000,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',
],
];
//--------------------------------------------------------------------

View File

@ -33,7 +33,7 @@ class Exceptions extends BaseConfig
* Any status codes here will NOT be logged if logging is turned on.
* By default, only 404 (Page Not Found) exceptions are ignored.
*
* @var list<int>
* @var int[]
*/
public array $ignoreCodes = [404];
@ -56,7 +56,7 @@ class Exceptions extends BaseConfig
* In order to specify 2 levels, use "/" to separate.
* ex. ['server', 'setup/password', 'secret_token']
*
* @var list<string>
* @var string[]
*/
public array $sensitiveDataInTrace = [];

View File

@ -11,21 +11,22 @@ use CodeIgniter\Config\BaseConfig;
*/
class Feature extends BaseConfig
{
/**
* Enable multiple filters for a route or not.
*
* If you enable this:
* - CodeIgniter\CodeIgniter::handleRequest() uses:
* - CodeIgniter\Filters\Filters::enableFilters(), instead of enableFilter()
* - CodeIgniter\CodeIgniter::tryToRouteIt() uses:
* - CodeIgniter\Router\Router::getFilters(), instead of getFilter()
* - CodeIgniter\Router\Router::handle() uses:
* - property $filtersInfo, instead of $filterInfo
* - CodeIgniter\Router\RouteCollection::getFiltersForRoute(), instead of getFilterForRoute()
*/
public bool $multipleFilters = false;
/**
* Use improved new auto routing instead of the default legacy version.
*/
public bool $autoRoutesImproved = false;
/**
* Use filter execution order in 4.4 or before.
*/
public bool $oldFilterOrder = false;
/**
* The behavior of `limit(0)` in Query Builder.
*
* If true, `limit(0)` returns all records. (the behavior of 4.4.x or before in version 4.x.)
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
*/
public bool $limitZeroAsAll = true;
}

View File

@ -8,11 +8,8 @@ use App\Filters\AllowCorsFilter;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\ForceHTTPS;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;
use Modules\Auth\Filters\PermissionFilter;
@ -21,10 +18,8 @@ class Filters extends BaseConfig
/**
* Configures aliases for Filter classes to make reading things nicer and simpler.
*
* @var array<string, class-string|list<class-string>>
*
* [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
* @var array<string, class-string|list<class-string>> [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
*/
public array $aliases = [
'csrf' => CSRF::class,
@ -33,41 +28,12 @@ class Filters extends BaseConfig
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'allow-cors' => AllowCorsFilter::class,
'cors' => Cors::class,
'forcehttps' => ForceHTTPS::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
];
/**
* List of special required filters.
*
* The filters listed here are special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* Filters set by default provide framework functionality. If removed,
* those functions will no longer work.
*
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
*
* @var array{before: list<string>, after: list<string>}
*/
public array $required = [
'before' => [
'forcehttps', // Force Global Secure Requests
'pagecache', // Web Page Caching
],
'after' => [
'pagecache', // Web Page Caching
'performance', // Performance Metrics
'toolbar', // Debug Toolbar
],
];
/**
* List of filter aliases that are always applied before and after every request.
*
* @var array<string, array<string, array<string, string|array<string>>>>>|array<string, list<string>>
* @var array<string, array<string, array<string, string>>>|array<string, list<string>>
*/
public array $globals = [
'before' => [
@ -78,6 +44,7 @@ class Filters extends BaseConfig
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
@ -86,12 +53,12 @@ class Filters extends BaseConfig
/**
* List of filter aliases that works on a particular HTTP method (GET, POST, etc.).
*
* Example: 'POST' => ['foo', 'bar']
* Example: 'post' => ['foo', 'bar']
*
* If you use this, you should disable auto-routing because auto-routing permits any HTTP method to access a
* controller. Accessing the controller with a method you dont expect could bypass the filter.
*
* @var array<string, list<string>>
* @var array<string, string[]>
*/
public array $methods = [];
@ -100,7 +67,7 @@ class Filters extends BaseConfig
*
* Example: 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array<string, array<string, list<string>>>
* @var array<string, array<string, string[]>>
*/
public array $filters = [];

View File

@ -24,7 +24,7 @@ class Format extends BaseConfig
* These formats are only checked when the data passed to the respond()
* method is an array.
*
* @var list<string>
* @var string[]
*/
public array $supportedResponseFormats = [
'application/json',

View File

@ -28,10 +28,8 @@ class Generators extends BaseConfig
* @var array<string, string>
*/
public array $views = [
'make:cell' => [
'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
'view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
],
'make:cell' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
'make:cell_view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Config;
use CodeIgniter\Config\BaseConfig;
use Kint\Parser\ConstructablePluginInterface;
use Kint\Renderer\AbstractRenderer;
use Kint\Renderer\Rich\TabPluginInterface;
@ -19,7 +20,7 @@ use Kint\Renderer\Rich\ValuePluginInterface;
*
* @see https://kint-php.github.io/kint/ for details on these settings.
*/
class Kint
class Kint extends BaseConfig
{
/*
|--------------------------------------------------------------------------

View File

@ -38,7 +38,7 @@ class Logger extends BaseConfig
* For a live site you'll usually enable Critical or higher (3) to be logged otherwise
* your log files will fill up very fast.
*
* @var int|list<int>
* @var int|int[]
*/
public int | array $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
@ -75,7 +75,7 @@ class Logger extends BaseConfig
* Handlers are executed in the order defined in this array, starting with
* the handler on top and continuing down.
*
* @var array<class-string, array<string, int|list<string>|string>>
* @var array<string, mixed>
*/
public array $handlers = [
/*

View File

@ -22,7 +22,7 @@ class Mimes
/**
* Map of extensions to mime types.
*
* @var array<string, list<string>|string>
* @var array<string, string|string[]>
*/
public static $mimes = [
'hqx' => [

View File

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace Config;
/**
* Optimization Configuration.
*
* NOTE: This class does not extend BaseConfig for performance reasons.
* So you cannot replace the property values with Environment Variables.
*
* @immutable
*/
class Optimize
{
/**
* --------------------------------------------------------------------------
* Config Caching
* --------------------------------------------------------------------------
*
* @see https://codeigniter.com/user_guide/concepts/factories.html#config-caching
*/
public bool $configCacheEnabled = false;
/**
* --------------------------------------------------------------------------
* Config Caching
* --------------------------------------------------------------------------
*
* @see https://codeigniter.com/user_guide/concepts/autoloader.html#file-locator-caching
*/
public bool $locatorCacheEnabled = false;
}

View File

@ -15,6 +15,7 @@ use CodeIgniter\Router\RouteCollection;
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}');
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
$routes->addPlaceholder('embedTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
$routes->addPlaceholder(
@ -127,9 +128,6 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
$routes->get('chapters', 'EpisodeController::chapters/$1/$2', [
'as' => 'episode-chapters',
]);
$routes->get('transcript', 'EpisodeController::transcript/$1/$2', [
'as' => 'episode-transcript',
]);
$routes->options('comments', 'ActivityPubController::preflight');
$routes->get('comments', 'EpisodeController::comments/$1/$2', [
'as' => 'episode-comments',
@ -207,9 +205,6 @@ $routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [
$routes->get('/p/(:uuid)/chapters', 'EpisodePreviewController::chapters/$1', [
'as' => 'episode-preview-chapters',
]);
$routes->get('/p/(:uuid)/transcript', 'EpisodePreviewController::transcript/$1', [
'as' => 'episode-preview-transcript',
]);
// Other pages
$routes->get('/credits', 'CreditsController', [

View File

@ -12,14 +12,13 @@ use CodeIgniter\Config\Routing as BaseRouting;
class Routing extends BaseRouting
{
/**
* For Defined Routes.
* An array of files that contain route definitions.
* Route files are read in order, with the first match
* found taking precedence.
*
* Default: APPPATH . 'Config/Routes.php'
*
* @var list<string>
* @var string[]
*/
public array $routeFiles = [
APPPATH . 'Config/Routes.php',
@ -29,13 +28,11 @@ class Routing extends BaseRouting
ROOTPATH . 'modules/Auth/Config/Routes.php',
ROOTPATH . 'modules/Fediverse/Config/Routes.php',
ROOTPATH . 'modules/Install/Config/Routes.php',
ROOTPATH . 'modules/Platforms/Config/Routes.php',
ROOTPATH . 'modules/PodcastImport/Config/Routes.php',
ROOTPATH . 'modules/PremiumPodcasts/Config/Routes.php',
];
/**
* For Defined Routes and Auto Routing.
* The default namespace to use for Controllers when no other
* namespace has been specified.
*
@ -44,7 +41,6 @@ class Routing extends BaseRouting
public string $defaultNamespace = 'App\Controllers';
/**
* For Auto Routing.
* The default controller to use when no other controller has been
* specified.
*
@ -53,7 +49,6 @@ class Routing extends BaseRouting
public string $defaultController = 'HomeController';
/**
* For Defined Routes and Auto Routing.
* The default method to call on the controller when no other
* method has been set in the route.
*
@ -62,8 +57,7 @@ class Routing extends BaseRouting
public string $defaultMethod = 'index';
/**
* For Auto Routing.
* Whether to translate dashes in URIs for controller/method to underscores.
* Whether to translate dashes in URIs to underscores.
* Primarily useful when using the auto-routing.
*
* Default: false
@ -99,7 +93,6 @@ class Routing extends BaseRouting
public bool $autoRoute = false;
/**
* For Defined Routes.
* If TRUE, will enable the use of the 'prioritize' option
* when defining routes.
*
@ -108,16 +101,7 @@ class Routing extends BaseRouting
public bool $prioritize = false;
/**
* For Defined Routes.
* If TRUE, matched multiple URI segments will be passed as one parameter.
*
* Default: false
*/
public bool $multipleSegmentsOneParam = false;
/**
* For Auto Routing (Improved).
* Map of URI segments and namespaces.
* Map of URI segments and namespaces. For Auto Routing (Improved).
*
* The key is the first URI segment. The value is the controller namespace.
* E.g.,
@ -128,15 +112,4 @@ class Routing extends BaseRouting
* @var array<string, string> [ uri_segment => namespace ]
*/
public array $moduleRoutes = [];
/**
* For Auto Routing (Improved).
* Whether to translate dashes in URIs for controller/method to CamelCase.
* E.g., blog-controller -> BlogController
*
* If you enable this, $translateURIDashes is ignored.
*
* Default: false
*/
public bool $translateUriToCamelCase = false;
}

View File

@ -80,9 +80,9 @@ class Security extends BaseConfig
* CSRF Redirect
* --------------------------------------------------------------------------
*
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
* Redirect to previous page with error on failure.
*/
public bool $redirect = (ENVIRONMENT === 'production');
public bool $redirect = false;
/**
* --------------------------------------------------------------------------

View File

@ -37,8 +37,8 @@ class Services extends BaseService
return static::getSharedInstance('router', $routes, $request);
}
$routes ??= static::routes();
$request ??= static::request();
$routes = $routes ?? static::routes();
$request = $request ?? static::request();
return new Router($routes, $request);
}
@ -53,7 +53,7 @@ class Services extends BaseService
return static::getSharedInstance('negotiator', $request);
}
$request ??= static::request();
$request = $request ?? static::request();
return new Negotiate($request);
}

View File

@ -101,29 +101,4 @@ class Session extends BaseConfig
* DB Group for the database session.
*/
public ?string $DBGroup = null;
/**
* --------------------------------------------------------------------------
* Lock Retry Interval (microseconds)
* --------------------------------------------------------------------------
*
* This is used for RedisHandler.
*
* Time (microseconds) to wait if lock cannot be acquired.
* The default is 100,000 microseconds (= 0.1 seconds).
*/
public int $lockRetryInterval = 100_000;
/**
* --------------------------------------------------------------------------
* Lock Max Retries
* --------------------------------------------------------------------------
*
* This is used for RedisHandler.
*
* Maximum number of lock acquisition attempts.
* The default is 300 times. That is lock timeout is about 30 (0.1 * 300)
* seconds.
*/
public int $lockMaxRetries = 300;
}

View File

@ -33,7 +33,7 @@ class Toolbar extends BaseConfig
* List of toolbar collectors that will be called when Debug Toolbar
* fires up and collects data from.
*
* @var list<class-string>
* @var string[]
*/
public array $collectors = [
Timers::class,
@ -51,7 +51,7 @@ class Toolbar extends BaseConfig
* Collect Var Data
* --------------------------------------------------------------------------
*
* If set to false var data from the views will not be collected. Useful to
* If set to false var data from the views will not be colleted. Useful to
* avoid high memory usage when there are lots of data passed to the view.
*/
public bool $collectVarData = true;
@ -102,7 +102,7 @@ class Toolbar extends BaseConfig
*
* NOTE: The ROOTPATH will be prepended to all values.
*
* @var list<string>
* @var string[]
*/
public array $watchedDirectories = ['app', 'modules', 'themes'];
@ -114,7 +114,7 @@ class Toolbar extends BaseConfig
* Contains an array of file extensions that will be watched for changes and
* used to determine if the hot-reload feature should reload the page or not.
*
* @var list<string>
* @var string[]
*/
public array $watchedExtensions = ['php', 'css', 'js', 'html', 'svg', 'json', 'env'];
}

View File

@ -16,7 +16,7 @@ class Validation extends BaseConfig
/**
* Stores the classes that contain the rules that are available.
*
* @var list<string>
* @var string[]
*/
public array $ruleSets = [
Rules::class,

View File

@ -9,8 +9,8 @@ use CodeIgniter\View\ViewDecoratorInterface;
use ViewComponents\Decorator;
/**
* @phpstan-type parser_callable (callable(mixed): mixed)
* @phpstan-type parser_callable_string (callable(mixed): mixed)&string
* @phpstan-type ParserCallable (callable(mixed): mixed)
* @phpstan-type ParserCallableString (callable(mixed): mixed)&string
*/
class View extends BaseView
{
@ -31,8 +31,8 @@ class View extends BaseView
*
* Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
*
* @var array<string, string>
* @phpstan-var array<string, parser_callable_string>
* @var array<string, string>
* @phpstan-var array<string, ParserCallableString>
*/
public $filters = [];
@ -40,8 +40,8 @@ class View extends BaseView
* Parser Plugins provide a way to extend the functionality provided by the core Parser by creating aliases that
* will be replaced with any callable. Can be single or tag pair.
*
* @var array<string, callable|list<string>|string>
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
* @var array<string, array<string>|callable|string>
* @phpstan-var array<string, array<ParserCallableString>|ParserCallableString|ParserCallable>
*/
public $plugins = [];
@ -51,7 +51,7 @@ class View extends BaseView
*
* All classes must implement CodeIgniter\View\ViewDecoratorInterface
*
* @var list<class-string<ViewDecoratorInterface>>
* @var class-string<ViewDecoratorInterface>[]
*/
public array $decorators = [Decorator::class];
}

View File

@ -13,6 +13,8 @@ use Psr\Log\LoggerInterface;
use ViewThemes\Theme;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components and performing functions that are needed by all
* your controllers. Extend this class in any new controllers: class Home extends BaseController
*
@ -39,7 +41,7 @@ abstract class BaseController extends Controller
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* @var list<string>
* @var string[]
*/
protected $helpers = [];

View File

@ -40,7 +40,7 @@ class EpisodeAudioController extends Controller
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
* other controllers that extend Analytics.
*
* @var list<string>
* @var string[]
*/
protected $helpers = ['analytics'];

View File

@ -106,7 +106,9 @@ class EpisodeController extends BaseController
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode/comments', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -155,7 +157,9 @@ class EpisodeController extends BaseController
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode/activity', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -163,7 +167,7 @@ class EpisodeController extends BaseController
return $cachedView;
}
public function chapters(): string
public function chapters(): String
{
// Prevent analytics hit when authenticated
if (! auth()->loggedIn()) {
@ -214,71 +218,9 @@ class EpisodeController extends BaseController
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode/chapters', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
public function transcript(): string
{
// Prevent analytics hit when authenticated
if (! auth()->loggedIn()) {
$this->registerPodcastWebpageHit($this->episode->podcast_id);
}
$cacheName = implode(
'_',
array_filter([
'page',
"podcast#{$this->podcast->id}",
"episode#{$this->episode->id}",
'transcript',
service('request')
->getLocale(),
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
auth()
->loggedIn() ? 'authenticated' : null,
]),
);
if (! ($cachedView = cache($cacheName))) {
// get transcript from json file
$data = [
'metatags' => get_episode_metatags($this->episode),
'podcast' => $this->podcast,
'episode' => $this->episode,
];
if ($this->episode->transcript !== null) {
$data['transcript'] = $this->episode->transcript;
if ($this->episode->transcript->json_key !== null) {
/** @var FileManagerInterface $fileManager */
$fileManager = service('file_manager');
$transcriptJsonString = (string) $fileManager->getFileContents(
$this->episode->transcript->json_key
);
$data['captions'] = json_decode($transcriptJsonString, true);
}
}
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
if (auth()->loggedIn()) {
helper('form');
return view('episode/transcript', $data);
}
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode/transcript', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -331,7 +273,9 @@ class EpisodeController extends BaseController
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('embed', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -410,9 +354,11 @@ class EpisodeController extends BaseController
* get comments: aggregated replies from posts referring to the episode
*/
$episodeComments = model(PostModel::class)
->whereIn('in_reply_to_id', fn (BaseBuilder $builder): BaseBuilder => $builder->select('id')
->from('fediverse_posts')
->where('episode_id', $this->episode->id))
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
return $builder->select('id')
->from('fediverse_posts')
->where('episode_id', $this->episode->id);
})
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->orderBy('published_at', 'ASC');

View File

@ -13,6 +13,7 @@ namespace App\Controllers;
use App\Entities\Episode;
use App\Models\EpisodeModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Modules\Media\FileManagers\FileManagerInterface;
class EpisodePreviewController extends BaseController
@ -44,7 +45,7 @@ class EpisodePreviewController extends BaseController
return $this->{$method}(...$params);
}
public function index(): string
public function index(): RedirectResponse | string
{
helper('form');
@ -54,7 +55,7 @@ class EpisodePreviewController extends BaseController
]);
}
public function activity(): string
public function activity(): RedirectResponse | string
{
helper('form');
@ -64,7 +65,7 @@ class EpisodePreviewController extends BaseController
]);
}
public function chapters(): string
public function chapters(): RedirectResponse | string
{
$data = [
'podcast' => $this->episode->podcast,
@ -83,30 +84,4 @@ class EpisodePreviewController extends BaseController
helper('form');
return view('episode/preview-chapters', $data);
}
public function transcript(): string
{
// get transcript from json file
$data = [
'podcast' => $this->episode->podcast,
'episode' => $this->episode,
];
if ($this->episode->transcript !== null) {
$data['transcript'] = $this->episode->transcript;
if ($this->episode->transcript->json_key !== null) {
/** @var FileManagerInterface $fileManager */
$fileManager = service('file_manager');
$transcriptJsonString = (string) $fileManager->getFileContents(
$this->episode->transcript->json_key
);
$data['captions'] = json_decode($transcriptJsonString, true);
}
}
helper('form');
return view('episode/preview-transcript', $data);
}
}

View File

@ -79,7 +79,13 @@ class FeedController extends Controller
);
cache()
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
);
}
return $this->response->setXML($found);

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use App\Models\PlatformModel;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;
/*
* Provide public access to all platforms so that they can be exported
*/
class PlatformController extends Controller
{
public function index(): ResponseInterface
{
$model = new PlatformModel();
return $this->response->setJSON($model->getPlatforms());
}
}

View File

@ -96,7 +96,9 @@ class PodcastController extends BaseController
);
return view('podcast/activity', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -146,7 +148,9 @@ class PodcastController extends BaseController
);
return view('podcast/about', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
@ -266,7 +270,9 @@ class PodcastController extends BaseController
$this->podcast->id,
);
return view('podcast/episodes', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}

View File

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
/**
* Class AddPodcastsVerifyTxtField adds 1 field to podcast table in database to support podcast:txt tag
*
* @copyright 2024 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
class AddPodcastsVerifyTxtField extends BaseMigration
{
public function up(): void
{
$fields = [
'verify_txt' => [
'type' => 'TEXT',
'null' => true,
'after' => 'location_osm',
],
];
$this->forge->addColumn('podcasts', $fields);
}
public function down(): void
{
$this->forge->dropColumn('podcasts', 'verify_txt');
}
}

View File

@ -1,154 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class RefactorPlatforms extends Migration
{
public function up(): void
{
$this->forge->addField([
'id' => [
'type' => 'INT',
'unsigned' => true,
'auto_increment' => true,
],
'podcast_id' => [
'type' => 'INT',
'unsigned' => true,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['podcasting', 'social', 'funding'],
'after' => 'podcast_id',
],
'slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'link_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
],
'account_id' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'is_visible' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE', 'platforms_podcast_id_foreign');
$this->forge->addUniqueKey(['podcast_id', 'type', 'slug']);
$this->forge->createTable('platforms_temp');
$platformsData = $this->db->table('podcasts_platforms')
->select('podcasts_platforms.*, type')
->join('platforms', 'platforms.slug = podcasts_platforms.platform_slug')
->get()
->getResultArray();
$data = [];
foreach ($platformsData as $platformData) {
$data[] = [
'podcast_id' => $platformData['podcast_id'],
'type' => $platformData['type'],
'slug' => $platformData['platform_slug'],
'link_url' => $platformData['link_url'],
'account_id' => $platformData['account_id'],
'is_visible' => $platformData['is_visible'],
];
}
if ($data !== []) {
$this->db->table('platforms_temp')
->insertBatch($data);
}
$this->forge->dropTable('platforms');
$this->forge->dropTable('podcasts_platforms');
$this->forge->renameTable('platforms_temp', 'platforms');
}
public function down(): void
{
// delete platforms
$this->forge->dropTable('platforms');
// recreate platforms and podcasts_platforms tables
$this->forge->addField([
'slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['podcasting', 'social', 'funding'],
],
'label' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'home_url' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'submit_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
]);
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()');
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()'
);
$this->forge->addPrimaryKey('slug');
$this->forge->createTable('platforms');
$this->forge->addField([
'podcast_id' => [
'type' => 'INT',
'unsigned' => true,
],
'platform_slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'link_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
],
'account_id' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'is_visible' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
'is_on_embed' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
]);
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('platform_slug', 'platforms', 'slug', 'CASCADE');
$this->forge->createTable('podcasts_platforms');
}
}

View File

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
/**
* CodeIgniter 4.5.1 introduces new DataCaster class that breaks deserialization of import queue tasks.
* This just removes them altogether.
*/
class ClearImportQueue extends Migration
{
public function up(): void
{
service('settings')->forget('Import.queue');
}
public function down(): void
{
// nothing
}
}

View File

@ -20,5 +20,6 @@ class AppSeeder extends Seeder
{
$this->call('CategorySeeder');
$this->call('LanguageSeeder');
$this->call('PlatformSeeder');
}
}

View File

@ -20,6 +20,7 @@ class DevSeeder extends Seeder
{
$this->call('CategorySeeder');
$this->call('LanguageSeeder');
$this->call('PlatformSeeder');
$this->call('DevSuperadminSeeder');
}
}

View File

@ -77,32 +77,34 @@ class FakePodcastsAnalyticsSeeder extends Seeder
for (
$lineNumber = 0;
$lineNumber < random_int(1, (int) $probability1);
$lineNumber < rand(1, (int) $probability1);
++$lineNumber
) {
$probability2 = floor(exp(6 - $age / 20)) + 10;
$player =
$jsonUserAgents[
random_int(1, count($jsonUserAgents) - 1)
rand(1, count($jsonUserAgents) - 1)
];
$service =
$jsonRSSUserAgents[
random_int(1, count($jsonRSSUserAgents) - 1)
rand(1, count($jsonRSSUserAgents) - 1)
]['slug'];
$app = $player['app'] ?? '';
$device = $player['device'] ?? '';
$os = $player['os'] ?? '';
$isBot = $player['bot'] ?? 0;
$app = isset($player['app']) ? $player['app'] : '';
$device = isset($player['device'])
? $player['device']
: '';
$os = isset($player['os']) ? $player['os'] : '';
$isBot = isset($player['bot']) ? $player['bot'] : 0;
$fakeIp =
random_int(0, 255) .
rand(0, 255) .
'.' .
random_int(0, 255) .
rand(0, 255) .
'.' .
random_int(0, 255) .
rand(0, 255) .
'.' .
random_int(0, 255);
rand(0, 255);
$cityReader = new Reader(WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb');
@ -113,7 +115,9 @@ class FakePodcastsAnalyticsSeeder extends Seeder
try {
$city = $cityReader->city($fakeIp);
$countryCode = $city->country->isoCode ?? 'N/A';
$countryCode = $city->country->isoCode === null
? 'N/A'
: $city->country->isoCode;
$regionCode = $city->subdivisions === []
? 'N/A'
@ -124,20 +128,20 @@ class FakePodcastsAnalyticsSeeder extends Seeder
//Bad luck, bad IP, nothing to do.
}
$hits = random_int(0, (int) $probability2);
$hits = rand(0, (int) $probability2);
$analyticsPodcasts[] = [
'podcast_id' => $podcast->id,
'date' => date('Y-m-d', $date),
'duration' => random_int(60, 3600),
'bandwidth' => random_int(1000000, 10000000),
'duration' => rand(60, 3600),
'bandwidth' => rand(1000000, 10000000),
'hits' => $hits,
'unique_listeners' => $hits,
];
$analyticsPodcastsByHour[] = [
'podcast_id' => $podcast->id,
'date' => date('Y-m-d', $date),
'hour' => random_int(0, 23),
'hour' => rand(0, 23),
'hits' => $hits,
];
$analyticsPodcastsByCountry[] = [

View File

@ -216,23 +216,23 @@ class FakeWebsiteAnalyticsSeeder extends Seeder
for (
$lineNumber = 0;
$lineNumber < random_int(1, $probability1);
$lineNumber < rand(1, $probability1);
++$lineNumber
) {
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
$domain =
$this->domains[random_int(0, count($this->domains) - 1)];
$this->domains[rand(0, count($this->domains) - 1)];
$keyword =
$this->keywords[
random_int(0, count($this->keywords) - 1)
rand(0, count($this->keywords) - 1)
];
$browser =
$this->browsers[
random_int(0, count($this->browsers) - 1)
rand(0, count($this->browsers) - 1)
];
$hits = random_int(0, $probability2);
$hits = rand(0, $probability2);
$websiteByBrowser[] = [
'podcast_id' => $podcast->id,

View File

@ -3,471 +3,643 @@
declare(strict_types=1);
/**
* @copyright 2024 Ad Aures
* Class PlatformsSeeder Inserts values in platforms table in database
*
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace Modules\Platforms;
namespace App\Database\Seeds;
class Platforms
use CodeIgniter\Database\Seeder;
class PlatformSeeder extends Seeder
{
/**
* @var array<string,array<string,array{label:string,home_url:string,submit_url:?string}>>
*/
public const DATA = [
'podcasting' => [
'podcastindex' => [
'label' => 'Podcast Index',
'home_url' => 'https://podcastindex.org/',
'submit_url' => 'https://podcastindex.org/add',
],
'apple' => [
'label' => 'Apple Podcasts',
'home_url' => 'https://www.apple.com/itunes/podcasts/',
'submit_url' => 'https://podcastsconnect.apple.com/my-podcasts/new-feed',
],
'spotify' => [
'label' => 'Spotify',
'home_url' => 'https://www.spotify.com/',
'submit_url' => 'https://podcasters.spotify.com/dash/submit',
],
'youtube-music' => [
'label' => 'YouTube Music',
'home_url' => 'https://www.youtube.com/creators/podcasts/',
'submit_url' => 'https://studio.youtube.com/channel/content/podcasts',
],
'amazon' => [
'label' => 'Amazon Music',
public function run(): void
{
$podcastingData = [
[
'slug' => 'amazon',
'type' => 'podcasting',
'label' => 'Amazon Music & Audible',
'home_url' => 'https://music.amazon.com/',
'submit_url' => 'https://podcasters.amazon.com/',
],
'antennapod' => [
[
'slug' => 'antennapod',
'type' => 'podcasting',
'label' => 'AntennaPod',
'home_url' => 'https://antennapod.org/',
'submit_url' => 'https://antennapod.org/documentation/podcasters-hosters/add-on-antennapod',
],
'blubrry' => [
[
'slug' => 'apple',
'type' => 'podcasting',
'label' => 'Apple Podcasts',
'home_url' => 'https://www.apple.com/itunes/podcasts/',
'submit_url' => 'https://podcastsconnect.apple.com/my-podcasts/new-feed',
],
[
'slug' => 'blubrry',
'type' => 'podcasting',
'label' => 'Blubrry',
'home_url' => 'https://www.blubrry.com/',
'submit_url' => 'https://www.blubrry.com/addpodcast.php',
],
'castbox' => [
[
'slug' => 'breaker',
'type' => 'podcasting',
'label' => 'Breaker',
'home_url' => 'https://www.breaker.audio/',
'submit_url' => 'https://podcasters.breaker.audio/',
],
[
'slug' => 'castbox',
'type' => 'podcasting',
'label' => 'Castbox',
'home_url' => 'https://castbox.fm/',
'submit_url' => 'https://helpcenter.castbox.fm/portal/kb/articles/submit-my-podcast',
],
'castopod' => [
[
'slug' => 'castopod',
'type' => 'podcasting',
'label' => 'Castopod',
'home_url' => 'https://castopod.org/',
'submit_url' => 'https://castopod.org/instances',
],
'castro' => [
[
'slug' => 'castro',
'type' => 'podcasting',
'label' => 'Castro',
'home_url' => 'http://castro.fm/',
'submit_url' => 'https://castro.fm/support/link-to-your-podcast-in-castro',
],
'deezer' => [
[
'slug' => 'deezer',
'type' => 'podcasting',
'label' => 'Deezer',
'home_url' => 'https://www.deezer.com/',
'submit_url' => 'https://podcasters.deezer.com/submission',
],
'fyyd' => [
[
'slug' => 'fyyd',
'type' => 'podcasting',
'label' => 'fyyd',
'home_url' => 'https://fyyd.de/',
'submit_url' => 'https://fyyd.de/add-feed',
],
'ivoox' => [
[
'slug' => 'google',
'type' => 'podcasting',
'label' => 'Google Podcasts',
'home_url' => 'https://podcasts.google.com/about',
'submit_url' => 'https://search.google.com/search-console/about',
],
[
'slug' => 'ivoox',
'type' => 'podcasting',
'label' => 'Ivoox',
'home_url' => 'https://www.ivoox.com/',
'submit_url' => null,
'submit_url' => 'http://www.ivoox.com/upload-podcast_u.html',
],
'listennotes' => [
[
'slug' => 'listennotes',
'type' => 'podcasting',
'label' => 'ListenNotes',
'home_url' => 'https://www.listennotes.com/',
'submit_url' => 'https://www.listennotes.com/submit/',
],
'overcast' => [
[
'slug' => 'overcast',
'type' => 'podcasting',
'label' => 'Overcast',
'home_url' => 'https://overcast.fm/',
'submit_url' => 'https://overcast.fm/podcasterinfo',
],
'playerfm' => [
[
'slug' => 'playerfm',
'type' => 'podcasting',
'label' => 'Player.Fm',
'home_url' => 'https://player.fm/',
'submit_url' => 'https://player.fm/importer/feed',
],
'pocketcasts' => [
[
'slug' => 'pocketcasts',
'type' => 'podcasting',
'label' => 'Pocketcasts',
'home_url' => 'https://www.pocketcasts.com/',
'submit_url' => 'https://www.pocketcasts.com/submit/',
],
'podbean' => [
[
'slug' => 'podbean',
'type' => 'podcasting',
'label' => 'Podbean',
'home_url' => 'https://www.podbean.com/',
'submit_url' => 'https://www.podbean.com/site/submitPodcast',
],
'podcastaddict' => [
[
'slug' => 'podcastaddict',
'type' => 'podcasting',
'label' => 'Podcast Addict',
'home_url' => 'https://podcastaddict.com/',
'submit_url' => 'https://podcastaddict.com/submit',
],
'podchaser' => [
[
'slug' => 'podcastindex',
'type' => 'podcasting',
'label' => 'Podcast Index',
'home_url' => 'https://podcastindex.org/',
'submit_url' => 'https://podcastindex.org/add',
],
[
'slug' => 'podchaser',
'type' => 'podcasting',
'label' => 'Podchaser',
'home_url' => 'https://www.podchaser.com/',
'submit_url' => 'https://www.podchaser.com/add',
'submit_url' => 'https://www.podchaser.com/creators/edit',
],
'podcloud' => [
[
'slug' => 'podcloud',
'type' => 'podcasting',
'label' => 'podCloud',
'home_url' => 'https://podcloud.fr/',
'submit_url' => 'https://podcloud.fr/studio/podcasts/new',
],
'podlink' => [
[
'slug' => 'podinstall',
'type' => 'podcasting',
'label' => 'Podinstall',
'home_url' => 'https://www.podinstall.com/',
'submit_url' => 'https://www.podinstall.com/claim.html',
],
[
'slug' => 'podlink',
'type' => 'podcasting',
'label' => 'pod.link',
'home_url' => 'https://pod.link/',
'submit_url' => null,
'submit_url' => 'https://pod.link',
],
'podtail' => [
[
'slug' => 'podtail',
'type' => 'podcasting',
'label' => 'Podtail',
'home_url' => 'https://podtail.com/',
'submit_url' => 'https://podtail.com/about/faq/',
],
'podfriend' => [
[
'slug' => 'podfriend',
'type' => 'podcasting',
'label' => 'Podfriend',
'home_url' => 'https://www.podfriend.com/',
'submit_url' => 'https://podcastindex.org/add',
],
'podverse' => [
[
'slug' => 'podverse',
'type' => 'podcasting',
'label' => 'Podverse',
'home_url' => 'https://podverse.fm/',
'submit_url' => 'https://docs.google.com/forms/d/e/1FAIpQLSdewKP-YrE8zGjDPrkmoJEwCxPl_gizEkmzAlTYsiWAuAk1Ng/viewform',
],
'radiopublic' => [
[
'slug' => 'radiopublic',
'type' => 'podcasting',
'label' => 'RadioPublic',
'home_url' => 'https://radiopublic.com/',
'submit_url' => 'https://podcasters.radiopublic.com/signup',
],
'spreaker' => [
[
'slug' => 'spotify',
'type' => 'podcasting',
'label' => 'Spotify',
'home_url' => 'https://www.spotify.com/',
'submit_url' => 'https://podcasters.spotify.com/submit',
],
[
'slug' => 'spreaker',
'type' => 'podcasting',
'label' => 'Spreaker',
'home_url' => 'https://www.spreaker.com/',
'submit_url' => 'https://www.spreaker.com/cms/shows/rss-import',
],
'tunein' => [
[
'slug' => 'stitcher',
'type' => 'podcasting',
'label' => 'Stitcher',
'home_url' => 'https://www.stitcher.com/',
'submit_url' => 'https://partners.stitcher.com/join',
],
[
'slug' => 'tunein',
'type' => 'podcasting',
'label' => 'TuneIn',
'home_url' => 'https://tunein.com/',
'submit_url' => 'https://help.tunein.com/contact/add-podcast-S19TR3Sdf',
],
'anytime' => [
[
'slug' => 'anytime',
'type' => 'podcasting',
'label' => 'Anytime Podcast Player',
'home_url' => 'https://anytimeplayer.app/',
'submit_url' => null,
'submit_url' => '',
],
'breez' => [
[
'slug' => 'breez',
'type' => 'podcasting',
'label' => 'Breez',
'home_url' => 'https://breez.technology/',
'submit_url' => null,
'submit_url' => '',
],
'castamatic' => [
[
'slug' => 'castamatic',
'type' => 'podcasting',
'label' => 'Castamatic',
'home_url' => 'https://castamatic.com/',
'submit_url' => null,
'submit_url' => '',
],
'curiocaster' => [
[
'slug' => 'castcoverage',
'type' => 'podcasting',
'label' => 'CastCoverage',
'home_url' => 'http://castcoverage.com/',
'submit_url' => '',
],
[
'slug' => 'curiocaster',
'type' => 'podcasting',
'label' => 'CurioCaster',
'home_url' => 'https://curiocaster.com/',
'submit_url' => null,
'submit_url' => '',
],
'episodes-fm' => [
'label' => 'Episodes.fm',
'home_url' => 'https://episodes.fm/',
'submit_url' => 'https://podcastindex.org/add',
[
'slug' => 'escapepod',
'type' => 'podcasting',
'label' => 'Escapepod',
'home_url' => 'http://y20k.org/escapepod/',
'submit_url' => '',
],
'fountain' => [
[
'slug' => 'fountain',
'type' => 'podcasting',
'label' => 'Fountain',
'home_url' => 'https://www.fountain.fm/',
'submit_url' => 'https://support.fountain.fm/article/56-how-to-claim-your-show-on-fountain',
'submit_url' => '',
],
'gpodder' => [
[
'slug' => 'gpodder',
'type' => 'podcasting',
'label' => 'gPodder',
'home_url' => 'https://gpodder.org/',
'submit_url' => null,
'submit_url' => '',
],
'hypercatcher' => [
[
'slug' => 'hypercatcher',
'type' => 'podcasting',
'label' => 'HyperCatcher',
'home_url' => 'https://hypercatcher.com/',
'submit_url' => null,
'submit_url' => '',
],
'ivyfm' => [
'label' => 'Ivy.fm',
[
'slug' => 'ivyfm',
'type' => 'podcasting',
'label' => 'Ivy Podcast Discovery',
'home_url' => 'https://ivy.fm/',
'submit_url' => null,
'submit_url' => '',
],
'jumplink' => [
[
'slug' => 'jumplink',
'type' => 'podcasting',
'label' => 'JumpLink',
'home_url' => 'https://jump.link/',
'submit_url' => 'https://jump.link/a/accounts/signup/',
],
'kasts' => [
[
'slug' => 'kasts',
'type' => 'podcasting',
'label' => 'Kasts',
'home_url' => 'https://apps.kde.org/kasts/',
'submit_url' => null,
'submit_url' => '',
],
'playapod' => [
[
'slug' => 'playapod',
'type' => 'podcasting',
'label' => 'Playapod',
'home_url' => 'https://playapod.com/',
'submit_url' => null,
'submit_url' => '',
],
'plink' => [
[
'slug' => 'plink',
'type' => 'podcasting',
'label' => 'Plink',
'home_url' => 'https://plinkhq.com/',
'submit_url' => null,
'submit_url' => '',
],
'podcastchapters' => [
[
'slug' => 'podcastchapters',
'type' => 'podcasting',
'label' => 'Podcast Chapters',
'home_url' => 'https://chaptersapp.com/',
'submit_url' => null,
'submit_url' => '',
],
'podcastguru' => [
[
'slug' => 'podcastguru',
'type' => 'podcasting',
'label' => 'Podcast Guru',
'home_url' => 'https://podcastguru.io/',
'submit_url' => 'https://podcastguru.io/promote-your-podcast/',
],
'podlp' => [
[
'slug' => 'podlp',
'type' => 'podcasting',
'label' => 'PodLP',
'home_url' => 'https://podlp.com/',
'submit_url' => 'https://podlp.com/submit.html',
],
'podnews' => [
[
'slug' => 'podnews',
'type' => 'podcasting',
'label' => 'Podnews',
'home_url' => 'https://podnews.net/',
'submit_url' => 'https://podnews.net/podcast/subscribe-pages',
'home_url' => 'https://podnews.net/podcast/subscribe-pages',
'submit_url' => '',
],
'podstation' => [
[
'slug' => 'podstation',
'type' => 'podcasting',
'label' => 'podStation',
'home_url' => 'https://podstation.github.io/',
'submit_url' => null,
'submit_url' => '',
],
'sphinxchat' => [
[
'slug' => 'sphinxchat',
'type' => 'podcasting',
'label' => 'Sphinx',
'home_url' => 'https://sphinx.chat/',
'submit_url' => null,
'submit_url' => '',
],
'truefans' => [
'label' => 'Truefans',
'home_url' => 'https://truefans.fm/',
'submit_url' => 'https://podcastindex.org/add',
],
'tsacdop' => [
[
'slug' => 'tsacdop',
'type' => 'podcasting',
'label' => 'Tsacdop',
'home_url' => 'https://www.tsacdop.app/',
'submit_url' => null,
'submit_url' => '',
],
],
'social' => [
'bluesky' => [
'label' => 'Bluesky',
'home_url' => 'https://bsky.app/',
'submit_url' => 'https://bsky.app/',
[
'slug' => 'youtube-music',
'type' => 'podcasting',
'label' => 'YouTube Music',
'home_url' => 'https://www.youtube.com/creators/podcasts/',
'submit_url' => 'https://studio.youtube.com/channel/content/podcasts',
],
'discord' => [
'label' => 'Discord',
'home_url' => 'https://discord.com/',
'submit_url' => 'https://discord.com/register',
],
'facebook' => [
'label' => 'Facebook',
'home_url' => 'https://www.facebook.com/',
'submit_url' => 'https://www.facebook.com/pages/creation/',
],
'funkwhale' => [
'label' => 'Funkwhale',
'home_url' => 'https://funkwhale.audio/',
'submit_url' => 'https://network.funkwhale.audio/dashboards/',
],
'instagram' => [
'label' => 'Instagram',
'home_url' => 'https://www.instagram.com/',
'submit_url' => 'https://www.instagram.com/accounts/emailsignup/',
],
'linkedin' => [
'label' => 'LinkedIn',
'home_url' => 'https://www.linkedin.com/',
'submit_url' => 'https://www.linkedin.com/company/setup/new/',
],
'mastodon' => [
'label' => 'Mastodon',
'home_url' => 'https://joinmastodon.org/',
'submit_url' => 'https://joinmastodon.org/communities',
],
'matrix' => [
'label' => 'Matrix',
'home_url' => 'https://matrix.org/',
'submit_url' => 'https://matrix.org/try-matrix/',
],
'misskey' => [
'label' => 'Misskey',
'home_url' => 'https://join.misskey.page/',
'submit_url' => 'https://join.misskey.page/en-US/instances',
],
'mobilizon' => [
'label' => 'Mobilizon',
'home_url' => 'https://joinmobilizon.org/',
'submit_url' => 'https://instances.joinmobilizon.org/instances',
],
'peertube' => [
'label' => 'PeerTube',
'home_url' => 'https://joinpeertube.org/',
'submit_url' => 'https://joinpeertube.org/instances',
],
'pixelfed' => [
'label' => 'Pixelfed',
'home_url' => 'https://pixelfed.org/',
'submit_url' => 'https://beta.joinpixelfed.org/',
],
'pleroma' => [
'label' => 'Pleroma',
'home_url' => 'https://pleroma.social/',
'submit_url' => 'https://pleroma.social/#featured-instances',
],
'plume' => [
'label' => 'Plume',
'home_url' => 'https://joinplu.me/',
'submit_url' => 'https://joinplu.me/#instances',
],
'slack' => [
'label' => 'Slack',
'home_url' => 'https://slack.com/',
'submit_url' => 'https://slack.com/get-started#/create',
],
'telegram' => [
'label' => 'Telegram',
'home_url' => 'https://www.telegram.org/',
'submit_url' => null,
],
'threads' => [
'label' => 'Threads',
'home_url' => 'https://www.threads.net/',
'submit_url' => 'https://www.threads.net/login',
],
'tiktok' => [
'label' => 'TikTok',
'home_url' => 'https://www.tiktok.com/',
'submit_url' => 'https://www.tiktok.com/signup',
],
'twitch' => [
'label' => 'Twitch',
'home_url' => 'https://www.twitch.tv/',
'submit_url' => 'https://www.twitch.tv/signup',
],
'writefreely' => [
'label' => 'WriteFreely',
'home_url' => 'https://writefreely.org/',
'submit_url' => 'https://writefreely.org/instances',
],
'youtube' => [
'label' => 'YouTube',
'home_url' => 'https://www.youtube.com/',
'submit_url' => 'https://studio.youtube.com/',
],
'x' => [
'label' => 'Twitter / X',
'home_url' => 'https://x.com/',
'submit_url' => 'https://x.com/i/flow/signup',
],
],
'funding' => [
'buymeacoffee' => [
];
$fundingData = [
[
'slug' => 'buymeacoffee',
'type' => 'funding',
'label' => 'Buy Me a Coffee',
'home_url' => 'https://www.buymeacoffee.com/',
'submit_url' => 'https://www.buymeacoffee.com/signup',
],
'paypal' => [
[
'slug' => 'paypal',
'type' => 'funding',
'label' => 'PayPal',
'home_url' => 'https://www.paypal.com/',
'submit_url' => 'https://www.paypal.com/paypalme/my/grab',
],
'fosspay' => [
[
'slug' => 'fosspay',
'type' => 'funding',
'label' => 'fosspay',
'home_url' => 'https://git.sr.ht/~sircmpwn/fosspay',
'submit_url' => null,
'submit_url' => '',
],
'gofundme' => [
[
'slug' => 'gofundme',
'type' => 'funding',
'label' => 'GoFundMe',
'home_url' => 'https://www.gofundme.com/',
'submit_url' => 'https://www.gofundme.com/sign-up',
],
'helloasso' => [
[
'slug' => 'helloasso',
'type' => 'funding',
'label' => 'HelloAsso',
'home_url' => 'https://www.helloasso.com/',
'submit_url' => 'https://auth.helloasso.com/inscription',
],
'indiegogo' => [
[
'slug' => 'indiegogo',
'type' => 'funding',
'label' => 'Indiegogo',
'home_url' => 'https://www.indiegogo.com/',
'submit_url' => 'https://www.indiegogo.com/start-a-campaign#/',
],
'kickstarter' => [
[
'slug' => 'kickstarter',
'type' => 'funding',
'label' => 'Kickstarter',
'home_url' => 'https://www.kickstarter.com/',
'submit_url' => 'https://www.kickstarter.com/learn',
],
'kisskissbankbank' => [
[
'slug' => 'kisskissbankbank',
'type' => 'funding',
'label' => 'KissKissBankBank',
'home_url' => 'https://www.kisskissbankbank.com/',
'submit_url' => 'https://www.kisskissbankbank.com/en/financer-mon-projet',
],
'kofi' => [
[
'slug' => 'kofi',
'type' => 'funding',
'label' => 'Ko-fi',
'home_url' => 'https://ko-fi.com/',
'submit_url' => 'https://ko-fi.com/account/register',
],
'liberapay' => [
[
'slug' => 'liberapay',
'type' => 'funding',
'label' => 'Liberapay',
'home_url' => 'https://liberapay.com/',
'submit_url' => 'https://liberapay.com/sign-up',
],
'patreon' => [
[
'slug' => 'patreon',
'type' => 'funding',
'label' => 'Patreon',
'home_url' => 'https://www.patreon.com/',
'submit_url' => 'https://www.patreon.com/create',
],
'tipeee' => [
[
'slug' => 'tipeee',
'type' => 'funding',
'label' => 'Tipeee',
'home_url' => 'https://tipeee.com/',
'submit_url' => 'https://tipeee.com/register/',
],
'ulule' => [
[
'slug' => 'ulule',
'type' => 'funding',
'label' => 'Ulule',
'home_url' => 'https://www.ulule.com/',
'submit_url' => 'https://www.ulule.com/projects/create/#/',
],
'donorbox' => [
'label' => 'Donorbox',
'home_url' => 'https://donorbox.org/',
'submit_url' => 'https://donorbox.org/orgs/new',
];
$socialData = [
[
'slug' => 'bluesky',
'type' => 'social',
'label' => 'Bluesky',
'home_url' => 'https://bsky.app/',
'submit_url' => 'https://bsky.app/',
],
],
];
[
'slug' => 'discord',
'type' => 'social',
'label' => 'Discord',
'home_url' => 'https://discord.com/',
'submit_url' => 'https://discord.com/register',
],
[
'slug' => 'facebook',
'type' => 'social',
'label' => 'Facebook',
'home_url' => 'https://www.facebook.com/',
'submit_url' => 'https://www.facebook.com/pages/creation/?ref_type=comet_home',
],
[
'slug' => 'funkwhale',
'type' => 'social',
'label' => 'Funkwhale',
'home_url' => 'https://funkwhale.audio/',
'submit_url' => 'https://network.funkwhale.audio/dashboards/',
],
[
'slug' => 'instagram',
'type' => 'social',
'label' => 'Instagram',
'home_url' => 'https://www.instagram.com/',
'submit_url' => 'https://www.instagram.com/accounts/emailsignup/',
],
[
'slug' => 'linkedin',
'type' => 'social',
'label' => 'LinkedIn',
'home_url' => 'https://www.linkedin.com/',
'submit_url' => 'https://www.linkedin.com/company/setup/new/',
],
[
'slug' => 'mastodon',
'type' => 'social',
'label' => 'Mastodon',
'home_url' => 'https://joinmastodon.org/',
'submit_url' => 'https://joinmastodon.org/communities',
],
[
'slug' => 'matrix',
'type' => 'social',
'label' => 'Matrix',
'home_url' => 'https://matrix.org/',
'submit_url' => 'https://matrix.org/try-matrix/',
],
[
'slug' => 'misskey',
'type' => 'social',
'label' => 'Misskey',
'home_url' => 'https://join.misskey.page/',
'submit_url' => 'https://join.misskey.page/en-US/instances',
],
[
'slug' => 'mobilizon',
'type' => 'social',
'label' => 'Mobilizon',
'home_url' => 'https://joinmobilizon.org/',
'submit_url' => 'https://instances.joinmobilizon.org/instances',
],
[
'slug' => 'peertube',
'type' => 'social',
'label' => 'PeerTube',
'home_url' => 'https://joinpeertube.org/',
'submit_url' => 'https://joinpeertube.org/instances',
],
[
'slug' => 'pixelfed',
'type' => 'social',
'label' => 'Pixelfed',
'home_url' => 'https://pixelfed.org/',
'submit_url' => 'https://beta.joinpixelfed.org/',
],
[
'slug' => 'pleroma',
'type' => 'social',
'label' => 'Pleroma',
'home_url' => 'https://pleroma.social/',
'submit_url' => 'https://pleroma.social/#featured-instances',
],
[
'slug' => 'plume',
'type' => 'social',
'label' => 'Plume',
'home_url' => 'https://joinplu.me/',
'submit_url' => 'https://joinplu.me/#instances',
],
[
'slug' => 'slack',
'type' => 'social',
'label' => 'Slack',
'home_url' => 'https://slack.com/',
'submit_url' => 'https://slack.com/get-started#/create',
],
[
'slug' => 'threads',
'type' => 'social',
'label' => 'Threads',
'home_url' => 'https://www.threads.net/',
'submit_url' => 'https://www.threads.net/login',
],
[
'slug' => 'tiktok',
'type' => 'social',
'label' => 'TikTok',
'home_url' => 'https://www.tiktok.com/',
'submit_url' => 'https://www.tiktok.com/signup',
],
[
'slug' => 'twitch',
'type' => 'social',
'label' => 'Twitch',
'home_url' => 'https://www.twitch.tv/',
'submit_url' => 'https://www.twitch.tv/signup',
],
[
'slug' => 'writefreely',
'type' => 'social',
'label' => 'WriteFreely',
'home_url' => 'https://writefreely.org/',
'submit_url' => 'https://writefreely.org/instances',
],
[
'slug' => 'youtube',
'type' => 'social',
'label' => 'YouTube',
'home_url' => 'https://www.youtube.com/',
'submit_url' => 'https://studio.youtube.com/',
],
[
'slug' => 'x',
'type' => 'social',
'label' => 'X',
'home_url' => 'https://x.com/',
'submit_url' => 'https://x.com/i/flow/signup',
],
];
/**
* @return array<string,array{label:string,home_url:string,submit_url:?string}>
*/
public function getPlatformsByType(string $type): array
{
return self::DATA[$type] ?? [];
}
/**
* @return null|array{label:string,home_url:string,submit_url:?string}>
*/
public function findPlatformBySlug(string $type, string $slug): ?array
{
$data = self::DATA[$type] ?? [];
if (! array_key_exists($slug, $data)) {
return null;
}
return $data[$slug];
$data = [...$podcastingData, ...$fundingData, ...$socialData];
$this->db
->table('platforms')
->ignore(true)
->insertBatch($data);
}
}

View File

@ -483,7 +483,7 @@ class Episode extends Entity
public function setGuid(?string $guid = null): static
{
$this->attributes['guid'] = $guid ?? $this->getLink();
$this->attributes['guid'] = $guid === null ? $this->getLink() : $guid;
return $this;
}

View File

@ -8,20 +8,20 @@ declare(strict_types=1);
* @link https://castopod.org/
*/
namespace Modules\Platforms\Entities;
namespace App\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $podcast_id
* @property string $slug
* @property string $type
* @property string $label
* @property string $link_url
* @property string|null $account_id
* @property bool $is_visible
* @property string $home_url
* @property string|null $submit_url
* @property string|null $link_url
* @property string|null $account_id
* @property bool|null $is_visible
* @property bool|null $is_on_embed
*/
class Platform extends Entity
{
@ -29,14 +29,14 @@ class Platform extends Entity
* @var array<string, string>
*/
protected $casts = [
'podcast_id' => 'int',
'slug' => 'string',
'type' => 'string',
'label' => 'string',
'link_url' => 'string',
'account_id' => '?string',
'is_visible' => 'boolean',
'home_url' => 'string',
'submit_url' => '?string',
'slug' => 'string',
'type' => 'string',
'label' => 'string',
'home_url' => 'string',
'submit_url' => '?string',
'link_url' => '?string',
'account_id' => '?string',
'is_visible' => '?boolean',
'is_on_embed' => '?boolean',
];
}

View File

@ -15,6 +15,7 @@ use App\Models\ActorModel;
use App\Models\CategoryModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PlatformModel;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile;
@ -31,8 +32,6 @@ use League\CommonMark\MarkdownConverter;
use Modules\Auth\Models\UserModel;
use Modules\Media\Entities\Image;
use Modules\Media\Models\MediaModel;
use Modules\Platforms\Entities\Platform;
use Modules\Platforms\Models\PlatformModel;
use Modules\PremiumPodcasts\Entities\Subscription;
use Modules\PremiumPodcasts\Models\SubscriptionModel;
use RuntimeException;
@ -529,7 +528,7 @@ class Podcast extends Entity
}
if ($this->podcasting_platforms === null) {
$this->podcasting_platforms = (new PlatformModel())->getPlatforms($this->id, 'podcasting');
$this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'podcasting');
}
return $this->podcasting_platforms;
@ -547,7 +546,7 @@ class Podcast extends Entity
}
if ($this->social_platforms === null) {
$this->social_platforms = (new PlatformModel())->getPlatforms($this->id, 'social');
$this->social_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'social');
}
return $this->social_platforms;
@ -565,7 +564,7 @@ class Podcast extends Entity
}
if ($this->funding_platforms === null) {
$this->funding_platforms = (new PlatformModel())->getPlatforms($this->id, 'funding');
$this->funding_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'funding');
}
return $this->funding_platforms;

View File

@ -156,20 +156,20 @@ if (! function_exists('publication_button')) {
$label = lang('Episode.publish');
$route = route_to('episode-publish', $podcastId, $episodeId);
$variant = 'primary';
$iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
$iconLeft = 'upload-cloud-fill';
break;
case 'with_podcast':
case 'scheduled':
$label = lang('Episode.publish_edit');
$route = route_to('episode-publish_edit', $podcastId, $episodeId);
$variant = 'warning';
$iconLeft = 'upload-cloud-fill'; // @icon('upload-cloud-fill')
$iconLeft = 'upload-cloud-fill';
break;
case 'published':
$label = lang('Episode.unpublish');
$route = route_to('episode-unpublish', $podcastId, $episodeId);
$variant = 'danger';
$iconLeft = 'cloud-off-fill'; // @icon('cloud-off-fill')
$iconLeft = 'cloud-off-fill';
break;
default:
$label = '';

View File

@ -42,16 +42,24 @@ if (! function_exists('write_audio_file_tags')) {
// populate data array
$TagData = [
'title' => [esc($episode->title)],
'artist' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
'title' => [esc($episode->title)],
'artist' => [
$episode->podcast->publisher === null
? esc($episode->podcast->owner_name)
: $episode->podcast->publisher,
],
'album' => [esc($episode->podcast->title)],
'year' => [$episode->published_at instanceof Time ? $episode->published_at->format('Y') : ''],
'genre' => ['Podcast'],
'comment' => [$episode->description],
'track_number' => [(string) $episode->number],
'copyright_message' => [$episode->podcast->copyright],
'publisher' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
'encoded_by' => ['Castopod'],
'publisher' => [
$episode->podcast->publisher === null
? esc($episode->podcast->owner_name)
: $episode->podcast->publisher,
],
'encoded_by' => ['Castopod'],
// TODO: find a way to add the remaining tags for podcasts as the library doesn't seem to allow it
// 'website' => [$podcast_url],

View File

@ -164,7 +164,7 @@ if (! function_exists('parse_size')) {
$size = (float) preg_replace('~[^0-9\.]~', '', $size); // Remove the non-numeric characters from the size.
if ($unit !== '') {
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round($size * 1024 ** ((float) stripos('bkmgtpezy', $unit[0])));
return round($size * pow(1024, (float) stripos('bkmgtpezy', $unit[0])));
}
return round($size);
@ -183,7 +183,7 @@ if (! function_exists('format_bytes')) {
$pow = floor(($bytes ? log($bytes) : 0) / log($is_binary ? 1024 : 1000));
$pow = min($pow, count($units) - 1);
$bytes /= ($is_binary ? 1024 : 1000) ** $pow;
$bytes /= pow($is_binary ? 1024 : 1000, $pow);
return round($bytes, $precision) . $units[$pow];
}

View File

@ -17,6 +17,7 @@ use Config\Mimes;
use Modules\Media\Entities\Chapters;
use Modules\Media\Entities\Transcript;
use Modules\PremiumPodcasts\Entities\Subscription;
use Modules\WebSub\Config\WebSub;
if (! function_exists('get_rss_feed')) {
/**
@ -121,12 +122,6 @@ if (! function_exists('get_rss_feed')) {
->addAttribute('owner', $podcast->owner_email);
}
if ($podcast->verify_txt !== null) {
$channel
->addChild('txt', $podcast->verify_txt, $podcastNamespace)
->addAttribute('purpose', 'verify');
}
if ($podcast->imported_feed_url !== null) {
$channel->addChild('previousUrl', $podcast->imported_feed_url, $podcastNamespace);
}
@ -260,7 +255,12 @@ if (! function_exists('get_rss_feed')) {
$itunesNamespace,
);
$channel->addChild('author', $podcast->publisher ?: $podcast->owner_name, $itunesNamespace, false);
$channel->addChild(
'author',
$podcast->publisher ? $podcast->publisher : $podcast->owner_name,
$itunesNamespace,
false
);
$channel->addChild('link', $podcast->link);
$owner = $channel->addChild('owner', null, $itunesNamespace);
@ -274,7 +274,7 @@ if (! function_exists('get_rss_feed')) {
$channel->addChild('type', $podcast->type, $itunesNamespace);
$podcast->copyright &&
$channel->addChild('copyright', $podcast->copyright);
if ($podcast->is_blocked || $subscription instanceof Subscription) {
if ($podcast->is_blocked) {
$channel->addChild('block', 'Yes', $itunesNamespace);
}
@ -348,21 +348,6 @@ if (! function_exists('get_rss_feed')) {
$item->addChild('season', (string) $episode->season_number, $itunesNamespace);
$item->addChild('episodeType', $episode->type, $itunesNamespace);
// If episode is of type trailer, add podcast:trailer tag on channel level
if ($episode->type === 'trailer') {
$trailer = $channel->addChild('trailer', $episode->title, $podcastNamespace);
$trailer->addAttribute('pubdate', $episode->published_at->format(DATE_RFC2822));
$trailer->addAttribute(
'url',
$episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams),
);
$trailer->addAttribute('length', (string) $episode->audio->file_size);
$trailer->addAttribute('type', $episode->audio->file_mimetype);
if ($episode->season_number !== null) {
$trailer->addAttribute('season', (string) $episode->season_number);
}
}
// add podcast namespace tags for season and episode
$episode->season_number &&
$item->addChild('season', (string) $episode->season_number, $podcastNamespace);

View File

@ -24,7 +24,6 @@ return [
'comments' => 'التعليقات',
'activity' => 'النشاط',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'وصف الحلقة',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -27,7 +27,6 @@ return [
'comments' => 'Evezhiadennoù',
'activity' => 'Oberiantiz',
'chapters' => 'Chabistroù',
'transcript' => 'Transcript',
'description' => 'Deskrivadur ar rann',
'number_of_comments' => '{numberOfComments, plural,
one {# evezhiadenn}
@ -51,6 +50,4 @@ return [
'publish_edit' => 'Kemmañ an embannadur',
],
'no_chapters' => 'N\'eus chabistr ebet evit ar rann.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comentaris',
'activity' => 'Activitat',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descripció de l\'episodi',
'number_of_comments' => '{numberOfComments, plural,
one {# comentari}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Kommentarer',
'activity' => 'Aktivitet',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episodebeskrivelse',
'number_of_comments' => '{numberOfComments, plural,
one {# kommentar}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -23,8 +23,7 @@ return [
'back_to_episodes' => 'Zurück zu Episoden von {podcast}',
'comments' => 'Kommentare',
'activity' => 'Aktivitäten',
'chapters' => 'Kapitel',
'transcript' => 'Transcript',
'chapters' => 'Chapters',
'description' => 'Beschreibung der Episode',
'number_of_comments' => '{numberOfComments, plural,
one {# Kommentar}
@ -44,7 +43,5 @@ return [
'publish' => 'Veröffentlichen',
'publish_edit' => 'Veröffentlichung bearbeiten',
],
'no_chapters' => 'Für diese Episode sind keine Kapitel verfügbar.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
'no_chapters' => 'No chapters are available for this episode.',
];

View File

@ -51,5 +51,5 @@ return [
other {# Personen}
}',
'persons_list' => 'Mitwirkende',
'castopod_website' => 'Castopod (Webseite)',
'castopod_website' => 'Castopod (website)',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Σχόλια',
'activity' => 'Δραστηριότητα',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Περιγραφή επεισοδίου',
'number_of_comments' => '{numberOfComments, plural,
one {# σχόλιο}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comentarios',
'activity' => 'Actividad',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descripción del episodio',
'number_of_comments' => '{numberOfComments, plural,
one {# comentario}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'title' => "{actorDisplayName}'s comment for {episodeTitle}",
'back_to_comments' => 'Back to comments',
'form' => [
'episode_message_placeholder' => 'Write a comment…',
'reply_to_placeholder' => 'Reply to @{actorUsername}',
'submit' => 'Send',
'submit_reply' => 'Reply',
],
'likes' => '{numberOfLikes, plural,
one {# like}
other {# likes}
}',
'replies' => '{numberOfReplies, plural,
one {# reply}
other {# replies}
}',
'like' => 'Like',
'reply' => 'Reply',
'view_replies' => 'View replies ({numberOfReplies})',
'block_actor' => 'Block user @{actorUsername}',
'block_domain' => 'Block domain @{actorDomain}',
'delete' => 'Delete comment',
];

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'yes' => 'Yes',
'no' => 'No',
'cancel' => 'Cancel',
'optional' => 'Optional',
'close' => 'Close',
'home' => 'Home',
'explicit' => 'Explicit',
'powered_by' => 'Powered by {castopod}',
'go_back' => 'Go back',
'play_episode_button' => [
'play' => 'Play',
'playing' => 'Playing',
],
'read_more' => 'Read more',
'read_less' => 'Read less',
'see_more' => 'See more',
'see_less' => 'See less',
'legal_notice' => 'Legal notice',
];

View File

@ -1,50 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'season' => 'Season {seasonNumber}',
'season_abbr' => 'S{seasonNumber}',
'number' => 'Episode {episodeNumber}',
'number_abbr' => 'Ep. {episodeNumber}',
'season_episode' => 'Season {seasonNumber} episode {episodeNumber}',
'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}',
'persons' => '{personsCount, plural,
one {# person}
other {# persons}
}',
'persons_list' => 'Persons',
'back_to_episodes' => 'Back to episodes of {podcast}',
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
other {# comments}
}',
'all_podcast_episodes' => 'All podcast episodes',
'back_to_podcast' => 'Go back to podcast',
'preview' => [
'title' => 'Preview',
'not_published' => 'Not published',
'text' => '{publication_status, select,
published {This episode is not yet published.}
scheduled {This episode is scheduled for publication on {publication_date}.}
with_podcast {This episode will be published at the same time as the podcast.}
other {This episode is not yet published.}
}',
'publish' => 'Publish',
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -1,37 +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/
*/
return [
'your_handle' => 'Your handle',
'your_handle_hint' => 'Enter the @username@domain you want to act from.',
'follow' => [
'label' => 'Follow',
'title' => 'Follow {actorDisplayName}',
'subtitle' => 'You are going to follow:',
'accountNotFound' => 'The account could not be found.',
'remoteFollowNotAllowed' => 'Seems like the account server does not allow remote follows…',
'submit' => 'Proceed to follow',
],
'favourite' => [
'title' => "Favourite {actorDisplayName}'s post",
'subtitle' => 'You are going to favourite:',
'submit' => 'Proceed to favourite',
],
'reblog' => [
'title' => "Share {actorDisplayName}'s post",
'subtitle' => 'You are going to share:',
'submit' => 'Proceed to share',
],
'reply' => [
'title' => "Reply to {actorDisplayName}'s post",
'subtitle' => 'You are going to reply to:',
'submit' => 'Proceed to reply',
],
];

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'all_podcasts' => 'All podcasts',
'sort_by' => 'Sort by',
'sort_options' => [
'activity' => 'Recent activity',
'created_desc' => 'Newest first',
'created_asc' => 'Oldest first',
],
'no_podcast' => 'No podcast found',
];

View File

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'back_to_home' => 'Back to home',
'map' => [
'title' => 'Map',
'description' => 'Discover podcast episodes on {siteName} that are placed on a map! Travel through the map and listen to episodes that talk about specific locations.',
],
];

View File

@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'feed' => 'RSS Podcast feed',
'season' => 'Season {seasonNumber}',
'list_of_episodes_year' => '{year} episodes ({episodeCount})',
'list_of_episodes_season' =>
'Season {seasonNumber} episodes ({episodeCount})',
'no_episode' => 'No episode found!',
'follow' => 'Follow',
'followTitle' => 'Follow {actorDisplayName} on the fediverse!',
'followers' => '{numberOfFollowers, plural,
one {# follower}
other {# followers}
}',
'posts' => '{numberOfPosts, plural,
one {# post}
other {# posts}
}',
'links' => 'Links',
'activity' => 'Activity',
'episodes' => 'Episodes',
'episodes_title' => 'Episodes of {podcastTitle}',
'about' => 'About',
'stats' => [
'title' => 'Stats',
'number_of_seasons' => '{0, plural,
one {# season}
other {# seasons}
}',
'number_of_episodes' => '{0, plural,
one {# episode}
other {# episodes}
}',
'first_published_at' => 'First episode published on {0, date, medium}',
],
'sponsor' => 'Sponsor',
'funding_links' => 'Funding links for {podcastTitle}',
'find_on' => 'Find {podcastTitle} on',
'listen_on' => 'Listen on',
'persons' => '{personsCount, plural,
one {# person}
other {# persons}
}',
'persons_list' => 'Persons',
'castopod_website' => 'Castopod (website)',
];

View File

@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'title' => "{actorDisplayName}'s post",
'back_to_actor_posts' => 'Back to {actor} posts',
'actor_shared' => '{actor} shared',
'reply_to' => 'Reply to @{actorUsername}',
'form' => [
'message_placeholder' => 'Write a message…',
'episode_message_placeholder' => 'Write a message for the episode…',
'episode_url_placeholder' => 'Episode URL',
'reply_to_placeholder' => 'Reply to @{actorUsername}',
'submit' => 'Send',
'submit_reply' => 'Reply',
],
'favourites' => '{numberOfFavourites, plural,
one {# favourite}
other {# favourites}
}',
'reblogs' => '{numberOfReblogs, plural,
one {# share}
other {# shares}
}',
'replies' => '{numberOfReplies, plural,
one {# reply}
other {# replies}
}',
'expand' => 'Expand post',
'block_actor' => 'Block user @{actorUsername}',
'block_domain' => 'Block domain @{actorDomain}',
'delete' => 'Delete post',
];

View File

@ -23,7 +23,6 @@ return [
'comments' => 'دیدگاه‌ها',
'activity' => 'فعّالیت',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'شرح قسمت',
'number_of_comments' => '{numberOfComments, plural,
other {# نظر}
@ -43,6 +42,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Commentaires',
'activity' => 'Activité',
'chapters' => 'Chapitres',
'transcript' => 'Transcript',
'description' => 'Description de lépisode',
'number_of_comments' => '{numberOfComments, plural,
one {# commentaire}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Modifier la publication',
],
'no_chapters' => 'Aucun chapitre nest disponible pour cet épisode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Commentaires',
'activity' => 'Activité',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Description de lépisode',
'number_of_comments' => '{numberOfComments, plural,
one {# commentaire}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -26,7 +26,6 @@ return [
'comments' => 'Beachdan',
'activity' => 'Gnìomhachd',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Tuairisgeul an eapasoid',
'number_of_comments' => '{numberOfComments, plural,
one {# bheachd}
@ -49,6 +48,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comentarios',
'activity' => 'Actividade',
'chapters' => 'Capítulos',
'transcript' => 'Transcript',
'description' => 'Descrición do episodio',
'number_of_comments' => '{numberOfComments, plural,
one {# comentario}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Editar publicación',
],
'no_chapters' => 'Non hai capítulos dispoñibles para este episodio.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -23,7 +23,6 @@ return [
'comments' => 'Komentar',
'activity' => 'Aktivitas',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Keterangan episode',
'number_of_comments' => '{numberOfComments, plural,
other {# komentar}
@ -43,6 +42,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Commenti',
'activity' => 'Attività',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descrizione dell\'episodio',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Modifica pubblicazione',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -27,8 +27,8 @@ return [
}',
'like' => 'いいね',
'reply' => '返信する',
'view_replies' => '返信を見る ({numberOfReplies})',
'block_actor' => 'ユーザー @{actorUsername} をブロック',
'block_domain' => 'ドメイン @{actorDomain} をブロック',
'view_replies' => 'View replies ({numberOfReplies})',
'block_actor' => 'Block user @{actorUsername}',
'block_domain' => 'Block domain @{actorDomain}',
'delete' => 'コメントを削除する',
];

View File

@ -13,10 +13,10 @@ return [
'no' => 'いいえ',
'cancel' => 'キャンセル',
'optional' => 'Optional',
'close' => '閉じる',
'close' => 'Close',
'home' => 'ホーム',
'explicit' => '過激な内容を含む',
'powered_by' => '提供: {castopod}',
'explicit' => 'Explicit',
'powered_by' => 'Powered by {castopod}',
'go_back' => '戻る',
'play_episode_button' => [
'play' => '再生',
@ -24,7 +24,7 @@ return [
],
'read_more' => '続きを読む',
'read_less' => '閉じる',
'see_more' => 'もっと見る',
'see_less' => '表示を減らす',
'legal_notice' => '法的事項',
'see_more' => 'See more',
'see_less' => 'See less',
'legal_notice' => 'Legal notice',
];

View File

@ -10,40 +10,38 @@ declare(strict_types=1);
return [
'season' => 'シーズン {seasonNumber}',
'season_abbr' => 'シーズン {seasonNumber}',
'season_abbr' => 'S{seasonNumber}',
'number' => 'エピソード {episodeNumber}',
'number_abbr' => 'エピソード {episodeNumber}',
'number_abbr' => 'Ep. {episodeNumber}',
'season_episode' => 'シーズン {seasonNumber} エピソード {episodeNumber}',
'season_episode_abbr' => 'シーズン{seasonNumber}エピソード{episodeNumber}',
'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}',
'persons' => '{personsCount, plural,
other {# 人}
one {# person}
other {# persons}
}',
'persons_list' => '人物',
'persons_list' => 'Persons',
'back_to_episodes' => '{podcast} のエピソードに戻る',
'comments' => 'コメント',
'activity' => 'アクティビティ',
'chapters' => '章',
'transcript' => 'Transcript',
'description' => 'エピソードの詳細',
'chapters' => 'Chapters',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
other {# comments}
}',
'all_podcast_episodes' => 'すべての Podcast エピソード',
'all_podcast_episodes' => 'All podcast episodes',
'back_to_podcast' => 'ポッドキャストへ戻る',
'preview' => [
'title' => 'プレビュー',
'not_published' => '未公開',
'not_published' => 'Not published',
'text' => '{publication_status, select,
published {このエピソードはまだ公開されていません}
scheduled {このエピソードは {publication_date} に公開される予定です}
with_podcast {このエピソードはPodCastと同時に公開されます}
other {このエピソードはまだ公開されていません。}
published {This episode is not yet published.}
scheduled {This episode is scheduled for publication on {publication_date}.}
with_podcast {This episode will be published at the same time as the podcast.}
other {This episode is not yet published.}
}',
'publish' => '公開する',
'publish_edit' => '出版物を編集',
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -9,29 +9,29 @@ declare(strict_types=1);
*/
return [
'your_handle' => 'あなたのユーザー ID',
'your_handle' => 'Your handle',
'your_handle_hint' => 'Enter the @username@domain you want to act from.',
'follow' => [
'label' => 'フォロー',
'title' => '{actorDisplayName} をフォロー',
'subtitle' => 'You are going to follow:',
'accountNotFound' => 'アカウントが見つかりませんでした',
'remoteFollowNotAllowed' => 'このアカウントサーバーはリモートフォローを許可しておりません',
'submit' => 'フォローする',
'remoteFollowNotAllowed' => 'Seems like the account server does not allow remote follows…',
'submit' => 'Proceed to follow',
],
'favourite' => [
'title' => "お気に入りの {actorDisplayName}の投稿",
'title' => "Favourite {actorDisplayName}'s post",
'subtitle' => 'You are going to favourite:',
'submit' => 'お気に入り登録する',
'submit' => 'Proceed to favourite',
],
'reblog' => [
'title' => "Share {actorDisplayName}'s post",
'subtitle' => 'You are going to share:',
'submit' => '共有する',
'submit' => 'Proceed to share',
],
'reply' => [
'title' => "Reply to {actorDisplayName}'s post",
'subtitle' => 'You are going to reply to:',
'submit' => '返信する',
'submit' => 'Proceed to reply',
],
];

View File

@ -9,9 +9,9 @@ declare(strict_types=1);
*/
return [
'back_to_home' => 'ホームへ戻る',
'back_to_home' => 'Back to home',
'map' => [
'title' => 'マップ',
'description' => '{siteName} でpodcastのエピソードを見つけましょうマップを旅して、特定の場所について話すエピソードを聞きましょう。',
'title' => 'Map',
'description' => 'Discover podcast episodes on {siteName} that are placed on a map! Travel through the map and listen to episodes that talk about specific locations.',
],
];

View File

@ -9,27 +9,29 @@ declare(strict_types=1);
*/
return [
'feed' => 'RSS PodCastフィード',
'season' => 'シーズン {seasonNumber}',
'list_of_episodes_year' => '{year} エピソード ({episodeCount})',
'feed' => 'RSS Podcast feed',
'season' => 'Season {seasonNumber}',
'list_of_episodes_year' => '{year} episodes ({episodeCount})',
'list_of_episodes_season' =>
'シーズン {seasonNumber} エピソード({episodeCount}',
'no_episode' => 'エピソードが見つかりませんでした',
'follow' => 'フォロー',
'followTitle' => 'Fediverseで {actorDisplayName} をフォロー!',
'Season {seasonNumber} episodes ({episodeCount})',
'no_episode' => 'No episode found!',
'follow' => 'Follow',
'followTitle' => 'Follow {actorDisplayName} on the fediverse!',
'followers' => '{numberOfFollowers, plural,
other {# 人のフォロワー}
one {# follower}
other {# followers}
}',
'posts' => '{numberOfPosts, plural,
other {#件の投稿}
one {# post}
other {# posts}
}',
'links' => 'リンク',
'activity' => 'アクティビティー',
'episodes' => 'エピソード',
'episodes_title' => '{podcastTitle} のエピソード',
'about' => '概要',
'links' => 'Links',
'activity' => 'Activity',
'episodes' => 'Episodes',
'episodes_title' => 'Episodes of {podcastTitle}',
'about' => 'About',
'stats' => [
'title' => '統計',
'title' => 'Stats',
'number_of_seasons' => '{0, plural,
one {# season}
other {# seasons}

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Reacties',
'activity' => 'Activiteiten',
'chapters' => 'Hoofdstukken',
'transcript' => 'Transcript',
'description' => 'Omschrijving aflevering',
'number_of_comments' => '{numberOfComments, plural,
one {# reactie}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Publicatie bewerken',
],
'no_chapters' => 'Voor deze aflevering zijn geen hoofdstukken beschikbaar.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Kommentarar',
'activity' => 'Aktivitet',
'chapters' => 'Kapittel',
'transcript' => 'Transcript',
'description' => 'Skildring av episoden',
'number_of_comments' => '{numberOfComments, plural,
one {# kommentar}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Rediger publiseringa',
],
'no_chapters' => 'Det finst ingen kapittel for denne episoden.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comentaris',
'activity' => 'Activitat',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descripcion de lepisòdi',
'number_of_comments' => '{numberOfComments, plural,
one {# comentari}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Modificar la publicacion',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -25,7 +25,6 @@ return [
'comments' => 'Komentarze',
'activity' => 'Aktywność',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Opis odcinka',
'number_of_comments' => '{numberOfComments, plural,
one {# komentarz}
@ -47,6 +46,4 @@ return [
'publish_edit' => 'Edytuj publikację',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comentários',
'activity' => 'Atividade',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descrição do episódio',
'number_of_comments' => '{numberOfComments, plural,
one {# comentário}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Editar Publicação',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Comments',
'activity' => 'Activity',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Episode description',
'number_of_comments' => '{numberOfComments, plural,
one {# comment}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -25,7 +25,6 @@ return [
'comments' => 'Comentarii',
'activity' => 'Activitate',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Descrierea episodului',
'number_of_comments' => '{numberOfComments, plural,
one {# răspuns}
@ -47,6 +46,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -26,7 +26,6 @@ return [
'comments' => 'Комментарии',
'activity' => 'Активность',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Описание серии',
'number_of_comments' => '{numberOfComments, plural,
one {# комментарий}
@ -49,6 +48,4 @@ return [
'publish_edit' => 'Редактировать публикацию',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -26,7 +26,6 @@ return [
'comments' => 'Komentáre',
'activity' => 'Aktivita',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Popis epizódy',
'number_of_comments' => '{numberOfComments, plural,
one {# komentár}
@ -49,6 +48,4 @@ return [
'publish_edit' => 'Upraviť zverejnené',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Komentari',
'activity' => 'Aktivnosti',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Opis epizode',
'number_of_comments' => '{numberOfComments, plural,
one {# komentar}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Uredi objavu',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => 'Kommentarer',
'activity' => 'Aktivitet',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Beskrivning av avsnitt',
'number_of_comments' => '{numberOfComments, plural,
one {# kommentar}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -26,7 +26,6 @@ return [
'comments' => 'Коментарі',
'activity' => 'Активність',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => 'Опис Серії',
'number_of_comments' => '{numberOfComments, plural,
one {# коментар}
@ -49,6 +48,4 @@ return [
'publish_edit' => 'Редагувати публікацію',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -24,7 +24,6 @@ return [
'comments' => '评论',
'activity' => '活动',
'chapters' => 'Chapters',
'transcript' => 'Transcript',
'description' => '剧集描述',
'number_of_comments' => '{numberOfComments, plural,
other {# 评论}
@ -45,6 +44,4 @@ return [
'publish_edit' => 'Edit publication',
],
'no_chapters' => 'No chapters are available for this episode.',
'download_transcript' => 'Download transcript ({extension})',
'no_transcript' => 'No transcript available for this episode.',
];

View File

@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'title' => "{actorDisplayName}' 對於 {episodeTitle} 之評論",
'back_to_comments' => '返回到評論',
'form' => [
'episode_message_placeholder' => '發表留言...',
'reply_to_placeholder' => '回覆 @{actorUsername}',
'submit' => '送出',
'submit_reply' => '回覆',
],
'likes' => '{numberOfLikes, plural,
other {# 讚}
}',
'replies' => '{numberOfReplies, plural,
other {# 回覆}
}',
'like' => '讚',
'reply' => '回覆',
'view_replies' => '檢視回覆 ({numberOfReplies})',
'block_actor' => '封鎖使用者 @{actorUsername}',
'block_domain' => '封鎖網域 @{actorDomain}',
'delete' => '刪除評論',
];

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'yes' => '是',
'no' => '否',
'cancel' => '取消',
'optional' => '選用項',
'close' => '關閉',
'home' => '首頁',
'explicit' => '露骨內容',
'powered_by' => '由 {castopod} 提供支援',
'go_back' => '返回',
'play_episode_button' => [
'play' => '播放',
'playing' => '播放中',
],
'read_more' => '閱讀更多',
'read_less' => '顯示更少',
'see_more' => '顯示更多',
'see_less' => '顯示較少',
'legal_notice' => '法律聲明',
];

Some files were not shown because too many files have changed in this diff Show More