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:
- quality

View File

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

View File

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

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-alt Manual configuration](#1-alt-manual-configuration)
- [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)
- [Privileges](#privileges)
- [(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
### 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)
- [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.
*
* @param int|string $permission
*/
public function groupHasPermission($permission, int $groupId): bool
public function groupHasPermission(int|string $permission, int $groupId): bool
{
// Get the Permission ID
$permissionId = $this->getPermissionID($permission);

View File

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

View File

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

View File

@ -12,25 +12,21 @@ class Database extends Config
/**
* The directory that holds the Migrations
* 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
* use if no other is specified.
*
* @var string
*/
public $defaultGroup = 'default';
public string $defaultGroup = 'default';
/**
* The default database connection.
*
* @var array
* @var array<string, string|bool|int|array>
*/
public $default = [
public array $default = [
'DSN' => '',
'hostname' => 'localhost',
'username' => '',
@ -54,9 +50,9 @@ class Database extends Config
* This database connection is used when
* running PHPUnit database tests.
*
* @var array
* @var array<string, string|bool|int|array>
*/
public $tests = [
public array $tests = [
'DSN' => '',
'hostname' => '127.0.0.1',
'username' => '',

View File

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

View File

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

View File

@ -23,7 +23,10 @@ class Kint extends BaseConfig
|--------------------------------------------------------------------------
*/
public $plugins;
/**
* @var string[]
*/
public $plugins = [];
/**
* @var int
@ -60,9 +63,15 @@ class Kint extends BaseConfig
*/
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
* 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
*/
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
* the handler on top and continuing down.
*
* @var array
* @var array<string, string|int|array<string, string>>
*/
public $handlers = [
public array $handlers = [
/*
* --------------------------------------------------------------------
* File Handler
@ -125,9 +125,9 @@ class Logger extends BaseConfig
],
/**
* The ChromeLoggerHandler requires the use of the Chrome web browser
* and the ChromeLogger extension. Uncomment this block to use it.
*/
* The ChromeLoggerHandler requires the use of the Chrome web browser
* and the ChromeLogger extension. Uncomment this block to use it.
*/
// 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => [
// /*
// * The log levels that this handler will handle.

View File

@ -164,13 +164,13 @@ $routes->group(
]);
$routes->group('persons', function ($routes): void {
$routes->get('/', 'PodcastPersonController/$1', [
$routes->get('/', 'PodcastPodcastController/$1', [
'as' => 'podcast-person-manage',
'filter' => 'permission:podcast-edit',
]);
$routes->post(
'/',
'PodcastPersonController::attemptAdd/$1',
'PodcastPodcastController::attemptAdd/$1',
[
'filter' => 'permission:podcast-edit',
],
@ -178,7 +178,7 @@ $routes->group(
$routes->get(
'(:num)/remove',
'PodcastPersonController::remove/$1/$2',
'PodcastPodcastController::remove/$1/$2',
[
'as' => 'podcast-person-remove',
'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 correct Controller and Method to execute.
*
* @param RouteCollectionInterface|null $routes
* @param Request|null $request
* @param boolean $getShared
*
* @return Router
*/
public static function router(
RouteCollectionInterface $routes = null,
Request $request = null,
?RouteCollectionInterface $routes = null,
?Request $request = null,
bool $getShared = true
) {
): Router {
if ($getShared) {
return static::getSharedInstance('router', $routes, $request);
}
@ -60,16 +54,11 @@ class Services extends BaseService
* The Negotiate class provides the content negotiation features for
* working the request to determine correct language, encoding, charset,
* and more.
*
* @param RequestInterface|null $request
* @param boolean $getShared
*
* @return Negotiate
*/
public static function negotiator(
RequestInterface $request = null,
?RequestInterface $request = null,
bool $getShared = true
) {
): Negotiate {
if ($getShared) {
return static::getSharedInstance('negotiator', $request);
}
@ -79,6 +68,9 @@ class Services extends BaseService
return new Negotiate($request);
}
/**
* @return mixed
*/
public static function authentication(
string $lib = 'local',
Model $userModel = null,
@ -112,6 +104,9 @@ class Services extends BaseService
return $instance->setUserModel($userModel)->setLoginModel($loginModel);
}
/**
* @return mixed
*/
public static function authorization(
Model $groupModel = null,
Model $permissionModel = null,
@ -144,7 +139,7 @@ class Services extends BaseService
return $instance->setUserModel($userModel);
}
public static function breadcrumb(bool $getShared = true)
public static function breadcrumb(bool $getShared = true): Breadcrumb
{
if ($getShared) {
return self::getSharedInstance('breadcrumb');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,22 +8,21 @@
namespace App\Controllers\Admin;
use CodeIgniter\HTTP\RedirectResponse;
use App\Entities\Podcast;
use CodeIgniter\Exceptions\PageNotFoundException;
use ErrorException;
use Config\Database;
use Podlibre\PodcastNamespace\ReversedTaxonomy;
use App\Entities\PodcastPerson;
use App\Entities\Episode;
use App\Entities\Image;
use App\Entities\Person;
use App\Models\CategoryModel;
use App\Models\LanguageModel;
use App\Models\PodcastModel;
use App\Models\EpisodeModel;
use App\Models\PlatformModel;
use App\Models\PersonModel;
use App\Models\PodcastPersonModel;
use App\Models\EpisodePersonModel;
use Config\Services;
use League\HTMLToMarkdown\HtmlConverter;
@ -34,20 +33,20 @@ class PodcastImportController extends BaseController
*/
protected $podcast;
public function _remap(string $method, string ...$params)
public function _remap(string $method, string ...$params): mixed
{
if (count($params) === 0) {
return $this->$method();
}
if ($this->podcast = (new PodcastModel())->getPodcastById($params[0])) {
if (($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null) {
return $this->$method();
}
throw PageNotFoundException::forPageNotFound();
}
public function index()
public function index(): string
{
helper(['form', 'misc']);
@ -65,7 +64,7 @@ class PodcastImportController extends BaseController
return view('admin/podcast/import', $data);
}
public function attemptImport()
public function attemptImport(): RedirectResponse
{
helper(['media', 'misc']);
@ -92,11 +91,11 @@ class PodcastImportController extends BaseController
->withInput()
->with('errors', [
$errorException->getMessage() .
': <a href="' .
$this->request->getPost('imported_feed_url') .
'" rel="noreferrer noopener" target="_blank">' .
$this->request->getPost('imported_feed_url') .
' ⎋</a>',
': <a href="' .
$this->request->getPost('imported_feed_url') .
'" rel="noreferrer noopener" target="_blank">' .
$this->request->getPost('imported_feed_url') .
' ⎋</a>',
]);
}
$nsItunes = $feed->channel[0]->children(
@ -151,40 +150,40 @@ class PodcastImportController extends BaseController
'language_code' => $this->request->getPost('language'),
'category_id' => $this->request->getPost('category'),
'parental_advisory' =>
$nsItunes->explicit === null
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
$nsItunes->explicit === null
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
'owner_name' => (string) $nsItunes->owner->name,
'owner_email' => (string) $nsItunes->owner->email,
'publisher' => (string) $nsItunes->author,
'type' =>
$nsItunes->type === null ? 'episodic' : $nsItunes->type,
$nsItunes->type === null ? 'episodic' : $nsItunes->type,
'copyright' => (string) $feed->channel[0]->copyright,
'is_blocked' =>
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
'is_completed' =>
$nsItunes->complete === null
? false
: $nsItunes->complete === 'yes',
$nsItunes->complete === null
? false
: $nsItunes->complete === 'yes',
'location_name' => $nsPodcast->location
? (string) $nsPodcast->location
: null,
'location_geo' =>
!$nsPodcast->location ||
!$nsPodcast->location ||
$nsPodcast->location->attributes()['geo'] === null
? null
: (string) $nsPodcast->location->attributes()['geo'],
? null
: (string) $nsPodcast->location->attributes()['geo'],
'location_osm_id' =>
!$nsPodcast->location ||
!$nsPodcast->location ||
$nsPodcast->location->attributes()['osm'] === null
? null
: (string) $nsPodcast->location->attributes()['osm'],
? null
: (string) $nsPodcast->location->attributes()['osm'],
'created_by' => user_id(),
'updated_by' => user_id(),
]);
@ -194,11 +193,11 @@ class PodcastImportController extends BaseController
->withInput()
->with('errors', [
$ex->getMessage() .
': <a href="' .
$this->request->getPost('imported_feed_url') .
'" rel="noreferrer noopener" target="_blank">' .
$this->request->getPost('imported_feed_url') .
' ⎋</a>',
': <a href="' .
$this->request->getPost('imported_feed_url') .
'" rel="noreferrer noopener" target="_blank">' .
$this->request->getPost('imported_feed_url') .
' ⎋</a>',
]);
}
@ -235,7 +234,7 @@ class PodcastImportController extends BaseController
foreach ($platformType['elements'] as $platform) {
$platformLabel = $platform->attributes()['platform'];
$platformSlug = slugify($platformLabel);
if ($platformModel->getPlatform($platformSlug)) {
if ($platformModel->getPlatform($platformSlug) !== null) {
$podcastsPlatformsData[] = [
'platform_slug' => $platformSlug,
'podcast_id' => $newPodcastId,
@ -255,45 +254,41 @@ class PodcastImportController extends BaseController
}
foreach ($nsPodcast->person as $podcastPerson) {
$fullName = (string) $podcastPerson;
$personModel = new PersonModel();
$newPersonId = null;
if ($newPerson = $personModel->getPerson($podcastPerson)) {
if (($newPerson = $personModel->getPerson($fullName)) !== null) {
$newPersonId = $newPerson->id;
} elseif (
!($newPersonId = $personModel->createPerson(
$podcastPerson,
$podcastPerson->attributes()['href'],
$podcastPerson->attributes()['img'],
))
) {
return redirect()
->back()
->withInput()
->with('errors', $personModel->errors());
} else {
$newPodcastPerson = new Person([
'full_name' => $fullName,
'unique_name' => slugify($fullName),
'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()
->back()
->withInput()
->with('errors', $personModel->errors());
}
}
$personGroup =
$podcastPerson->attributes()['group'] === null
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
(string) $podcastPerson->attributes()['group']
];
isset($podcastPerson->attributes()['group'])
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[(string) $podcastPerson->attributes()['group']];
$personRole =
$podcastPerson->attributes()['role'] === null ||
isset($podcastPerson->attributes()['role']) ||
$personGroup === null
? ['slug' => '']
: $personGroup['roles'][
strval($podcastPerson->attributes()['role'])
];
$newPodcastPerson = new PodcastPerson([
'podcast_id' => $newPodcastId,
'person_id' => $newPersonId,
'person_group' => $personGroup['slug'],
'person_role' => $personRole['slug'],
]);
$podcastPersonModel = new PodcastPersonModel();
? ['slug' => '']
: $personGroup['roles'][strval($podcastPerson->attributes()['role'])];
if (!$podcastPersonModel->insert($newPodcastPerson)) {
$podcastPersonModel = new PersonModel();
if (!$podcastPersonModel->addPodcastPerson($newPodcastId, $newPersonId, $personGroup['slug'], $personRole['slug'])) {
return redirect()
->back()
->withInput()
@ -305,8 +300,8 @@ class PodcastImportController extends BaseController
$lastItem =
$this->request->getPost('max_episodes') !== null &&
$this->request->getPost('max_episodes') < $numberItems
? $this->request->getPost('max_episodes')
: $numberItems;
? $this->request->getPost('max_episodes')
: $numberItems;
$slugs = [];
@ -338,22 +333,12 @@ class PodcastImportController extends BaseController
$slug = $slug . '-' . $slugNumber;
}
$slugs[] = $slug;
$itemDescriptionHtml = null;
switch ($this->request->getPost('description_field')) {
case 'content':
$itemDescriptionHtml = $nsContent->encoded;
break;
case 'summary':
$itemDescriptionHtml = $nsItunes->summary;
break;
case 'subtitle_summary':
$itemDescriptionHtml =
$nsItunes->subtitle . '<br/>' . $nsItunes->summary;
break;
default:
$itemDescriptionHtml = $item->description;
}
$itemDescriptionHtml = match ($this->request->getPost('description_field')) {
'content' => $nsContent->encoded,
'summary' => $nsItunes->summary,
'subtitle_summary' => $nsItunes->subtitle . '<br/>' . $nsItunes->summary,
default => $item->description,
};
if (
$nsItunes->image !== null &&
@ -382,42 +367,42 @@ class PodcastImportController extends BaseController
'description_html' => $itemDescriptionHtml,
'image' => $episodeImage,
'parental_advisory' =>
$nsItunes->explicit === null
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
$nsItunes->explicit === null
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
'number' =>
$this->request->getPost('force_renumber') === 'yes'
? $itemNumber
: $nsItunes->episode,
$this->request->getPost('force_renumber') === 'yes'
? $itemNumber
: $nsItunes->episode,
'season_number' =>
$this->request->getPost('season_number') === null
? $nsItunes->season
: $this->request->getPost('season_number'),
$this->request->getPost('season_number') === null
? $nsItunes->season
: $this->request->getPost('season_number'),
'type' =>
$nsItunes->episodeType === null
? 'full'
: $nsItunes->episodeType,
$nsItunes->episodeType === null
? 'full'
: $nsItunes->episodeType,
'is_blocked' =>
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
'location_name' => $nsPodcast->location
? $nsPodcast->location
: null,
'location_geo' =>
!$nsPodcast->location ||
!$nsPodcast->location ||
$nsPodcast->location->attributes()['geo'] === null
? null
: $nsPodcast->location->attributes()['geo'],
? null
: $nsPodcast->location->attributes()['geo'],
'location_osm_id' =>
!$nsPodcast->location ||
!$nsPodcast->location ||
$nsPodcast->location->attributes()['osm'] === null
? null
: $nsPodcast->location->attributes()['osm'],
? null
: $nsPodcast->location->attributes()['osm'],
'created_by' => user_id(),
'updated_by' => user_id(),
'published_at' => strtotime($item->pubDate),
@ -434,46 +419,40 @@ class PodcastImportController extends BaseController
}
foreach ($nsPodcast->person as $episodePerson) {
$fullName = (string) $episodePerson;
$personModel = new PersonModel();
$newPersonId = null;
if ($newPerson = $personModel->getPerson($episodePerson)) {
if (($newPerson = $personModel->getPerson($fullName)) !== null) {
$newPersonId = $newPerson->id;
} elseif (
!($newPersonId = $personModel->createPerson(
$episodePerson,
$episodePerson->attributes()['href'],
$episodePerson->attributes()['img'],
))
) {
return redirect()
->back()
->withInput()
->with('errors', $personModel->errors());
} else {
$newEpisodePerson = new Person([
'full_name' => $fullName,
'slug' => slugify($fullName),
'information_url' => $episodePerson->attributes()['href'],
'image' => new Image(download_file($episodePerson->attributes()['img']))
]);
if (!($newPersonId = $personModel->insert($newEpisodePerson))) {
return redirect()
->back()
->withInput()
->with('errors', $personModel->errors());
}
}
$personGroup =
$episodePerson->attributes()['group'] === null
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
strval($episodePerson->attributes()['group'])
];
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[strval($episodePerson->attributes()['group'])];
$personRole =
$episodePerson->attributes()['role'] === null ||
$personGroup === null
? ['slug' => '']
: $personGroup['roles'][
strval($episodePerson->attributes()['role'])
];
$newEpisodePerson = new PodcastPerson([
'podcast_id' => $newPodcastId,
'episode_id' => $newEpisodeId,
'person_id' => $newPersonId,
'person_group' => $personGroup['slug'],
'person_role' => $personRole['slug'],
]);
? ['slug' => '']
: $personGroup['roles'][strval($episodePerson->attributes()['role'])];
$episodePersonModel = new EpisodePersonModel();
if (!$episodePersonModel->insert($newEpisodePerson)) {
$episodePersonModel = new PersonModel();
if (!$episodePersonModel->addEpisodePerson($newPodcastId, $newEpisodeId, $newPersonId, $personGroup['slug'], $personRole['slug'])) {
return redirect()
->back()
->withInput()

View File

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

View File

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

View File

@ -12,6 +12,7 @@ use CodeIgniter\Exceptions\PageNotFoundException;
use App\Authorization\GroupModel;
use App\Entities\User;
use App\Models\UserModel;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services;
class UserController extends BaseController
@ -21,7 +22,7 @@ class UserController extends BaseController
*/
protected $user;
public function _remap($method, ...$params)
public function _remap(string $method, string ...$params): mixed
{
if (count($params) === 0) {
return $this->$method();
@ -34,14 +35,14 @@ class UserController extends BaseController
throw PageNotFoundException::forPageNotFound();
}
public function list()
public function list(): string
{
$data = ['users' => (new UserModel())->findAll()];
return view('admin/user/list', $data);
}
public function view()
public function view(): string
{
$data = ['user' => $this->user];
@ -49,7 +50,7 @@ class UserController extends BaseController
return view('admin/user/view', $data);
}
public function create()
public function create(): string
{
helper('form');
@ -60,7 +61,7 @@ class UserController extends BaseController
return view('admin/user/create', $data);
}
public function attemptCreate()
public function attemptCreate(): RedirectResponse
{
$userModel = new UserModel();
@ -108,7 +109,7 @@ class UserController extends BaseController
);
}
public function edit()
public function edit(): string
{
helper('form');
@ -131,7 +132,7 @@ class UserController extends BaseController
return view('admin/user/edit', $data);
}
public function attemptEdit()
public function attemptEdit(): RedirectResponse
{
$authorize = Services::authorization();
@ -149,7 +150,7 @@ class UserController extends BaseController
);
}
public function forcePassReset()
public function forcePassReset(): RedirectResponse
{
$userModel = new UserModel();
$this->user->forcePasswordReset();
@ -171,7 +172,7 @@ class UserController extends BaseController
);
}
public function ban()
public function ban(): RedirectResponse
{
$authorize = Services::authorization();
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();
$this->user->unBan();
@ -225,7 +226,7 @@ class UserController extends BaseController
);
}
public function delete()
public function delete(): RedirectResponse
{
$authorize = Services::authorization();
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
* upon class instantiation.
*
* @var array
* @var string[]
*/
protected $helpers = ['components'];
/**
* Attempt to register a new user.
*/
public function attemptRegister()
public function attemptRegister(): RedirectResponse
{
// Check if registration is allowed
if (!$this->config->allowRegistration) {
@ -61,9 +61,9 @@ class AuthController extends MythAuthController
);
$user = new User($this->request->getPost($allowedPostFields));
$this->config->requireActivation !== false
? $user->generateActivateHash()
: $user->activate();
$this->config->requireActivation === null
? $user->activate()
: $user->generateActivateHash();
// Ensure default group gets assigned if set
if ($this->config->defaultUserGroup !== null) {
@ -77,7 +77,7 @@ class AuthController extends MythAuthController
->with('errors', $users->errors());
}
if ($this->config->requireActivation !== false) {
if ($this->config->requireActivation !== null) {
$activator = service('activator');
$sent = $activator->send($user);
@ -109,7 +109,7 @@ class AuthController extends MythAuthController
*/
public function attemptReset(): RedirectResponse
{
if ($this->config->activeResetter === false) {
if ($this->config->activeResetter === null) {
return redirect()
->route('login')
->with('error', lang('Auth.forgotDisabled'));
@ -173,7 +173,7 @@ class AuthController extends MythAuthController
->with('message', lang('Auth.resetSuccess'));
}
public function attemptInteractAsActor()
public function attemptInteractAsActor(): RedirectResponse
{
$rules = [
'actor_id' => 'required|numeric',

View File

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

View File

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

View File

@ -9,20 +9,18 @@
namespace App\Controllers;
use App\Models\PodcastModel;
use CodeIgniter\HTTP\RedirectResponse;
class HomeController extends BaseController
{
/**
* @return RedirectResponse|string
*/
public function index()
public function index(): RedirectResponse|string
{
$model = new PodcastModel();
$allPodcasts = $model->findAll();
// 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', [
$allPodcasts[0]->name,
]);

View File

@ -19,6 +19,7 @@ use Config\Database;
use App\Entities\User;
use App\Models\UserModel;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services;
use Dotenv\Dotenv;
@ -51,7 +52,14 @@ class InstallController extends Controller
public function index(): string
{
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
@ -85,7 +93,7 @@ class InstallController extends Controller
try {
$dotenv->required('cache.handler');
} catch (ValidationException $validationException) {
} catch (ValidationException) {
return $this->cacheConfig();
}
} else {
@ -101,7 +109,7 @@ class InstallController extends Controller
'database.default.DBPrefix',
'cache.handler',
]);
} catch (ValidationException $e) {
} catch (ValidationException) {
return view('install/manual_config');
}
}
@ -117,7 +125,7 @@ class InstallController extends Controller
// if so, show a 404 page
throw PageNotFoundException::forPageNotFound();
}
} catch (DatabaseException $databaseException) {
} catch (DatabaseException) {
// Could not connect to the database
// show database config view to fix value
session()->setFlashdata(
@ -137,28 +145,12 @@ class InstallController extends Controller
return $this->createSuperAdmin();
}
/**
* 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()
public function instanceConfig(): string
{
return view('install/instance_config');
}
public function attemptInstanceConfig()
public function attemptInstanceConfig(): RedirectResponse
{
$rules = [
'hostname' => 'required|validate_url',
@ -198,12 +190,12 @@ class InstallController extends Controller
);
}
public function databaseConfig()
public function databaseConfig(): string
{
return view('install/database_config');
}
public function attemptDatabaseConfig()
public function attemptDatabaseConfig(): RedirectResponse
{
$rules = [
'db_hostname' => 'required',
@ -236,12 +228,12 @@ class InstallController extends Controller
return redirect()->back();
}
public function cacheConfig()
public function cacheConfig(): string
{
return view('install/cache_config');
}
public function attemptCacheConfig()
public function attemptCacheConfig(): RedirectResponse
{
$rules = [
'cache_handler' => 'required',
@ -288,7 +280,7 @@ class InstallController extends Controller
/**
* 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');
}
@ -298,7 +290,7 @@ class InstallController extends Controller
*
* After creation, user is redirected to login page to input its credentials.
*/
public function attemptCreateSuperAdmin()
public function attemptCreateSuperAdmin(): RedirectResponse
{
$userModel = new UserModel();
@ -356,7 +348,7 @@ class InstallController extends Controller
* writes config values in .env file
* 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
{
@ -370,7 +362,7 @@ class InstallController extends Controller
$keyVal,
&$replaced
) {
if (strpos($line, (string) $key) === 0) {
if (str_starts_with($line, (string) $key)) {
$replaced = true;
return $keyVal;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -11,8 +11,8 @@ namespace App\Entities;
use App\Libraries\SimpleRSSElement;
use App\Models\CategoryModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PlatformModel;
use App\Models\PodcastPersonModel;
use CodeIgniter\Entity\Entity;
use App\Models\UserModel;
use CodeIgniter\I18n\Time;
@ -22,7 +22,7 @@ use RuntimeException;
/**
* @property int $id
* @property int $actor_id
* @property Actor $actor
* @property Actor|null $actor
* @property string $name
* @property string $link
* @property string $feed_url
@ -35,7 +35,7 @@ use RuntimeException;
* @property string $image_mimetype
* @property string $language_code
* @property int $category_id
* @property Category $category
* @property Category|null $category
* @property int[] $other_categories_ids
* @property Category[] $other_categories
* @property string|null $parental_advisory
@ -68,7 +68,7 @@ use RuntimeException;
* @property Time|null $deleted_at;
*
* @property Episode[] $episodes
* @property PodcastPerson[] $persons
* @property Person[] $persons
* @property User[] $contributors
* @property Platform[] $podcasting_platforms
* @property Platform[] $social_platforms
@ -77,80 +77,54 @@ use RuntimeException;
*/
class Podcast extends Entity
{
/**
* @var string
*/
protected $link;
/**
* @var Actor
*/
protected $actor;
/**
* @var Image
*/
protected $image;
/**
* @var string
*/
protected $description;
/**
* @var Category
*/
protected $category;
protected string $link;
protected ?Actor $actor;
protected Image $image;
protected string $description;
protected ?Category $category;
/**
* @var Category[]
*/
protected $other_categories;
protected $other_categories = [];
/**
* @var string[]
*/
protected $other_categories_ids;
protected $other_categories_ids = [];
/**
* @var Episode[]
*/
protected $episodes;
protected $episodes = [];
/**
* @var PodcastPerson[]
* @var Person[]
*/
protected $persons;
protected $persons = [];
/**
* @var User[]
*/
protected $contributors;
protected $contributors = [];
/**
* @var Platform[]
*/
protected $podcasting_platforms;
protected $podcasting_platforms = [];
/**
* @var Platform[]
*/
protected $social_platforms;
protected $social_platforms = [];
/**
* @var Platform[]
*/
protected $funding_platforms;
protected $funding_platforms = [];
/**
* @var Location|null
*/
protected $location;
/**
* @var string
*/
protected $custom_rss_string;
protected ?Location $location;
protected string $custom_rss_string;
/**
* @var array<string, string>
@ -193,7 +167,7 @@ class Podcast extends Entity
public function getActor(): Actor
{
if (!$this->actor_id) {
if ($this->actor_id === 0) {
throw new RuntimeException(
'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/`
*
* @param Image $image
*/
public function setImage($image): self
public function setImage(Image $image): static
{
// Save image
$image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
@ -263,7 +235,7 @@ class Podcast extends Entity
/**
* Returns the podcast's persons
*
* @return PodcastPerson[]
* @return Person[]
*/
public function getPersons(): array
{
@ -274,9 +246,7 @@ class Podcast extends Entity
}
if (empty($this->persons)) {
$this->persons = (new PodcastPersonModel())->getPodcastPersons(
$this->id,
);
$this->persons = (new PersonModel())->getPodcastPersons($this->id);
}
return $this->persons;
@ -284,18 +254,16 @@ class Podcast extends 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(
'Podcast must be created before getting category.',
);
}
if (empty($this->category)) {
if ($this->category === null) {
$this->category = (new CategoryModel())->getCategoryById(
$this->category_id,
);
@ -326,7 +294,7 @@ class Podcast extends Entity
return $this->contributors;
}
public function setDescriptionMarkdown(string $descriptionMarkdown): self
public function setDescriptionMarkdown(string $descriptionMarkdown): static
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
@ -343,7 +311,7 @@ class Podcast extends Entity
public function setEpisodeDescriptionFooterMarkdown(
?string $episodeDescriptionFooterMarkdown = null
): self {
): static {
if ($episodeDescriptionFooterMarkdown) {
$converter = new CommonMarkConverter([
'html_input' => 'strip',
@ -363,13 +331,13 @@ class Podcast extends Entity
public function getDescription(): string
{
if ($this->description) {
if ($this->description !== '') {
return $this->description;
}
return trim(
preg_replace(
'/\s+/',
'~\s+~',
' ',
strip_tags($this->attributes['description_html']),
),
@ -483,7 +451,7 @@ class Podcast extends Entity
/**
* Saves the location name and fetches OpenStreetMap info
*/
public function setLocation(?string $newLocationName = null)
public function setLocation(?string $newLocationName = null): static
{
if ($newLocationName === null) {
$this->attributes['location_name'] = null;
@ -529,8 +497,6 @@ class Podcast extends Entity
/**
* Get custom rss tag as XML String
*
* @return string
*/
function getCustomRssString(): string
{
@ -555,10 +521,8 @@ class Podcast extends Entity
/**
* Saves custom rss tag into json
*
* @param string $customRssString
*/
function setCustomRssString($customRssString): self
function setCustomRssString(string $customRssString): static
{
if (empty($customRssString)) {
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,
* redirects, etc.
*
* @param array|null $params
* @param string[]|null $params
* @return void|mixed
*/
public function before(RequestInterface $request, $params = null)
@ -50,8 +50,8 @@ class PermissionFilter implements FilterInterface
foreach ($params as $permission) {
// check if permission is for a specific podcast
if (
(startsWith($permission, 'podcast-') ||
startsWith($permission, 'podcast_episodes-')) &&
(str_starts_with($permission, 'podcast-') ||
str_starts_with($permission, 'podcast_episodes-')) &&
count($routerParams) > 0
) {
if (
@ -91,7 +91,7 @@ class PermissionFilter implements FilterInterface
* to stop execution of other after filters, short of
* throwing an Exception or Error.
*
* @param array|null $arguments
* @param string[]|null $arguments
*/
public function after(
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
*/
function set_interact_as_actor($actorId): void
function set_interact_as_actor(int $actorId): void
{
$authenticate = Services::authentication();
$authenticate->check();
@ -65,10 +65,8 @@ if (!function_exists('interact_as_actor_id')) {
if (!function_exists('interact_as_actor')) {
/**
* 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->check();

View File

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

View File

@ -15,9 +15,7 @@ if (!function_exists('form_section')) {
*
* @param string $title The section title
* @param string $subtitle The section subtitle
* @param array $attributes Additional attributes
*
* @return string
* @param array<string, string> $attributes Additional attributes
*/
function form_section(
string $title = '',
@ -54,9 +52,7 @@ if (!function_exists('form_section_close')) {
/**
* Form Section close Tag
*
* @param string $extra
*
* @return 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
*
* @return string
* @param mixed[] $data
* @param mixed[] $extra
*/
function form_switch(
$label = '',
string $label = '',
array $data = [],
string $value = '',
bool $checked = false,
@ -104,11 +101,9 @@ if (!function_exists('form_label')) {
*
* @param string $label_text The text to appear onscreen
* @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 boolean $isOptional adds an optional text if true
*
* @return string
*/
function form_label(
string $label_text = '',
@ -151,7 +146,9 @@ if (!function_exists('form_multiselect')) {
/**
* Multi-select menu
*
* @return string
* @param array<string, string> $options
* @param string[] $selected
* @param array<string, string> $customExtra
*/
function form_multiselect(
string $name = '',

View File

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

View File

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

View File

@ -20,7 +20,7 @@ if (!function_exists('save_media')) {
function save_media(
File $file,
string $folder = '',
string $filename
string $filename = ''
): string {
if (($extension = $file->getExtension()) !== '') {
$filename = $filename . '.' . $extension;
@ -91,9 +91,9 @@ if (!function_exists('media_path')) {
/**
* 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
if (is_array($uri)) {
@ -109,9 +109,9 @@ if (!function_exists('media_base_url')) {
/**
* Return the media base URL to use in views
*
* @param string|string[] $uri URI string or array of URI segments
* @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
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')) {
function slugify($text)
function slugify(string $text): string
{
if (empty($text)) {
return 'n-a';
}
// replace non letter or digits by -
$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) {
$podcastPersonElement = $channel->addChild(
'person',
htmlspecialchars($podcastPerson->person->full_name),
htmlspecialchars($podcastPerson->full_name),
$podcast_namespace,
);
if (
$podcastPerson->person_role !== null &&
$podcastPerson->person_group !== null
$podcastPerson->role !== null &&
$podcastPerson->role !== null
) {
$podcastPersonElement->addAttribute(
'role',
htmlspecialchars(
lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.roles.{$podcastPerson->person_role}.label",
"PersonsTaxonomy.persons.{$podcastPerson->group}.roles.{$podcastPerson->role}.label",
[],
'en',
),
@ -207,27 +207,28 @@ if (!function_exists('get_rss_feed')) {
);
}
if ($podcastPerson->person_group !== null) {
if ($podcastPerson->group !== null) {
$podcastPersonElement->addAttribute(
'group',
htmlspecialchars(
lang(
"PersonsTaxonomy.persons.{$podcastPerson->person_group}.label",
"PersonsTaxonomy.persons.{$podcastPerson->group}.label",
[],
'en',
),
),
);
}
$podcastPersonElement->addAttribute(
'img',
$podcastPerson->person->image->large_url,
$podcastPerson->image->large_url,
);
if ($podcastPerson->person->information_url !== null) {
if ($podcastPerson->information_url !== null) {
$podcastPersonElement->addAttribute(
'href',
$podcastPerson->person->information_url,
$podcastPerson->information_url,
);
}
}
@ -417,18 +418,18 @@ if (!function_exists('get_rss_feed')) {
foreach ($episode->persons as $episodePerson) {
$episodePersonElement = $item->addChild(
'person',
htmlspecialchars($episodePerson->person->full_name),
htmlspecialchars($episodePerson->full_name),
$podcast_namespace,
);
if (
!empty($episodePerson->person_role) &&
!empty($episodePerson->person_group)
!empty($episodePerson->role) &&
!empty($episodePerson->group)
) {
$episodePersonElement->addAttribute(
'role',
htmlspecialchars(
lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.roles.{$episodePerson->person_role}.label",
"PersonsTaxonomy.persons.{$episodePerson->group}.roles.{$episodePerson->role}.label",
[],
'en',
),
@ -440,7 +441,7 @@ if (!function_exists('get_rss_feed')) {
'group',
htmlspecialchars(
lang(
"PersonsTaxonomy.persons.{$episodePerson->person_group}.label",
"PersonsTaxonomy.persons.{$episodePerson->group}.label",
[],
'en',
),
@ -449,12 +450,12 @@ if (!function_exists('get_rss_feed')) {
}
$episodePersonElement->addAttribute(
'img',
$episodePerson->person->image->large_url,
$episodePerson->image->large_url,
);
if (!empty($episodePerson->person->information_url)) {
if (!empty($episodePerson->information_url)) {
$episodePersonElement->addAttribute(
'href',
$episodePerson->person->information_url,
$episodePerson->information_url,
);
}
}
@ -512,10 +513,11 @@ if (!function_exists('rss_to_array')) {
/**
* Converts XML to array
*
* FIXME: should be SimpleRSSElement
* @param SimpleXMLElement $xmlNode
* FIXME: param should be SimpleRSSElement
*
* @return array<string, mixed>
*/
function rss_to_array(SimpleXMLElement $xmlNode): array
function rss_to_array(SimpleXMLElement $rssNode): array
{
$nameSpaces = [
'',
@ -523,17 +525,17 @@ if (!function_exists('rss_to_array')) {
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
];
$arrayNode = [];
$arrayNode['name'] = $xmlNode->getName();
$arrayNode['namespace'] = $xmlNode->getNamespaces(false);
foreach ($xmlNode->attributes() as $key => $value) {
$arrayNode['name'] = $rssNode->getName();
$arrayNode['namespace'] = $rssNode->getNamespaces(false);
foreach ($rssNode->attributes() as $key => $value) {
$arrayNode['attributes'][$key] = (string) $value;
}
$textcontent = trim((string) $xmlNode);
$textcontent = trim((string) $rssNode);
if (strlen($textcontent) > 0) {
$arrayNode['content'] = $textcontent;
}
foreach ($nameSpaces as $currentNameSpace) {
foreach ($xmlNode->children($currentNameSpace) as $childXmlNode) {
foreach ($rssNode->children($currentNameSpace) as $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
*
* @param array<string, mixed> $arrayNode
* @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)) {
foreach ($arrayNode['elements'] as $childArrayNode) {
$childXmlNode = $xmlNode->addChild(

View File

@ -47,7 +47,9 @@ if (!function_exists('current_season_url')) {
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
{

View File

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

View File

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

View File

@ -8,6 +8,8 @@
namespace ActivityPub\Config;
use ActivityPub\Objects\ActorObject;
use ActivityPub\Objects\NoteObject;
use CodeIgniter\Config\BaseConfig;
class ActivityPub extends BaseConfig
@ -18,12 +20,12 @@ class ActivityPub extends BaseConfig
* --------------------------------------------------------------------
* @var string
*/
public $actorObject = 'ActivityPub\Objects\ActorObject';
public $actorObject = ActorObject::class;
/**
* @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');
}
public function _remap($method, ...$params)
public function _remap(string $method, string ...$params): mixed
{
if (
count($params) > 0 &&
@ -301,10 +301,7 @@ class ActorController extends Controller
->setBody($followersCollection->toJSON());
}
/**
* @return mixed|ResponseInterface
*/
public function attemptFollow()
public function attemptFollow(): RedirectResponse|ResponseInterface
{
$rules = [
'handle' =>
@ -354,7 +351,7 @@ class ActorController extends Controller
);
}
public function activity($activityId): RedirectResponse
public function activity(string $activityId): RedirectResponse
{
if (
!($activity = model('ActivityModel')->getActivityById($activityId))

View File

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

View File

@ -41,7 +41,7 @@ class NoteController extends Controller
$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]))) {
throw PageNotFoundException::forPageNotFound();
@ -63,8 +63,7 @@ class NoteController extends Controller
public function replies(): RedirectResponse
{
/** get note replies
* @var NoteModel */
/** get note replies */
$noteReplies = model('NoteModel')
->where(
'in_reply_to_id',
@ -216,10 +215,7 @@ class NoteController extends Controller
return redirect()->back();
}
/**
* @return mixed|ResponseInterface
*/
public function attemptRemoteAction(string $action)
public function attemptRemoteAction(string $action): RedirectResponse|ResponseInterface
{
$rules = [
'handle' =>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,8 +18,6 @@ use CodeIgniter\HTTP\Exceptions\HTTPException;
if (!function_exists('get_webfinger_data')) {
/**
* Retrieve actor webfinger data from username and domain
*
* @return object|null
*/
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)
*
* @param string $handle
* @return bool|array
* @return array<string, string>|false
*/
function split_handle(string $handle)
{
@ -107,7 +104,7 @@ if (!function_exists('accept_follow')) {
);
$acceptRequest->sign($actor->public_key_id, $actor->private_key);
$acceptRequest->post();
} catch (Exception $exception) {
} catch (Exception) {
$db->transRollback();
}
@ -163,8 +160,6 @@ if (!function_exists('extract_urls_from_message')) {
if (!function_exists('create_preview_card_from_url')) {
/**
* Extract open graph metadata from given url and create preview card
*
* @return PreviewCard|null
*/
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')) {
/**
* 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
{
@ -246,8 +239,6 @@ if (!function_exists('get_or_create_actor_from_uri')) {
/**
* Retrieves actor from database using the actor uri
* 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
{
@ -265,8 +256,6 @@ if (!function_exists('get_or_create_actor')) {
/**
* 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.
*
* @return Actor|null
*/
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
* the info gathered from the actorUri parameter
*
* @return Actor|null
*/
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')) {
/**
* Extracts the text from html content
*
* @return string|null
*/
function extract_text_from_html(string $content): ?string
{
@ -381,7 +366,7 @@ if (!function_exists('linkify')) {
case 'https':
$text = preg_replace_callback(
'~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i',
function ($match) use ($protocol, &$links) {
function (array $match) use ($protocol, &$links) {
if ($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
return '<' .
array_push($links, $match[0]) .
@ -485,7 +470,7 @@ if (!function_exists('linkify')) {
'~' .
preg_quote($protocol, '~') .
'://([^\s<]+?)(?<![\.,:])~i',
function ($match) use ($protocol, &$links) {
function (array $match) use ($protocol, &$links) {
return '<' .
array_push(
$links,

View File

@ -41,7 +41,7 @@ class HttpSignature
/**
* @var IncomingRequest
*/
protected $request;
protected ?IncomingRequest $request;
public function __construct(IncomingRequest $request = null)
{
@ -130,9 +130,9 @@ class HttpSignature
/**
* 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)) {
// Signature pattern failed
@ -150,7 +150,7 @@ class HttpSignature
/**
* 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
{

View File

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

View File

@ -58,7 +58,7 @@ class ActorModel extends Model
*/
protected $useTimestamps = true;
public function getActorById($id): Actor
public function getActorById(int $id): Actor
{
$cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}";
if (!($found = cache($cacheName))) {
@ -98,7 +98,7 @@ class ActorModel extends Model
return $found;
}
public function getActorByUri($actorUri)
public function getActorByUri(string $actorUri): ?Actor
{
$hashedActorUri = md5($actorUri);
$cacheName =
@ -112,7 +112,10 @@ class ActorModel extends Model
return $found;
}
public function getFollowers($actorId)
/**
* @return Actor[]
*/
public function getFollowers(int $actorId): array
{
$cacheName =
config('ActivityPub')->cachePrefix . "actor#{$actorId}_followers";
@ -137,7 +140,7 @@ class ActorModel extends Model
*/
public function isActorBlocked(string $actorUri): bool
{
if ($actor = $this->getActorByUri($actorUri)) {
if (($actor = $this->getActorByUri($actorUri)) !== null) {
return $actor->is_blocked;
}
@ -161,7 +164,7 @@ class ActorModel extends Model
return $found;
}
public function blockActor($actorId): void
public function blockActor(int $actorId): void
{
$prefix = config('ActivityPub')->cachePrefix;
cache()->delete($prefix . 'blocked_actors');
@ -172,7 +175,7 @@ class ActorModel extends Model
$this->update($actorId, ['is_blocked' => 1]);
}
public function unblockActor($actorId): void
public function unblockActor(int $actorId): void
{
$prefix = config('ActivityPub')->cachePrefix;
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.
*
* @return BlockedDomain[]
*/
public function getBlockedDomains()
public function getBlockedDomains(): array
{
$cacheName = config('ActivityPub')->cachePrefix . 'blocked_domains';
if (!($found = cache($cacheName))) {
@ -61,14 +63,14 @@ class BlockedDomainModel extends Model
return $found;
}
public function isDomainBlocked($domain)
public function isDomainBlocked(string $name): bool
{
$hashedDomain = md5($domain);
$hashedDomainName = md5($name);
$cacheName =
config('ActivityPub')->cachePrefix .
"domain#{$hashedDomain}_isBlocked";
"domain#{$hashedDomainName}_isBlocked";
if (!($found = cache($cacheName))) {
$found = (bool) $this->find($domain);
$found = (bool) $this->find($name);
cache()->save($cacheName, $found, DECADE);
}
@ -76,7 +78,7 @@ class BlockedDomainModel extends Model
return $found;
}
public function blockDomain($name)
public function blockDomain(string $name): int|bool
{
$hashedDomain = md5($name);
$prefix = config('ActivityPub')->cachePrefix;
@ -104,10 +106,7 @@ class BlockedDomainModel extends Model
return $result;
}
/**
* @return bool|BaseResult
*/
public function unblockDomain($name)
public function unblockDomain(string $name): BaseResult|bool
{
$hashedDomain = md5($name);
$prefix = config('ActivityPub')->cachePrefix;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ class AnalyticsController extends Controller
*/
protected $methodName;
public function _remap($method, ...$params)
public function _remap(string $method, string ...$params): mixed
{
if (!isset($params[1])) {
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();
$methodName = $this->methodName;
if ($episodeId) {
if ($episodeId !== 0) {
return $this->response->setJSON(
$analytics_model->$methodName($podcastId, $episodeId),
);

View File

@ -23,7 +23,7 @@ class EpisodeAnalyticsController extends Controller
* class instantiation. These helpers will be available
* to all other controllers that extend Analytics.
*
* @var array
* @var string[]
*/
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
{
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));
}

View File

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

View File

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

View File

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

View File

@ -13,17 +13,24 @@ namespace Analytics\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property int $useragent
* @property int $hits
* @property Time $created_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>
*/
protected $casts = [
'id' => 'integer',
'useragent' => 'integer',
'hits' => 'integer',
];

View File

@ -1,5 +1,6 @@
<?php
use CodeIgniter\I18n\Time;
use Config\Services;
use Podlibre\Ipcat\IpDb;
use GeoIp2\Database\Reader;
@ -18,7 +19,7 @@ if (!function_exists('base64_url_encode')) {
/**
* Encode Base64 for URLs
*/
function base64_url_encode($input)
function base64_url_encode(string $input): string
{
return strtr(base64_encode($input), '+/=', '._-');
}
@ -28,7 +29,7 @@ if (!function_exists('base64_url_decode')) {
/**
* Decode Base64 from URL
*/
function base64_url_decode($input)
function base64_url_decode(string $input): string
{
return base64_decode(strtr($input, '._-', '+/='));
}
@ -131,7 +132,7 @@ if (!function_exists('set_user_session_location')) {
'longitude' => round($city->location->longitude, 3),
];
// If things go wrong the show must go on and the user must be able to download the file
} catch (Exception $exception) {
} catch (Exception) {
}
$session->set('location', $location);
}
@ -154,7 +155,7 @@ if (!function_exists('set_user_session_player')) {
try {
$playerFound = UserAgents::find($userAgent);
// 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) {
$session->set('player', $playerFound);
@ -176,7 +177,7 @@ if (!function_exists('set_user_session_player')) {
[$userAgent],
);
// 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 {
$whichbrowser = new Parser(getallheaders());
$browserName = $whichbrowser->browser->name;
} catch (Exception $exception) {
} catch (Exception) {
$browserName = '- Could not get browser name -';
}
if ($browserName == null) {
@ -267,6 +268,8 @@ if (!function_exists('podcast_hit')) {
* @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 $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
*/
function podcast_hit(
@ -274,8 +277,8 @@ if (!function_exists('podcast_hit')) {
int $episodeId,
int $bytesThreshold,
int $fileSize,
$duration,
$publicationDate,
int $duration,
int $publicationTime,
string $serviceName
): void {
$session = Services::session();
@ -341,7 +344,7 @@ if (!function_exists('podcast_hit')) {
$db = Database::connect();
$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):
$listenerHashId =

View File

@ -10,24 +10,20 @@
namespace Analytics\Models;
use Analytics\Entities\AnalyticsUnknownUseragents;
use Analytics\Entities\AnalyticsUnknownUserAgent;
use CodeIgniter\Model;
class AnalyticsUnknownUseragentsModel extends Model
class AnalyticsUnknownUserAgentModel extends Model
{
/**
* @var string
*/
protected $table = 'analytics_unknown_useragents';
/**
* @var string
*/
protected $primaryKey = 'id';
/**
* @var string
*/
protected $returnType = AnalyticsUnknownUseragents::class;
protected $returnType = AnalyticsUnknownUserAgent::class;
/**
* @var bool
*/
@ -37,4 +33,12 @@ class AnalyticsUnknownUseragentsModel extends Model
* @var bool
*/
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.
*
* @var array
* $links = [
* 'text' => (string) the anchor text,
* 'href' => (string) the anchor href,
* 'text' => 'Example Link',
* 'href' => 'https://example.com/',
* ]
*
* @var array<array<string, string>>
*/
protected $links = [];
@ -57,6 +58,8 @@ class Breadcrumb
* replaceParams($newParams);
*
* The breadcrumb is now `Home / podcasts / foo / episodes / bar`
*
* @param string[] $newParams
*/
public function replaceParams(array $newParams): void
{
@ -71,7 +74,7 @@ class Breadcrumb
/**
* Renders the breadcrumb object as an accessible html breadcrumb nav
*/
public function render($class = null): string
public function render(string $class = null): string
{
$listItems = '';
$keys = array_keys($this->links);

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