refactor: update code base to php 8 and set phpstan lvl to 6

This commit is contained in:
Yassine Doghri 2021-05-14 17:59:35 +00:00
parent 4a33c50fb6
commit 6b74a9e98a
No known key found for this signature in database
GPG Key ID: 3E7F89498B960C9F
124 changed files with 1811 additions and 2158 deletions

View File

@ -1,4 +1,4 @@
image: php:7.3-fpm image: php:8.0-fpm
stages: stages:
- quality - quality

View File

@ -4,7 +4,7 @@
{ {
"files": "*.php", "files": "*.php",
"options": { "options": {
"phpVersion": "7.3", "phpVersion": "7.4",
"singleQuote": true "singleQuote": true
} }
}, },

View File

@ -5,7 +5,7 @@
# should be used only for development purposes # should be used only for development purposes
#################################################### ####################################################
FROM php:7.3-fpm FROM php:8.0-fpm
LABEL maintainer="Yassine Doghri<yassine@podlibre.org>" LABEL maintainer="Yassine Doghri<yassine@podlibre.org>"
@ -37,7 +37,7 @@ RUN apt-get update && apt-get install -y \
zlib1g-dev \ zlib1g-dev \
&& docker-php-ext-install intl && docker-php-ext-install intl
RUN docker-php-ext-configure gd --with-jpeg-dir=/usr/include/ \ RUN docker-php-ext-configure gd --with-jpeg \
&& docker-php-ext-install gd && docker-php-ext-install gd
RUN pecl install -o -f redis \ RUN pecl install -o -f redis \

View File

