chore: update CodeIgniter to 4.5.1 + other dependencies to latest

This commit is contained in:
Yassine Doghri 2024-04-26 09:26:22 +00:00
parent 303a900f66
commit d4a36f811b
103 changed files with 3140 additions and 2806 deletions

View File

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

View File

@ -12,7 +12,7 @@ services:
environment:
CI_ENVIRONMENT: development
vite_environment: development
app_forceGlobalSecureRequests: false
app_forceGlobalSecureRequests: 0 #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: true
restapi_enabled: 1 #true
email_fromEmail: hello@castopod.local
email_SMTPCrypto: ""
email_SMTPHost: mailpit

1
.gitignore vendored
View File

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

View File

@ -1,6 +1,2 @@
<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,6 +61,30 @@ 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,23 +29,17 @@ 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 '/app' and '/system' directories are already mapped for you.
* you may change the name of the 'App' namespace if you wish,
* The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) 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/',
@ -68,7 +62,6 @@ 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,8 +11,10 @@ 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,25 +48,6 @@ 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
@ -170,4 +151,23 @@ 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

@ -35,28 +35,28 @@ class ContentSecurityPolicy extends BaseConfig
/**
* Will default to self if not overridden
*
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $defaultSrc = null;
/**
* Lists allowed scripts' URLs.
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $scriptSrc = 'self';
/**
* Lists allowed stylesheets' URLs.
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $styleSrc = 'self';
/**
* Defines the origins from which images can be loaded.
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $imageSrc = 'self';
@ -65,35 +65,35 @@ class ContentSecurityPolicy extends BaseConfig
*
* Will default to self if not overridden
*
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $baseURI = null;
/**
* Lists the URLs for workers and embedded frame contents
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $childSrc = 'self';
/**
* Limits the origins that you can connect to (via XHR, WebSockets, and EventSource).
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $connectSrc = 'self';
/**
* Specifies the origins that can serve web fonts.
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $fontSrc;
/**
* Lists valid endpoints for submission from `<form>` tags.
*
* @var string|string[]
* @var list<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 string|string[]|null
* @var list<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 string[]|string|null
* @var list<string>|string|null
*/
public string | array | null $frameSrc = null;
/**
* Restricts the origins allowed to deliver video and audio.
*
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $mediaSrc = null;
/**
* Allows control over Flash and other plugins.
*
* @var string|string[]
* @var list<string>|string
*/
public string | array $objectSrc = 'self';
/**
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $manifestSrc = null;
/**
* Limits the kinds of plugins a page may invoke.
*
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $pluginTypes = null;
/**
* List of actions allowed.
*
* @var string|string[]|null
* @var list<string>|string|null
*/
public string | array | null $sandbox = null;

107
app/Config/Cors.php Normal file
View File

@ -0,0 +1,107 @@
<?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,6 +45,11 @@ 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',
],
];
/**
@ -64,7 +69,7 @@ class Database extends Config
'pConnect' => false,
'DBDebug' => true,
'charset' => 'utf8',
'DBCollat' => 'utf8_general_ci',
'DBCollat' => '',
'swapPre' => '',
'encrypt' => false,
'compress' => false,
@ -73,6 +78,11 @@ 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 int[]
* @var list<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 string[]
* @var list<string>
*/
public array $sensitiveDataInTrace = [];

View File