@ -10,7 +10,7 @@ or shared hosting, you can install it on most PHP-MySQL compatible web servers.
- [1. Install Wizard](#1-install-wizard) - [1. Install Wizard](#1-install-wizard)
- [1-alt Manual configuration](#1-alt-manual-configuration) - [1-alt Manual configuration](#1-alt-manual-configuration)
- [Web Server Requirements](#web-server-requirements) - [Web Server Requirements](#web-server-requirements)
- [PHP v7.3 or higher](#php-v73-or-higher) - [PHP v8.0 or higher](#php-v73-or-higher)
- [MySQL compatible database](#mysql-compatible-database) - [MySQL compatible database](#mysql-compatible-database)
- [Privileges](#privileges) - [Privileges](#privileges)
- [(Optional) Other recommendations](#optional-other-recommendations) - [(Optional) Other recommendations](#optional-other-recommendations)
@ -59,9 +59,9 @@ through the install wizard, you can create and update the `.env` file yourself:
## Web Server Requirements ## Web Server Requirements
### PHP v7.3 or higher ### PHP v8.0 or higher
PHP version 7.3 or higher is required, with the following extensions installed: PHP version 8.0 or higher is required, with the following extensions installed:
- [intl](https://php.net/manual/en/intl.requirements.php) - [intl](https://php.net/manual/en/intl.requirements.php)
- [libcurl](https://php.net/manual/en/curl.requirements.php) - [libcurl](https://php.net/manual/en/curl.requirements.php)

View File

@ -17,10 +17,8 @@ class FlatAuthorization extends MythAuthFlatAuthorization
/** /**
* Checks a group to see if they have the specified permission. * Checks a group to see if they have the specified permission.
*
* @param int|string $permission
*/ */
public function groupHasPermission($permission, int $groupId): bool public function groupHasPermission(int|string $permission, int $groupId): bool
{ {
// Get the Permission ID // Get the Permission ID
$permissionId = $this->getPermissionID($permission); $permissionId = $this->getPermissionID($permission);

View File

@ -1,5 +1,7 @@
<?php namespace Config; <?php namespace Config;
use App\Libraries\PodcastActor;
use App\Libraries\NoteObject;
use ActivityPub\Config\ActivityPub as ActivityPubBase; use ActivityPub\Config\ActivityPub as ActivityPubBase;
class ActivityPub extends ActivityPubBase class ActivityPub extends ActivityPubBase
@ -8,18 +10,32 @@ class ActivityPub extends ActivityPubBase
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* ActivityPub Objects * ActivityPub Objects
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* @var string
*/ */
public $actorObject = 'App\Libraries\PodcastActor'; public $actorObject = PodcastActor::class;
public $noteObject = 'App\Libraries\NoteObject'; /**
* @var string
*/
public $noteObject = NoteObject::class;
/** /**
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* Default avatar and cover images * Default avatar and cover images
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* @var string
*/ */
public $defaultAvatarImagePath = 'assets/images/castopod-avatar-default.jpg'; public $defaultAvatarImagePath = 'assets/images/castopod-avatar-default.jpg';
/**
* @var string
*/
public $defaultAvatarImageMimetype = 'image/jpeg'; public $defaultAvatarImageMimetype = 'image/jpeg';
/**
* @var string
*/
public $defaultCoverImagePath = 'assets/images/castopod-cover-default.jpg'; public $defaultCoverImagePath = 'assets/images/castopod-cover-default.jpg';
/**
* @var string
*/
public $defaultCoverImageMimetype = 'image/jpeg'; public $defaultCoverImageMimetype = 'image/jpeg';
} }

View File

@ -10,6 +10,7 @@ class Analytics extends AnalyticsBase
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* Route filters options * Route filters options
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* @var array<string, string>
*/ */
public $routeFilters = [ public $routeFilters = [
'analytics-full-data' => 'permission:podcasts-view,podcast-view', 'analytics-full-data' => 'permission:podcasts-view,podcast-view',

View File

@ -12,25 +12,21 @@ class Database extends Config
/** /**
* The directory that holds the Migrations * The directory that holds the Migrations
* and Seeds directories. * and Seeds directories.
*
* @var string
*/ */
public $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR; public string $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR;
/** /**
* Lets you choose which connection group to * Lets you choose which connection group to
* use if no other is specified. * use if no other is specified.
*
* @var string
*/ */
public $defaultGroup = 'default'; public string $defaultGroup = 'default';
/** /**
* The default database connection. * The default database connection.
* *
* @var array * @var array<string, string|bool|int|array>
*/ */
public $default = [ public array $default = [
'DSN' => '', 'DSN' => '',
'hostname' => 'localhost', 'hostname' => 'localhost',
'username' => '', 'username' => '',
@ -54,9 +50,9 @@ class Database extends Config
* This database connection is used when * This database connection is used when
* running PHPUnit database tests. * running PHPUnit database tests.
* *
* @var array * @var array<string, string|bool|int|array>
*/ */
public $tests = [ public array $tests = [
'DSN' => '', 'DSN' => '',
'hostname' => '127.0.0.1', 'hostname' => '127.0.0.1',
'username' => '', 'username' => '',

View File

@ -29,7 +29,7 @@ class Exceptions extends BaseConfig
* Any status codes here will NOT be logged if logging is turned on. * Any status codes here will NOT be logged if logging is turned on.
* By default, only 404 (Page Not Found) exceptions are ignored. * By default, only 404 (Page Not Found) exceptions are ignored.
* *
* @var array * @var int[]
*/ */
public $ignoreCodes = [404]; public $ignoreCodes = [404];

View File

@ -17,7 +17,7 @@ class Filters extends BaseConfig
* Configures aliases for Filter classes to * Configures aliases for Filter classes to
* make reading things nicer and simpler. * make reading things nicer and simpler.
* *
* @var array * @var array<string, string>
*/ */
public $aliases = [ public $aliases = [
'csrf' => CSRF::class, 'csrf' => CSRF::class,
@ -33,7 +33,7 @@ class Filters extends BaseConfig
* List of filter aliases that are always * List of filter aliases that are always
* applied before and after every request. * applied before and after every request.
* *
* @var array * @var array<string, string[]>
*/ */
public $globals = [ public $globals = [
'before' => [ 'before' => [
@ -53,7 +53,7 @@ class Filters extends BaseConfig
* Example: * Example:
* 'post' => ['csrf', 'throttle'] * 'post' => ['csrf', 'throttle']
* *
* @var array * @var array<string, string[]>
*/ */
public $methods = []; public $methods = [];
@ -64,7 +64,7 @@ class Filters extends BaseConfig
* Example: * Example:
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']] * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
* *
* @var array * @var array<string, array<string, string[]>>
*/ */
public $filters = []; public $filters = [];

View File

@ -23,7 +23,10 @@ class Kint extends BaseConfig
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
public $plugins; /**
* @var string[]
*/
public $plugins = [];
/** /**
* @var int * @var int
@ -60,9 +63,15 @@ class Kint extends BaseConfig
*/ */
public $richSort = Renderer::SORT_FULL; public $richSort = Renderer::SORT_FULL;
public $richObjectPlugins; /**
* @var string[]
*/
public $richObjectPlugins = [];
public $richTabPlugins; /**
* @var string[]
*/
public $richTabPlugins = [];
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

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

View File

@ -164,13 +164,13 @@ $routes->group(
]); ]);
$routes->group('persons', function ($routes): void { $routes->group('persons', function ($routes): void {
$routes->get('/', 'PodcastPersonController/$1', [ $routes->get('/', 'PodcastPodcastController/$1', [
'as' => 'podcast-person-manage', 'as' => 'podcast-person-manage',
'filter' => 'permission:podcast-edit', 'filter' => 'permission:podcast-edit',
]); ]);
$routes->post( $routes->post(
'/', '/',
'PodcastPersonController::attemptAdd/$1', 'PodcastPodcastController::attemptAdd/$1',
[ [
'filter' => 'permission:podcast-edit', 'filter' => 'permission:podcast-edit',
], ],
@ -178,7 +178,7 @@ $routes->group(
$routes->get( $routes->get(
'(:num)/remove', '(:num)/remove',
'PodcastPersonController::remove/$1/$2', 'PodcastPodcastController::remove/$1/$2',
[ [
'as' => 'podcast-person-remove', 'as' => 'podcast-person-remove',
'filter' => 'permission:podcast-edit', 'filter' => 'permission:podcast-edit',

View File

@ -34,18 +34,12 @@ class Services extends BaseService
/** /**
* The Router class uses a RouteCollection's array of routes, and determines * The Router class uses a RouteCollection's array of routes, and determines
* the correct Controller and Method to execute. * the correct Controller and Method to execute.
*
* @param RouteCollectionInterface|null $routes
* @param Request|null $request
* @param boolean $getShared
*
* @return Router
*/ */
public static function router( public static function router(
RouteCollectionInterface $routes = null, ?RouteCollectionInterface $routes = null,
Request $request = null, ?Request $request = null,
bool $getShared = true bool $getShared = true
) { ): Router {
if ($getShared) { if ($getShared) {
return static::getSharedInstance('router', $routes, $request); return static::getSharedInstance('router', $routes, $request);
} }
@ -60,16 +54,11 @@ class Services extends BaseService
* The Negotiate class provides the content negotiation features for * The Negotiate class provides the content negotiation features for
* working the request to determine correct language, encoding, charset, * working the request to determine correct language, encoding, charset,
* and more. * and more.
*
* @param RequestInterface|null $request
* @param boolean $getShared
*
* @return Negotiate
*/ */
public static function negotiator( public static function negotiator(
RequestInterface $request = null, ?RequestInterface $request = null,
bool $getShared = true bool $getShared = true
) { ): Negotiate {
if ($getShared) { if ($getShared) {
return static::getSharedInstance('negotiator', $request); return static::getSharedInstance('negotiator', $request);
} }
@ -79,6 +68,9 @@ class Services extends BaseService
return new Negotiate($request); return new Negotiate($request);
} }
/**
* @return mixed
*/
public static function authentication( public static function authentication(
string $lib = 'local', string $lib = 'local',
Model $userModel = null, Model $userModel = null,
@ -112,6 +104,9 @@ class Services extends BaseService
return $instance->setUserModel($userModel)->setLoginModel($loginModel); return $instance->setUserModel($userModel)->setLoginModel($loginModel);
} }
/**
* @return mixed
*/
public static function authorization( public static function authorization(
Model $groupModel = null, Model $groupModel = null,
Model $permissionModel = null, Model $permissionModel = null,
@ -144,7 +139,7 @@ class Services extends BaseService
return $instance->setUserModel($userModel); return $instance->setUserModel($userModel);
} }
public static function breadcrumb(bool $getShared = true) public static function breadcrumb(bool $getShared = true): Breadcrumb
{ {
if ($getShared) { if ($getShared) {
return self::getSharedInstance('breadcrumb'); return self::getSharedInstance('breadcrumb');

View File

@ -29,7 +29,7 @@ class View extends BaseView
* { title|esc(js) } * { title|esc(js) }
* { created_on|date(Y-m-d)|esc(attr) } * { created_on|date(Y-m-d)|esc(attr) }
* *
* @var array * @var string[]
*/ */
public $filters = []; public $filters = [];
@ -38,7 +38,7 @@ class View extends BaseView
* by the core Parser by creating aliases that will be replaced with * by the core Parser by creating aliases that will be replaced with
* any callable. Can be single or tag pair. * any callable. Can be single or tag pair.
* *
* @var array * @var string[]
*/ */
public $plugins = []; public $plugins = [];
} }

View File

@ -15,9 +15,12 @@ class ActorController extends ActivityPubActorController
{ {
use AnalyticsTrait; use AnalyticsTrait;
/**
* @var string[]
*/
protected $helpers = ['auth', 'svg', 'components', 'misc']; protected $helpers = ['auth', 'svg', 'components', 'misc'];
public function follow() public function follow(): string
{ {
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
if (!can_user_interact()) { if (!can_user_interact()) {

View File

@ -25,7 +25,7 @@ class BaseController extends Controller
* class instantiation. These helpers will be available * class instantiation. These helpers will be available
* to all other controllers that extend BaseController. * to all other controllers that extend BaseController.
* *
* @var array * @var string[]
*/ */
protected $helpers = ['auth', 'breadcrumb', 'svg', 'components', 'misc']; protected $helpers = ['auth', 'breadcrumb', 'svg', 'components', 'misc'];

View File

@ -15,6 +15,7 @@ use Exception;
use App\Authorization\GroupModel; use App\Authorization\GroupModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\HTTP\RedirectResponse;
class ContributorController extends BaseController class ContributorController extends BaseController
{ {
@ -28,9 +29,9 @@ class ContributorController extends BaseController
*/ */
protected $user; protected $user;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
$this->podcast = (new PodcastModel())->getPodcastById($params[0]); $this->podcast = (new PodcastModel())->getPodcastById((int) $params[0]);
if (count($params) <= 1) { if (count($params) <= 1) {
return $this->$method(); return $this->$method();
@ -38,8 +39,8 @@ class ContributorController extends BaseController
if ( if (
$this->user = (new UserModel())->getPodcastContributor( $this->user = (new UserModel())->getPodcastContributor(
$params[1], (int) $params[1],
$params[0], (int) $params[0],
) )
) { ) {
return $this->$method(); return $this->$method();
@ -48,7 +49,7 @@ class ContributorController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function list() public function list(): string
{ {
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
@ -58,7 +59,7 @@ class ContributorController extends BaseController
return view('admin/contributor/list', $data); return view('admin/contributor/list', $data);
} }
public function view() public function view(): string
{ {
$data = [ $data = [
'contributor' => (new UserModel())->getPodcastContributor( 'contributor' => (new UserModel())->getPodcastContributor(
@ -74,7 +75,7 @@ class ContributorController extends BaseController
return view('admin/contributor/view', $data); return view('admin/contributor/view', $data);
} }
public function add() public function add(): string
{ {
helper('form'); helper('form');
@ -108,7 +109,7 @@ class ContributorController extends BaseController
return view('admin/contributor/add', $data); return view('admin/contributor/add', $data);
} }
public function attemptAdd() public function attemptAdd(): RedirectResponse
{ {
try { try {
(new PodcastModel())->addPodcastContributor( (new PodcastModel())->addPodcastContributor(
@ -116,7 +117,7 @@ class ContributorController extends BaseController
$this->podcast->id, $this->podcast->id,
$this->request->getPost('role'), $this->request->getPost('role'),
); );
} catch (Exception $exception) { } catch (Exception) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
@ -128,7 +129,7 @@ class ContributorController extends BaseController
return redirect()->route('contributor-list', [$this->podcast->id]); return redirect()->route('contributor-list', [$this->podcast->id]);
} }
public function edit() public function edit(): string
{ {
helper('form'); helper('form');
@ -159,7 +160,7 @@ class ContributorController extends BaseController
return view('admin/contributor/edit', $data); return view('admin/contributor/edit', $data);
} }
public function attemptEdit() public function attemptEdit(): RedirectResponse
{ {
(new PodcastModel())->updatePodcastContributor( (new PodcastModel())->updatePodcastContributor(
$this->user->id, $this->user->id,
@ -170,7 +171,7 @@ class ContributorController extends BaseController
return redirect()->route('contributor-list', [$this->podcast->id]); return redirect()->route('contributor-list', [$this->podcast->id]);
} }
public function remove() public function remove(): RedirectResponse
{ {
if ($this->podcast->created_by === $this->user->id) { if ($this->podcast->created_by === $this->user->id) {
return redirect() return redirect()

View File

@ -8,6 +8,9 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Database;
use App\Entities\Episode; use App\Entities\Episode;
use App\Entities\Note; use App\Entities\Note;
use App\Entities\Podcast; use App\Entities\Podcast;
@ -29,12 +32,14 @@ class EpisodeController extends BaseController
*/ */
protected $episode; protected $episode;
public function _remap(string $method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if ( if (
!($this->podcast = (new PodcastModel())->getPodcastById($params[0])) ($this->podcast = (new PodcastModel())->getPodcastById(
(int) $params[0],
)) === null
) { ) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if (count($params) > 1) { if (count($params) > 1) {
@ -46,7 +51,7 @@ class EpisodeController extends BaseController
]) ])
->first()) ->first())
) { ) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
unset($params[1]); unset($params[1]);
@ -56,7 +61,7 @@ class EpisodeController extends BaseController
return $this->$method(...$params); return $this->$method(...$params);
} }
public function list() public function list(): string
{ {
$episodes = (new EpisodeModel()) $episodes = (new EpisodeModel())
->where('podcast_id', $this->podcast->id) ->where('podcast_id', $this->podcast->id)
@ -74,7 +79,7 @@ class EpisodeController extends BaseController
return view('admin/episode/list', $data); return view('admin/episode/list', $data);
} }
public function view() public function view(): string
{ {
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
@ -88,7 +93,7 @@ class EpisodeController extends BaseController
return view('admin/episode/view', $data); return view('admin/episode/view', $data);
} }
public function create() public function create(): string
{ {
helper(['form']); helper(['form']);
@ -102,7 +107,7 @@ class EpisodeController extends BaseController
return view('admin/episode/create', $data); return view('admin/episode/create', $data);
} }
public function attemptCreate() public function attemptCreate(): RedirectResponse
{ {
$rules = [ $rules = [
'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]', 'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
@ -204,7 +209,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function edit() public function edit(): string
{ {
helper(['form']); helper(['form']);
@ -220,7 +225,7 @@ class EpisodeController extends BaseController
return view('admin/episode/edit', $data); return view('admin/episode/edit', $data);
} }
public function attemptEdit() public function attemptEdit(): RedirectResponse
{ {
$rules = [ $rules = [
'audio_file' => 'audio_file' =>
@ -282,18 +287,15 @@ class EpisodeController extends BaseController
} }
} elseif ($transcriptChoice === 'remote-url') { } elseif ($transcriptChoice === 'remote-url') {
if ( if (
$transcriptFileRemoteUrl = $this->request->getPost( ($transcriptFileRemoteUrl = $this->request->getPost(
'transcript_file_remote_url', 'transcript_file_remote_url',
) )) &&
) { (($transcriptFile = $this->episode->transcript_file) &&
if ( $transcriptFile !== null)
($transcriptFile = $this->episode->transcript_file) &&
$transcriptFile !== null
) { ) {
unlink($transcriptFile); unlink($transcriptFile);
$this->episode->transcript_file_path = null; $this->episode->transcript_file_path = null;
} }
}
$this->episode->transcript_file_remote_url = $transcriptFileRemoteUrl; $this->episode->transcript_file_remote_url = $transcriptFileRemoteUrl;
} }
@ -306,18 +308,15 @@ class EpisodeController extends BaseController
} }
} elseif ($chaptersChoice === 'remote-url') { } elseif ($chaptersChoice === 'remote-url') {
if ( if (
$chaptersFileRemoteUrl = $this->request->getPost( ($chaptersFileRemoteUrl = $this->request->getPost(
'chapters_file_remote_url', 'chapters_file_remote_url',
) )) &&
) { (($chaptersFile = $this->episode->chapters_file) &&
if ( $chaptersFile !== null)
($chaptersFile = $this->episode->chapters_file) &&
$chaptersFile !== null
) { ) {
unlink($chaptersFile); unlink($chaptersFile);
$this->episode->chapters_file_path = null; $this->episode->chapters_file_path = null;
} }
}
$this->episode->chapters_file_remote_url = $chaptersFileRemoteUrl; $this->episode->chapters_file_remote_url = $chaptersFileRemoteUrl;
} }
@ -351,7 +350,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function transcriptDelete() public function transcriptDelete(): RedirectResponse
{ {
unlink($this->episode->transcript_file); unlink($this->episode->transcript_file);
$this->episode->transcript_file_path = null; $this->episode->transcript_file_path = null;
@ -368,7 +367,7 @@ class EpisodeController extends BaseController
return redirect()->back(); return redirect()->back();
} }
public function chaptersDelete() public function chaptersDelete(): RedirectResponse
{ {
unlink($this->episode->chapters_file); unlink($this->episode->chapters_file);
$this->episode->chapters_file_path = null; $this->episode->chapters_file_path = null;
@ -385,7 +384,7 @@ class EpisodeController extends BaseController
return redirect()->back(); return redirect()->back();
} }
public function publish() public function publish(): string
{ {
if ($this->episode->publication_status === 'not_published') { if ($this->episode->publication_status === 'not_published') {
helper(['form']); helper(['form']);
@ -400,12 +399,12 @@ class EpisodeController extends BaseController
1 => $this->episode->title, 1 => $this->episode->title,
]); ]);
return view('admin/episode/publish', $data); return view('admin/episode/publish', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
} }
public function attemptPublish() throw PageNotFoundException::forPageNotFound();
}
public function attemptPublish(): RedirectResponse
{ {
$rules = [ $rules = [
'publication_method' => 'required', 'publication_method' => 'required',
@ -420,7 +419,7 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$db = \Config\Database::connect(); $db = Database::connect();
$db->transStart(); $db->transStart();
$newNote = new Note([ $newNote = new Note([
@ -482,7 +481,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function publishEdit() public function publishEdit(): string
{ {
if ($this->episode->publication_status === 'scheduled') { if ($this->episode->publication_status === 'scheduled') {
helper(['form']); helper(['form']);
@ -503,12 +502,11 @@ class EpisodeController extends BaseController
1 => $this->episode->title, 1 => $this->episode->title,
]); ]);
return view('admin/episode/publish_edit', $data); return view('admin/episode/publish_edit', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
} }
throw PageNotFoundException::forPageNotFound();
} }
public function attemptPublishEdit() public function attemptPublishEdit(): RedirectResponse
{ {
$rules = [ $rules = [
'note_id' => 'required', 'note_id' => 'required',
@ -524,7 +522,7 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$db = \Config\Database::connect(); $db = Database::connect();
$db->transStart(); $db->transStart();
$note = (new NoteModel())->getNoteById( $note = (new NoteModel())->getNoteById(
@ -584,7 +582,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function unpublish() public function unpublish(): string
{ {
if ($this->episode->publication_status === 'published') { if ($this->episode->publication_status === 'published') {
helper(['form']); helper(['form']);
@ -599,12 +597,12 @@ class EpisodeController extends BaseController
1 => $this->episode->title, 1 => $this->episode->title,
]); ]);
return view('admin/episode/unpublish', $data); return view('admin/episode/unpublish', $data);
} else {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
} }
public function attemptUnpublish() throw PageNotFoundException::forPageNotFound();
}
public function attemptUnpublish(): RedirectResponse
{ {
$rules = [ $rules = [
'understand' => 'required', 'understand' => 'required',
@ -617,7 +615,7 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$db = \Config\Database::connect(); $db = Database::connect();
$db->transStart(); $db->transStart();
@ -650,14 +648,14 @@ class EpisodeController extends BaseController
]); ]);
} }
public function delete() public function delete(): RedirectResponse
{ {
(new EpisodeModel())->delete($this->episode->id); (new EpisodeModel())->delete($this->episode->id);
return redirect()->route('episode-list', [$this->podcast->id]); return redirect()->route('episode-list', [$this->podcast->id]);
} }
public function soundbitesEdit() public function soundbitesEdit(): string
{ {
helper(['form']); helper(['form']);
@ -673,7 +671,7 @@ class EpisodeController extends BaseController
return view('admin/episode/soundbites', $data); return view('admin/episode/soundbites', $data);
} }
public function soundbitesAttemptEdit() public function soundbitesAttemptEdit(): RedirectResponse
{ {
$soundbites_array = $this->request->getPost('soundbites_array'); $soundbites_array = $this->request->getPost('soundbites_array');
$rules = [ $rules = [
@ -682,7 +680,7 @@ class EpisodeController extends BaseController
'soundbites_array.0.duration' => 'soundbites_array.0.duration' =>
'permit_empty|required_with[soundbites_array.0.start_time]|decimal|greater_than_equal_to[0]', 'permit_empty|required_with[soundbites_array.0.start_time]|decimal|greater_than_equal_to[0]',
]; ];
foreach ($soundbites_array as $soundbite_id => $soundbite) { foreach (array_keys($soundbites_array) as $soundbite_id) {
$rules += [ $rules += [
"soundbites_array.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]', "soundbites_array.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]',
"soundbites_array.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]', "soundbites_array.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]',
@ -728,7 +726,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function soundbiteDelete($soundbiteId) public function soundbiteDelete(int $soundbiteId): RedirectResponse
{ {
(new SoundbiteModel())->deleteSoundbite( (new SoundbiteModel())->deleteSoundbite(
$this->podcast->id, $this->podcast->id,
@ -742,7 +740,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function embeddablePlayer() public function embeddablePlayer(): string
{ {
helper(['form']); helper(['form']);

View File

@ -8,27 +8,20 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Podcast; use App\Entities\Podcast;
use App\Entities\Episode; use App\Entities\Episode;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\EpisodePersonModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PersonModel; use App\Models\PersonModel;
class EpisodePersonController extends BaseController class EpisodePersonController extends BaseController
{ {
/** protected Podcast $podcast;
* @var Podcast protected Episode $episode;
*/
protected $podcast;
/** public function _remap(string $method, string ...$params): mixed
* @var Episode
*/
protected $episode;
public function _remap($method, ...$params)
{ {
if (count($params) <= 2) { if (count($params) <= 2) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
@ -36,7 +29,7 @@ class EpisodePersonController extends BaseController
if ( if (
($this->podcast = (new PodcastModel())->getPodcastById( ($this->podcast = (new PodcastModel())->getPodcastById(
$params[0], (int) $params[0],
)) && )) &&
($this->episode = (new EpisodeModel()) ($this->episode = (new EpisodeModel())
->where([ ->where([
@ -54,14 +47,14 @@ class EpisodePersonController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
helper('form'); helper('form');
$data = [ $data = [
'episode' => $this->episode, 'episode' => $this->episode,
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'episodePersons' => (new EpisodePersonModel())->getEpisodePersons( 'episodePersons' => (new PersonModel())->getEpisodePersons(
$this->podcast->id, $this->podcast->id,
$this->episode->id, $this->episode->id,
), ),
@ -75,7 +68,7 @@ class EpisodePersonController extends BaseController
return view('admin/episode/person', $data); return view('admin/episode/person', $data);
} }
public function attemptAdd() public function attemptAdd(): RedirectResponse
{ {
$rules = [ $rules = [
'person' => 'required', 'person' => 'required',
@ -88,7 +81,7 @@ class EpisodePersonController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
(new EpisodePersonModel())->addEpisodePersons( (new PersonModel())->addEpisodePersons(
$this->podcast->id, $this->podcast->id,
$this->episode->id, $this->episode->id,
$this->request->getPost('person'), $this->request->getPost('person'),
@ -98,9 +91,9 @@ class EpisodePersonController extends BaseController
return redirect()->back(); return redirect()->back();
} }
public function remove($episodePersonId) public function remove(int $episodePersonId): RedirectResponse
{ {
(new EpisodePersonModel())->removeEpisodePersons( (new PersonModel())->removeEpisodePersons(
$this->podcast->id, $this->podcast->id,
$this->episode->id, $this->episode->id,
$episodePersonId, $episodePersonId,

View File

@ -10,12 +10,12 @@ namespace App\Controllers\Admin;
class FediverseController extends BaseController class FediverseController extends BaseController
{ {
public function dashboard() public function dashboard(): string
{ {
return view('admin/fediverse/dashboard'); return view('admin/fediverse/dashboard');
} }
public function blockedActors() public function blockedActors(): string
{ {
helper(['form']); helper(['form']);
@ -26,7 +26,7 @@ class FediverseController extends BaseController
]); ]);
} }
public function blockedDomains() public function blockedDomains(): string
{ {
helper(['form']); helper(['form']);

View File

@ -8,9 +8,10 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
class HomeController extends BaseController class HomeController extends BaseController
{ {
public function index() public function index(): RedirectResponse
{ {
session()->keepFlashdata('message'); session()->keepFlashdata('message');
return redirect()->route('podcast-list'); return redirect()->route('podcast-list');

View File

@ -8,24 +8,25 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services; use Config\Services;
use App\Models\UserModel; use App\Models\UserModel;
class MyAccountController extends BaseController class MyAccountController extends BaseController
{ {
public function index() public function index(): string
{ {
return view('admin/my_account/view'); return view('admin/my_account/view');
} }
public function changePassword() public function changePassword(): string
{ {
helper('form'); helper('form');
return view('admin/my_account/change_password'); return view('admin/my_account/change_password');
} }
public function attemptChange() public function attemptChange(): RedirectResponse
{ {
$auth = Services::authentication(); $auth = Services::authentication();
$userModel = new UserModel(); $userModel = new UserModel();

View File

@ -8,6 +8,7 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Page; use App\Entities\Page;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PageModel; use App\Models\PageModel;
@ -19,7 +20,7 @@ class PageController extends BaseController
*/ */
protected $page; protected $page;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
@ -32,7 +33,7 @@ class PageController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
function list() function list(): string
{ {
$data = [ $data = [
'pages' => (new PageModel())->findAll(), 'pages' => (new PageModel())->findAll(),
@ -41,19 +42,19 @@ class PageController extends BaseController
return view('admin/page/list', $data); return view('admin/page/list', $data);
} }
function view() function view(): string
{ {
return view('admin/page/view', ['page' => $this->page]); return view('admin/page/view', ['page' => $this->page]);
} }
function create() function create(): string
{ {
helper('form'); helper('form');
return view('admin/page/create'); return view('admin/page/create');
} }
function attemptCreate() function attemptCreate(): RedirectResponse
{ {
$page = new Page([ $page = new Page([
'title' => $this->request->getPost('title'), 'title' => $this->request->getPost('title'),
@ -80,7 +81,7 @@ class PageController extends BaseController
); );
} }
function edit() function edit(): string
{ {
helper('form'); helper('form');
@ -88,7 +89,7 @@ class PageController extends BaseController
return view('admin/page/edit', ['page' => $this->page]); return view('admin/page/edit', ['page' => $this->page]);
} }
function attemptEdit() function attemptEdit(): RedirectResponse
{ {
$this->page->title = $this->request->getPost('title'); $this->page->title = $this->request->getPost('title');
$this->page->slug = $this->request->getPost('slug'); $this->page->slug = $this->request->getPost('slug');
@ -106,7 +107,7 @@ class PageController extends BaseController
return redirect()->route('page-list'); return redirect()->route('page-list');
} }
public function delete() public function delete(): RedirectResponse
{ {
(new PageModel())->delete($this->page->id); (new PageModel())->delete($this->page->id);

View File

@ -8,6 +8,7 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Person; use App\Entities\Person;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PersonModel; use App\Models\PersonModel;
@ -19,27 +20,31 @@ class PersonController extends BaseController
*/ */
protected $person; protected $person;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
} }
if ($this->person = (new PersonModel())->getPersonById($params[0])) { if (
($this->person = (new PersonModel())->getPersonById(
(int) $params[0],
)) !== null
) {
return $this->$method(); return $this->$method();
} }
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
$data = ['persons' => (new PersonModel())->findAll()]; $data = ['persons' => (new PersonModel())->findAll()];
return view('admin/person/list', $data); return view('admin/person/list', $data);
} }
public function view() public function view(): string
{ {
$data = ['person' => $this->person]; $data = ['person' => $this->person];
@ -47,14 +52,14 @@ class PersonController extends BaseController
return view('admin/person/view', $data); return view('admin/person/view', $data);
} }
public function create() public function create(): string
{ {
helper(['form']); helper(['form']);
return view('admin/person/create'); return view('admin/person/create');
} }
public function attemptCreate() public function attemptCreate(): RedirectResponse
{ {
$rules = [ $rules = [
'image' => 'image' =>
@ -89,7 +94,7 @@ class PersonController extends BaseController
return redirect()->route('person-list'); return redirect()->route('person-list');
} }
public function edit() public function edit(): string
{ {
helper('form'); helper('form');
@ -101,7 +106,7 @@ class PersonController extends BaseController
return view('admin/person/edit', $data); return view('admin/person/edit', $data);
} }
public function attemptEdit() public function attemptEdit(): RedirectResponse
{ {
$rules = [ $rules = [
'image' => 'image' =>
@ -138,7 +143,7 @@ class PersonController extends BaseController
return redirect()->route('person-view', [$this->person->id]); return redirect()->route('person-view', [$this->person->id]);
} }
public function delete() public function delete(): RedirectResponse
{ {
(new PersonModel())->delete($this->person->id); (new PersonModel())->delete($this->person->id);

View File

@ -26,18 +26,17 @@ class PodcastController extends BaseController
*/ */
protected $podcast; protected $podcast;
/** public function _remap(string $method, string ...$params): mixed
*
* @param array<string> $params
* @return static|string
*/
public function _remap(string $method, ...$params)
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
} }
if ($this->podcast = (new PodcastModel())->getPodcastById($params[0])) { if (
($this->podcast = (new PodcastModel())->getPodcastById(
(int) $params[0],
)) !== null
) {
return $this->$method(); return $this->$method();
} }

View File

@ -8,22 +8,21 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Podcast; use App\Entities\Podcast;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use ErrorException; use ErrorException;
use Config\Database; use Config\Database;
use Podlibre\PodcastNamespace\ReversedTaxonomy; use Podlibre\PodcastNamespace\ReversedTaxonomy;
use App\Entities\PodcastPerson;
use App\Entities\Episode; use App\Entities\Episode;
use App\Entities\Image; use App\Entities\Image;
use App\Entities\Person;
use App\Models\CategoryModel; use App\Models\CategoryModel;
use App\Models\LanguageModel; use App\Models\LanguageModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PlatformModel; use App\Models\PlatformModel;
use App\Models\PersonModel; use App\Models\PersonModel;
use App\Models\PodcastPersonModel;
use App\Models\EpisodePersonModel;
use Config\Services; use Config\Services;
use League\HTMLToMarkdown\HtmlConverter; use League\HTMLToMarkdown\HtmlConverter;
@ -34,20 +33,20 @@ class PodcastImportController extends BaseController
*/ */
protected $podcast; protected $podcast;
public function _remap(string $method, string ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
} }
if ($this->podcast = (new PodcastModel())->getPodcastById($params[0])) { if (($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null) {
return $this->$method(); return $this->$method();
} }
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
helper(['form', 'misc']); helper(['form', 'misc']);
@ -65,7 +64,7 @@ class PodcastImportController extends BaseController
return view('admin/podcast/import', $data); return view('admin/podcast/import', $data);
} }
public function attemptImport() public function attemptImport(): RedirectResponse
{ {
helper(['media', 'misc']); helper(['media', 'misc']);
@ -235,7 +234,7 @@ class PodcastImportController extends BaseController
foreach ($platformType['elements'] as $platform) { foreach ($platformType['elements'] as $platform) {
$platformLabel = $platform->attributes()['platform']; $platformLabel = $platform->attributes()['platform'];
$platformSlug = slugify($platformLabel); $platformSlug = slugify($platformLabel);
if ($platformModel->getPlatform($platformSlug)) { if ($platformModel->getPlatform($platformSlug) !== null) {
$podcastsPlatformsData[] = [ $podcastsPlatformsData[] = [
'platform_slug' => $platformSlug, 'platform_slug' => $platformSlug,
'podcast_id' => $newPodcastId, 'podcast_id' => $newPodcastId,
@ -255,45 +254,41 @@ class PodcastImportController extends BaseController
} }
foreach ($nsPodcast->person as $podcastPerson) { foreach ($nsPodcast->person as $podcastPerson) {
$fullName = (string) $podcastPerson;
$personModel = new PersonModel(); $personModel = new PersonModel();
$newPersonId = null; $newPersonId = null;
if ($newPerson = $personModel->getPerson($podcastPerson)) { if (($newPerson = $personModel->getPerson($fullName)) !== null) {
$newPersonId = $newPerson->id; $newPersonId = $newPerson->id;
} elseif ( } else {
!($newPersonId = $personModel->createPerson( $newPodcastPerson = new Person([
$podcastPerson, 'full_name' => $fullName,
$podcastPerson->attributes()['href'], 'unique_name' => slugify($fullName),
$podcastPerson->attributes()['img'], 'information_url' => $podcastPerson->attributes()['href'],
)) 'image' => new Image(download_file($podcastPerson->attributes()['img'])),
) { 'created_by' => user_id(),
'updated_by' => user_id(),
]);
if (!$newPersonId = $personModel->insert($newPodcastPerson)) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $personModel->errors()); ->with('errors', $personModel->errors());
} }
}
$personGroup = $personGroup =
$podcastPerson->attributes()['group'] === null isset($podcastPerson->attributes()['group'])
? ['slug' => ''] ? ['slug' => '']
: ReversedTaxonomy::$taxonomy[ : ReversedTaxonomy::$taxonomy[(string) $podcastPerson->attributes()['group']];
(string) $podcastPerson->attributes()['group']
];
$personRole = $personRole =
$podcastPerson->attributes()['role'] === null || isset($podcastPerson->attributes()['role']) ||
$personGroup === null $personGroup === null
? ['slug' => ''] ? ['slug' => '']
: $personGroup['roles'][ : $personGroup['roles'][strval($podcastPerson->attributes()['role'])];
strval($podcastPerson->attributes()['role'])
];
$newPodcastPerson = new PodcastPerson([
'podcast_id' => $newPodcastId,
'person_id' => $newPersonId,
'person_group' => $personGroup['slug'],
'person_role' => $personRole['slug'],
]);
$podcastPersonModel = new PodcastPersonModel();
if (!$podcastPersonModel->insert($newPodcastPerson)) { $podcastPersonModel = new PersonModel();
if (!$podcastPersonModel->addPodcastPerson($newPodcastId, $newPersonId, $personGroup['slug'], $personRole['slug'])) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
@ -338,22 +333,12 @@ class PodcastImportController extends BaseController
$slug = $slug . '-' . $slugNumber; $slug = $slug . '-' . $slugNumber;
} }
$slugs[] = $slug; $slugs[] = $slug;
$itemDescriptionHtml = match ($this->request->getPost('description_field')) {
$itemDescriptionHtml = null; 'content' => $nsContent->encoded,
switch ($this->request->getPost('description_field')) { 'summary' => $nsItunes->summary,
case 'content': 'subtitle_summary' => $nsItunes->subtitle . '<br/>' . $nsItunes->summary,
$itemDescriptionHtml = $nsContent->encoded; default => $item->description,
break; };
case 'summary':
$itemDescriptionHtml = $nsItunes->summary;
break;
case 'subtitle_summary':
$itemDescriptionHtml =
$nsItunes->subtitle . '<br/>' . $nsItunes->summary;
break;
default:
$itemDescriptionHtml = $item->description;
}
if ( if (
$nsItunes->image !== null && $nsItunes->image !== null &&
@ -434,46 +419,40 @@ class PodcastImportController extends BaseController
} }
foreach ($nsPodcast->person as $episodePerson) { foreach ($nsPodcast->person as $episodePerson) {
$fullName = (string) $episodePerson;
$personModel = new PersonModel(); $personModel = new PersonModel();
$newPersonId = null; $newPersonId = null;
if ($newPerson = $personModel->getPerson($episodePerson)) { if (($newPerson = $personModel->getPerson($fullName)) !== null) {
$newPersonId = $newPerson->id; $newPersonId = $newPerson->id;
} elseif ( } else {
!($newPersonId = $personModel->createPerson( $newEpisodePerson = new Person([
$episodePerson, 'full_name' => $fullName,
$episodePerson->attributes()['href'], 'slug' => slugify($fullName),
$episodePerson->attributes()['img'], 'information_url' => $episodePerson->attributes()['href'],
)) 'image' => new Image(download_file($episodePerson->attributes()['img']))
) { ]);
if (!($newPersonId = $personModel->insert($newEpisodePerson))) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $personModel->errors()); ->with('errors', $personModel->errors());
} }
}
$personGroup = $personGroup =
$episodePerson->attributes()['group'] === null $episodePerson->attributes()['group'] === null
? ['slug' => ''] ? ['slug' => '']
: ReversedTaxonomy::$taxonomy[ : ReversedTaxonomy::$taxonomy[strval($episodePerson->attributes()['group'])];
strval($episodePerson->attributes()['group'])
];
$personRole = $personRole =
$episodePerson->attributes()['role'] === null || $episodePerson->attributes()['role'] === null ||
$personGroup === null $personGroup === null
? ['slug' => ''] ? ['slug' => '']
: $personGroup['roles'][ : $personGroup['roles'][strval($episodePerson->attributes()['role'])];
strval($episodePerson->attributes()['role'])
];
$newEpisodePerson = new PodcastPerson([
'podcast_id' => $newPodcastId,
'episode_id' => $newEpisodeId,
'person_id' => $newPersonId,
'person_group' => $personGroup['slug'],
'person_role' => $personRole['slug'],
]);
$episodePersonModel = new EpisodePersonModel();
if (!$episodePersonModel->insert($newEpisodePerson)) { $episodePersonModel = new PersonModel();
if (!$episodePersonModel->addEpisodePerson($newPodcastId, $newEpisodeId, $newPersonId, $personGroup['slug'], $personRole['slug'])) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()

View File

@ -8,9 +8,9 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Podcast; use App\Entities\Podcast;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PodcastPersonModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\PersonModel; use App\Models\PersonModel;
@ -21,13 +21,17 @@ class PodcastPersonController extends BaseController
*/ */
protected $podcast; protected $podcast;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ($this->podcast = (new PodcastModel())->getPodcastById($params[0])) { if (
($this->podcast = (new PodcastModel())->getPodcastById(
(int) $params[0],
)) !== null
) {
unset($params[0]); unset($params[0]);
return $this->$method(...$params); return $this->$method(...$params);
} }
@ -35,13 +39,13 @@ class PodcastPersonController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
helper('form'); helper('form');
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'podcastPersons' => (new PodcastPersonModel())->getPodcastPersons( 'podcastPersons' => (new PersonModel())->getPodcastPersons(
$this->podcast->id, $this->podcast->id,
), ),
'personOptions' => (new PersonModel())->getPersonOptions(), 'personOptions' => (new PersonModel())->getPersonOptions(),
@ -53,7 +57,7 @@ class PodcastPersonController extends BaseController
return view('admin/podcast/person', $data); return view('admin/podcast/person', $data);
} }
public function attemptAdd() public function attemptAdd(): RedirectResponse
{ {
$rules = [ $rules = [
'person' => 'required', 'person' => 'required',
@ -66,7 +70,7 @@ class PodcastPersonController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
(new PodcastPersonModel())->addPodcastPersons( (new PersonModel())->addPodcastPersons(
$this->podcast->id, $this->podcast->id,
$this->request->getPost('person'), $this->request->getPost('person'),
$this->request->getPost('person_group_role'), $this->request->getPost('person_group_role'),
@ -75,9 +79,9 @@ class PodcastPersonController extends BaseController
return redirect()->back(); return redirect()->back();
} }
public function remove($podcastPersonId) public function remove(int $podcastPersonId): RedirectResponse
{ {
(new PodcastPersonModel())->removePodcastPersons( (new PersonModel())->removePodcastPersons(
$this->podcast->id, $this->podcast->id,
$podcastPersonId, $podcastPersonId,
); );

View File

@ -8,6 +8,7 @@
namespace App\Controllers\Admin; namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Podcast; use App\Entities\Podcast;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PlatformModel; use App\Models\PlatformModel;
@ -21,13 +22,17 @@ class PodcastPlatformController extends BaseController
*/ */
protected $podcast; protected $podcast;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
} }
if ($this->podcast = (new PodcastModel())->getPodcastById($params[0])) { if (
($this->podcast = (new PodcastModel())->getPodcastById(
(int) $params[0],
)) !== null
) {
unset($params[0]); unset($params[0]);
return $this->$method(...$params); return $this->$method(...$params);
} }
@ -35,12 +40,12 @@ class PodcastPlatformController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
return view('admin/podcast/platforms/dashboard'); return view('admin/podcast/platforms/dashboard');
} }
public function platforms($platformType) public function platforms(string $platformType): string
{ {
helper('form'); helper('form');
@ -57,8 +62,9 @@ class PodcastPlatformController extends BaseController
return view('admin/podcast/platforms', $data); return view('admin/podcast/platforms', $data);
} }
public function attemptPlatformsUpdate($platformType) public function attemptPlatformsUpdate(
{ string $platformType
): RedirectResponse {
$platformModel = new PlatformModel(); $platformModel = new PlatformModel();
$validation = Services::validation(); $validation = Services::validation();
@ -105,8 +111,9 @@ class PodcastPlatformController extends BaseController
->with('message', lang('Platforms.messages.updateSuccess')); ->with('message', lang('Platforms.messages.updateSuccess'));
} }
public function removePodcastPlatform($platformSlug) public function removePodcastPlatform(
{ string $platformSlug
): RedirectResponse {
(new PlatformModel())->removePodcastPlatform( (new PlatformModel())->removePodcastPlatform(
$this->podcast->id, $this->podcast->id,
$platformSlug, $platformSlug,

View File

@ -12,6 +12,7 @@ use CodeIgniter\Exceptions\PageNotFoundException;
use App\Authorization\GroupModel; use App\Authorization\GroupModel;
use App\Entities\User; use App\Entities\User;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services; use Config\Services;
class UserController extends BaseController class UserController extends BaseController
@ -21,7 +22,7 @@ class UserController extends BaseController
*/ */
protected $user; protected $user;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
@ -34,14 +35,14 @@ class UserController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function list() public function list(): string
{ {
$data = ['users' => (new UserModel())->findAll()]; $data = ['users' => (new UserModel())->findAll()];
return view('admin/user/list', $data); return view('admin/user/list', $data);
} }
public function view() public function view(): string
{ {
$data = ['user' => $this->user]; $data = ['user' => $this->user];
@ -49,7 +50,7 @@ class UserController extends BaseController
return view('admin/user/view', $data); return view('admin/user/view', $data);
} }
public function create() public function create(): string
{ {
helper('form'); helper('form');
@ -60,7 +61,7 @@ class UserController extends BaseController
return view('admin/user/create', $data); return view('admin/user/create', $data);
} }
public function attemptCreate() public function attemptCreate(): RedirectResponse
{ {
$userModel = new UserModel(); $userModel = new UserModel();
@ -108,7 +109,7 @@ class UserController extends BaseController
); );
} }
public function edit() public function edit(): string
{ {
helper('form'); helper('form');
@ -131,7 +132,7 @@ class UserController extends BaseController
return view('admin/user/edit', $data); return view('admin/user/edit', $data);
} }
public function attemptEdit() public function attemptEdit(): RedirectResponse
{ {
$authorize = Services::authorization(); $authorize = Services::authorization();
@ -149,7 +150,7 @@ class UserController extends BaseController
); );
} }
public function forcePassReset() public function forcePassReset(): RedirectResponse
{ {
$userModel = new UserModel(); $userModel = new UserModel();
$this->user->forcePasswordReset(); $this->user->forcePasswordReset();
@ -171,7 +172,7 @@ class UserController extends BaseController
); );
} }
public function ban() public function ban(): RedirectResponse
{ {
$authorize = Services::authorization(); $authorize = Services::authorization();
if ($authorize->inGroup('superadmin', $this->user->id)) { if ($authorize->inGroup('superadmin', $this->user->id)) {
@ -204,7 +205,7 @@ class UserController extends BaseController
); );
} }
public function unBan() public function unBan(): RedirectResponse
{ {
$userModel = new UserModel(); $userModel = new UserModel();
$this->user->unBan(); $this->user->unBan();
@ -225,7 +226,7 @@ class UserController extends BaseController
); );
} }
public function delete() public function delete(): RedirectResponse
{ {
$authorize = Services::authorization(); $authorize = Services::authorization();
if ($authorize->inGroup('superadmin', $this->user->id)) { if ($authorize->inGroup('superadmin', $this->user->id)) {

View File

@ -18,14 +18,14 @@ class AuthController extends MythAuthController
* An array of helpers to be automatically loaded * An array of helpers to be automatically loaded
* upon class instantiation. * upon class instantiation.
* *
* @var array * @var string[]
*/ */
protected $helpers = ['components']; protected $helpers = ['components'];
/** /**
* Attempt to register a new user. * Attempt to register a new user.
*/ */
public function attemptRegister() public function attemptRegister(): RedirectResponse
{ {
// Check if registration is allowed // Check if registration is allowed
if (!$this->config->allowRegistration) { if (!$this->config->allowRegistration) {
@ -61,9 +61,9 @@ class AuthController extends MythAuthController
); );
$user = new User($this->request->getPost($allowedPostFields)); $user = new User($this->request->getPost($allowedPostFields));
$this->config->requireActivation !== false $this->config->requireActivation === null
? $user->generateActivateHash() ? $user->activate()
: $user->activate(); : $user->generateActivateHash();
// Ensure default group gets assigned if set // Ensure default group gets assigned if set
if ($this->config->defaultUserGroup !== null) { if ($this->config->defaultUserGroup !== null) {
@ -77,7 +77,7 @@ class AuthController extends MythAuthController
->with('errors', $users->errors()); ->with('errors', $users->errors());
} }
if ($this->config->requireActivation !== false) { if ($this->config->requireActivation !== null) {
$activator = service('activator'); $activator = service('activator');
$sent = $activator->send($user); $sent = $activator->send($user);
@ -109,7 +109,7 @@ class AuthController extends MythAuthController
*/ */
public function attemptReset(): RedirectResponse public function attemptReset(): RedirectResponse
{ {
if ($this->config->activeResetter === false) { if ($this->config->activeResetter === null) {
return redirect() return redirect()
->route('login') ->route('login')
->with('error', lang('Auth.forgotDisabled')); ->with('error', lang('Auth.forgotDisabled'));
@ -173,7 +173,7 @@ class AuthController extends MythAuthController
->with('message', lang('Auth.resetSuccess')); ->with('message', lang('Auth.resetSuccess'));
} }
public function attemptInteractAsActor() public function attemptInteractAsActor(): RedirectResponse
{ {
$rules = [ $rules = [
'actor_id' => 'required|numeric', 'actor_id' => 'required|numeric',

View File

@ -24,7 +24,7 @@ class BaseController extends Controller
* class instantiation. These helpers will be available * class instantiation. These helpers will be available
* to all other controllers that extend BaseController. * to all other controllers that extend BaseController.
* *
* @var array * @var string[]
*/ */
protected $helpers = ['auth', 'svg', 'components', 'misc']; protected $helpers = ['auth', 'svg', 'components', 'misc'];

View File

@ -8,6 +8,8 @@
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Analytics\AnalyticsTrait; use Analytics\AnalyticsTrait;
use App\Entities\Episode; use App\Entities\Episode;
use App\Entities\Podcast; use App\Entities\Podcast;
@ -30,25 +32,25 @@ class EpisodeController extends BaseController
*/ */
protected $episode; protected $episode;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) < 2) { if (count($params) < 2) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ( if (
!($this->podcast = (new PodcastModel())->getPodcastByName( ($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0], $params[0],
)) )) === null
) { ) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ( if (
$this->episode = (new EpisodeModel())->getEpisodeBySlug( ($this->episode = (new EpisodeModel())->getEpisodeBySlug(
$this->podcast->id, $this->podcast->id,
$params[1], $params[1],
) )) !== null
) { ) {
unset($params[1]); unset($params[1]);
unset($params[0]); unset($params[0]);
@ -58,7 +60,7 @@ class EpisodeController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
if (!can_user_interact()) { if (!can_user_interact()) {
@ -71,17 +73,9 @@ class EpisodeController extends BaseController
(can_user_interact() ? '_authenticated' : ''); (can_user_interact() ? '_authenticated' : '');
if (!($cachedView = cache($cacheName))) { if (!($cachedView = cache($cacheName))) {
helper('persons');
$episodePersons = [];
construct_person_array($this->episode->persons, $episodePersons);
$podcastPersons = [];
construct_person_array($this->podcast->persons, $podcastPersons);
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'episode' => $this->episode, 'episode' => $this->episode,
'episodePersons' => $episodePersons,
'persons' => $podcastPersons,
]; ];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@ -91,7 +85,7 @@ class EpisodeController extends BaseController
if (can_user_interact()) { if (can_user_interact()) {
helper('form'); helper('form');
return view('podcast/episode_authenticated', $data); return view('podcast/episode_authenticated', $data);
} else { }
// The page cache is set to a decade so it is deleted manually upon podcast update // The page cache is set to a decade so it is deleted manually upon podcast update
return view('podcast/episode', $data, [ return view('podcast/episode', $data, [
'cache' => $secondsToNextUnpublishedEpisode 'cache' => $secondsToNextUnpublishedEpisode
@ -100,13 +94,13 @@ class EpisodeController extends BaseController
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
}
return $cachedView; return $cachedView;
} }
public function embeddablePlayer($theme = 'light-transparent') public function embeddablePlayer(
{ string $theme = 'light-transparent'
): string {
header('Content-Security-Policy: frame-ancestors https://* http://*'); header('Content-Security-Policy: frame-ancestors https://* http://*');
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
@ -114,7 +108,7 @@ class EpisodeController extends BaseController
$this->registerPodcastWebpageHit($this->episode->podcast_id); $this->registerPodcastWebpageHit($this->episode->podcast_id);
} }
$session = \Config\Services::session(); $session = Services::session();
$session->start(); $session->start();
if (isset($_SERVER['HTTP_REFERER'])) { if (isset($_SERVER['HTTP_REFERER'])) {
$session->set( $session->set(
@ -152,7 +146,7 @@ class EpisodeController extends BaseController
return $cachedView; return $cachedView;
} }
public function oembedJSON() public function oembedJSON(): ResponseInterface
{ {
return $this->response->setJSON([ return $this->response->setJSON([
'type' => 'rich', 'type' => 'rich',
@ -174,7 +168,7 @@ class EpisodeController extends BaseController
]); ]);
} }
public function oembedXML() public function oembedXML(): ResponseInterface
{ {
$oembed = new SimpleXMLElement( $oembed = new SimpleXMLElement(
"<?xml version='1.0' encoding='utf-8' standalone='yes'?><oembed></oembed>", "<?xml version='1.0' encoding='utf-8' standalone='yes'?><oembed></oembed>",

View File

@ -9,20 +9,18 @@
namespace App\Controllers; namespace App\Controllers;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\HTTP\RedirectResponse;
class HomeController extends BaseController class HomeController extends BaseController
{ {
/** public function index(): RedirectResponse|string
* @return RedirectResponse|string
*/
public function index()
{ {
$model = new PodcastModel(); $model = new PodcastModel();
$allPodcasts = $model->findAll(); $allPodcasts = $model->findAll();
// check if there's only one podcast to redirect user to it // check if there's only one podcast to redirect user to it
if (count($allPodcasts) == 1) { if (count($allPodcasts) === 1) {
return redirect()->route('podcast-activity', [ return redirect()->route('podcast-activity', [
$allPodcasts[0]->name, $allPodcasts[0]->name,
]); ]);

View File

@ -19,6 +19,7 @@ use Config\Database;
use App\Entities\User; use App\Entities\User;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\Controller; use CodeIgniter\Controller;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services; use Config\Services;
use Dotenv\Dotenv; use Dotenv\Dotenv;
@ -51,7 +52,14 @@ class InstallController extends Controller
public function index(): string public function index(): string
{ {
if (!file_exists(ROOTPATH . '.env')) { if (!file_exists(ROOTPATH . '.env')) {
$this->createEnv(); // create empty .env file
try {
$envFile = fopen(ROOTPATH . '.env', 'w');
fclose($envFile);
} catch (Throwable) {
// Could not create the .env file, redirect to a view with instructions on how to add it manually
return view('install/manual_config');
}
} }
// Check if .env has all required fields // Check if .env has all required fields
@ -85,7 +93,7 @@ class InstallController extends Controller
try { try {
$dotenv->required('cache.handler'); $dotenv->required('cache.handler');
} catch (ValidationException $validationException) { } catch (ValidationException) {
return $this->cacheConfig(); return $this->cacheConfig();
} }
} else { } else {
@ -101,7 +109,7 @@ class InstallController extends Controller
'database.default.DBPrefix', 'database.default.DBPrefix',
'cache.handler', 'cache.handler',
]); ]);
} catch (ValidationException $e) { } catch (ValidationException) {
return view('install/manual_config'); return view('install/manual_config');
} }
} }
@ -117,7 +125,7 @@ class InstallController extends Controller
// if so, show a 404 page // if so, show a 404 page
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
} catch (DatabaseException $databaseException) { } catch (DatabaseException) {
// Could not connect to the database // Could not connect to the database
// show database config view to fix value // show database config view to fix value
session()->setFlashdata( session()->setFlashdata(
@ -137,28 +145,12 @@ class InstallController extends Controller
return $this->createSuperAdmin(); return $this->createSuperAdmin();
} }
/** public function instanceConfig(): string
* Returns the form to generate the .env config file for the instance.
* @return mixed|void
*/
public function createEnv()
{
// create empty .env file
try {
$envFile = fopen(ROOTPATH . '.env', 'w');
fclose($envFile);
} catch (Throwable $throwable) {
// Could not create the .env file, redirect to a view with manual instructions on how to add it
return view('install/manual_config');
}
}
public function instanceConfig()
{ {
return view('install/instance_config'); return view('install/instance_config');
} }
public function attemptInstanceConfig() public function attemptInstanceConfig(): RedirectResponse
{ {
$rules = [ $rules = [
'hostname' => 'required|validate_url', 'hostname' => 'required|validate_url',
@ -198,12 +190,12 @@ class InstallController extends Controller
); );
} }
public function databaseConfig() public function databaseConfig(): string
{ {
return view('install/database_config'); return view('install/database_config');
} }
public function attemptDatabaseConfig() public function attemptDatabaseConfig(): RedirectResponse
{ {
$rules = [ $rules = [
'db_hostname' => 'required', 'db_hostname' => 'required',
@ -236,12 +228,12 @@ class InstallController extends Controller
return redirect()->back(); return redirect()->back();
} }
public function cacheConfig() public function cacheConfig(): string
{ {
return view('install/cache_config'); return view('install/cache_config');
} }
public function attemptCacheConfig() public function attemptCacheConfig(): RedirectResponse
{ {
$rules = [ $rules = [
'cache_handler' => 'required', 'cache_handler' => 'required',
@ -288,7 +280,7 @@ class InstallController extends Controller
/** /**
* Returns the form to create a the first superadmin user for the instance. * Returns the form to create a the first superadmin user for the instance.
*/ */
public function createSuperAdmin() public function createSuperAdmin(): string
{ {
return view('install/create_superadmin'); return view('install/create_superadmin');
} }
@ -298,7 +290,7 @@ class InstallController extends Controller
* *
* After creation, user is redirected to login page to input its credentials. * After creation, user is redirected to login page to input its credentials.
*/ */
public function attemptCreateSuperAdmin() public function attemptCreateSuperAdmin(): RedirectResponse
{ {
$userModel = new UserModel(); $userModel = new UserModel();
@ -356,7 +348,7 @@ class InstallController extends Controller
* writes config values in .env file * writes config values in .env file
* overwrites any existing key and appends new ones * overwrites any existing key and appends new ones
* *
* @param array $configData key/value config pairs * @param array<string, string> $configData key/value config pairs
*/ */
public static function writeEnv(array $configData): void public static function writeEnv(array $configData): void
{ {
@ -370,7 +362,7 @@ class InstallController extends Controller
$keyVal, $keyVal,
&$replaced &$replaced
) { ) {
if (strpos($line, (string) $key) === 0) { if (str_starts_with($line, (string) $key)) {
$replaced = true; $replaced = true;
return $keyVal; return $keyVal;
} }

View File

@ -8,6 +8,7 @@
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\Exceptions\PageNotFoundException;
use ActivityPub\Controllers\NoteController as ActivityPubNoteController; use ActivityPub\Controllers\NoteController as ActivityPubNoteController;
use ActivityPub\Entities\Note as ActivityPubNote; use ActivityPub\Entities\Note as ActivityPubNote;
use Analytics\AnalyticsTrait; use Analytics\AnalyticsTrait;
@ -34,24 +35,28 @@ class NoteController extends ActivityPubNoteController
*/ */
protected $actor; protected $actor;
/**
* @var string[]
*/
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc']; protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if ( if (
!($this->podcast = (new PodcastModel())->getPodcastByName( ($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0], $params[0],
)) )) === null
) { ) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
$this->actor = $this->podcast->actor; $this->actor = $this->podcast->actor;
if (count($params) > 1) { if (
if (!($this->note = model('NoteModel')->getNoteById($params[1]))) { count($params) > 1 &&
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); !($this->note = model('NoteModel')->getNoteById($params[1]))
} ) {
throw PageNotFoundException::forPageNotFound();
} }
unset($params[0]); unset($params[0]);
unset($params[1]); unset($params[1]);
@ -77,28 +82,22 @@ class NoteController extends ActivityPubNoteController
); );
if (!($cachedView = cache($cacheName))) { if (!($cachedView = cache($cacheName))) {
helper('persons');
$persons = [];
construct_person_array($this->podcast->persons, $persons);
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'actor' => $this->actor, 'actor' => $this->actor,
'note' => $this->note, 'note' => $this->note,
'persons' => $persons,
]; ];
// if user is logged in then send to the authenticated activity view // if user is logged in then send to the authenticated activity view
if (can_user_interact()) { if (can_user_interact()) {
helper('form'); helper('form');
return view('podcast/note_authenticated', $data); return view('podcast/note_authenticated', $data);
} else { }
return view('podcast/note', $data, [ return view('podcast/note', $data, [
'cache' => DECADE, 'cache' => DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
}
return $cachedView; return $cachedView;
} }
@ -129,24 +128,21 @@ class NoteController extends ActivityPubNoteController
$episodeUri = $this->request->getPost('episode_url'); $episodeUri = $this->request->getPost('episode_url');
if ( if (
$episodeUri && $episodeUri &&
($params = extract_params_from_episode_uri(new URI($episodeUri))) ($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
) { ($episode = (new EpisodeModel())->getEpisodeBySlug(
if (
$episode = (new EpisodeModel())->getEpisodeBySlug(
$params['podcastName'], $params['podcastName'],
$params['episodeSlug'], $params['episodeSlug'],
) ))
) { ) {
$newNote->episode_id = $episode->id; $newNote->episode_id = $episode->id;
} }
}
$newNote->message = $message; $newNote->message = $message;
if ( if (
!model('NoteModel')->addNote( !model('NoteModel')->addNote(
$newNote, $newNote,
$newNote->episode_id ? false : true, !(bool) $newNote->episode_id,
true, true,
) )
) { ) {

View File

@ -21,7 +21,7 @@ class PageController extends BaseController
*/ */
protected $page; protected $page;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
return $this->$method(); return $this->$method();
@ -36,7 +36,7 @@ class PageController extends BaseController
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function index() public function index(): string
{ {
$cacheName = "page-{$this->page->slug}"; $cacheName = "page-{$this->page->slug}";
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
@ -53,7 +53,7 @@ class PageController extends BaseController
return $found; return $found;
} }
public function credits() public function credits(): string
{ {
$locale = service('request')->getLocale(); $locale = service('request')->getLocale();
$allPodcasts = (new PodcastModel())->findAll(); $allPodcasts = (new PodcastModel())->findAll();

View File

@ -8,6 +8,7 @@
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\Exceptions\PageNotFoundException;
use Analytics\AnalyticsTrait; use Analytics\AnalyticsTrait;
use App\Entities\Podcast; use App\Entities\Podcast;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
@ -23,23 +24,25 @@ class PodcastController extends BaseController
*/ */
protected $podcast; protected $podcast;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if (count($params) === 0) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ( if (
$this->podcast = (new PodcastModel())->getPodcastByName($params[0]) ($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0],
)) !== null
) { ) {
unset($params[0]); unset($params[0]);
return $this->$method(...$params); return $this->$method(...$params);
} }
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
public function activity() public function activity(): string
{ {
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
if (!can_user_interact()) { if (!can_user_interact()) {
@ -58,34 +61,28 @@ class PodcastController extends BaseController
); );
if (!($cachedView = cache($cacheName))) { if (!($cachedView = cache($cacheName))) {
helper('persons');
$persons = [];
construct_person_array($this->podcast->persons, $persons);
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'notes' => (new NoteModel())->getActorPublishedNotes( 'notes' => (new NoteModel())->getActorPublishedNotes(
$this->podcast->actor_id, $this->podcast->actor_id,
), ),
'persons' => $persons,
]; ];
// if user is logged in then send to the authenticated activity view // if user is logged in then send to the authenticated activity view
if (can_user_interact()) { if (can_user_interact()) {
helper('form'); helper('form');
return view('podcast/activity_authenticated', $data); return view('podcast/activity_authenticated', $data);
} else { }
return view('podcast/activity', $data, [ return view('podcast/activity', $data, [
'cache' => DECADE, 'cache' => DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
}
return $cachedView; return $cachedView;
} }
public function episodes() public function episodes(): string
{ {
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
if (!can_user_interact()) { if (!can_user_interact()) {
@ -95,7 +92,7 @@ class PodcastController extends BaseController
$yearQuery = $this->request->getGet('year'); $yearQuery = $this->request->getGet('year');
$seasonQuery = $this->request->getGet('season'); $seasonQuery = $this->request->getGet('season');
if (!$yearQuery and !$seasonQuery) { if (!$yearQuery && !$seasonQuery) {
$defaultQuery = (new PodcastModel())->getDefaultQuery( $defaultQuery = (new PodcastModel())->getDefaultQuery(
$this->podcast->id, $this->podcast->id,
); );
@ -130,7 +127,7 @@ class PodcastController extends BaseController
$episodesNavigation = []; $episodesNavigation = [];
$activeQuery = null; $activeQuery = null;
foreach ($years as $year) { foreach ($years as $year) {
$isActive = $yearQuery == $year['year']; $isActive = $yearQuery === $year['year'];
if ($isActive) { if ($isActive) {
$activeQuery = [ $activeQuery = [
'type' => 'year', 'type' => 'year',
@ -140,7 +137,7 @@ class PodcastController extends BaseController
]; ];
} }
array_push($episodesNavigation, [ $episodesNavigation[] = [
'label' => $year['year'], 'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'], 'number_of_episodes' => $year['number_of_episodes'],
'route' => 'route' =>
@ -148,11 +145,11 @@ class PodcastController extends BaseController
'?year=' . '?year=' .
$year['year'], $year['year'],
'is_active' => $isActive, 'is_active' => $isActive,
]); ];
} }
foreach ($seasons as $season) { foreach ($seasons as $season) {
$isActive = $seasonQuery == $season['season_number']; $isActive = $seasonQuery === $season['season_number'];
if ($isActive) { if ($isActive) {
$activeQuery = [ $activeQuery = [
'type' => 'season', 'type' => 'season',
@ -164,7 +161,7 @@ class PodcastController extends BaseController
]; ];
} }
array_push($episodesNavigation, [ $episodesNavigation[] = [
'label' => lang('Podcast.season', [ 'label' => lang('Podcast.season', [
'seasonNumber' => $season['season_number'], 'seasonNumber' => $season['season_number'],
]), ]),
@ -174,13 +171,9 @@ class PodcastController extends BaseController
'?season=' . '?season=' .
$season['season_number'], $season['season_number'],
'is_active' => $isActive, 'is_active' => $isActive,
]); ];
} }
helper('persons');
$persons = [];
construct_person_array($this->podcast->persons, $persons);
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'episodesNav' => $episodesNavigation, 'episodesNav' => $episodesNavigation,
@ -191,7 +184,6 @@ class PodcastController extends BaseController
$yearQuery, $yearQuery,
$seasonQuery, $seasonQuery,
), ),
'persons' => $persons,
]; ];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@ -201,7 +193,7 @@ class PodcastController extends BaseController
// if user is logged in then send to the authenticated episodes view // if user is logged in then send to the authenticated episodes view
if (can_user_interact()) { if (can_user_interact()) {
return view('podcast/episodes_authenticated', $data); return view('podcast/episodes_authenticated', $data);
} else { }
return view('podcast/episodes', $data, [ return view('podcast/episodes', $data, [
'cache' => $secondsToNextUnpublishedEpisode 'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode ? $secondsToNextUnpublishedEpisode
@ -209,7 +201,6 @@ class PodcastController extends BaseController
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
}
return $cachedView; return $cachedView;
} }

View File

@ -123,7 +123,7 @@ class FakePodcastsAnalyticsSeeder extends Seeder
: $city->subdivisions[0]->isoCode; : $city->subdivisions[0]->isoCode;
$latitude = round($city->location->latitude, 3); $latitude = round($city->location->latitude, 3);
$longitude = round($city->location->longitude, 3); $longitude = round($city->location->longitude, 3);
} catch (AddressNotFoundException $addressNotFoundException) { } catch (AddressNotFoundException) {
//Bad luck, bad IP, nothing to do. //Bad luck, bad IP, nothing to do.
} }

View File

@ -18,15 +18,8 @@ use RuntimeException;
*/ */
class Actor extends ActivityPubActor class Actor extends ActivityPubActor
{ {
/** protected ?Podcast $podcast;
* @var Podcast|null protected bool $is_podcast;
*/
protected $podcast;
/**
* @var boolean
*/
protected $is_podcast;
public function getIsPodcast(): bool public function getIsPodcast(): bool
{ {

View File

@ -21,10 +21,7 @@ use CodeIgniter\Entity\Entity;
*/ */
class Category extends Entity class Category extends Entity
{ {
/** protected ?Category $parent;
* @var Category|null
*/
protected $parent;
/** /**
* @var array<string, string> * @var array<string, string>

View File

@ -16,7 +16,7 @@ use CodeIgniter\Entity\Entity;
/** /**
* @property int $podcast_id * @property int $podcast_id
* @property Podcast $podcast * @property Podcast|null $podcast
* @property int|null $episode_id * @property int|null $episode_id
* @property Episode|null $episode * @property Episode|null $episode
* @property string $full_name * @property string $full_name
@ -25,34 +25,15 @@ use CodeIgniter\Entity\Entity;
* @property string $person_role * @property string $person_role
* @property string $role_label * @property string $role_label
* @property int $person_id * @property int $person_id
* @property Person $person * @property Person|null $person
*/ */
class Credit extends Entity class Credit extends Entity
{ {
/** protected ?Person $person;
* @var Person protected ?Podcast $podcast;
*/ protected ?Episode $episode;
protected $person; protected string $group_label;
protected string $role_label;
/**
* @var Podcast
*/
protected $podcast;
/**
* @var Episode|null
*/
protected $episode;
/**
* @var string
*/
protected $group_label;
/**
* @var string
*/
protected $role_label;
/** /**
* @var array<string, string> * @var array<string, string>
@ -66,7 +47,7 @@ class Credit extends Entity
'person_role' => 'string', 'person_role' => 'string',
]; ];
public function getPerson(): Person public function getPerson(): ?Person
{ {
if ($this->person_id === null) { if ($this->person_id === null) {
throw new RuntimeException( throw new RuntimeException(
@ -83,7 +64,7 @@ class Credit extends Entity
return $this->person; return $this->person;
} }
public function getPodcast(): Podcast public function getPodcast(): ?Podcast
{ {
if ($this->podcast_id === null) { if ($this->podcast_id === null) {
throw new RuntimeException( throw new RuntimeException(

View File

@ -12,8 +12,8 @@ use App\Entities\Location;
use App\Libraries\SimpleRSSElement; use App\Libraries\SimpleRSSElement;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\SoundbiteModel; use App\Models\SoundbiteModel;
use App\Models\EpisodePersonModel;
use App\Models\NoteModel; use App\Models\NoteModel;
use App\Models\PersonModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File; use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
@ -39,7 +39,7 @@ use RuntimeException;
* @property string $audio_file_mimetype * @property string $audio_file_mimetype
* @property int $audio_file_size * @property int $audio_file_size
* @property int $audio_file_header_size * @property int $audio_file_header_size
* @property string $description Holds text only description, striped of any markdown or html special characters * @property string|null $description Holds text only description, striped of any markdown or html special characters
* @property string $description_markdown * @property string $description_markdown
* @property string $description_html * @property string $description_html
* @property Image $image * @property Image $image
@ -75,101 +75,43 @@ use RuntimeException;
* @property Time $updated_at; * @property Time $updated_at;
* @property Time|null $deleted_at; * @property Time|null $deleted_at;
* *
* @property EpisodePerson[] $persons; * @property Person[] $persons;
* @property Soundbite[] $soundbites; * @property Soundbite[] $soundbites;
* @property string $embeddable_player_url; * @property string $embeddable_player_url;
*/ */
class Episode extends Entity class Episode extends Entity
{ {
/** protected Podcast $podcast;
* @var Podcast protected string $link;
*/ protected File $audio_file;
protected $podcast; protected string $audio_file_url;
protected string $audio_file_analytics_url;
protected string $audio_file_web_url;
protected string $audio_file_opengraph_url;
protected string $embeddable_player_url;
protected Image $image;
protected ?string $description;
protected File $transcript_file;
protected File $chapters_file;
/** /**
* @var string * @var Person[]
*/ */
protected $link; protected $persons = [];
/**
* @var File
*/
protected $audio_file;
/**
* @var string
*/
protected $audio_file_url;
/**
* @var string
*/
protected $audio_file_analytics_url;
/**
* @var string
*/
protected $audio_file_web_url;
/**
* @var string
*/
protected $audio_file_opengraph_url;
/**
* @var string
*/
protected $embeddable_player_url;
/**
* @var Image
*/
protected $image;
/**
* @var string
*/
protected $description;
/**
* @var File
*/
protected $transcript_file;
/**
* @var File
*/
protected $chapters_file;
/**
* @var EpisodePerson[]
*/
protected $persons;
/** /**
* @var Soundbite[] * @var Soundbite[]
*/ */
protected $soundbites; protected $soundbites = [];
/** /**
* @var Note[] * @var Note[]
*/ */
protected $notes; protected $notes = [];
/** protected ?Location $location;
* @var Location|null protected string $custom_rss_string;
*/ protected string $publication_status;
protected $location;
/**
* @var string
*/
protected $custom_rss_string;
/**
* @var string
*/
protected $publication_status;
/** /**
* @var string[] * @var string[]
@ -221,10 +163,8 @@ class Episode extends Entity
/** /**
* Saves an episode image * Saves an episode image
*
* @param Image|null $image
*/ */
public function setImage($image = null): self public function setImage(?Image $image = null): static
{ {
if ($image === null) { if ($image === null) {
return $this; return $this;
@ -257,10 +197,8 @@ class Episode extends Entity
/** /**
* Saves an audio file * Saves an audio file
*
* @param UploadedFile|File $audioFile
*/ */
public function setAudioFile($audioFile) public function setAudioFile(UploadedFile|File $audioFile): static
{ {
helper(['media', 'id3']); helper(['media', 'id3']);
@ -283,10 +221,8 @@ class Episode extends Entity
/** /**
* Saves an episode transcript file * Saves an episode transcript file
*
* @param UploadedFile|File $transcriptFile
*/ */
public function setTranscriptFile($transcriptFile) public function setTranscriptFile(UploadedFile|File $transcriptFile): static
{ {
helper('media'); helper('media');
@ -301,10 +237,8 @@ class Episode extends Entity
/** /**
* Saves an episode chapters file * Saves an episode chapters file
*
* @param UploadedFile|File $chaptersFile
*/ */
public function setChaptersFile($chaptersFile) public function setChaptersFile(UploadedFile|File $chaptersFile): static
{ {
helper('media'); helper('media');
@ -390,9 +324,8 @@ class Episode extends Entity
{ {
if ($this->attributes['transcript_file_path']) { if ($this->attributes['transcript_file_path']) {
return media_base_url($this->attributes['transcript_file_path']); return media_base_url($this->attributes['transcript_file_path']);
} else {
return $this->attributes['transcript_file_remote_url'];
} }
return $this->attributes['transcript_file_remote_url'];
} }
/** /**
@ -411,7 +344,7 @@ class Episode extends Entity
/** /**
* Returns the episode's persons * Returns the episode's persons
* *
* @return EpisodePerson[] * @return Person[]
*/ */
public function getPersons(): array public function getPersons(): array
{ {
@ -422,7 +355,7 @@ class Episode extends Entity
} }
if (empty($this->persons)) { if (empty($this->persons)) {
$this->persons = (new EpisodePersonModel())->getEpisodePersons( $this->persons = (new PersonModel())->getEpisodePersons(
$this->podcast_id, $this->podcast_id,
$this->id, $this->id,
); );
@ -483,7 +416,7 @@ class Episode extends Entity
); );
} }
public function getEmbeddablePlayerUrl($theme = null): string public function getEmbeddablePlayerUrl(string $theme = null): string
{ {
return base_url( return base_url(
$theme $theme
@ -501,25 +434,21 @@ class Episode extends Entity
); );
} }
public function setGuid(?string $guid = null) public function setGuid(?string $guid = null): static
{ {
if ($guid === null) { $this->attributes['guid'] = $guid === null ? $this->getLink() : $guid;
$this->attributes['guid'] = $this->getLink();
} else {
$this->attributes['guid'] = $guid;
}
return $this; return $this;
} }
public function getPodcast(): Podcast public function getPodcast(): ?Podcast
{ {
return (new PodcastModel())->getPodcastById( return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id'], $this->attributes['podcast_id'],
); );
} }
public function setDescriptionMarkdown(string $descriptionMarkdown) public function setDescriptionMarkdown(string $descriptionMarkdown): static
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',
@ -563,7 +492,7 @@ class Episode extends Entity
if ($this->description === null) { if ($this->description === null) {
$this->description = trim( $this->description = trim(
preg_replace( preg_replace(
'/\s+/', '~\s+~',
' ', ' ',
strip_tags($this->attributes['description_html']), strip_tags($this->attributes['description_html']),
), ),
@ -575,11 +504,11 @@ class Episode extends Entity
public function getPublicationStatus(): string public function getPublicationStatus(): string
{ {
if ($this->publication_status) { if ($this->publication_status !== '') {
return $this->publication_status; return $this->publication_status;
} }
if (!$this->published_at) { if ($this->published_at === null) {
return 'not_published'; return 'not_published';
} }
@ -594,7 +523,7 @@ class Episode extends Entity
/** /**
* Saves the location name and fetches OpenStreetMap info * Saves the location name and fetches OpenStreetMap info
*/ */
public function setLocation(?string $newLocationName = null) public function setLocation(?string $newLocationName = null): static
{ {
if ($newLocationName === null) { if ($newLocationName === null) {
$this->attributes['location_name'] = null; $this->attributes['location_name'] = null;
@ -667,7 +596,7 @@ class Episode extends Entity
/** /**
* Saves custom rss tag into json * Saves custom rss tag into json
*/ */
function setCustomRssString(?string $customRssString = null) function setCustomRssString(?string $customRssString = null): static
{ {
if ($customRssString === null) { if ($customRssString === null) {
return $this; return $this;
@ -709,19 +638,16 @@ class Episode extends Entity
return $partnerLink; return $partnerLink;
} }
function getPartnerImageUrl($serviceSlug = null): string function getPartnerImageUrl(string $serviceSlug = null): string
{ {
$partnerImageUrl = if ($serviceSlug !== null) {
rtrim($this->getPodcast()->partner_image_url, '/') . return '&_from=' . $serviceSlug;
}
return rtrim($this->getPodcast()->partner_image_url, '/') .
'?pid=' . '?pid=' .
$this->getPodcast()->partner_id . $this->getPodcast()->partner_id .
'&guid=' . '&guid=' .
urlencode($this->attributes['guid']); urlencode($this->attributes['guid']);
if ($serviceSlug !== null) {
$partnerImageUrl = '&_from=' . $serviceSlug;
}
return $partnerImageUrl;
} }
} }

View File

@ -1,48 +0,0 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity\Entity;
use App\Models\PersonModel;
/**
* @property int $id
* @property int $podcast_id
* @property int $episode_id
* @property int $person_id
* @property Person $person
* @property string|null $person_group
* @property string|null $person_role
*/
class EpisodePerson extends Entity
{
/**
* @var Person
*/
protected $person;
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
'episode_id' => 'integer',
'person_id' => 'integer',
'person_group' => '?string',
'person_role' => '?string',
];
public function getPerson(): Person
{
return (new PersonModel())->getPersonById(
$this->attributes['person_id'],
);
}
}

View File

@ -10,7 +10,7 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File; use CodeIgniter\Files\File;
use Config\Images as ImagesConfig; use Config\Images;
use Config\Services; use Config\Services;
use RuntimeException; use RuntimeException;
@ -35,30 +35,11 @@ use RuntimeException;
*/ */
class Image extends Entity class Image extends Entity
{ {
/** protected Images $config;
* @var ImagesConfig protected ?File $file;
*/ protected string $dirname;
protected $config; protected string $filename;
protected string $extension;
/**
* @var null|File
*/
protected $file;
/**
* @var string
*/
protected $dirname;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $extension;
public function __construct( public function __construct(
?File $file, ?File $file,

View File

@ -9,21 +9,23 @@
namespace App\Entities; namespace App\Entities;
use ActivityPub\Entities\Note as ActivityPubNote; use ActivityPub\Entities\Note as ActivityPubNote;
use App\Models\ActorModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use RuntimeException; use RuntimeException;
/** /**
* @property int|null $episode_id * @property int|null $episode_id
* @property Episode|null $episode * @property Episode|null $episode
* @property Actor $actor
* @property Note $reblog_of_note
* @property Note $reply_to_note
*/ */
class Note extends ActivityPubNote class Note extends ActivityPubNote
{ {
/** protected ?Episode $episode;
* @var Episode|null
*/
protected $episode;
/**
* @var array<string, string>
*/
protected $casts = [ protected $casts = [
'id' => 'string', 'id' => 'string',
'uri' => 'string', 'uri' => 'string',
@ -41,10 +43,8 @@ class Note extends ActivityPubNote
/** /**
* Returns the note's attached episode * Returns the note's attached episode
*
* @return \App\Entities\Episode
*/ */
public function getEpisode() public function getEpisode(): ?Episode
{ {
if ($this->episode_id === null) { if ($this->episode_id === null) {
throw new RuntimeException( throw new RuntimeException(

View File

@ -25,15 +25,8 @@ use League\CommonMark\CommonMarkConverter;
*/ */
class Page extends Entity class Page extends Entity
{ {
/** protected string $link;
* @var string protected string $content_html;
*/
protected $link;
/**
* @var string
*/
protected $content_html;
/** /**
* @var array<string, string> * @var array<string, string>
@ -51,7 +44,7 @@ class Page extends Entity
return url_to('page', $this->attributes['slug']); return url_to('page', $this->attributes['slug']);
} }
public function setContentMarkdown(string $contentMarkdown): self public function setContentMarkdown(string $contentMarkdown): static
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',

View File

@ -20,13 +20,16 @@ use CodeIgniter\Entity\Entity;
* @property string $image_mimetype * @property string $image_mimetype
* @property int $created_by * @property int $created_by
* @property int $updated_by * @property int $updated_by
* @property string|null $group
* @property string|null $role
* @property Podcast|null $podcast
* @property Episode|null $episode
*/ */
class Person extends Entity class Person extends Entity
{ {
/** protected Image $image;
* @var Image protected ?Podcast $podcast;
*/ protected ?Episode $episode;
protected $image;
/** /**
* @var array<string, string> * @var array<string, string>
@ -38,6 +41,10 @@ class Person extends Entity
'information_url' => '?string', 'information_url' => '?string',
'image_path' => 'string', 'image_path' => 'string',
'image_mimetype' => 'string', 'image_mimetype' => 'string',
'podcast_id' => '?integer',
'episode_id' => '?integer',
'group' => '?string',
'role' => '?string',
'created_by' => 'integer', 'created_by' => 'integer',
'updated_by' => 'integer', 'updated_by' => 'integer',
]; ];
@ -45,7 +52,7 @@ class Person extends Entity
/** /**
* Saves a picture in `public/media/persons/` * Saves a picture in `public/media/persons/`
*/ */
public function setImage(Image $image): self public function setImage(Image $image): static
{ {
helper('media'); helper('media');

View File

@ -11,8 +11,8 @@ namespace App\Entities;
use App\Libraries\SimpleRSSElement; use App\Libraries\SimpleRSSElement;
use App\Models\CategoryModel; use App\Models\CategoryModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PlatformModel; use App\Models\PlatformModel;
use App\Models\PodcastPersonModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
@ -22,7 +22,7 @@ use RuntimeException;
/** /**
* @property int $id * @property int $id
* @property int $actor_id * @property int $actor_id
* @property Actor $actor * @property Actor|null $actor
* @property string $name * @property string $name
* @property string $link * @property string $link
* @property string $feed_url * @property string $feed_url
@ -35,7 +35,7 @@ use RuntimeException;
* @property string $image_mimetype * @property string $image_mimetype
* @property string $language_code * @property string $language_code
* @property int $category_id * @property int $category_id
* @property Category $category * @property Category|null $category
* @property int[] $other_categories_ids * @property int[] $other_categories_ids
* @property Category[] $other_categories * @property Category[] $other_categories
* @property string|null $parental_advisory * @property string|null $parental_advisory
@ -68,7 +68,7 @@ use RuntimeException;
* @property Time|null $deleted_at; * @property Time|null $deleted_at;
* *
* @property Episode[] $episodes * @property Episode[] $episodes
* @property PodcastPerson[] $persons * @property Person[] $persons
* @property User[] $contributors * @property User[] $contributors
* @property Platform[] $podcasting_platforms * @property Platform[] $podcasting_platforms
* @property Platform[] $social_platforms * @property Platform[] $social_platforms
@ -77,80 +77,54 @@ use RuntimeException;
*/ */
class Podcast extends Entity class Podcast extends Entity
{ {
/** protected string $link;
* @var string protected ?Actor $actor;
*/ protected Image $image;
protected $link; protected string $description;
protected ?Category $category;
/**
* @var Actor
*/
protected $actor;
/**
* @var Image
*/
protected $image;
/**
* @var string
*/
protected $description;
/**
* @var Category
*/
protected $category;
/** /**
* @var Category[] * @var Category[]
*/ */
protected $other_categories; protected $other_categories = [];
/** /**
* @var string[] * @var string[]
*/ */
protected $other_categories_ids; protected $other_categories_ids = [];
/** /**
* @var Episode[] * @var Episode[]
*/ */
protected $episodes; protected $episodes = [];
/** /**
* @var PodcastPerson[] * @var Person[]
*/ */
protected $persons; protected $persons = [];
/** /**
* @var User[] * @var User[]
*/ */
protected $contributors; protected $contributors = [];
/** /**
* @var Platform[] * @var Platform[]
*/ */
protected $podcasting_platforms; protected $podcasting_platforms = [];
/** /**
* @var Platform[] * @var Platform[]
*/ */
protected $social_platforms; protected $social_platforms = [];
/** /**
* @var Platform[] * @var Platform[]
*/ */
protected $funding_platforms; protected $funding_platforms = [];
/** protected ?Location $location;
* @var Location|null protected string $custom_rss_string;
*/
protected $location;
/**
* @var string
*/
protected $custom_rss_string;
/** /**
* @var array<string, string> * @var array<string, string>
@ -193,7 +167,7 @@ class Podcast extends Entity
public function getActor(): Actor public function getActor(): Actor
{ {
if (!$this->actor_id) { if ($this->actor_id === 0) {
throw new RuntimeException( throw new RuntimeException(
'Podcast must have an actor_id before getting actor.', 'Podcast must have an actor_id before getting actor.',
); );
@ -208,10 +182,8 @@ class Podcast extends Entity
/** /**
* Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/` * Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/`
*
* @param Image $image
*/ */
public function setImage($image): self public function setImage(Image $image): static
{ {
// Save image // Save image
$image->saveImage('podcasts/' . $this->attributes['name'], 'cover'); $image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
@ -263,7 +235,7 @@ class Podcast extends Entity
/** /**
* Returns the podcast's persons * Returns the podcast's persons
* *
* @return PodcastPerson[] * @return Person[]
*/ */
public function getPersons(): array public function getPersons(): array
{ {
@ -274,9 +246,7 @@ class Podcast extends Entity
} }
if (empty($this->persons)) { if (empty($this->persons)) {
$this->persons = (new PodcastPersonModel())->getPodcastPersons( $this->persons = (new PersonModel())->getPodcastPersons($this->id);
$this->id,
);
} }
return $this->persons; return $this->persons;
@ -284,18 +254,16 @@ class Podcast extends Entity
/** /**
* Returns the podcast category entity * Returns the podcast category entity
*
* @return Category
*/ */
public function getCategory(): Category public function getCategory(): ?Category
{ {
if (empty($this->id)) { if ($this->id === null) {
throw new RuntimeException( throw new RuntimeException(
'Podcast must be created before getting category.', 'Podcast must be created before getting category.',
); );
} }
if (empty($this->category)) { if ($this->category === null) {
$this->category = (new CategoryModel())->getCategoryById( $this->category = (new CategoryModel())->getCategoryById(
$this->category_id, $this->category_id,
); );
@ -326,7 +294,7 @@ class Podcast extends Entity
return $this->contributors; return $this->contributors;
} }
public function setDescriptionMarkdown(string $descriptionMarkdown): self public function setDescriptionMarkdown(string $descriptionMarkdown): static
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',
@ -343,7 +311,7 @@ class Podcast extends Entity
public function setEpisodeDescriptionFooterMarkdown( public function setEpisodeDescriptionFooterMarkdown(
?string $episodeDescriptionFooterMarkdown = null ?string $episodeDescriptionFooterMarkdown = null
): self { ): static {
if ($episodeDescriptionFooterMarkdown) { if ($episodeDescriptionFooterMarkdown) {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',
@ -363,13 +331,13 @@ class Podcast extends Entity
public function getDescription(): string public function getDescription(): string
{ {
if ($this->description) { if ($this->description !== '') {
return $this->description; return $this->description;
} }
return trim( return trim(
preg_replace( preg_replace(
'/\s+/', '~\s+~',
' ', ' ',
strip_tags($this->attributes['description_html']), strip_tags($this->attributes['description_html']),
), ),
@ -483,7 +451,7 @@ class Podcast extends Entity
/** /**
* Saves the location name and fetches OpenStreetMap info * Saves the location name and fetches OpenStreetMap info
*/ */
public function setLocation(?string $newLocationName = null) public function setLocation(?string $newLocationName = null): static
{ {
if ($newLocationName === null) { if ($newLocationName === null) {
$this->attributes['location_name'] = null; $this->attributes['location_name'] = null;
@ -529,8 +497,6 @@ class Podcast extends Entity
/** /**
* Get custom rss tag as XML String * Get custom rss tag as XML String
*
* @return string
*/ */
function getCustomRssString(): string function getCustomRssString(): string
{ {
@ -555,10 +521,8 @@ class Podcast extends Entity
/** /**
* Saves custom rss tag into json * Saves custom rss tag into json
*
* @param string $customRssString
*/ */
function setCustomRssString($customRssString): self function setCustomRssString(string $customRssString): static
{ {
if (empty($customRssString)) { if (empty($customRssString)) {
return $this; return $this;

View File

@ -1,46 +0,0 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity\Entity;
use App\Models\PersonModel;
/**
* @property int $id
* @property int $podcast_id
* @property int $person_id
* @property Person $person
* @property string|null $person_group
* @property string|null $person_role
*/
class PodcastPerson extends Entity
{
/**
* @var Person
*/
protected $person;
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
'person_id' => 'integer',
'person_group' => '?string',
'person_role' => '?string',
];
public function getPerson(): ?Person
{
return (new PersonModel())->getPersonById(
$this->attributes['person_id'],
);
}
}

View File

@ -21,7 +21,7 @@ class PermissionFilter implements FilterInterface
* sent back to the client, allowing for error pages, * sent back to the client, allowing for error pages,
* redirects, etc. * redirects, etc.
* *
* @param array|null $params * @param string[]|null $params
* @return void|mixed * @return void|mixed
*/ */
public function before(RequestInterface $request, $params = null) public function before(RequestInterface $request, $params = null)
@ -50,8 +50,8 @@ class PermissionFilter implements FilterInterface
foreach ($params as $permission) { foreach ($params as $permission) {
// check if permission is for a specific podcast // check if permission is for a specific podcast
if ( if (
(startsWith($permission, 'podcast-') || (str_starts_with($permission, 'podcast-') ||
startsWith($permission, 'podcast_episodes-')) && str_starts_with($permission, 'podcast_episodes-')) &&
count($routerParams) > 0 count($routerParams) > 0
) { ) {
if ( if (
@ -91,7 +91,7 @@ class PermissionFilter implements FilterInterface
* to stop execution of other after filters, short of * to stop execution of other after filters, short of
* throwing an Exception or Error. * throwing an Exception or Error.
* *
* @param array|null $arguments * @param string[]|null $arguments
*/ */
public function after( public function after(
RequestInterface $request, RequestInterface $request,

View File

@ -27,7 +27,7 @@ if (!function_exists('set_interact_as_actor')) {
/** /**
* Sets the actor id of which the user is acting as * Sets the actor id of which the user is acting as
*/ */
function set_interact_as_actor($actorId): void function set_interact_as_actor(int $actorId): void
{ {
$authenticate = Services::authentication(); $authenticate = Services::authentication();
$authenticate->check(); $authenticate->check();
@ -65,10 +65,8 @@ if (!function_exists('interact_as_actor_id')) {
if (!function_exists('interact_as_actor')) { if (!function_exists('interact_as_actor')) {
/** /**
* Get the actor the user is currently interacting as * Get the actor the user is currently interacting as
*
* @return Actor|false
*/ */
function interact_as_actor() function interact_as_actor(): Actor|false
{ {
$authenticate = Services::authentication(); $authenticate = Services::authentication();
$authenticate->check(); $authenticate->check();

View File

@ -23,7 +23,10 @@ if (!function_exists('render_breadcrumb')) {
} }
if (!function_exists('replace_breadcrumb_params')) { if (!function_exists('replace_breadcrumb_params')) {
function replace_breadcrumb_params($newParams): void /**
* @param string[] $newParams
*/
function replace_breadcrumb_params(array $newParams): void
{ {
$breadcrumb = Services::breadcrumb(); $breadcrumb = Services::breadcrumb();
$breadcrumb->replaceParams($newParams); $breadcrumb->replaceParams($newParams);

View File

@ -16,10 +16,8 @@ if (!function_exists('button')) {
* *
* Creates a stylized button or button like anchor tag if the URL is defined. * Creates a stylized button or button like anchor tag if the URL is defined.
* *
* @param array $customOptions button options: variant, size, iconLeft, iconRight * @param array<string, string|null|bool> $customOptions button options: variant, size, iconLeft, iconRight
* @param array $customAttributes Additional attributes * @param array<string, string> $customAttributes Additional attributes
*
* @return string
*/ */
function button( function button(
string $label = '', string $label = '',
@ -130,10 +128,8 @@ if (!function_exists('icon_button')) {
* *
* @param string $icon The button icon * @param string $icon The button icon
* @param string $title The button label * @param string $title The button label
* @param array $customOptions button options: variant, size, iconLeft, iconRight * @param array<string, string|null|bool> $customOptions button options: variant, size, iconLeft, iconRight
* @param array $customAttributes Additional attributes * @param array<string, string> $customAttributes Additional attributes
*
* @return string
*/ */
function icon_button( function icon_button(
string $icon, string $icon,
@ -167,8 +163,6 @@ if (!function_exists('hint_tooltip')) {
* Used to produce tooltip with a question mark icon for hint texts * Used to produce tooltip with a question mark icon for hint texts
* *
* @param string $hintText The hint text * @param string $hintText The hint text
*
* @return string
*/ */
function hint_tooltip(string $hintText = '', string $class = ''): string function hint_tooltip(string $hintText = '', string $class = ''): string
{ {
@ -193,11 +187,9 @@ if (!function_exists('data_table')) {
* *
* Creates a stylized table. * Creates a stylized table.
* *
* @param array $columns array of associate arrays with `header` and `cell` keys where `cell` is a function with a row of $data as parameter * @param array<array<string, mixed>> $columns array of associate arrays with `header` and `cell` keys where `cell` is a function with a row of $data as parameter
* @param array $data data to loop through and display in rows * @param mixed[] $data data to loop through and display in rows
* @param array ...$rest Any other argument to pass to the `cell` function * @param mixed ...$rest Any other argument to pass to the `cell` function
*
* @return string
*/ */
function data_table(array $columns, array $data = [], ...$rest): string function data_table(array $columns, array $data = [], ...$rest): string
{ {
@ -252,8 +244,6 @@ if (!function_exists('publication_pill')) {
* Publication pill component * Publication pill component
* *
* Shows the stylized publication datetime in regards to current datetime. * Shows the stylized publication datetime in regards to current datetime.
*
* @return string
*/ */
function publication_pill( function publication_pill(
?Time $publicationDate, ?Time $publicationDate,
@ -303,7 +293,6 @@ if (!function_exists('publication_button')) {
* Displays the appropriate publication button depending on the publication status. * Displays the appropriate publication button depending on the publication status.
* *
* @param boolean $publicationStatus the episode's publication status * * @param boolean $publicationStatus the episode's publication status *
* @return string
*/ */
function publication_button( function publication_button(
int $podcastId, int $podcastId,

View File

@ -15,9 +15,7 @@ if (!function_exists('form_section')) {
* *
* @param string $title The section title * @param string $title The section title
* @param string $subtitle The section subtitle * @param string $subtitle The section subtitle
* @param array $attributes Additional attributes * @param array<string, string> $attributes Additional attributes
*
* @return string
*/ */
function form_section( function form_section(
string $title = '', string $title = '',
@ -54,9 +52,7 @@ if (!function_exists('form_section_close')) {
/** /**
* Form Section close Tag * Form Section close Tag
* *
* @param string $extra
* *
* @return string
*/ */
function form_section_close(string $extra = ''): string function form_section_close(string $extra = ''): string
{ {
@ -72,10 +68,11 @@ if (!function_exists('form_switch')) {
* *
* Abstracts form_label to stylize it as a switch toggle * Abstracts form_label to stylize it as a switch toggle
* *
* @return string * @param mixed[] $data
* @param mixed[] $extra
*/ */
function form_switch( function form_switch(
$label = '', string $label = '',
array $data = [], array $data = [],
string $value = '', string $value = '',
bool $checked = false, bool $checked = false,
@ -104,11 +101,9 @@ if (!function_exists('form_label')) {
* *
* @param string $label_text The text to appear onscreen * @param string $label_text The text to appear onscreen
* @param string $id The id the label applies to * @param string $id The id the label applies to
* @param array $attributes Additional attributes * @param array<string, string> $attributes Additional attributes
* @param string $hintText Hint text to add next to the label * @param string $hintText Hint text to add next to the label
* @param boolean $isOptional adds an optional text if true * @param boolean $isOptional adds an optional text if true
*
* @return string
*/ */
function form_label( function form_label(
string $label_text = '', string $label_text = '',
@ -151,7 +146,9 @@ if (!function_exists('form_multiselect')) {
/** /**
* Multi-select menu * Multi-select menu
* *
* @return string * @param array<string, string> $options
* @param string[] $selected
* @param array<string, string> $customExtra
*/ */
function form_multiselect( function form_multiselect(
string $name = '', string $name = '',

View File

@ -68,7 +68,7 @@ if (!function_exists('write_audio_file_tags')) {
], ],
'album' => [$episode->podcast->title], 'album' => [$episode->podcast->title],
'year' => [ 'year' => [
$episode->published_at $episode->published_at !== null
? $episode->published_at->format('Y') ? $episode->published_at->format('Y')
: '', : '',
], ],

View File

@ -11,6 +11,10 @@ use Config\Services;
if (!function_exists('fetch_osm_location')) { if (!function_exists('fetch_osm_location')) {
/** /**
* Fetches places from Nominatim OpenStreetMap * Fetches places from Nominatim OpenStreetMap
*
* TODO: move this to Location object?
*
* @return array<string, string>|null
*/ */
function fetch_osm_location(string $locationName): ?array function fetch_osm_location(string $locationName): ?array
{ {

View File

@ -20,7 +20,7 @@ if (!function_exists('save_media')) {
function save_media( function save_media(
File $file, File $file,
string $folder = '', string $folder = '',
string $filename string $filename = ''
): string { ): string {
if (($extension = $file->getExtension()) !== '') { if (($extension = $file->getExtension()) !== '') {
$filename = $filename . '.' . $extension; $filename = $filename . '.' . $extension;
@ -91,9 +91,9 @@ if (!function_exists('media_path')) {
/** /**
* Prefixes the root media path to a given uri * Prefixes the root media path to a given uri
* *
* @param string|array $uri URI string or array of URI segments * @param string|string[] $uri URI string or array of URI segments
*/ */
function media_path($uri = ''): string function media_path(string|array $uri = ''): string
{ {
// convert segment array to string // convert segment array to string
if (is_array($uri)) { if (is_array($uri)) {
@ -111,7 +111,7 @@ if (!function_exists('media_base_url')) {
* *
* @param string|string[] $uri URI string or array of URI segments * @param string|string[] $uri URI string or array of URI segments
*/ */
function media_base_url($uri = ''): string function media_base_url(string|array $uri = ''): string
{ {
// convert segment array to string // convert segment array to string
if (is_array($uri)) { if (is_array($uri)) {

View File

@ -23,23 +23,9 @@ if (!function_exists('get_browser_language')) {
} }
} }
if (!function_exists('startsWith')) {
/**
* Check if a string starts with some characters
*/
function startsWith(string $string, string $query): bool
{
return substr($string, 0, strlen($query)) === $query;
}
}
if (!function_exists('slugify')) { if (!function_exists('slugify')) {
function slugify($text) function slugify(string $text): string
{ {
if (empty($text)) {
return 'n-a';
}
// replace non letter or digits by - // replace non letter or digits by -
$text = preg_replace('~[^\pL\d]+~u', '-', $text); $text = preg_replace('~[^\pL\d]+~u', '-', $text);

View File

@ -1,56 +0,0 @@
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
use App\Entities\Person;
use App\Entities\EpisodePerson;
use App\Entities\PodcastPerson;
if (!function_exists('construct_person_array')) {
/**
* Fetches persons from an episode
*
* @param Person[]|PodcastPerson[]|EpisodePerson[] $persons
*/
function construct_person_array(array $persons, array &$personsArray): void
{
foreach ($persons as $person) {
if (array_key_exists($person->id, $personsArray)) {
$personsArray[$person->id]['roles'] .=
empty($person->person_group) || empty($person->person_role)
? ''
: (empty($personsArray[$person->id]['roles'])
? ''
: ', ') .
lang(
'PersonsTaxonomy.persons.' .
$person->person_group .
'.roles.' .
$person->person_role .
'.label',
);
} else {
$personsArray[$person->person->id] = [
'full_name' => $person->person->full_name,
'information_url' => $person->person->information_url,
'thumbnail_url' => $person->person->image->thumbnail_url,
'roles' =>
empty($person->person_group) ||
empty($person->person_role)
? ''
: lang(
'PersonsTaxonomy.persons.' .
$person->person_group .
'.roles.' .
$person->person_role .
'.label',
),
];
}
}
}
}

View File

@ -187,19 +187,19 @@ if (!function_exists('get_rss_feed')) {
foreach ($podcast->persons as $podcastPerson) { foreach ($podcast->persons as $podcastPerson) {
$podcastPersonElement = $channel->addChild( $podcastPersonElement = $channel->addChild(
'person', 'person',
htmlspecialchars($podcastPerson->person->full_name), htmlspecialchars($podcastPerson->full_name),
$podcast_namespace, $podcast_namespace,
); );
if ( if (
$podcastPerson->person_role !== null && $podcastPerson->role !== null &&
$podcastPerson->person_group !== null $podcastPerson->role !== null
) { ) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'role', 'role',
htmlspecialchars( htmlspecialchars(
lang( lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.roles.{$podcastPerson->person_role}.label", "PersonsTaxonomy.persons.{$podcastPerson->group}.roles.{$podcastPerson->role}.label",
[], [],
'en', 'en',
), ),
@ -207,27 +207,28 @@ if (!function_exists('get_rss_feed')) {
); );
} }
if ($podcastPerson->person_group !== null) { if ($podcastPerson->group !== null) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'group', 'group',
htmlspecialchars( htmlspecialchars(
lang( lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.label", "PersonsTaxonomy.persons.{$podcastPerson->group}.label",
[], [],
'en', 'en',
), ),
), ),
); );
} }
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'img', 'img',
$podcastPerson->person->image->large_url, $podcastPerson->image->large_url,
); );
if ($podcastPerson->person->information_url !== null) { if ($podcastPerson->information_url !== null) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'href', 'href',
$podcastPerson->person->information_url, $podcastPerson->information_url,
); );
} }
} }
@ -417,18 +418,18 @@ if (!function_exists('get_rss_feed')) {
foreach ($episode->persons as $episodePerson) { foreach ($episode->persons as $episodePerson) {
$episodePersonElement = $item->addChild( $episodePersonElement = $item->addChild(
'person', 'person',
htmlspecialchars($episodePerson->person->full_name), htmlspecialchars($episodePerson->full_name),
$podcast_namespace, $podcast_namespace,
); );
if ( if (
!empty($episodePerson->person_role) && !empty($episodePerson->role) &&
!empty($episodePerson->person_group) !empty($episodePerson->group)
) { ) {
$episodePersonElement->addAttribute( $episodePersonElement->addAttribute(
'role', 'role',
htmlspecialchars( htmlspecialchars(
lang( lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.roles.{$episodePerson->person_role}.label", "PersonsTaxonomy.persons.{$episodePerson->group}.roles.{$episodePerson->role}.label",
[], [],
'en', 'en',
), ),
@ -440,7 +441,7 @@ if (!function_exists('get_rss_feed')) {
'group', 'group',
htmlspecialchars( htmlspecialchars(
lang( lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.label", "PersonsTaxonomy.persons.{$episodePerson->group}.label",
[], [],
'en', 'en',
), ),
@ -449,12 +450,12 @@ if (!function_exists('get_rss_feed')) {
} }
$episodePersonElement->addAttribute( $episodePersonElement->addAttribute(
'img', 'img',
$episodePerson->person->image->large_url, $episodePerson->image->large_url,
); );
if (!empty($episodePerson->person->information_url)) { if (!empty($episodePerson->information_url)) {
$episodePersonElement->addAttribute( $episodePersonElement->addAttribute(
'href', 'href',
$episodePerson->person->information_url, $episodePerson->information_url,
); );
} }
} }
@ -512,10 +513,11 @@ if (!function_exists('rss_to_array')) {
/** /**
* Converts XML to array * Converts XML to array
* *
* FIXME: should be SimpleRSSElement * FIXME: param should be SimpleRSSElement
* @param SimpleXMLElement $xmlNode *
* @return array<string, mixed>
*/ */
function rss_to_array(SimpleXMLElement $xmlNode): array function rss_to_array(SimpleXMLElement $rssNode): array
{ {
$nameSpaces = [ $nameSpaces = [
'', '',
@ -523,17 +525,17 @@ if (!function_exists('rss_to_array')) {
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md', 'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
]; ];
$arrayNode = []; $arrayNode = [];
$arrayNode['name'] = $xmlNode->getName(); $arrayNode['name'] = $rssNode->getName();
$arrayNode['namespace'] = $xmlNode->getNamespaces(false); $arrayNode['namespace'] = $rssNode->getNamespaces(false);
foreach ($xmlNode->attributes() as $key => $value) { foreach ($rssNode->attributes() as $key => $value) {
$arrayNode['attributes'][$key] = (string) $value; $arrayNode['attributes'][$key] = (string) $value;
} }
$textcontent = trim((string) $xmlNode); $textcontent = trim((string) $rssNode);
if (strlen($textcontent) > 0) { if (strlen($textcontent) > 0) {
$arrayNode['content'] = $textcontent; $arrayNode['content'] = $textcontent;
} }
foreach ($nameSpaces as $currentNameSpace) { foreach ($nameSpaces as $currentNameSpace) {
foreach ($xmlNode->children($currentNameSpace) as $childXmlNode) { foreach ($rssNode->children($currentNameSpace) as $childXmlNode) {
$arrayNode['elements'][] = rss_to_array($childXmlNode); $arrayNode['elements'][] = rss_to_array($childXmlNode);
} }
} }
@ -546,10 +548,13 @@ if (!function_exists('array_to_rss')) {
/** /**
* Inserts array (converted to XML node) in XML node * Inserts array (converted to XML node) in XML node
* *
* @param array<string, mixed> $arrayNode
* @param SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached * @param SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached
*/ */
function array_to_rss(array $arrayNode, SimpleRSSElement &$xmlNode) function array_to_rss(
{ array $arrayNode,
SimpleRSSElement &$xmlNode
): SimpleRSSElement {
if (array_key_exists('elements', $arrayNode)) { if (array_key_exists('elements', $arrayNode)) {
foreach ($arrayNode['elements'] as $childArrayNode) { foreach ($arrayNode['elements'] as $childArrayNode) {
$childXmlNode = $xmlNode->addChild( $childXmlNode = $xmlNode->addChild(

View File

@ -47,7 +47,9 @@ if (!function_exists('current_season_url')) {
if (!function_exists('extract_params_from_episode_uri')) { if (!function_exists('extract_params_from_episode_uri')) {
/** /**
* Returns podcast name and episode slug from episode string uri * Returns podcast name and episode slug from episode string
*
* @return array<string, string>|null
*/ */
function extract_params_from_episode_uri(URI $episodeUri): ?array function extract_params_from_episode_uri(URI $episodeUri): ?array
{ {

View File

@ -14,6 +14,7 @@
namespace ActivityPub\Activities; namespace ActivityPub\Activities;
use ActivityPub\Core\Activity; use ActivityPub\Core\Activity;
use ActivityPub\Entities\Note;
class AnnounceActivity extends Activity class AnnounceActivity extends Activity
{ {
@ -22,7 +23,7 @@ class AnnounceActivity extends Activity
*/ */
protected $type = 'Announce'; protected $type = 'Announce';
public function __construct($reblogNote) public function __construct(Note $reblogNote)
{ {
$this->actor = $reblogNote->actor->uri; $this->actor = $reblogNote->actor->uri;
$this->object = $reblogNote->reblog_of_note->uri; $this->object = $reblogNote->reblog_of_note->uri;

View File

@ -34,7 +34,7 @@ class ActivityRequest
protected $activity; protected $activity;
/** /**
* @var array * @var array<string, string[]>
*/ */
protected $options = [ protected $options = [
'headers' => [ 'headers' => [
@ -71,7 +71,7 @@ class ActivityRequest
($this->uri->getPort() ? ':' . $this->uri->getPort() : ''); ($this->uri->getPort() ? ':' . $this->uri->getPort() : '');
} }
public function sign($keyId, $privateKey): void public function sign(string $keyId, string $privateKey): void
{ {
$rsa = new RSA(); $rsa = new RSA();
$rsa->loadKey($privateKey); // private key $rsa->loadKey($privateKey); // private key

View File

@ -8,6 +8,8 @@
namespace ActivityPub\Config; namespace ActivityPub\Config;
use ActivityPub\Objects\ActorObject;
use ActivityPub\Objects\NoteObject;
use CodeIgniter\Config\BaseConfig; use CodeIgniter\Config\BaseConfig;
class ActivityPub extends BaseConfig class ActivityPub extends BaseConfig
@ -18,12 +20,12 @@ class ActivityPub extends BaseConfig
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* @var string * @var string
*/ */
public $actorObject = 'ActivityPub\Objects\ActorObject'; public $actorObject = ActorObject::class;
/** /**
* @var string * @var string
*/ */
public $noteObject = 'ActivityPub\Objects\NoteObject'; public $noteObject = NoteObject::class;
/** /**
* -------------------------------------------------------------------- * --------------------------------------------------------------------

View File

@ -41,7 +41,7 @@ class ActorController extends Controller
$this->config = config('ActivityPub'); $this->config = config('ActivityPub');
} }
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if ( if (
count($params) > 0 && count($params) > 0 &&
@ -301,10 +301,7 @@ class ActorController extends Controller
->setBody($followersCollection->toJSON()); ->setBody($followersCollection->toJSON());
} }
/** public function attemptFollow(): RedirectResponse|ResponseInterface
* @return mixed|ResponseInterface
*/
public function attemptFollow()
{ {
$rules = [ $rules = [
'handle' => 'handle' =>
@ -354,7 +351,7 @@ class ActorController extends Controller
); );
} }
public function activity($activityId): RedirectResponse public function activity(string $activityId): RedirectResponse
{ {
if ( if (
!($activity = model('ActivityModel')->getActivityById($activityId)) !($activity = model('ActivityModel')->getActivityById($activityId))

View File

@ -8,6 +8,7 @@
namespace ActivityPub\Controllers; namespace ActivityPub\Controllers;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\Controller; use CodeIgniter\Controller;
class BlockController extends Controller class BlockController extends Controller
@ -17,7 +18,7 @@ class BlockController extends Controller
*/ */
protected $helpers = ['activitypub']; protected $helpers = ['activitypub'];
public function attemptBlockActor() public function attemptBlockActor(): RedirectResponse
{ {
$rules = [ $rules = [
'handle' => 'required', 'handle' => 'required',
@ -51,7 +52,7 @@ class BlockController extends Controller
return redirect()->back(); return redirect()->back();
} }
function attemptBlockDomain() function attemptBlockDomain(): RedirectResponse
{ {
$rules = [ $rules = [
'domain' => 'required', 'domain' => 'required',
@ -71,7 +72,7 @@ class BlockController extends Controller
return redirect()->back(); return redirect()->back();
} }
function attemptUnblockActor() function attemptUnblockActor(): RedirectResponse
{ {
$rules = [ $rules = [
'actor_id' => 'required', 'actor_id' => 'required',
@ -89,7 +90,7 @@ class BlockController extends Controller
return redirect()->back(); return redirect()->back();
} }
function attemptUnblockDomain() function attemptUnblockDomain(): RedirectResponse
{ {
$rules = [ $rules = [
'domain' => 'required', 'domain' => 'required',

View File

@ -41,7 +41,7 @@ class NoteController extends Controller
$this->config = config('ActivityPub'); $this->config = config('ActivityPub');
} }
public function _remap(string $method, string ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (!($this->note = model('NoteModel')->getNoteById($params[0]))) { if (!($this->note = model('NoteModel')->getNoteById($params[0]))) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
@ -63,8 +63,7 @@ class NoteController extends Controller
public function replies(): RedirectResponse public function replies(): RedirectResponse
{ {
/** get note replies /** get note replies */
* @var NoteModel */
$noteReplies = model('NoteModel') $noteReplies = model('NoteModel')
->where( ->where(
'in_reply_to_id', 'in_reply_to_id',
@ -216,10 +215,7 @@ class NoteController extends Controller
return redirect()->back(); return redirect()->back();
} }
/** public function attemptRemoteAction(string $action): RedirectResponse|ResponseInterface
* @return mixed|ResponseInterface
*/
public function attemptRemoteAction(string $action)
{ {
$rules = [ $rules = [
'handle' => 'handle' =>

View File

@ -20,7 +20,7 @@ class WebFingerController extends Controller
{ {
try { try {
$webfinger = new WebFinger($this->request->getGet('resource')); $webfinger = new WebFinger($this->request->getGet('resource'));
} catch (Exception $exception) { } catch (Exception) {
// return 404, actor not found // return 404, actor not found
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }

View File

@ -18,7 +18,7 @@ abstract class AbstractObject
/** /**
* @param mixed $value * @param mixed $value
*/ */
public function set(string $property, $value): self public function set(string $property, $value): static
{ {
$this->$property = $value; $this->$property = $value;
@ -49,10 +49,7 @@ abstract class AbstractObject
}); });
} }
/** public function toJSON(): string|bool
* @return string|bool
*/
public function toJSON()
{ {
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE); return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
} }

View File

@ -16,7 +16,7 @@ namespace ActivityPub\Core;
class ObjectType extends AbstractObject class ObjectType extends AbstractObject
{ {
/** /**
* @var array|string * @var string|string[]
*/ */
protected $context = 'https://www.w3.org/ns/activitystreams'; protected $context = 'https://www.w3.org/ns/activitystreams';
@ -41,12 +41,12 @@ class ObjectType extends AbstractObject
protected $published; protected $published;
/** /**
* @var array * @var string[]
*/ */
protected $to = ['https://www.w3.org/ns/activitystreams#Public']; protected $to = ['https://www.w3.org/ns/activitystreams#Public'];
/** /**
* @var array * @var string[]
*/ */
protected $cc = []; protected $cc = [];
} }

View File

@ -243,7 +243,7 @@ class Note extends UuidEntity
return $this->reblog_of_note; return $this->reblog_of_note;
} }
public function setMessage(string $message): self public function setMessage(string $message): static
{ {
helper('activitypub'); helper('activitypub');

View File

@ -23,7 +23,7 @@ class ActivityPubFilter implements FilterInterface
* sent back to the client, allowing for error pages, * sent back to the client, allowing for error pages,
* redirects, etc. * redirects, etc.
* *
* @param array|null $params * @param string[]|null $params
* @return void|mixed * @return void|mixed
*/ */
public function before(RequestInterface $request, $params = null) public function before(RequestInterface $request, $params = null)
@ -67,7 +67,7 @@ class ActivityPubFilter implements FilterInterface
try { try {
// securityCheck: check activity signature before handling it // securityCheck: check activity signature before handling it
(new HttpSignature())->verify(); (new HttpSignature())->verify();
} catch (Exception $exception) { } catch (Exception) {
// Invalid HttpSignature (401 = unauthorized) // Invalid HttpSignature (401 = unauthorized)
// TODO: show error message? // TODO: show error message?
return service('response')->setStatusCode(401); return service('response')->setStatusCode(401);
@ -82,7 +82,7 @@ class ActivityPubFilter implements FilterInterface
* to stop execution of other after filters, short of * to stop execution of other after filters, short of
* throwing an Exception or Error. * throwing an Exception or Error.
* *
* @param array|null $arguments * @param string[]|null $arguments
*/ */
public function after( public function after(
RequestInterface $request, RequestInterface $request,

View File

@ -18,8 +18,6 @@ use CodeIgniter\HTTP\Exceptions\HTTPException;
if (!function_exists('get_webfinger_data')) { if (!function_exists('get_webfinger_data')) {
/** /**
* Retrieve actor webfinger data from username and domain * Retrieve actor webfinger data from username and domain
*
* @return object|null
*/ */
function get_webfinger_data(string $username, string $domain): ?object function get_webfinger_data(string $username, string $domain): ?object
{ {
@ -45,8 +43,7 @@ if (!function_exists('split_handle')) {
/** /**
* Splits handle into its parts (username, host and port) * Splits handle into its parts (username, host and port)
* *
* @param string $handle * @return array<string, string>|false
* @return bool|array
*/ */
function split_handle(string $handle) function split_handle(string $handle)
{ {
@ -107,7 +104,7 @@ if (!function_exists('accept_follow')) {
); );
$acceptRequest->sign($actor->public_key_id, $actor->private_key); $acceptRequest->sign($actor->public_key_id, $actor->private_key);
$acceptRequest->post(); $acceptRequest->post();
} catch (Exception $exception) { } catch (Exception) {
$db->transRollback(); $db->transRollback();
} }
@ -163,8 +160,6 @@ if (!function_exists('extract_urls_from_message')) {
if (!function_exists('create_preview_card_from_url')) { if (!function_exists('create_preview_card_from_url')) {
/** /**
* Extract open graph metadata from given url and create preview card * Extract open graph metadata from given url and create preview card
*
* @return PreviewCard|null
*/ */
function create_preview_card_from_url(URI $url): ?PreviewCard function create_preview_card_from_url(URI $url): ?PreviewCard
{ {
@ -223,8 +218,6 @@ if (!function_exists('create_preview_card_from_url')) {
if (!function_exists('get_or_create_preview_card_from_url')) { if (!function_exists('get_or_create_preview_card_from_url')) {
/** /**
* Extract open graph metadata from given url and create preview card * Extract open graph metadata from given url and create preview card
*
* @return PreviewCard|null
*/ */
function get_or_create_preview_card_from_url(URI $url): ?PreviewCard function get_or_create_preview_card_from_url(URI $url): ?PreviewCard
{ {
@ -246,8 +239,6 @@ if (!function_exists('get_or_create_actor_from_uri')) {
/** /**
* Retrieves actor from database using the actor uri * Retrieves actor from database using the actor uri
* If Actor is not present, it creates the record in the database and returns it. * If Actor is not present, it creates the record in the database and returns it.
*
* @return Actor|null
*/ */
function get_or_create_actor_from_uri(string $actorUri): ?Actor function get_or_create_actor_from_uri(string $actorUri): ?Actor
{ {
@ -265,8 +256,6 @@ if (!function_exists('get_or_create_actor')) {
/** /**
* Retrieves actor from database using the actor username and domain * Retrieves actor from database using the actor username and domain
* If actor is not present, it creates the record in the database and returns it. * If actor is not present, it creates the record in the database and returns it.
*
* @return Actor|null
*/ */
function get_or_create_actor(string $username, string $domain): ?Actor function get_or_create_actor(string $username, string $domain): ?Actor
{ {
@ -292,8 +281,6 @@ if (!function_exists('create_actor_from_uri')) {
/** /**
* Creates actor record in database using * Creates actor record in database using
* the info gathered from the actorUri parameter * the info gathered from the actorUri parameter
*
* @return Actor|null
*/ */
function create_actor_from_uri(string $actorUri): ?Actor function create_actor_from_uri(string $actorUri): ?Actor
{ {
@ -352,8 +339,6 @@ if (!function_exists('get_current_domain')) {
if (!function_exists('extract_text_from_html')) { if (!function_exists('extract_text_from_html')) {
/** /**
* Extracts the text from html content * Extracts the text from html content
*
* @return string|null
*/ */
function extract_text_from_html(string $content): ?string function extract_text_from_html(string $content): ?string
{ {
@ -381,7 +366,7 @@ if (!function_exists('linkify')) {
case 'https': case 'https':
$text = preg_replace_callback( $text = preg_replace_callback(
'~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i', '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i',
function ($match) use ($protocol, &$links) { function (array $match) use ($protocol, &$links) {
if ($match[1]) { if ($match[1]) {
$protocol = $match[1]; $protocol = $match[1];
} }
@ -452,7 +437,7 @@ if (!function_exists('linkify')) {
]), ]),
) . ) .
'>'; '>';
} catch (\CodeIgniter\HTTP\Exceptions\HTTPException $httpException) { } catch (\CodeIgniter\HTTP\Exceptions\HTTPException) {
// Couldn't retrieve actor, do not wrap the text in link // Couldn't retrieve actor, do not wrap the text in link
return '<' . return '<' .
array_push($links, $match[0]) . array_push($links, $match[0]) .
@ -485,7 +470,7 @@ if (!function_exists('linkify')) {
'~' . '~' .
preg_quote($protocol, '~') . preg_quote($protocol, '~') .
'://([^\s<]+?)(?<![\.,:])~i', '://([^\s<]+?)(?<![\.,:])~i',
function ($match) use ($protocol, &$links) { function (array $match) use ($protocol, &$links) {
return '<' . return '<' .
array_push( array_push(
$links, $links,

View File

@ -41,7 +41,7 @@ class HttpSignature
/** /**
* @var IncomingRequest * @var IncomingRequest
*/ */
protected $request; protected ?IncomingRequest $request;
public function __construct(IncomingRequest $request = null) public function __construct(IncomingRequest $request = null)
{ {
@ -130,9 +130,9 @@ class HttpSignature
/** /**
* Split HTTP signature into its parts (keyId, headers and signature) * Split HTTP signature into its parts (keyId, headers and signature)
* *
* @return bool|mixed * @return array<string, string>|false
*/ */
private function splitSignature(string $signature) private function splitSignature(string $signature): array|false
{ {
if (!preg_match(self::SIGNATURE_PATTERN, $signature, $matches)) { if (!preg_match(self::SIGNATURE_PATTERN, $signature, $matches)) {
// Signature pattern failed // Signature pattern failed
@ -150,7 +150,7 @@ class HttpSignature
/** /**
* Get plain text that has been originally signed * Get plain text that has been originally signed
* *
* @param array $headers HTTP header keys * @param string[] $headers HTTP header keys
*/ */
private function getPlainText(array $headers): string private function getPlainText(array $headers): string
{ {

View File

@ -10,6 +10,7 @@ namespace ActivityPub\Models;
use ActivityPub\Entities\Activity; use ActivityPub\Entities\Activity;
use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\BaseResult;
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use DateTimeInterface; use DateTimeInterface;
use Michalsn\Uuid\UuidModel; use Michalsn\Uuid\UuidModel;
@ -59,7 +60,7 @@ class ActivityModel extends UuidModel
protected $useTimestamps = true; protected $useTimestamps = true;
protected $updatedField; protected $updatedField;
public function getActivityById($activityId) public function getActivityById(string $activityId): ?Activity
{ {
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . "activity#{$activityId}"; config('ActivityPub')->cachePrefix . "activity#{$activityId}";
@ -76,8 +77,6 @@ class ActivityModel extends UuidModel
* Inserts a new activity record in the database * Inserts a new activity record in the database
* *
* @param Time $scheduledAt * @param Time $scheduledAt
*
* @return BaseResult|int|string|false
*/ */
public function newActivity( public function newActivity(
string $type, string $type,
@ -87,7 +86,7 @@ class ActivityModel extends UuidModel
string $payload, string $payload,
DateTimeInterface $scheduledAt = null, DateTimeInterface $scheduledAt = null,
?string $status = null ?string $status = null
) { ): BaseResult|int|string|false {
return $this->insert( return $this->insert(
[ [
'actor_id' => $actorId, 'actor_id' => $actorId,
@ -102,7 +101,10 @@ class ActivityModel extends UuidModel
); );
} }
public function getScheduledActivities() /**
* @return Activity[]
*/
public function getScheduledActivities(): array
{ {
return $this->where('`scheduled_at` <= NOW()', null, false) return $this->where('`scheduled_at` <= NOW()', null, false)
->where('status', 'queued') ->where('status', 'queued')

View File

@ -58,7 +58,7 @@ class ActorModel extends Model
*/ */
protected $useTimestamps = true; protected $useTimestamps = true;
public function getActorById($id): Actor public function getActorById(int $id): Actor
{ {
$cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}"; $cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}";
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
@ -98,7 +98,7 @@ class ActorModel extends Model
return $found; return $found;
} }
public function getActorByUri($actorUri) public function getActorByUri(string $actorUri): ?Actor
{ {
$hashedActorUri = md5($actorUri); $hashedActorUri = md5($actorUri);
$cacheName = $cacheName =
@ -112,7 +112,10 @@ class ActorModel extends Model
return $found; return $found;
} }
public function getFollowers($actorId) /**
* @return Actor[]
*/
public function getFollowers(int $actorId): array
{ {
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . "actor#{$actorId}_followers"; config('ActivityPub')->cachePrefix . "actor#{$actorId}_followers";
@ -137,7 +140,7 @@ class ActorModel extends Model
*/ */
public function isActorBlocked(string $actorUri): bool public function isActorBlocked(string $actorUri): bool
{ {
if ($actor = $this->getActorByUri($actorUri)) { if (($actor = $this->getActorByUri($actorUri)) !== null) {
return $actor->is_blocked; return $actor->is_blocked;
} }
@ -161,7 +164,7 @@ class ActorModel extends Model
return $found; return $found;
} }
public function blockActor($actorId): void public function blockActor(int $actorId): void
{ {
$prefix = config('ActivityPub')->cachePrefix; $prefix = config('ActivityPub')->cachePrefix;
cache()->delete($prefix . 'blocked_actors'); cache()->delete($prefix . 'blocked_actors');
@ -172,7 +175,7 @@ class ActorModel extends Model
$this->update($actorId, ['is_blocked' => 1]); $this->update($actorId, ['is_blocked' => 1]);
} }
public function unblockActor($actorId): void public function unblockActor(int $actorId): void
{ {
$prefix = config('ActivityPub')->cachePrefix; $prefix = config('ActivityPub')->cachePrefix;
cache()->delete($prefix . 'blocked_actors'); cache()->delete($prefix . 'blocked_actors');

View File

@ -49,8 +49,10 @@ class BlockedDomainModel extends Model
/** /**
* Retrieves instance or podcast domain blocks depending on whether or not $podcastId param is set. * Retrieves instance or podcast domain blocks depending on whether or not $podcastId param is set.
*
* @return BlockedDomain[]
*/ */
public function getBlockedDomains() public function getBlockedDomains(): array
{ {
$cacheName = config('ActivityPub')->cachePrefix . 'blocked_domains'; $cacheName = config('ActivityPub')->cachePrefix . 'blocked_domains';
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
@ -61,14 +63,14 @@ class BlockedDomainModel extends Model
return $found; return $found;
} }
public function isDomainBlocked($domain) public function isDomainBlocked(string $name): bool
{ {
$hashedDomain = md5($domain); $hashedDomainName = md5($name);
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . config('ActivityPub')->cachePrefix .
"domain#{$hashedDomain}_isBlocked"; "domain#{$hashedDomainName}_isBlocked";
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
$found = (bool) $this->find($domain); $found = (bool) $this->find($name);
cache()->save($cacheName, $found, DECADE); cache()->save($cacheName, $found, DECADE);
} }
@ -76,7 +78,7 @@ class BlockedDomainModel extends Model
return $found; return $found;
} }
public function blockDomain($name) public function blockDomain(string $name): int|bool
{ {
$hashedDomain = md5($name); $hashedDomain = md5($name);
$prefix = config('ActivityPub')->cachePrefix; $prefix = config('ActivityPub')->cachePrefix;
@ -104,10 +106,7 @@ class BlockedDomainModel extends Model
return $result; return $result;
} }
/** public function unblockDomain(string $name): BaseResult|bool
* @return bool|BaseResult
*/
public function unblockDomain($name)
{ {
$hashedDomain = md5($name); $hashedDomain = md5($name);
$prefix = config('ActivityPub')->cachePrefix; $prefix = config('ActivityPub')->cachePrefix;

View File

@ -109,9 +109,9 @@ class FavouriteModel extends UuidModel
} }
public function removeFavourite( public function removeFavourite(
$actor, Actor $actor,
$note, Note $note,
$registerActivity = true bool $registerActivity = true
): void { ): void {
$this->db->transStart(); $this->db->transStart();

View File

@ -103,7 +103,7 @@ class FollowModel extends Model
} }
$this->db->transComplete(); $this->db->transComplete();
} catch (Exception $exception) { } catch (Exception) {
// follow already exists, do nothing // follow already exists, do nothing
} }
} }
@ -117,7 +117,7 @@ class FollowModel extends Model
public function removeFollower( public function removeFollower(
Actor $actor, Actor $actor,
Actor $targetActor, Actor $targetActor,
$registerActivity = true bool $registerActivity = true
): void { ): void {
$this->db->transStart(); $this->db->transStart();

View File

@ -21,6 +21,8 @@ use CodeIgniter\Database\BaseResult;
use CodeIgniter\Events\Events; use CodeIgniter\Events\Events;
use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use CodeIgniter\Router\Exceptions\RouterException;
use InvalidArgumentException;
use Michalsn\Uuid\UuidModel; use Michalsn\Uuid\UuidModel;
class NoteModel extends UuidModel class NoteModel extends UuidModel
@ -86,7 +88,7 @@ class NoteModel extends UuidModel
*/ */
protected $beforeInsert = ['setNoteId']; protected $beforeInsert = ['setNoteId'];
public function getNoteById($noteId) public function getNoteById(string $noteId): ?Note
{ {
$cacheName = config('ActivityPub')->cachePrefix . "note#{$noteId}"; $cacheName = config('ActivityPub')->cachePrefix . "note#{$noteId}";
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
@ -98,7 +100,7 @@ class NoteModel extends UuidModel
return $found; return $found;
} }
public function getNoteByUri($noteUri) public function getNoteByUri(string $noteUri): ?Note
{ {
$hashedNoteUri = md5($noteUri); $hashedNoteUri = md5($noteUri);
$cacheName = $cacheName =
@ -117,7 +119,7 @@ class NoteModel extends UuidModel
* *
* @return Note[] * @return Note[]
*/ */
public function getActorPublishedNotes($actorId): array public function getActorPublishedNotes(int $actorId): array
{ {
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . config('ActivityPub')->cachePrefix .
@ -179,8 +181,10 @@ class NoteModel extends UuidModel
/** /**
* Retrieves all published reblogs for a given note * Retrieves all published reblogs for a given note
*
* @return Note[]
*/ */
public function getNoteReblogs($noteId) public function getNoteReblogs(string $noteId): array
{ {
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . "note#{$noteId}_reblogs"; config('ActivityPub')->cachePrefix . "note#{$noteId}_reblogs";
@ -200,10 +204,7 @@ class NoteModel extends UuidModel
return $found; return $found;
} }
/** public function addPreviewCard(string $noteId, int $previewCardId): Query|bool
* @return bool|Query
*/
public function addPreviewCard($noteId, $previewCardId)
{ {
return $this->db->table('activitypub_notes_preview_cards')->insert([ return $this->db->table('activitypub_notes_preview_cards')->insert([
'note_id' => $this->uuid->fromString($noteId)->getBytes(), 'note_id' => $this->uuid->fromString($noteId)->getBytes(),
@ -220,7 +221,7 @@ class NoteModel extends UuidModel
Note $note, Note $note,
bool $createPreviewCard = true, bool $createPreviewCard = true,
bool $registerActivity = true bool $registerActivity = true
) { ): string|false {
helper('activitypub'); helper('activitypub');
$this->db->transStart(); $this->db->transStart();
@ -301,7 +302,7 @@ class NoteModel extends UuidModel
return $newNoteId; return $newNoteId;
} }
public function editNote($updatedNote): bool public function editNote(Note $updatedNote): bool
{ {
$this->db->transStart(); $this->db->transStart();
@ -341,10 +342,8 @@ class NoteModel extends UuidModel
/** /**
* Removes a note from the database and decrements meta data * Removes a note from the database and decrements meta data
*
* @return BaseResult|bool
*/ */
public function removeNote(Note $note, bool $registerActivity = true) public function removeNote(Note $note, bool $registerActivity = true): BaseResult|bool
{ {
$this->db->transStart(); $this->db->transStart();
@ -450,14 +449,11 @@ class NoteModel extends UuidModel
return $result; return $result;
} }
/**
* @return string|bool
*/
public function addReply( public function addReply(
$reply, Note $reply,
$createPreviewCard = true, bool $createPreviewCard = true,
$registerActivity = true bool $registerActivity = true
) { ): string|false {
if (!$reply->in_reply_to_id) { if (!$reply->in_reply_to_id) {
throw new Exception('Passed note is not a reply!'); throw new Exception('Passed note is not a reply!');
} }
@ -489,10 +485,7 @@ class NoteModel extends UuidModel
return $noteId; return $noteId;
} }
/** public function reblog(Actor $actor, Note $note, bool $registerActivity = true): string|false
* @return BaseResult|int|string|false
*/
public function reblog(Actor $actor, Note $note, $registerActivity = true)
{ {
$this->db->transStart(); $this->db->transStart();
@ -503,7 +496,7 @@ class NoteModel extends UuidModel
]); ]);
// add reblog // add reblog
$reblogId = $this->insert($reblog, true); $reblogId = $this->insert($reblog);
model('ActorModel') model('ActorModel')
->where('id', $actor->id) ->where('id', $actor->id)
@ -554,10 +547,7 @@ class NoteModel extends UuidModel
return $reblogId; return $reblogId;
} }
/** public function undoReblog(Note $reblogNote, bool $registerActivity = true): BaseResult|bool
* @return BaseResult|bool
*/
public function undoReblog(Note $reblogNote, bool $registerActivity = true)
{ {
$this->db->transStart(); $this->db->transStart();
@ -649,7 +639,7 @@ class NoteModel extends UuidModel
return $result; return $result;
} }
public function toggleReblog($actor, $note): void public function toggleReblog(Actor $actor, Note $note): void
{ {
if ( if (
!($reblogNote = $this->where([ !($reblogNote = $this->where([
@ -665,7 +655,11 @@ class NoteModel extends UuidModel
} }
} }
protected function setNoteId($data) /**
* @param array<string, array<string|int, mixed>> $data
* @return array<string, array<string|int, mixed>>
*/
protected function setNoteId(array $data): array
{ {
$uuid4 = $this->uuid->{$this->uuidVersion}(); $uuid4 = $this->uuid->{$this->uuidVersion}();
$data['data']['id'] = $uuid4->toString(); $data['data']['id'] = $uuid4->toString();

View File

@ -51,7 +51,7 @@ class PreviewCardModel extends Model
*/ */
protected $useTimestamps = true; protected $useTimestamps = true;
public function getPreviewCardFromUrl($url) public function getPreviewCardFromUrl(string $url): ?PreviewCard
{ {
$hashedPreviewCardUrl = md5($url); $hashedPreviewCardUrl = md5($url);
$cacheName = $cacheName =
@ -65,7 +65,7 @@ class PreviewCardModel extends Model
return $found; return $found;
} }
public function getNotePreviewCard($noteId) public function getNotePreviewCard(string $noteId): ?PreviewCard
{ {
$cacheName = $cacheName =
config('ActivityPub')->cachePrefix . "note#{$noteId}_preview_card"; config('ActivityPub')->cachePrefix . "note#{$noteId}_preview_card";
@ -89,10 +89,7 @@ class PreviewCardModel extends Model
return $found; return $found;
} }
/** public function deletePreviewCard(int $id, string $url): BaseResult|bool
* @return bool|BaseResult
*/
public function deletePreviewCard($id, $url)
{ {
$hashedPreviewCardUrl = md5($url); $hashedPreviewCardUrl = md5($url);
cache()->delete( cache()->delete(

View File

@ -14,7 +14,7 @@ use ActivityPub\Core\ObjectType;
class ActorObject extends ObjectType class ActorObject extends ObjectType
{ {
/** /**
* @var array|string * @var string|string[]
*/ */
protected $context = [ protected $context = [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
@ -62,12 +62,12 @@ class ActorObject extends ObjectType
protected $url; protected $url;
/** /**
* @var array|null * @var array<string, string>|null
*/ */
protected $image; protected $image;
/** /**
* @var array * @var array<string, string>
*/ */
protected $icon = []; protected $icon = [];

View File

@ -38,10 +38,7 @@ class NoteObject extends ObjectType
*/ */
protected $replies; protected $replies;
/** public function __construct(Note $note)
* @param Note $note
*/
public function __construct($note)
{ {
$this->id = $note->uri; $this->id = $note->uri;

View File

@ -42,15 +42,10 @@ class OrderedCollectionObject extends ObjectType
protected $last; protected $last;
/** /**
* @var array|null * @param ObjectType[] $orderedItems
*/
protected $orderedItems;
/**
* @param array $orderedItems
*/ */
public function __construct( public function __construct(
?array $orderedItems = null, protected ?array $orderedItems = null,
?Pager $pager = null ?Pager $pager = null
) { ) {
$this->id = current_url(); $this->id = current_url();
@ -65,7 +60,5 @@ class OrderedCollectionObject extends ObjectType
$this->last = $pager->getPageURI($pager->getLastPage()); $this->last = $pager->getPageURI($pager->getLastPage());
} }
} }
$this->orderedItems = $orderedItems;
} }
} }

View File

@ -38,29 +38,19 @@ class WebFinger
protected $port; protected $port;
/** /**
* @var string * @var string[]
*/
protected $subject;
/**
* @var array
*/ */
protected $aliases = []; protected $aliases = [];
/** /**
* @var array * @var array<array<string, string>>
*/ */
protected $links = []; protected $links = [];
/** public function __construct(protected string $subject)
* @param string $resource
*/
public function __construct($resource)
{ {
$this->subject = $resource;
// Split resource into its parts (username, domain) // Split resource into its parts (username, domain)
$parts = $this->splitResource($resource); $parts = $this->splitResource($subject);
if (!$parts) { if (!$parts) {
throw new Exception('Wrong WebFinger resource pattern.'); throw new Exception('Wrong WebFinger resource pattern.');
} }
@ -120,9 +110,9 @@ class WebFinger
/** /**
* Split resource into its parts (username, domain) * Split resource into its parts (username, domain)
* *
* @return bool|mixed * @return array<string, string>|false
*/ */
private function splitResource(string $resource) private function splitResource(string $resource): array|false
{ {
if (!preg_match(self::RESOURCE_PATTERN, $resource, $matches)) { if (!preg_match(self::RESOURCE_PATTERN, $resource, $matches)) {
// Resource pattern failed // Resource pattern failed

View File

@ -10,6 +10,7 @@ namespace Analytics;
use Config\Services; use Config\Services;
use Config\Database; use Config\Database;
trait AnalyticsTrait trait AnalyticsTrait
{ {
protected function registerPodcastWebpageHit(int $podcastId): void protected function registerPodcastWebpageHit(int $podcastId): void

View File

@ -31,7 +31,7 @@ class Analytics extends BaseConfig
* *
* @param string|string[] $audioFilePath * @param string|string[] $audioFilePath
*/ */
public function getAudioFileUrl($audioFilePath): string public function getAudioFileUrl(string|array $audioFilePath): string
{ {
return base_url($audioFilePath); return base_url($audioFilePath);
} }

View File

@ -24,7 +24,7 @@ class AnalyticsController extends Controller
*/ */
protected $methodName; protected $methodName;
public function _remap($method, ...$params) public function _remap(string $method, string ...$params): mixed
{ {
if (!isset($params[1])) { if (!isset($params[1])) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
@ -39,11 +39,11 @@ class AnalyticsController extends Controller
); );
} }
public function getData($podcastId, $episodeId): ResponseInterface public function getData(int $podcastId, int $episodeId): ResponseInterface
{ {
$analytics_model = new $this->className(); $analytics_model = new $this->className();
$methodName = $this->methodName; $methodName = $this->methodName;
if ($episodeId) { if ($episodeId !== 0) {
return $this->response->setJSON( return $this->response->setJSON(
$analytics_model->$methodName($podcastId, $episodeId), $analytics_model->$methodName($podcastId, $episodeId),
); );

View File

@ -23,7 +23,7 @@ class EpisodeAnalyticsController extends Controller
* class instantiation. These helpers will be available * class instantiation. These helpers will be available
* to all other controllers that extend Analytics. * to all other controllers that extend Analytics.
* *
* @var array * @var string[]
*/ */
protected $helpers = ['analytics']; protected $helpers = ['analytics'];

View File

@ -0,0 +1,200 @@
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Analytics\AnalyticsTrait;
use App\Entities\Episode;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use SimpleXMLElement;
class EpisodeController extends BaseController
{
use AnalyticsTrait;
/**
* @var Podcast
*/
protected $podcast;
/**
* @var Episode
*/
protected $episode;
public function _remap(string $method, string ...$params): mixed
{
if (count($params) < 2) {
throw PageNotFoundException::forPageNotFound();
}
if (
($this->podcast = (new PodcastModel())->getPodcastByName(
$params[0],
)) === null
) {
throw PageNotFoundException::forPageNotFound();
}
if (
($this->episode = (new EpisodeModel())->getEpisodeBySlug(
$this->podcast->id,
$params[1],
)) !== null
) {
unset($params[1]);
unset($params[0]);
return $this->$method(...$params);
}
throw PageNotFoundException::forPageNotFound();
}
public function index(): string
{
// Prevent analytics hit when authenticated
if (!can_user_interact()) {
$this->registerPodcastWebpageHit($this->episode->podcast_id);
}
$locale = service('request')->getLocale();
$cacheName =
"page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" .
(can_user_interact() ? '_authenticated' : '');
if (!($cachedView = cache($cacheName))) {
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
if (can_user_interact()) {
helper('form');
return view('podcast/episode_authenticated', $data);
}
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('podcast/episode', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
public function embeddablePlayer(
string $theme = 'light-transparent'
): string {
header('Content-Security-Policy: frame-ancestors https://* http://*');
// Prevent analytics hit when authenticated
if (!can_user_interact()) {
$this->registerPodcastWebpageHit($this->episode->podcast_id);
}
$session = Services::session();
$session->start();
if (isset($_SERVER['HTTP_REFERER'])) {
$session->set(
'embeddable_player_domain',
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST),
);
}
$locale = service('request')->getLocale();
$cacheName = "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_embeddable_player_{$theme}_{$locale}";
if (!($cachedView = cache($cacheName))) {
$theme = EpisodeModel::$themes[$theme];
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'theme' => $theme,
];
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('embeddable_player', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
public function oembedJSON(): ResponseInterface
{
return $this->response->setJSON([
'type' => 'rich',
'version' => '1.0',
'title' => $this->episode->title,
'provider_name' => $this->podcast->title,
'provider_url' => $this->podcast->link,
'author_name' => $this->podcast->title,
'author_url' => $this->podcast->link,
'html' =>
'<iframe src="' .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
'width' => 600,
'height' => 200,
'thumbnail_url' => $this->episode->image->large_url,
'thumbnail_width' => config('Images')->largeSize,
'thumbnail_height' => config('Images')->largeSize,
]);
}
public function oembedXML(): ResponseInterface
{
$oembed = new SimpleXMLElement(
"<?xml version='1.0' encoding='utf-8' standalone='yes'?><oembed></oembed>",
);
$oembed->addChild('type', 'rich');
$oembed->addChild('version', '1.0');
$oembed->addChild('title', $this->episode->title);
$oembed->addChild('provider_name', $this->podcast->title);
$oembed->addChild('provider_url', $this->podcast->link);
$oembed->addChild('author_name', $this->podcast->title);
$oembed->addChild('author_url', $this->podcast->link);
$oembed->addChild('thumbnail', $this->episode->image->large_url);
$oembed->addChild('thumbnail_width', config('Images')->largeSize);
$oembed->addChild('thumbnail_height', config('Images')->largeSize);
$oembed->addChild(
'html',
htmlentities(
'<iframe src="' .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
),
);
$oembed->addChild('width', '600');
$oembed->addChild('height', '200');
return $this->response->setXML($oembed);
}
}

View File

@ -13,9 +13,9 @@ use CodeIgniter\Controller;
class UnknownUserAgentsController extends Controller class UnknownUserAgentsController extends Controller
{ {
public function index($lastKnownId = 0): ResponseInterface public function index(int $lastKnownId = 0): ResponseInterface
{ {
$model = model('UnknownUserAgentsModel'); $model = model('AnalyticsUnknownUserAgentsModel');
return $this->response->setJSON($model->getUserAgents($lastKnownId)); return $this->response->setJSON($model->getUserAgents($lastKnownId));
} }

View File

@ -37,7 +37,7 @@ class AnalyticsPodcastsByCountry extends Entity
'hits' => 'integer', 'hits' => 'integer',
]; ];
public function getLabels() public function getLabels(): string
{ {
return lang('Countries.' . $this->attributes['labels']); return lang('Countries.' . $this->attributes['labels']);
} }

View File

@ -42,7 +42,7 @@ class AnalyticsPodcastsByRegion extends Entity
'hits' => 'integer', 'hits' => 'integer',
]; ];
public function getCountryCode() public function getCountryCode(): string
{ {
return lang('Countries.' . $this->attributes['country_code']); return lang('Countries.' . $this->attributes['country_code']);
} }

View File

@ -44,7 +44,7 @@ class AnalyticsPodcastsByService extends Entity
'hits' => 'integer', 'hits' => 'integer',
]; ];
public function getLabels() public function getLabels(): string
{ {
return UserAgentsRSS::getName($this->attributes['labels']) ?? return UserAgentsRSS::getName($this->attributes['labels']) ??
$this->attributes['labels']; $this->attributes['labels'];

View File

@ -13,17 +13,24 @@ namespace Analytics\Entities;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
/** /**
* @property int $id
* @property int $useragent * @property int $useragent
* @property int $hits * @property int $hits
* @property Time $created_at * @property Time $created_at
* @property Time $updated_at * @property Time $updated_at
*/ */
class AnalyticsUnknownUseragents extends Entity class AnalyticsUnknownUserAgent extends Entity
{ {
/**
* @var string[]
*/
protected $dates = ['created_at', 'updated_at'];
/** /**
* @var array<string, string> * @var array<string, string>
*/ */
protected $casts = [ protected $casts = [
'id' => 'integer',
'useragent' => 'integer', 'useragent' => 'integer',
'hits' => 'integer', 'hits' => 'integer',
]; ];

View File

@ -1,5 +1,6 @@
<?php <?php
use CodeIgniter\I18n\Time;
use Config\Services; use Config\Services;
use Podlibre\Ipcat\IpDb; use Podlibre\Ipcat\IpDb;
use GeoIp2\Database\Reader; use GeoIp2\Database\Reader;
@ -18,7 +19,7 @@ if (!function_exists('base64_url_encode')) {
/** /**
* Encode Base64 for URLs * Encode Base64 for URLs
*/ */
function base64_url_encode($input) function base64_url_encode(string $input): string
{ {
return strtr(base64_encode($input), '+/=', '._-'); return strtr(base64_encode($input), '+/=', '._-');
} }
@ -28,7 +29,7 @@ if (!function_exists('base64_url_decode')) {
/** /**
* Decode Base64 from URL * Decode Base64 from URL
*/ */
function base64_url_decode($input) function base64_url_decode(string $input): string
{ {
return base64_decode(strtr($input, '._-', '+/=')); return base64_decode(strtr($input, '._-', '+/='));
} }
@ -131,7 +132,7 @@ if (!function_exists('set_user_session_location')) {
'longitude' => round($city->location->longitude, 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 // If things go wrong the show must go on and the user must be able to download the file
} catch (Exception $exception) { } catch (Exception) {
} }
$session->set('location', $location); $session->set('location', $location);
} }
@ -154,7 +155,7 @@ if (!function_exists('set_user_session_player')) {
try { try {
$playerFound = UserAgents::find($userAgent); $playerFound = UserAgents::find($userAgent);
// If things go wrong the show must go on and the user must be able to download the file // If things go wrong the show must go on and the user must be able to download the file
} catch (Exception $exception) { } catch (Exception) {
} }
if ($playerFound) { if ($playerFound) {
$session->set('player', $playerFound); $session->set('player', $playerFound);
@ -176,7 +177,7 @@ if (!function_exists('set_user_session_player')) {
[$userAgent], [$userAgent],
); );
// If things go wrong the show must go on and the user must be able to download the file // If things go wrong the show must go on and the user must be able to download the file
} catch (Exception $exception) { } catch (Exception) {
} }
} }
} }
@ -197,7 +198,7 @@ if (!function_exists('set_user_session_browser')) {
try { try {
$whichbrowser = new Parser(getallheaders()); $whichbrowser = new Parser(getallheaders());
$browserName = $whichbrowser->browser->name; $browserName = $whichbrowser->browser->name;
} catch (Exception $exception) { } catch (Exception) {
$browserName = '- Could not get browser name -'; $browserName = '- Could not get browser name -';
} }
if ($browserName == null) { if ($browserName == null) {
@ -267,6 +268,8 @@ if (!function_exists('podcast_hit')) {
* @param integer $episodeId The Episode ID * @param integer $episodeId The Episode ID
* @param integer $bytesThreshold The minimum total number of bytes that must be downloaded so that an episode is counted (>1mn) * @param integer $bytesThreshold The minimum total number of bytes that must be downloaded so that an episode is counted (>1mn)
* @param integer $fileSize The podcast complete file size * @param integer $fileSize The podcast complete file size
* @param integer $duration The episode duration in seconds
* @param int $publicationTime The episode's publication time as a UNIX timestamp
* @param string $serviceName The name of the service that had fetched the RSS feed * @param string $serviceName The name of the service that had fetched the RSS feed
*/ */
function podcast_hit( function podcast_hit(
@ -274,8 +277,8 @@ if (!function_exists('podcast_hit')) {
int $episodeId, int $episodeId,
int $bytesThreshold, int $bytesThreshold,
int $fileSize, int $fileSize,
$duration, int $duration,
$publicationDate, int $publicationTime,
string $serviceName string $serviceName
): void { ): void {
$session = Services::session(); $session = Services::session();
@ -341,7 +344,7 @@ if (!function_exists('podcast_hit')) {
$db = Database::connect(); $db = Database::connect();
$procedureName = $db->prefixTable('analytics_podcasts'); $procedureName = $db->prefixTable('analytics_podcasts');
$age = intdiv(time() - $publicationDate, 86400); $age = intdiv(time() - $publicationTime, 86400);
// We create a sha1 hash for this IP_Address+User_Agent+Podcast_ID (used to count unique listeners): // We create a sha1 hash for this IP_Address+User_Agent+Podcast_ID (used to count unique listeners):
$listenerHashId = $listenerHashId =

View File

@ -10,24 +10,20 @@
namespace Analytics\Models; namespace Analytics\Models;
use Analytics\Entities\AnalyticsUnknownUseragents; use Analytics\Entities\AnalyticsUnknownUserAgent;
use CodeIgniter\Model; use CodeIgniter\Model;
class AnalyticsUnknownUseragentsModel extends Model class AnalyticsUnknownUserAgentModel extends Model
{ {
/** /**
* @var string * @var string
*/ */
protected $table = 'analytics_unknown_useragents'; protected $table = 'analytics_unknown_useragents';
/**
* @var string
*/
protected $primaryKey = 'id';
/** /**
* @var string * @var string
*/ */
protected $returnType = AnalyticsUnknownUseragents::class; protected $returnType = AnalyticsUnknownUserAgent::class;
/** /**
* @var bool * @var bool
*/ */
@ -37,4 +33,12 @@ class AnalyticsUnknownUseragentsModel extends Model
* @var bool * @var bool
*/ */
protected $useTimestamps = false; protected $useTimestamps = false;
/**
* @return mixed[]
*/
public function getUserAgents(int $lastKnownId = 0): array
{
return $this->where('id >', $lastKnownId)->findAll();
}
} }

View File

@ -1,26 +0,0 @@
<?php
/**
* Class UnknownUserAgentsModel
* Model for analytics_unknown_useragents table in database
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace Analytics\Models;
use CodeIgniter\Model;
class UnknownUserAgentsModel extends Model
{
/**
* @var string
*/
protected $table = 'analytics_unknown_useragents';
public function getUserAgents($last_known_id = 0)
{
return $this->where('id >', $last_known_id)->findAll();
}
}

View File

@ -15,11 +15,12 @@ class Breadcrumb
/** /**
* List of breadcrumb links. * List of breadcrumb links.
* *
* @var array
* $links = [ * $links = [
* 'text' => (string) the anchor text, * 'text' => 'Example Link',
* 'href' => (string) the anchor href, * 'href' => 'https://example.com/',
* ] * ]
*
* @var array<array<string, string>>
*/ */
protected $links = []; protected $links = [];
@ -57,6 +58,8 @@ class Breadcrumb
* replaceParams($newParams); * replaceParams($newParams);
* *
* The breadcrumb is now `Home / podcasts / foo / episodes / bar` * The breadcrumb is now `Home / podcasts / foo / episodes / bar`
*
* @param string[] $newParams
*/ */
public function replaceParams(array $newParams): void public function replaceParams(array $newParams): void
{ {
@ -71,7 +74,7 @@ class Breadcrumb
/** /**
* Renders the breadcrumb object as an accessible html breadcrumb nav * Renders the breadcrumb object as an accessible html breadcrumb nav
*/ */
public function render($class = null): string public function render(string $class = null): string
{ {
$listItems = ''; $listItems = '';
$keys = array_keys($this->links); $keys = array_keys($this->links);

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