@ -11,22 +11,21 @@ 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,8 +8,11 @@ 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;
@ -18,8 +21,10 @@ 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,
@ -28,12 +33,41 @@ 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, list<string>>
* @var array<string, array<string, array<string, string|array<string>>>>>|array<string, list<string>>
*/
public array $globals = [
'before' => [
@ -44,7 +78,6 @@ class Filters extends BaseConfig
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
@ -53,12 +86,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, string[]>
* @var array<string, list<string>>
*/
public array $methods = [];
@ -67,7 +100,7 @@ class Filters extends BaseConfig
*
* Example: 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array<string, array<string, string[]>>
* @var array<string, array<string, list<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 string[]
* @var list<string>
*/
public array $supportedResponseFormats = [
'application/json',

View File

@ -28,8 +28,10 @@ class Generators extends BaseConfig
* @var array<string, string>
*/
public array $views = [
'make:cell' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
'make:cell_view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
'make:cell' => [
'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
'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,7 +4,6 @@ declare(strict_types=1);
namespace Config;
use CodeIgniter\Config\BaseConfig;
use Kint\Parser\ConstructablePluginInterface;
use Kint\Renderer\AbstractRenderer;
use Kint\Renderer\Rich\TabPluginInterface;
@ -20,7 +19,7 @@ use Kint\Renderer\Rich\ValuePluginInterface;
*
* @see https://kint-php.github.io/kint/ for details on these settings.
*/
class Kint extends BaseConfig
class Kint
{
/*
|--------------------------------------------------------------------------

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|int[]
* @var int|list<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<string, mixed>
* @var array<class-string, array<string, int|list<string>|string>>
*/
public array $handlers = [
/*

View File

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

34
app/Config/Optimize.php Normal file
View File

@ -0,0 +1,34 @@
<?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

@ -12,13 +12,14 @@ 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 string[]
* @var list<string>
*/
public array $routeFiles = [
APPPATH . 'Config/Routes.php',
@ -34,6 +35,7 @@ class Routing extends BaseRouting
];
/**
* For Defined Routes and Auto Routing.
* The default namespace to use for Controllers when no other
* namespace has been specified.
*
@ -42,6 +44,7 @@ class Routing extends BaseRouting
public string $defaultNamespace = 'App\Controllers';
/**
* For Auto Routing.
* The default controller to use when no other controller has been
* specified.
*
@ -50,6 +53,7 @@ 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.
*
@ -58,7 +62,8 @@ class Routing extends BaseRouting
public string $defaultMethod = 'index';
/**
* Whether to translate dashes in URIs to underscores.
* For Auto Routing.
* Whether to translate dashes in URIs for controller/method to underscores.
* Primarily useful when using the auto-routing.
*
* Default: false
@ -94,6 +99,7 @@ class Routing extends BaseRouting
public bool $autoRoute = false;
/**
* For Defined Routes.
* If TRUE, will enable the use of the 'prioritize' option
* when defining routes.
*
@ -102,7 +108,16 @@ class Routing extends BaseRouting
public bool $prioritize = false;
/**
* Map of URI segments and namespaces. For Auto Routing (Improved).
* 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.
*
* The key is the first URI segment. The value is the controller namespace.
* E.g.,
@ -113,4 +128,15 @@ 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
* --------------------------------------------------------------------------
*
* Redirect to previous page with error on failure.
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
*/
public bool $redirect = false;
public bool $redirect = (ENVIRONMENT === 'production');
/**
* --------------------------------------------------------------------------

View File

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

View File

@ -101,4 +101,29 @@ 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 string[]
* @var list<class-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 colleted. Useful to
* If set to false var data from the views will not be collected. 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 string[]
* @var list<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 string[]
* @var list<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 string[]
* @var list<string>
*/
public array $ruleSets = [
Rules::class,

View File

@ -9,8 +9,8 @@ use CodeIgniter\View\ViewDecoratorInterface;
use ViewComponents\Decorator;
/**
* @phpstan-type ParserCallable (callable(mixed): mixed)
* @phpstan-type ParserCallableString (callable(mixed): mixed)&string
* @phpstan-type parser_callable (callable(mixed): mixed)
* @phpstan-type parser_callable_string (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, ParserCallableString>
* @var array<string, string>
* @phpstan-var array<string, parser_callable_string>
*/
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, array<string>|callable|string>
* @phpstan-var array<string, array<ParserCallableString>|ParserCallableString|ParserCallable>
* @var array<string, callable|list<string>|string>
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
*/
public $plugins = [];
@ -51,7 +51,7 @@ class View extends BaseView
*
* All classes must implement CodeIgniter\View\ViewDecoratorInterface
*
* @var class-string<ViewDecoratorInterface>[]
* @var list<class-string<ViewDecoratorInterface>>
*/
public array $decorators = [Decorator::class];
}

View File

@ -13,8 +13,6 @@ 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
*
@ -41,7 +39,7 @@ abstract class BaseController extends Controller
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* @var string[]
* @var list<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 string[]
* @var list<string>
*/
protected $helpers = ['analytics'];

View File

@ -106,9 +106,7 @@ 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
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
@ -157,9 +155,7 @@ 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
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
@ -218,9 +214,7 @@ 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
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
@ -284,9 +278,7 @@ class EpisodeController extends BaseController
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode/transcript', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
@ -339,9 +331,7 @@ 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
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
}
@ -420,11 +410,9 @@ class EpisodeController extends BaseController
* get comments: aggregated replies from posts referring to the episode
*/
$episodeComments = model(PostModel::class)
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
return $builder->select('id')
->from('fediverse_posts')
->where('episode_id', $this->episode->id);
})
->whereIn('in_reply_to_id', fn (BaseBuilder $builder): BaseBuilder => $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

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

View File

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

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;

View File

@ -0,0 +1,24 @@
<?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

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

View File

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

View File

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

View File

@ -42,24 +42,16 @@ if (! function_exists('write_audio_file_tags')) {
// populate data array
$TagData = [
'title' => [esc($episode->title)],
'artist' => [
$episode->podcast->publisher === null
? esc($episode->podcast->owner_name)
: $episode->podcast->publisher,
],
'title' => [esc($episode->title)],
'artist' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
'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 === null
? esc($episode->podcast->owner_name)
: $episode->podcast->publisher,
],
'encoded_by' => ['Castopod'],
'publisher' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
'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 * pow(1024, (float) stripos('bkmgtpezy', $unit[0])));
return round($size * 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 /= pow($is_binary ? 1024 : 1000, $pow);
$bytes /= ($is_binary ? 1024 : 1000) ** $pow;
return round($bytes, $precision) . $units[$pow];
}

View File

@ -260,12 +260,7 @@ if (! function_exists('get_rss_feed')) {
$itunesNamespace,
);
$channel->addChild(
'author',
$podcast->publisher ? $podcast->publisher : $podcast->owner_name,
$itunesNamespace,
false
);
$channel->addChild('author', $podcast->publisher ?: $podcast->owner_name, $itunesNamespace, false);
$channel->addChild('link', $podcast->link);
$owner = $channel->addChild('owner', null, $itunesNamespace);
@ -354,7 +349,7 @@ if (! function_exists('get_rss_feed')) {
$item->addChild('episodeType', $episode->type, $itunesNamespace);
// If episode is of type trailer, add podcast:trailer tag on channel level
if ($episode->type == 'trailer') {
if ($episode->type === 'trailer') {
$trailer = $channel->addChild('trailer', $episode->title, $podcastNamespace);
$trailer->addAttribute('pubdate', $episode->published_at->format(DATE_RFC2822));
$trailer->addAttribute(

View File

@ -48,7 +48,7 @@ class Router extends CodeIgniterRouter
$matchedKey = $routeKey;
// Are we dealing with a locale?
if (strpos($routeKey, '{locale}') !== false) {
if (str_contains($routeKey, '{locale}')) {
$routeKey = str_replace('{locale}', '[^/]+', $routeKey);
}
@ -73,7 +73,7 @@ class Router extends CodeIgniterRouter
// Store our locale so CodeIgniter object can
// assign it to the Request.
if (strpos($matchedKey, '{locale}') !== false) {
if (str_contains($matchedKey, '{locale}')) {
preg_match(
'#^' . str_replace('{locale}', '(?<locale>[^/]+)', $matchedKey) . '$#u',
$uri,
@ -183,13 +183,13 @@ class Router extends CodeIgniterRouter
[$controller] = explode('::', (string) $handler);
// Checks `/` in controller name
if (strpos($controller, '/') !== false) {
if (str_contains($controller, '/')) {
throw RouterException::forInvalidControllerName($handler);
}
if (strpos((string) $handler, '$') !== false && strpos($routeKey, '(') !== false) {
if (str_contains((string) $handler, '$') && str_contains($routeKey, '(')) {
// Checks dynamic controller
if (strpos($controller, '$') !== false) {
if (str_contains($controller, '$')) {
throw RouterException::forDynamicController($handler);
}

View File

@ -7,8 +7,6 @@ namespace ViewComponents;
use CodeIgniter\View\ViewDecoratorInterface;
/**
* Class Decorator
*
* Enables rendering of View Components into the views.
*
* Borrowed and adapted from https://github.com/lonnieezell/Bonfire2/

View File

@ -19,7 +19,7 @@ class Theme
protected static $defaultTheme = 'app';
/**
* @var string
* @var ?string
*/
protected static $currentTheme;
@ -71,9 +71,7 @@ class Theme
*/
public static function current(): string
{
return static::$currentTheme !== null
? static::$currentTheme
: static::$defaultTheme;
return static::$currentTheme ?? static::$defaultTheme;
}
/**

View File

@ -26,7 +26,7 @@ class CategoryModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = ['parent_id', 'code', 'apple_category', 'google_category'];

View File

@ -33,7 +33,7 @@ class ClipModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = [
'id',

View File

@ -40,7 +40,7 @@ class EpisodeCommentModel extends UuidModel
protected $uuidFields = ['id', 'in_reply_to_id'];
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = [
'id',
@ -57,7 +57,7 @@ class EpisodeCommentModel extends UuidModel
];
/**
* @var string[]
* @var list<string>
*/
protected $beforeInsert = ['setCommentId'];

View File

@ -62,7 +62,7 @@ class EpisodeModel extends UuidModel
protected $table = 'episodes';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = [
'id',
@ -127,17 +127,17 @@ class EpisodeModel extends UuidModel
];
/**
* @var string[]
* @var list<string>
*/
protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
/**
* @var string[]
* @var list<string>
*/
protected $afterUpdate = ['clearCache', 'writeEnclosureMetadata'];
/**
* @var string[]
* @var list<string>
*/
protected $beforeDelete = ['clearCache'];
@ -272,13 +272,7 @@ class EpisodeModel extends UuidModel
$secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode($podcastId);
cache()
->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
);
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
}
return $found;

View File

@ -26,7 +26,7 @@ class LanguageModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = ['code', 'native_name'];

View File

@ -31,7 +31,7 @@ class LikeModel extends UuidModel
protected $uuidFields = ['comment_id'];
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = ['actor_id', 'comment_id'];

View File

@ -26,7 +26,7 @@ class PageModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = ['id', 'title', 'slug', 'content_markdown', 'content_html'];
@ -55,19 +55,19 @@ class PageModel extends Model
];
/**
* @var string[]
* @var list<string>
*/
protected $afterInsert = ['clearCache'];
/**
* Before update because slug or title might change
*
* @var string[]
* @var list<string>
*/
protected $beforeUpdate = ['clearCache'];
/**
* @var string[]
* @var list<string>
*/
protected $beforeDelete = ['clearCache'];

View File

@ -26,7 +26,7 @@ class PersonModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = [
'id',
@ -64,19 +64,19 @@ class PersonModel extends Model
];
/**
* @var string[]
* @var list<string>
*/
protected $afterInsert = ['clearCache'];
/**
* clear cache before update if by any chance, the person name changes, so will the person link
*
* @var string[]
* @var list<string>
*/
protected $beforeUpdate = ['clearCache'];
/**
* @var string[]
* @var list<string>
*/
protected $beforeDelete = ['clearCache'];

View File

@ -30,7 +30,7 @@ class PodcastModel extends Model
protected $primaryKey = 'id';
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = [
'id',
@ -104,29 +104,29 @@ class PodcastModel extends Model
];
/**
* @var string[]
* @var list<string>
*/
protected $beforeInsert = ['setPodcastGUID', 'createPodcastActor'];
/**
* @var string[]
* @var list<string>
*/
protected $afterInsert = ['setActorAvatar'];
/**
* @var string[]
* @var list<string>
*/
protected $afterUpdate = ['updatePodcastActor'];
/**
* clear cache before update if by any chance, the podcast name changes, so will the podcast link
*
* @var string[]
* @var list<string>
*/
protected $beforeUpdate = ['clearCache'];
/**
* @var string[]
* @var list<string>
*/
protected $beforeDelete = ['clearCache'];
@ -259,13 +259,7 @@ class PodcastModel extends Model
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode($podcastId);
cache()
->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
);
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
}
return $found;
@ -295,13 +289,7 @@ class PodcastModel extends Model
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode($podcastId);
cache()
->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
);
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
}
return $found;
@ -335,11 +323,7 @@ class PodcastModel extends Model
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode($podcastId);
cache()
->save(
$cacheName,
$defaultQuery,
$secondsToNextUnpublishedEpisode ? $secondsToNextUnpublishedEpisode : DECADE
);
->save($cacheName, $defaultQuery, $secondsToNextUnpublishedEpisode ?: DECADE);
}
return $defaultQuery;

View File

@ -42,7 +42,7 @@ class Alert extends Component
$this->variant = 'default';
}
$glyph = icon(($this->glyph === null ? $variants[$this->variant]['glyph'] : $this->glyph), 'flex-shrink-0 mr-2 text-lg');
$glyph = icon(($this->glyph ?? $variants[$this->variant]['glyph']), 'flex-shrink-0 mr-2 text-lg');
$title = $this->title === null ? '' : '<div class="font-semibold">' . $this->title . '</div>';
$class = 'inline-flex w-full p-2 text-sm border rounded ' . $variants[$this->variant]['class'] . ' ' . $this->class;

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
use CodeIgniter\CLI\CLI;
// The main Exception
CLI::write('[' . get_class($exception) . ']', 'light_gray', 'red');
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
CLI::write($message);
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
CLI::newLine();
@ -16,7 +16,7 @@ while ($prevException = $last->getPrevious()) {
$last = $prevException;
CLI::write(' Caused by:');
CLI::write(' [' . get_class($prevException) . ']', 'red');
CLI::write(' [' . $prevException::class . ']', 'red');
CLI::write(' ' . $prevException->getMessage());
CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
CLI::newLine();
@ -52,20 +52,11 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
$function .= $padClass . $error['function'];
}
$args = implode(', ', array_map(static function ($value) {
switch (true) {
case is_object($value):
return 'Object(' . get_class($value) . ')';
case is_array($value):
return count($value) ? '[...]' : '[]';
case $value === null:
return 'null'; // return the lowercased version
default:
return var_export($value, true);
}
$args = implode(', ', array_map(static fn ($value) => match (true) {
is_object($value) => 'Object(' . $value::class . ')',
is_array($value) => count($value) ? '[...]' : '[]',
$value === null => 'null', // return the lowercased version
default => var_export($value, true),
}, array_values($error['args'] ?? [])));
$function .= '(' . $args . ')';

View File

@ -18,62 +18,69 @@ body {
margin: 0;
padding: 0;
}
h1 {
font-weight: lighter;
letter-spacing: 0.8;
font-size: 3rem;
color: var(--dark-text-color);
margin: 0;
}
h1.headline {
margin-top: 20%;
font-size: 5rem;
}
.text-center {
text-align: center;
}
p.lead {
font-size: 1.6rem;
}
.container {
max-width: 75rem;
margin: 0 auto;
padding: 1rem;
}
.header {
background: var(--light-bg-color);
color: var(--dark-text-color);
}
.header .container {
padding: 1rem 1.75rem 1.75rem 1.75rem;
padding: 1rem;
}
.header h1 {
font-size: 2.5rem;
font-weight: 500;
}
.header p {
font-size: 1.2rem;
margin: 0;
line-height: 2.5;
}
.header a {
color: var(--brand-primary-color);
margin-left: 2rem;
display: none;
text-decoration: none;
}
.header:hover a {
display: inline;
}
.footer {
.environment {
background: var(--dark-bg-color);
color: var(--light-text-color);
}
.footer .container {
border-top: 1px solid #e7e7e7;
margin-top: 1rem;
text-align: center;
padding: 0.2rem;
}
.source {
@ -86,17 +93,21 @@ p.lead {
margin: 0;
overflow-x: scroll;
}
.source span.line {
line-height: 1.4;
}
.source span.line .number {
color: #666;
}
.source .line .highlight {
display: block;
background: var(--dark-text-color);
color: var(--light-text-color);
}
.source span.highlight .number {
color: #fff;
}
@ -108,37 +119,44 @@ p.lead {
padding: 0;
margin-bottom: -1px;
}
.tabs li {
display: inline;
}
.tabs a:link,
.tabs a:visited {
padding: 0rem 1rem;
padding: 0 1rem;
line-height: 2.7;
text-decoration: none;
color: var(--dark-text-color);
background: var(--light-bg-color);
border: 1px solid rgba(0, 0, 0, 0.15);
border: 1px solid rgb(0 0 0 / 15%);
border-bottom: 0;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
display: inline-block;
}
.tabs a:hover {
background: var(--light-bg-color);
border-color: rgba(0, 0, 0, 0.15);
border-color: rgb(0 0 0 / 15%);
}
.tabs a.active {
background: var(--main-bg-color);
color: var(--main-text-color);
}
.tab-content {
background: var(--main-bg-color);
border: 1px solid rgba(0, 0, 0, 0.15);
border: 1px solid rgb(0 0 0 / 15%);
}
.content {
padding: 1rem;
}
.hide {
display: none;
}
@ -153,26 +171,26 @@ p.lead {
border-radius: 5px;
color: #31708f;
}
ul,
ol {
line-height: 1.8;
}
table {
width: 100%;
overflow: hidden;
}
th {
text-align: left;
border-bottom: 1px solid #e7e7e7;
padding-bottom: 0.5rem;
}
td {
padding: 0.2rem 0.5rem 0.2rem 0;
}
tr:hover td {
background: #f1f1f1;
}
td pre {
white-space: pre-wrap;
}
@ -180,20 +198,25 @@ td pre {
.trace a {
color: inherit;
}
.trace table {
width: auto;
}
.trace tr td:first-child {
min-width: 5em;
font-weight: bold;
}
.trace td {
background: var(--light-bg-color);
padding: 0 1rem;
}
.trace td pre {
margin: 0;
}
.args {
display: none;
}

View File

@ -3,6 +3,7 @@
declare(strict_types=1);
use CodeIgniter\CodeIgniter;
use CodeIgniter\HTTP\Header;
use Config\Services;
$errorId = uniqid('error', true);
@ -26,6 +27,12 @@ $errorId = uniqid('error', true);
<!-- Header -->
<div class="header">
<div class="environment">
Displayed at <?= esc(date('H:i:sa')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
</div>
<div class="container">
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
@ -57,10 +64,10 @@ while ($prevException = $last->getPrevious()) {
<pre>
Caused by:
<?= esc(get_class($prevException)), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= nl2br(esc($prevException->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode(get_class($prevException) . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
</pre>
@ -117,7 +124,7 @@ while ($prevException = $last->getPrevious()) {
<?php
$params = null;
// Reflection by name is not available for closure function
if (substr($row['function'], -1) !== '}') {
if (! str_ends_with($row['function'], '}')) {
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
@ -231,7 +238,7 @@ while ($prevException = $last->getPrevious()) {
</tr>
<tr>
<td>HTTP Method</td>
<td><?= esc(strtoupper($request->getMethod())) ?></td>
<td><?= esc($request->getMethod()) ?></td>
</tr>
<tr>
<td>IP Address</td>
@ -315,22 +322,22 @@ while ($prevException = $last->getPrevious()) {
</tr>
</thead>
<tbody>
<?php foreach ($headers as $value) : ?>
<?php
if (empty($value)) {
continue;
}
if (! is_array($value)) {
$value = [$value];
} ?>
<?php foreach ($value as $h) : ?>
<tr>
<td><?= esc($h->getName(), 'html') ?></td>
<td><?= esc($h->getValueLine(), 'html') ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($value->getValueLine(), 'html');
} else {
foreach ($value as $i => $header) {
echo ' (' . $i + 1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
@ -352,8 +359,6 @@ $response->setStatusCode(http_response_code());
<?php $headers = $response->headers(); ?>
<?php if (! empty($headers)) : ?>
<?php natsort($headers) ?>
<h3>Headers</h3>
<table>
@ -364,12 +369,22 @@ $response->setStatusCode(http_response_code());
</tr>
</thead>
<tbody>
<?php foreach (array_keys($headers) as $name) : ?>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td><?= esc($response->getHeaderLine($name), 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($response->getHeaderLine($name), 'html');
} else {
foreach ($value as $i => $header) {
echo ' (' . $i + 1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
@ -414,18 +429,5 @@ $response->setStatusCode(http_response_code());
</div> <!-- /container -->
<?php endif; ?>
<div class="footer">
<div class="container">
<p>
Displayed at <?= esc(date('H:i:sa')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
</p>
</div>
</div>
</body>
</html>

2
builds
View File

@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
define('LATEST_RELEASE', '^4.0');
define('GITHUB_URL', 'https://github.com/codeigniter4/codeigniter4');

View File

@ -9,38 +9,41 @@
"php": "^8.1",
"adaures/ipcat-php": "^v1.0.0",
"adaures/podcast-persons-taxonomy": "^v1.0.1",
"aws/aws-sdk-php": "^3.300.8",
"aws/aws-sdk-php": "^3.305.3",
"chrisjean/php-ico": "^1.0.4",
"cocur/slugify": "^v4.5.1",
"codeigniter4/framework": "v4.4.6",
"codeigniter4/framework": "v4.5.1",
"codeigniter4/settings": "v2.2.0",
"codeigniter4/shield": "v1.0.1",
"codeigniter4/shield": "v1.0.3",
"codeigniter4/tasks": "dev-develop",
"geoip2/geoip2": "v3.0.0",
"james-heinrich/getid3": "^2.0.0-beta5",
"league/commonmark": "^2.4.2",
"league/html-to-markdown": "5.1.1",
"melbahja/seo": "^v2.1.1",
"michalsn/codeigniter4-uuid": "v1.0.2",
"mpratt/embera": "^2.0.36",
"michalsn/codeigniter4-uuid": "v1.1.0",
"mpratt/embera": "^2.0.38",
"opawg/user-agents-v2-php": "dev-main",
"phpseclib/phpseclib": "~2.0.47",
"vlucas/phpdotenv": "v5.6.0",
"whichbrowser/parser": "^v2.1.7",
"whichbrowser/parser": "^v2.1.8",
"yassinedoghri/podcast-feed": "dev-main"
},
"require-dev": {
"captainhook/captainhook": "^5.21.2",
"captainhook/captainhook": "^5.23.0",
"codeigniter/phpstan-codeigniter": "v1.4.3",
"mikey179/vfsstream": "^v1.6.11",
"phpstan/extension-installer": "^1.3.1",
"phpstan/phpstan": "^1.10.59",
"phpunit/phpunit": "^10.5.11",
"rector/rector": "^1.0.1",
"symplify/coding-standard": "^12.0.7",
"symplify/easy-coding-standard": "^12.0.13"
"phpstan/phpstan": "^1.10.67",
"phpunit/phpunit": "^10.5.20",
"rector/rector": "^1.0.4",
"symplify/coding-standard": "^12.1.4",
"symplify/easy-coding-standard": "^12.1.14"
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"exclude-from-classmap": [
"**/Database/Migrations/**"
]

626
composer.lock generated

File diff suppressed because it is too large Load Diff

30
ecs.php
View File

@ -10,26 +10,20 @@ use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer;
use Symplify\CodingStandard\Fixer\Naming\StandardizeHereNowDocKeywordFixer;
use Symplify\CodingStandard\Fixer\Spacing\MethodChainingNewlineFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;
return static function (ECSConfig $ecsConfig): void {
// alternative to CLI arguments, easier to maintain and extend
$ecsConfig->paths([
return ECSConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/modules',
__DIR__ . '/themes',
__DIR__ . '/tests',
__DIR__ . '/public',
__DIR__ . '/builds',
__DIR__ . '/ecs.php',
__DIR__ . '/preload.php',
__DIR__ . '/rector.php',
__DIR__ . '/spark',
]);
$ecsConfig->sets([SetList::CLEAN_CODE, SetList::COMMON, SetList::SYMPLIFY, SetList::PSR_12]);
$ecsConfig->skip([
])
->withRootFiles()
->withPreparedSets(cleanCode: true, common: true, symplify: true, strict: true, psr12: true)
->withSkip([
// skip specific generated files
__DIR__ . '/modules/Admin/Language/*/PersonsTaxonomy.php',
@ -40,11 +34,7 @@ return static function (ECSConfig $ecsConfig): void {
__DIR__ . '/app/Helpers/components_helper.php',
],
LineLengthFixer::class => [
__DIR__ . '/app/Views/*',
__DIR__ . '/modules/**/Views/*',
__DIR__ . '/themes/*',
],
LineLengthFixer::class => [__DIR__ . '/app/Views/*', __DIR__ . '/modules/**/Views/*', __DIR__ . '/themes/*'],
IndentationTypeFixer::class => [
__DIR__ . '/app/Views/*',
@ -65,11 +55,9 @@ return static function (ECSConfig $ecsConfig): void {
BinaryOperatorSpacesFixer::class => [__DIR__ . '/app/Language/*', __DIR__ . '/modules/**/Language/*'],
AssignmentInConditionSniff::class,
]);
$ecsConfig->ruleWithConfiguration(BinaryOperatorSpacesFixer::class, [
])
->withConfiguredRule(BinaryOperatorSpacesFixer::class, [
'operators' => [
'=>' => 'align_single_space_minimal',
],
]);
};

80
env
View File

@ -30,12 +30,15 @@
# DATABASE
#--------------------------------------------------------------------
# If you use MySQLi as tests, first update the values of Config\Database::$tests.
# database.default.hostname = localhost
# database.default.database = ci4
# database.default.username = root
# database.default.password = root
# database.default.DBDriver = MySQLi
# database.default.DBPrefix =
# database.tests.charset = utf8mb4
# database.tests.DBCollat = utf8mb4_general_ci
# database.default.port = 3306
# database.tests.hostname = localhost
@ -46,98 +49,21 @@
# database.tests.DBPrefix =
# database.tests.port = 3306
#--------------------------------------------------------------------
# CONTENT SECURITY POLICY
#--------------------------------------------------------------------
# contentsecuritypolicy.reportOnly = false
# contentsecuritypolicy.defaultSrc = 'none'
# contentsecuritypolicy.scriptSrc = 'self'
# contentsecuritypolicy.styleSrc = 'self'
# contentsecuritypolicy.imageSrc = 'self'
# contentsecuritypolicy.baseURI = null
# contentsecuritypolicy.childSrc = null
# contentsecuritypolicy.connectSrc = 'self'
# contentsecuritypolicy.fontSrc = null
# contentsecuritypolicy.formAction = null
# contentsecuritypolicy.frameAncestors = null
# contentsecuritypolicy.frameSrc = null
# contentsecuritypolicy.mediaSrc = null
# contentsecuritypolicy.objectSrc = null
# contentsecuritypolicy.pluginTypes = null
# contentsecuritypolicy.reportURI = null
# contentsecuritypolicy.sandbox = false
# contentsecuritypolicy.upgradeInsecureRequests = false
# contentsecuritypolicy.styleNonceTag = '{csp-style-nonce}'
# contentsecuritypolicy.scriptNonceTag = '{csp-script-nonce}'
# contentsecuritypolicy.autoNonce = true
#--------------------------------------------------------------------
# COOKIE
#--------------------------------------------------------------------
# cookie.prefix = ''
# cookie.expires = 0
# cookie.path = '/'
# cookie.domain = ''
# cookie.secure = false
# cookie.httponly = false
# cookie.samesite = 'Lax'
# cookie.raw = false
#--------------------------------------------------------------------
# ENCRYPTION
#--------------------------------------------------------------------
# encryption.key =
# encryption.driver = OpenSSL
# encryption.blockSize = 16
# encryption.digest = SHA512
#--------------------------------------------------------------------
# HONEYPOT
#--------------------------------------------------------------------
# honeypot.hidden = 'true'
# honeypot.label = 'Fill This Field'
# honeypot.name = 'honeypot'
# honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
# honeypot.container = '<div style="display:none">{template}</div>'
#--------------------------------------------------------------------
# SECURITY
#--------------------------------------------------------------------
# security.csrfProtection = 'cookie'
# security.tokenRandomize = false
# security.tokenName = 'csrf_token_name'
# security.headerName = 'X-CSRF-TOKEN'
# security.cookieName = 'csrf_cookie_name'
# security.expires = 7200
# security.regenerate = true
# security.redirect = false
# security.samesite = 'Lax'
#--------------------------------------------------------------------
# SESSION
#--------------------------------------------------------------------
# session.driver = 'CodeIgniter\Session\Handlers\FileHandler'
# session.cookieName = 'ci_session'
# session.expiration = 7200
# session.savePath = null
# session.matchIP = false
# session.timeToUpdate = 300
# session.regenerateDestroy = false
#--------------------------------------------------------------------
# LOGGER
#--------------------------------------------------------------------
# logger.threshold = 4
#--------------------------------------------------------------------
# CURLRequest
#--------------------------------------------------------------------
# curlrequest.shareOptions = false

View File

@ -12,8 +12,6 @@ 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
*

View File

@ -326,12 +326,8 @@ class EpisodeController extends BaseController
$this->request->getPost('parental_advisory') !== 'undefined'
? $this->request->getPost('parental_advisory')
: null;
$this->episode->number = $this->request->getPost('episode_number')
? $this->request->getPost('episode_number')
: null;
$this->episode->season_number = $this->request->getPost('season_number')
? $this->request->getPost('season_number')
: null;
$this->episode->number = $this->request->getPost('episode_number') ?: null;
$this->episode->season_number = $this->request->getPost('season_number') ?: null;
$this->episode->type = $this->request->getPost('type');
$this->episode->is_blocked = $this->request->getPost('block') === 'yes';
$this->episode->custom_rss_string = $this->request->getPost('custom_rss');

View File

@ -17,7 +17,7 @@ use CodeIgniter\HTTP\RedirectResponse;
class PageController extends BaseController
{
protected ?Page $page;
protected ?Page $page = null;
public function _remap(string $method, string ...$params): mixed
{

View File

@ -18,7 +18,7 @@ use Modules\Media\Models\MediaModel;
class PersonController extends BaseController
{
protected ?Person $person;
protected ?Person $person = null;
public function _remap(string $method, string ...$params): mixed
{

View File

@ -30,9 +30,7 @@ trait AnalyticsTrait
$referer = $session->get('referer');
$domain =
parse_url((string) $referer, PHP_URL_HOST) === null
? '- Direct -'
: parse_url((string) $referer, PHP_URL_HOST);
parse_url((string) $referer, PHP_URL_HOST) ?? '- Direct -';
parse_str((string) parse_url((string) $referer, PHP_URL_QUERY), $queries);
$keywords = $queries['q'] ?? null;

View File

@ -2,7 +2,6 @@
declare(strict_types=1);
use CodeIgniter\Router\RouteCollection;
use Modules\Analytics\Config\Analytics;
/**

View File

@ -85,14 +85,10 @@ if (! function_exists('set_user_session_location')) {
$city = $cityReader->city(client_ip());
$location = [
'countryCode' => $city->country->isoCode === null
? 'N/A'
: $city->country->isoCode,
'regionCode' => $city->subdivisions[0]->isoCode === null
? 'N/A'
: $city->subdivisions[0]->isoCode,
'latitude' => round($city->location->latitude, 3),
'longitude' => round($city->location->longitude, 3),
'countryCode' => $city->country->isoCode ?? 'N/A',
'regionCode' => $city->subdivisions[0]->isoCode ?? 'N/A',
'latitude' => round($city->location->latitude, 3),
'longitude' => round($city->location->longitude, 3),
];
// If things go wrong the show must go on and the user must be able to download the file
} catch (Exception) {
@ -179,9 +175,7 @@ if (! function_exists('set_user_session_referer')) {
{
$session = Services::session();
$newreferer = isset($_SERVER['HTTP_REFERER'])
? $_SERVER['HTTP_REFERER']
: '- Direct -';
$newreferer = $_SERVER['HTTP_REFERER'] ?? '- Direct -';
$newreferer =
parse_url((string) $newreferer, PHP_URL_HOST) ===
parse_url(current_url(false), PHP_URL_HOST)
@ -250,9 +244,7 @@ if (! function_exists('podcast_hit')) {
}
//We get the HTTP header field `Range`:
$httpRange = isset($_SERVER['HTTP_RANGE'])
? $_SERVER['HTTP_RANGE']
: null;
$httpRange = $_SERVER['HTTP_RANGE'] ?? null;
$salt = config(Analytics::class)
->salt;

View File

@ -65,7 +65,7 @@ class EpisodeController extends Controller
return $this->failNotFound('Episode not found');
}
return $this->respond($this->mapEpisode($episode));
return $this->respond(static::mapEpisode($episode));
}
protected static function mapEpisode(Episode $episode): Episode

View File

@ -36,7 +36,7 @@ class ApiFilter implements FilterInterface
}
$authHeader = $request->getHeaderLine('Authorization');
if (substr($authHeader, 0, 6) !== 'Basic ') {
if (! str_starts_with($authHeader, 'Basic ')) {
$response->setStatusCode(401);
return $response;
@ -44,7 +44,7 @@ class ApiFilter implements FilterInterface
$auth_token = base64_decode(substr($authHeader, 6), true);
list($username, $password) = explode(':', (string) $auth_token);
[$username, $password] = explode(':', (string) $auth_token);
if (! ($username === $restApiConfig->basicAuthUsername && $password === $restApiConfig->basicAuthPassword)) {
$response->setStatusCode(401);

View File

@ -11,8 +11,6 @@ use Psr\Log\LoggerInterface;
use ViewThemes\Theme;
/**
* Class ActionController
*
* A generic controller to handle Authentication Actions.
*/
class ActionController extends ShieldActionController

View File

@ -22,7 +22,7 @@ class ContributorController extends BaseController
{
protected Podcast $podcast;
protected ?User $contributor;
protected ?User $contributor = null;
public function _remap(string $method, string ...$params): mixed
{

View File

@ -8,8 +8,6 @@ use CodeIgniter\Controller;
use CodeIgniter\HTTP\RedirectResponse;
/**
* Class ActionController
*
* A generic controller to handle Authentication Actions.
*/
class InteractController extends Controller

View File

@ -11,8 +11,6 @@ use Psr\Log\LoggerInterface;
use ViewThemes\Theme;
/**
* Class RegisterController
*
* Handles displaying registration form, and handling actual registration flow.
*/
class RegisterController extends ShieldRegisterController

View File

@ -22,7 +22,7 @@ use Modules\Auth\Models\UserModel;
class UserController extends BaseController
{
protected ?User $user;
protected ?User $user = null;
public function _remap(string $method, string ...$params): mixed
{

View File

@ -102,9 +102,10 @@ if (! function_exists('add_podcast_group')) {
if (! function_exists('get_instance_group')) {
function get_instance_group(User $user): ?string
{
$instanceGroups = array_filter($user->getGroups() ?? [], static function ($group): bool {
return ! str_starts_with($group, 'podcast#');
});
$instanceGroups = array_filter(
$user->getGroups() ?? [],
static fn ($group): bool => ! str_starts_with((string) $group, 'podcast#')
);
if ($instanceGroups === []) {
return null;
@ -138,9 +139,10 @@ if (! function_exists('set_instance_group')) {
if (! function_exists('get_podcast_group')) {
function get_podcast_group(User $user, int $podcastId, bool $removePrefix = true): ?string
{
$podcastGroups = array_filter($user->getGroups() ?? [], static function ($group) use ($podcastId): bool {
return str_starts_with($group, "podcast#{$podcastId}-");
});
$podcastGroups = array_filter(
$user->getGroups() ?? [],
static fn ($group): bool => str_starts_with((string) $group, "podcast#{$podcastId}-")
);
if ($podcastGroups === []) {
return null;
@ -180,9 +182,10 @@ if (! function_exists('get_podcast_groups')) {
*/
function get_user_podcast_ids(User $user): array
{
$podcastGroups = array_filter($user->getGroups() ?? [], static function ($group): bool {
return str_starts_with($group, 'podcast#');
});
$podcastGroups = array_filter(
$user->getGroups() ?? [],
static fn ($group): bool => str_starts_with((string) $group, 'podcast#')
);
$userPodcastIds = [];
// extract all podcast ids from groups

View File

@ -42,9 +42,7 @@ abstract class AbstractObject
}
// removes all NULL, FALSE and Empty Strings but leaves 0 (zero) values
return array_filter($array, static function ($value): bool {
return $value !== null && $value !== false && $value !== '';
});
return array_filter($array, static fn ($value): bool => $value !== null && $value !== false && $value !== '');
}
public function toJSON(): string

View File

@ -175,12 +175,10 @@ if (! function_exists('create_preview_card_from_url')) {
// Check that, at least, the url and title are set
$newPreviewCard = new PreviewCard([
'url' => $mediaUrl,
'title' => $media['title'] ?? '',
'description' => $media['description'] ?? '',
'type' => isset($typeMapping[$media['type']])
? $typeMapping[$media['type']]
: 'link',
'url' => $mediaUrl,
'title' => $media['title'] ?? '',
'description' => $media['description'] ?? '',
'type' => $typeMapping[$media['type']] ?? 'link',
'author_name' => $media['author_name'] ?? null,
'author_url' => $media['author_url'] ?? null,
'provider_name' => $media['provider_name'] ?? '',

View File

@ -134,7 +134,7 @@ class PostModel extends UuidModel
$secondsToNextUnpublishedPost = $this->getSecondsToNextUnpublishedPosts($actorId);
cache()
->save($cacheName, $found, $secondsToNextUnpublishedPost ? $secondsToNextUnpublishedPost : DECADE);
->save($cacheName, $found, $secondsToNextUnpublishedPost ?: DECADE);
}
return $found;

View File

@ -160,9 +160,7 @@ class InstallController extends Controller
if (! $this->validate($rules)) {
return redirect()
->to(
(host_url() === null ? config(App::class) ->baseURL : host_url()) . config(Install::class)->gateway
)
->to((host_url() ?? config(App::class) ->baseURL) . config(Install::class)->gateway)
->withInput()
->with('errors', $this->validator->getErrors());
}

View File

@ -85,16 +85,12 @@ class Transcript extends BaseMedia
}
$transcript_format = $this->file->getExtension();
switch ($transcript_format) {
case 'vtt':
$transcriptJson = $transcriptParser->loadString($transcriptContent)
->parseVtt();
break;
case 'srt':
default:
$transcriptJson = $transcriptParser->loadString($transcriptContent)
->parseSrt();
}
$transcriptJson = match ($transcript_format) {
'vtt' => $transcriptParser->loadString($transcriptContent)
->parseVtt(),
default => $transcriptParser->loadString($transcriptContent)
->parseSrt(),
};
$tempFilePath = WRITEPATH . 'uploads/' . $this->file->getRandomName();
file_put_contents($tempFilePath, $transcriptJson);

View File

@ -543,9 +543,9 @@ class VideoClipper
# find unique color
do {
$r = rand(0, 255);
$g = rand(0, 255);
$b = rand(0, 255);
$r = random_int(0, 255);
$g = random_int(0, 255);
$b = random_int(0, 255);
} while (imagecolorexact($src, $r, $g, $b) < 0);
$ns = $s * $q;

View File

@ -66,7 +66,7 @@ class PlatformModel extends Model
}
foreach ($platformsData as $slug => $platform) {
if (! in_array($slug, $knownSlugs)) {
if (! in_array($slug, $knownSlugs, true)) {
$found[] = new Platform([
'podcast_id' => $podcastId,
'slug' => $slug,

View File

@ -52,9 +52,9 @@ class PodcastImport extends BaseCommand
$importQueue = get_import_tasks();
$currentImport = current(array_filter($importQueue, static function ($task): bool {
return $task->status === TaskStatus::Running;
}));
$currentImport = current(
array_filter($importQueue, static fn ($task): bool => $task->status === TaskStatus::Running)
);
if ($currentImport instanceof PodcastImportTask) {
$currentImport->syncWithProcess();
@ -68,9 +68,7 @@ class PodcastImport extends BaseCommand
}
// Get the next queued import
$queuedImports = array_filter($importQueue, static function ($task): bool {
return $task->status === TaskStatus::Queued;
});
$queuedImports = array_filter($importQueue, static fn ($task): bool => $task->status === TaskStatus::Queued);
$nextImport = end($queuedImports);
if (! $nextImport instanceof PodcastImportTask) {
@ -527,9 +525,7 @@ class PodcastImport extends BaseCommand
->get()
->getResultArray();
return array_map(static function (array $element) {
return $element['guid'];
}, $result);
return array_map(static fn (array $element) => $element['guid'], $result);
}
/**

View File

@ -26,11 +26,10 @@ if (! function_exists('get_import_tasks')) {
}
if ($podcastHandle !== null) {
$podcastImportsQueue = array_filter($podcastImportsQueue, static function ($importTask) use (
$podcastHandle
): bool {
return $importTask->handle === $podcastHandle;
});
$podcastImportsQueue = array_filter(
$podcastImportsQueue,
static fn ($importTask): bool => $importTask->handle === $podcastHandle
);
}
usort($podcastImportsQueue, static function (PodcastImportTask $a, PodcastImportTask $b): int {

View File

@ -30,16 +30,16 @@
"dependencies": {
"@amcharts/amcharts4": "^4.10.38",
"@amcharts/amcharts4-geodata": "^4.1.28",
"@codemirror/commands": "^6.3.3",
"@codemirror/lang-xml": "^6.0.2",
"@codemirror/commands": "^6.5.0",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.24.1",
"@codemirror/view": "^6.26.3",
"@floating-ui/dom": "^1.6.3",
"@github/clipboard-copy-element": "^1.3.0",
"@github/hotkey": "^3.1.0",
"@github/markdown-toolbar-element": "^2.2.1",
"@github/relative-time-element": "^4.3.1",
"@github/markdown-toolbar-element": "^2.2.3",
"@github/relative-time-element": "^4.4.0",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@vime/core": "^5.4.1",
"choices.js": "^10.2.0",
@ -47,28 +47,28 @@
"flatpickr": "^4.6.13",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"lit": "^3.1.2",
"marked": "^12.0.0",
"wavesurfer.js": "^7.7.3",
"lit": "^3.1.3",
"marked": "^12.0.2",
"wavesurfer.js": "^7.7.11",
"xml-formatter": "^3.6.2"
},
"devDependencies": {
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@csstools/css-tokenizer": "^2.2.3",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@csstools/css-tokenizer": "^2.2.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/gitlab": "^13.0.3",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@types/leaflet": "^1.9.8",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@tailwindcss/typography": "^0.5.12",
"@types/leaflet": "^1.9.12",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"all-contributors-cli": "^6.26.1",
"commitizen": "^4.3.0",
"cross-env": "^7.0.3",
"cssnano": "^6.0.3",
"cssnano": "^7.0.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
@ -76,25 +76,25 @@
"husky": "^9.0.11",
"is-ci": "^3.0.1",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"postcss-import": "^16.0.1",
"postcss-nesting": "^12.0.4",
"postcss-preset-env": "^9.4.0",
"postcss": "^8.4.38",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.2",
"postcss-preset-env": "^9.5.9",
"postcss-reporter": "^7.1.0",
"prettier": "3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"semantic-release": "^23.0.2",
"stylelint": "^16.2.1",
"semantic-release": "^23.0.8",
"stylelint": "^16.4.0",
"stylelint-config-standard": "^36.0.0",
"svgo": "^3.2.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.1.4",
"vite-plugin-pwa": "^0.19.2",
"workbox-build": "^7.0.0",
"workbox-core": "^7.0.0",
"workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0"
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite-plugin-pwa": "^0.19.8",
"workbox-build": "^7.1.0",
"workbox-core": "^7.1.0",
"workbox-routing": "^7.1.0",
"workbox-strategies": "^7.1.0"
},
"lint-staged": {
"*.{js,ts,css,md,json}": "prettier --write",

View File

@ -1,6 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php" backupGlobals="false" colors="true" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache">
<coverage includeUncoveredFiles="true">
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/codeigniter4/framework/system/Test/bootstrap.php"
backupGlobals="false"
beStrictAboutOutputDuringTests="true"
colors="true"
columns="max"
failOnRisky="true"
failOnWarning="true"
cacheDirectory="build/.phpunit.cache">
<coverage
includeUncoveredFiles="true"
pathCoverage="false"
ignoreDeprecatedCodeUnits="true"
disableCodeCoverageIgnore="true">
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/logs/html"/>
@ -18,6 +31,15 @@
<testdoxText outputFile="build/logs/testdox.txt"/>
<junit outputFile="build/logs/logfile.xml"/>
</logging>
<source>
<include>
<directory suffix=".php">./app</directory>
</include>
<exclude>
<directory suffix=".php">./app/Views</directory>
<file>./app/Config/Routes.php</file>
</exclude>
</source>
<php>
<server name="app.baseURL" value="http://example.com/"/>
<!-- Directory containing phpunit.xml -->
@ -35,13 +57,4 @@
<env name="database.tests.DBPrefix" value="tests_"/>
<env name="restapi.enabled" value="true"/>
</php>
<source>
<include>
<directory suffix=".php">./app</directory>
</include>
<exclude>
<directory suffix=".php">./app/Views</directory>
<file>./app/Config/Routes.php</file>
</exclude>
</source>
</phpunit>

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ class preload
*/
private array $paths = [
[
'include' => __DIR__ . '/vendor/codeigniter4/framework/system',
'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation
'exclude' => [
// Not needed if you don't use them.
'/system/Database/OCI8/',

View File

@ -45,5 +45,5 @@ Options -Indexes
</IfModule>
# Disable server signature start
ServerSignature Off
ServerSignature Off
# Disable server signature end

View File

@ -2,11 +2,15 @@
declare(strict_types=1);
use CodeIgniter\Config\DotEnv;
use CodeIgniter\Boot;
use Config\Paths;
use Config\Services;
// Check PHP version.
/*
*---------------------------------------------------------------
* CHECK PHP VERSION
*---------------------------------------------------------------
*/
$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`.
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
$message = sprintf(
@ -15,9 +19,18 @@ if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
PHP_VERSION
);
exit($message);
header('HTTP/1.1 503 Service Unavailable.', true, 503);
echo $message;
exit(1);
}
/*
*---------------------------------------------------------------
* SET THE CURRENT DIRECTORY
*---------------------------------------------------------------
*/
// Path to the front controller (this file)
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);
@ -35,59 +48,14 @@ if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) {
* and fires up an environment-specific bootstrapping.
*/
// Load our paths config file
// LOAD OUR PATHS CONFIG FILE
// This is the line that might need to be changed, depending on your folder structure.
require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder
$paths = new Paths();
// Location of the framework bootstrap file.
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
// LOAD THE FRAMEWORK BOOTSTRAP FILE
require $paths->systemDirectory . '/Boot.php';
// Load environment settings from .env files into $_SERVER and $_ENV
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new DotEnv(ROOTPATH))->load();
// Define ENVIRONMENT
if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
}
// Load Config Cache
// $factoriesCache = new \CodeIgniter\Cache\FactoriesCache();
// $factoriesCache->load('config');
// ^^^ Uncomment these lines if you want to use Config Caching.
/*
* ---------------------------------------------------------------
* GRAB OUR CODEIGNITER INSTANCE
* ---------------------------------------------------------------
*
* The CodeIgniter class contains the core functionality to make
* the application run, and does all the dirty work to get
* the pieces all working together.
*/
$app = Services::codeigniter();
$app->initialize();
$context = is_cli() ? 'php-cli' : 'web';
$app->setContext($context);
/*
*---------------------------------------------------------------
* LAUNCH THE APPLICATION
*---------------------------------------------------------------
* Now that everything is set up, it's time to actually fire
* up the engines and make this app do its thang.
*/
$app->run();
// Save Config Cache
// $factoriesCache->save('config');
// ^^^ Uncomment this line if you want to use Config Caching.
// Exits the application, setting the exit code for CLI-based applications
// that might be watching.
exit(EXIT_SUCCESS);
exit(Boot::bootWeb($paths));

View File

@ -2,6 +2,7 @@
declare(strict_types=1);
use Rector\CodeQuality\Rector\ClassMethod\ExplicitReturnNullRector;
use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
use Rector\CodingStyle\Rector\String_\SymplifyQuoteEscapeRector;
@ -12,31 +13,22 @@ use Rector\EarlyReturn\Rector\If_\ChangeAndIfToEarlyReturnRector;
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Set\ValueObject\SetList;
use Rector\ValueObject\PhpVersion;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([__DIR__ . '/app', __DIR__ . '/modules', __DIR__ . '/tests', __DIR__ . '/public']);
// do you need to include constants, class aliases or custom autoloader? files listed will be executed
$rectorConfig->bootstrapFiles([__DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php']);
// Define what rule sets will be applied
$rectorConfig->sets([
SetList::PHP_81,
SetList::TYPE_DECLARATION,
SetList::CODE_QUALITY,
SetList::CODING_STYLE,
SetList::EARLY_RETURN,
SetList::DEAD_CODE,
]);
// auto import fully qualified class names
$rectorConfig->importNames();
$rectorConfig->phpVersion(PhpVersion::PHP_81);
$rectorConfig->skip([
return RectorConfig::configure()
->withPaths([__DIR__ . '/app', __DIR__ . '/modules', __DIR__ . '/tests', __DIR__ . '/public'])
->withBootstrapFiles([__DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php'])
->withPhpVersion(PhpVersion::PHP_81)
->withPhpSets(php81: true)
->withPreparedSets(
typeDeclarations: true,
codeQuality: true,
codingStyle: true,
earlyReturn: true,
deadCode: true,
)
->withImportNames(true, true, true, true)
->withSkip([
// .mp3 files were somehow processed by rector, so skip all media files
__DIR__ . '/public/media/*',
@ -50,6 +42,7 @@ return static function (RectorConfig $rectorConfig): void {
EncapsedStringsToSprintfRector::class,
RemoveExtraParametersRector::class,
UnwrapFutureCompatibleIfPhpVersionRector::class,
ExplicitReturnNullRector::class,
// skip rule in specific directory
StringClassNameToClassConstantRector::class => [
@ -65,9 +58,5 @@ return static function (RectorConfig $rectorConfig): void {
],
ChangeAndIfToEarlyReturnRector::class => [__DIR__ . '/modules/Install/Controllers/InstallController.php'],
]);
// Path to phpstan with extensions, that PHPStan in Rector uses to determine types
$rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon');
$rectorConfig->phpstanConfigs(['vendor/codeigniter/phpstan-codeigniter/extension.neon']);
};
])
->withPHPStanConfigs([__DIR__ . '/phpstan.neon', 'vendor/codeigniter/phpstan-codeigniter/extension.neon']);

64
spark
View File

@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
@ -12,13 +14,16 @@
/*
* --------------------------------------------------------------------
* CodeIgniter command-line tools
* CODEIGNITER COMMAND-LINE TOOLS
* --------------------------------------------------------------------
* The main entry point into the CLI system and allows you to run
* commands and perform maintenance on your application.
*
* Because CodeIgniter can handle CLI requests as just another web request
* this class mainly acts as a passthru to the framework itself.
*/
/*
*---------------------------------------------------------------
* CHECK SERVER API
*---------------------------------------------------------------
*/
// Refuse to run when called from php-cgi
@ -26,7 +31,12 @@ if (strpos(PHP_SAPI, 'cgi') === 0) {
exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n");
}
// Check PHP version.
/*
*---------------------------------------------------------------
* CHECK PHP VERSION
*---------------------------------------------------------------
*/
$minPhpVersion = '8.1'; // If you update this, don't forget to update `public/index.php`.
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
$message = sprintf(
@ -42,12 +52,11 @@ if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
error_reporting(E_ALL);
ini_set('display_errors', '1');
/**
* @var bool
*
* @deprecated No longer in use. `CodeIgniter` has `$context` property.
/*
*---------------------------------------------------------------
* SET THE CURRENT DIRECTORY
*---------------------------------------------------------------
*/
define('SPARKED', true);
// Path to the front controller
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
@ -64,41 +73,14 @@ chdir(FCPATH);
* and fires up an environment-specific bootstrapping.
*/
// Load our paths config file
// LOAD OUR PATHS CONFIG FILE
// This is the line that might need to be changed, depending on your folder structure.
require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder
$paths = new Config\Paths();
// Location of the framework bootstrap file.
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
// LOAD THE FRAMEWORK BOOTSTRAP FILE
require $paths->systemDirectory . '/Boot.php';
// Load environment settings from .env files into $_SERVER and $_ENV
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
// Define ENVIRONMENT
if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
}
// Grab our CodeIgniter
$app = Config\Services::codeigniter();
$app->initialize();
// Grab our Console
$console = new CodeIgniter\CLI\Console();
// Show basic information before we do anything else.
if (is_int($suppress = array_search('--no-header', $_SERVER['argv'], true))) {
unset($_SERVER['argv'][$suppress]); // @codeCoverageIgnore
$suppress = true;
}
$console->showHeader($suppress);
// fire off the command in the main framework.
$exit = $console->run();
exit(is_int($exit) ? $exit : EXIT_SUCCESS);
exit(CodeIgniter\Boot::bootSpark($paths));

6
tests/.htaccess Normal file
View File

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

View File

@ -36,8 +36,6 @@ namespace Tests\Support\Libraries;
use Config\App;
/**
* Class ConfigReader
*
* An extension of BaseConfig that prevents the constructor from loading external values. Used to read actual local
* values from a config file.
*/

View File

@ -29,7 +29,7 @@ class ExampleModel extends Model
protected $useSoftDeletes = false;
/**
* @var string[]
* @var list<string>
*/
protected $allowedFields = ['name', 'uid', 'class', 'icon', 'summary'];
@ -39,12 +39,12 @@ class ExampleModel extends Model
protected $useTimestamps = true;
/**
* @var mixed[]
* @var array<string, array<string, array<string, string>|string>|string>|string
*/
protected $validationRules = [];
/**
* @var mixed[]
* @var array<string, array<string, string>>
*/
protected $validationMessages = [];

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Database;
use CodeIgniter\Database\Seeder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Tests\Support\Database\Seeds\ExampleSeeder;
@ -14,7 +15,7 @@ class ExampleDatabaseTest extends CIUnitTestCase
use DatabaseTestTrait;
/**
* @var string
* @var class-string<Seeder>|list<class-string<Seeder>>
*/
protected $seed = ExampleSeeder::class;

9
tests/index.html Normal file
View File

@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace modules\Api\Rest\V1;
use App\Database\Seeds\FakeSinglePodcastApiSeeder;
use CodeIgniter\Database\Seeder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
@ -31,9 +32,9 @@ class EpisodeTest extends CIUnitTestCase
protected $namespace;
/**
* @var string
* @var class-string<Seeder>|list<class-string<Seeder>>
*/
protected $seed = 'FakeSinglePodcastApiSeeder';
protected $seed = FakeSinglePodcastApiSeeder::class;
/**
* @var string

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace modules\Api\Rest\V1;
use App\Database\Seeds\FakeSinglePodcastApiSeeder;
use CodeIgniter\Database\Seeder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
@ -31,9 +32,9 @@ class PodcastTest extends CIUnitTestCase
protected $namespace;
/**
* @var string
* @var class-string<Seeder>|list<class-string<Seeder>>
*/
protected $seed = 'FakeSinglePodcastApiSeeder';
protected $seed = FakeSinglePodcastApiSeeder::class;
/**
* @var string

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