refactor: add phpstan and update code to adhere to level 5

- move and refactor Image.php from Libraries to Entities folder
- update some database field names
/ types
- update composer packages
This commit is contained in:
Yassine Doghri 2021-05-12 14:00:25 +00:00
parent b691b927fe
commit 231d578d64
No known key found for this signature in database
GPG Key ID: 3E7F89498B960C9F
148 changed files with 5107 additions and 5339 deletions

View File

@ -7,13 +7,17 @@
"workspaceFolder": "/castopod-host",
"postCreateCommand": "cron && php spark serve --host 0.0.0.0",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"terminal.integrated.defaultProfile.linux": "/bin/bash",
"editor.formatOnSave": true,
"[php]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"phpSniffer.autoDetect": true,
"color-highlight.markerType": "dot-before"
"color-highlight.markerType": "dot-before",
"files.associations": {
"*.xml.dist": "xml",
"spark": "php"
}
},
"extensions": [
"mikestead.dotenv",
@ -28,6 +32,8 @@
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"wongjn.php-sniffer",
"eamodio.gitlens"
"eamodio.gitlens",
"breezelin.phpstan",
"kasik96.latte"
]
}

View File

@ -2,8 +2,19 @@
namespace App\Authorization;
class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
use Myth\Auth\Authorization\FlatAuthorization as MythAuthFlatAuthorization;
class FlatAuthorization extends MythAuthFlatAuthorization
{
/**
* The group model to use. Usually the class noted
* below (or an extension thereof) but can be any
* compatible CodeIgniter Model.
*
* @var PermissionModel
*/
protected $permissionModel;
/**
* Checks a group to see if they have the specified permission.
*
@ -18,7 +29,7 @@ class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
return false;
}
return (bool) $this->permissionModel->doesGroupHavePermission(
return $this->permissionModel->doesGroupHavePermission(
$groupId,
$permissionId,
);
@ -27,14 +38,14 @@ class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
/**
* Makes user part of given groups.
*
* @param array $groups Either collection of ID or names
* @param array<string, string> $groups Either collection of ID or names
*/
public function setUserGroups(int $userId, array $groups = []): bool
{
// remove user from all groups before resetting it in new groups
$this->groupModel->removeUserFromAllGroups($userId);
if ($groups = []) {
if ($groups === []) {
return true;
}

View File

@ -2,7 +2,9 @@
namespace App\Authorization;
class GroupModel extends \Myth\Auth\Authorization\GroupModel
use Myth\Auth\Authorization\GroupModel as MythAuthGroupModel;
class GroupModel extends MythAuthGroupModel
{
/**
* @return mixed[]

View File

@ -2,7 +2,9 @@
namespace App\Authorization;
class PermissionModel extends \Myth\Auth\Authorization\PermissionModel
use Myth\Auth\Authorization\PermissionModel as MythAuthPermissionModel;
class PermissionModel extends MythAuthPermissionModel
{
/**
* Checks to see if a user, or one of their groups,

View File

@ -28,8 +28,10 @@ class Analytics extends AnalyticsBase
/**
* get the full audio file url
*
* @param string|string[] $audioFilePath
*/
public function getAudioFileUrl(string $audioFilePath): string
public function getAudioFileUrl($audioFilePath): string
{
helper('media');

View File

@ -2,7 +2,9 @@
namespace Config;
class Auth extends \Myth\Auth\Config\Auth
use Myth\Auth\Config\Auth as MythAuthConfig;
class Auth extends MythAuthConfig
{
/**
* --------------------------------------------------------------------------

View File

@ -2,6 +2,9 @@
namespace Config;
use App\Entities\Actor;
use App\Entities\Note;
use App\Entities\User;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
@ -23,6 +26,7 @@ use CodeIgniter\Exceptions\FrameworkException;
*/
Events::on('pre_system', function () {
// @phpstan-ignore-next-line
if (ENVIRONMENT !== 'testing') {
if (ini_get('zlib.output_compression')) {
throw FrameworkException::forEnabledZlibOutputCompression();
@ -42,6 +46,8 @@ Events::on('pre_system', function () {
* Debug Toolbar Listeners.
* --------------------------------------------------------------------
* If you delete, they will no longer be collected.
*
* @phpstan-ignore-next-line
*/
if (CI_DEBUG) {
Events::on(
@ -52,7 +58,7 @@ Events::on('pre_system', function () {
}
});
Events::on('login', function ($user): void {
Events::on('login', function (User $user): void {
helper('auth');
// set interact_as_actor_id value
@ -62,7 +68,7 @@ Events::on('login', function ($user): void {
}
});
Events::on('logout', function ($user): void {
Events::on('logout', function (User $user): void {
helper('auth');
// remove user's interact_as_actor session
@ -75,7 +81,7 @@ Events::on('logout', function ($user): void {
* --------------------------------------------------------------------
* Update episode metadata counts
*/
Events::on('on_note_add', function ($note): void {
Events::on('on_note_add', function (Note $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
@ -87,7 +93,7 @@ Events::on('on_note_add', function ($note): void {
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
});
Events::on('on_note_remove', function ($note): void {
Events::on('on_note_remove', function (Note $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
@ -106,7 +112,7 @@ Events::on('on_note_remove', function ($note): void {
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_note_reblog', function ($actor, $note): void {
Events::on('on_note_reblog', function (Actor $actor, Note $note): void {
if ($episodeId = $note->episode_id) {
model('EpisodeModel')
->where('id', $episodeId)
@ -125,7 +131,7 @@ Events::on('on_note_reblog', function ($actor, $note): void {
}
});
Events::on('on_note_undo_reblog', function ($reblogNote): void {
Events::on('on_note_undo_reblog', function (Note $reblogNote): void {
$note = $reblogNote->reblog_of_note;
if ($episodeId = $note->episode_id) {
model('EpisodeModel')
@ -147,21 +153,21 @@ Events::on('on_note_undo_reblog', function ($reblogNote): void {
}
});
Events::on('on_note_reply', function ($reply): void {
Events::on('on_note_reply', function (Note $reply): void {
$note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_reply_remove', function ($reply): void {
Events::on('on_reply_remove', function (Note $reply): void {
$note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_note_favourite', function ($actor, $note): void {
Events::on('on_note_favourite', function (Actor $actor, Note $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
@ -176,7 +182,7 @@ Events::on('on_note_favourite', function ($actor, $note): void {
}
});
Events::on('on_note_undo_favourite', function ($actor, $note): void {
Events::on('on_note_undo_favourite', function (Actor $actor, Note $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
@ -191,22 +197,22 @@ Events::on('on_note_undo_favourite', function ($actor, $note): void {
}
});
Events::on('on_block_actor', function ($actorId): void {
Events::on('on_block_actor', function (int $actorId): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_unblock_actor', function ($actorId): void {
Events::on('on_unblock_actor', function (int $actorId): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_block_domain', function ($domainName): void {
Events::on('on_block_domain', function (string $domainName): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_unblock_domain', function ($domainName): void {
Events::on('on_unblock_domain', function (string $domainName): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});

View File

@ -80,25 +80,25 @@ class Images extends BaseConfig
/**
* @var string
*/
public $thumbnailExtension = '_thumbnail';
public $thumbnailSuffix = '_thumbnail';
/**
* @var string
*/
public $mediumExtension = '_medium';
public $mediumSuffix = '_medium';
/**
* @var string
*/
public $largeExtension = '_large';
public $largeSuffix = '_large';
/**
* @var string
*/
public $feedExtension = '_feed';
public $feedSuffix = '_feed';
/**
* @var string
*/
public $id3Extension = '_id3';
public $id3Suffix = '_id3';
}

View File

@ -22,7 +22,7 @@ class Mimes
/**
* Map of extensions to mime types.
*
* @var array<string, string>
* @var array<string, string|string[]>
*/
public static $mimes = [
'hqx' => [
@ -350,25 +350,25 @@ class Mimes
$proposedExtension = trim(strtolower($proposedExtension));
if ($proposedExtension !== '') {
if (
array_key_exists($proposedExtension, static::$mimes) &&
in_array(
$type,
is_string(static::$mimes[$proposedExtension])
? [static::$mimes[$proposedExtension]]
: static::$mimes[$proposedExtension],
true,
)
) {
// The detected mime type matches with the proposed extension.
return $proposedExtension;
}
if ($proposedExtension === '') {
// An extension was proposed, but the media type does not match the mime type list.
return null;
}
if (
array_key_exists($proposedExtension, static::$mimes) &&
in_array(
$type,
is_string(static::$mimes[$proposedExtension])
? [static::$mimes[$proposedExtension]]
: static::$mimes[$proposedExtension],
true,
)
) {
// The detected mime type matches with the proposed extension.
return $proposedExtension;
}
// Reverse check the mime type list if no extension was proposed.
// This search is order sensitive!
foreach (static::$mimes as $ext => $types) {

View File

@ -51,23 +51,35 @@ $routes->addPlaceholder(
// We get a performance increase by specifying the default
// route since we don't have to scan directories.
$routes->get('/', 'Home::index', ['as' => 'home']);
$routes->get('/', 'HomeController::index', ['as' => 'home']);
// Install Wizard route
$routes->group(config('App')->installGateway, function ($routes): void {
$routes->get('/', 'Install', ['as' => 'install']);
$routes->post('instance-config', 'Install::attemptInstanceConfig', [
'as' => 'instance-config',
]);
$routes->post('database-config', 'Install::attemptDatabaseConfig', [
'as' => 'database-config',
]);
$routes->post('cache-config', 'Install::attemptCacheConfig', [
$routes->get('/', 'InstallController', ['as' => 'install']);
$routes->post(
'instance-config',
'InstallController::attemptInstanceConfig',
[
'as' => 'instance-config',
],
);
$routes->post(
'database-config',
'InstallController::attemptDatabaseConfig',
[
'as' => 'database-config',
],
);
$routes->post('cache-config', 'InstallController::attemptCacheConfig', [
'as' => 'cache-config',
]);
$routes->post('create-superadmin', 'Install::attemptCreateSuperAdmin', [
'as' => 'create-superadmin',
]);
$routes->post(
'create-superadmin',
'InstallController::attemptCreateSuperAdmin',
[
'as' => 'create-superadmin',
],
);
});
$routes->get('.well-known/platforms', 'Platform');
@ -77,35 +89,35 @@ $routes->group(
config('App')->adminGateway,
['namespace' => 'App\Controllers\Admin'],
function ($routes): void {
$routes->get('/', 'Home', [
$routes->get('/', 'HomeController', [
'as' => 'admin',
]);
$routes->group('persons', function ($routes): void {
$routes->get('/', 'Person', [
$routes->get('/', 'PersonController', [
'as' => 'person-list',
'filter' => 'permission:person-list',
]);
$routes->get('new', 'Person::create', [
$routes->get('new', 'PersonController::create', [
'as' => 'person-create',
'filter' => 'permission:person-create',
]);
$routes->post('new', 'Person::attemptCreate', [
$routes->post('new', 'PersonController::attemptCreate', [
'filter' => 'permission:person-create',
]);
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'Person::view/$1', [
$routes->get('/', 'PersonController::view/$1', [
'as' => 'person-view',
'filter' => 'permission:person-view',
]);
$routes->get('edit', 'Person::edit/$1', [
$routes->get('edit', 'PersonController::edit/$1', [
'as' => 'person-edit',
'filter' => 'permission:person-edit',
]);
$routes->post('edit', 'Person::attemptEdit/$1', [
$routes->post('edit', 'PersonController::attemptEdit/$1', [
'filter' => 'permission:person-edit',
]);
$routes->add('delete', 'Person::delete/$1', [
$routes->add('delete', 'PersonController::delete/$1', [
'as' => 'person-delete',
'filter' => 'permission:person-delete',
]);
@ -114,55 +126,59 @@ $routes->group(
// Podcasts
$routes->group('podcasts', function ($routes): void {
$routes->get('/', 'Podcast::list', [
$routes->get('/', 'PodcastController::list', [
'as' => 'podcast-list',
]);
$routes->get('new', 'Podcast::create', [
$routes->get('new', 'PodcastController::create', [
'as' => 'podcast-create',
'filter' => 'permission:podcasts-create',
]);
$routes->post('new', 'Podcast::attemptCreate', [
$routes->post('new', 'PodcastController::attemptCreate', [
'filter' => 'permission:podcasts-create',
]);
$routes->get('import', 'PodcastImport', [
$routes->get('import', 'PodcastImportController', [
'as' => 'podcast-import',
'filter' => 'permission:podcasts-import',
]);
$routes->post('import', 'PodcastImport::attemptImport', [
$routes->post('import', 'PodcastImportController::attemptImport', [
'filter' => 'permission:podcasts-import',
]);
// Podcast
// Use ids in admin area to help permission and group lookups
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'Podcast::view/$1', [
$routes->get('/', 'PodcastController::view/$1', [
'as' => 'podcast-view',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get('edit', 'Podcast::edit/$1', [
$routes->get('edit', 'PodcastController::edit/$1', [
'as' => 'podcast-edit',
'filter' => 'permission:podcast-edit',
]);
$routes->post('edit', 'Podcast::attemptEdit/$1', [
$routes->post('edit', 'PodcastController::attemptEdit/$1', [
'filter' => 'permission:podcast-edit',
]);
$routes->get('delete', 'Podcast::delete/$1', [
$routes->get('delete', 'PodcastController::delete/$1', [
'as' => 'podcast-delete',
'filter' => 'permission:podcasts-delete',
]);
$routes->group('persons', function ($routes): void {
$routes->get('/', 'PodcastPerson/$1', [
$routes->get('/', 'PodcastPersonController/$1', [
'as' => 'podcast-person-manage',
'filter' => 'permission:podcast-edit',
]);
$routes->post('/', 'PodcastPerson::attemptAdd/$1', [
'filter' => 'permission:podcast-edit',
]);
$routes->post(
'/',
'PodcastPersonController::attemptAdd/$1',
[
'filter' => 'permission:podcast-edit',
],
);
$routes->get(
'(:num)/remove',
'PodcastPerson::remove/$1/$2',
'PodcastPersonController::remove/$1/$2',
[
'as' => 'podcast-person-remove',
'filter' => 'permission:podcast-edit',
@ -171,13 +187,13 @@ $routes->group(
});
$routes->group('analytics', function ($routes): void {
$routes->get('/', 'Podcast::viewAnalytics/$1', [
$routes->get('/', 'PodcastController::viewAnalytics/$1', [
'as' => 'podcast-analytics',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get(
'webpages',
'Podcast::viewAnalyticsWebpages/$1',
'PodcastController::viewAnalyticsWebpages/$1',
[
'as' => 'podcast-analytics-webpages',
'filter' => 'permission:podcasts-view,podcast-view',
@ -185,7 +201,7 @@ $routes->group(
);
$routes->get(
'locations',
'Podcast::viewAnalyticsLocations/$1',
'PodcastController::viewAnalyticsLocations/$1',
[
'as' => 'podcast-analytics-locations',
'filter' => 'permission:podcasts-view,podcast-view',
@ -193,7 +209,7 @@ $routes->group(
);
$routes->get(
'unique-listeners',
'Podcast::viewAnalyticsUniqueListeners/$1',
'PodcastController::viewAnalyticsUniqueListeners/$1',
[
'as' => 'podcast-analytics-unique-listeners',
'filter' => 'permission:podcasts-view,podcast-view',
@ -201,7 +217,7 @@ $routes->group(
);
$routes->get(
'listening-time',
'Podcast::viewAnalyticsListeningTime/$1',
'PodcastController::viewAnalyticsListeningTime/$1',
[
'as' => 'podcast-analytics-listening-time',
'filter' => 'permission:podcasts-view,podcast-view',
@ -209,7 +225,7 @@ $routes->group(
);
$routes->get(
'time-periods',
'Podcast::viewAnalyticsTimePeriods/$1',
'PodcastController::viewAnalyticsTimePeriods/$1',
[
'as' => 'podcast-analytics-time-periods',
'filter' => 'permission:podcasts-view,podcast-view',
@ -217,7 +233,7 @@ $routes->group(
);
$routes->get(
'players',
'Podcast::viewAnalyticsPlayers/$1',
'PodcastController::viewAnalyticsPlayers/$1',
[
'as' => 'podcast-analytics-players',
'filter' => 'permission:podcasts-view,podcast-view',
@ -227,41 +243,53 @@ $routes->group(
// Podcast episodes
$routes->group('episodes', function ($routes): void {
$routes->get('/', 'Episode::list/$1', [
$routes->get('/', 'EpisodeController::list/$1', [
'as' => 'episode-list',
'filter' =>
'permission:episodes-list,podcast_episodes-list',
]);
$routes->get('new', 'Episode::create/$1', [
$routes->get('new', 'EpisodeController::create/$1', [
'as' => 'episode-create',
'filter' => 'permission:podcast_episodes-create',
]);
$routes->post('new', 'Episode::attemptCreate/$1', [
'filter' => 'permission:podcast_episodes-create',
]);
$routes->post(
'new',
'EpisodeController::attemptCreate/$1',
[
'filter' => 'permission:podcast_episodes-create',
],
);
// Episode
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'Episode::view/$1/$2', [
$routes->get('/', 'EpisodeController::view/$1/$2', [
'as' => 'episode-view',
'filter' =>
'permission:episodes-view,podcast_episodes-view',
]);
$routes->get('edit', 'Episode::edit/$1/$2', [
$routes->get('edit', 'EpisodeController::edit/$1/$2', [
'as' => 'episode-edit',
'filter' => 'permission:podcast_episodes-edit',
]);
$routes->post('edit', 'Episode::attemptEdit/$1/$2', [
'filter' => 'permission:podcast_episodes-edit',
]);
$routes->get('publish', 'Episode::publish/$1/$2', [
'as' => 'episode-publish',
'filter' =>
'permission:podcast-manage_publications',
]);
$routes->post(
'edit',
'EpisodeController::attemptEdit/$1/$2',
[
'filter' => 'permission:podcast_episodes-edit',
],
);
$routes->get(
'publish',
'EpisodeController::publish/$1/$2',
[
'as' => 'episode-publish',
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->post(
'publish',
'Episode::attemptPublish/$1/$2',
'EpisodeController::attemptPublish/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
@ -269,7 +297,7 @@ $routes->group(
);
$routes->get(
'publish-edit',
'Episode::publishEdit/$1/$2',
'EpisodeController::publishEdit/$1/$2',
[
'as' => 'episode-publish_edit',
'filter' =>
@ -278,32 +306,41 @@ $routes->group(
);
$routes->post(
'publish-edit',
'Episode::attemptPublishEdit/$1/$2',
'EpisodeController::attemptPublishEdit/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get('unpublish', 'Episode::unpublish/$1/$2', [
'as' => 'episode-unpublish',
'filter' =>
'permission:podcast-manage_publications',
]);
$routes->get(
'unpublish',
'EpisodeController::unpublish/$1/$2',
[
'as' => 'episode-unpublish',
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->post(
'unpublish',
'Episode::attemptUnpublish/$1/$2',
'EpisodeController::attemptUnpublish/$1/$2',
[
'filter' =>
'permission:podcast-manage_publications',
],
);
$routes->get('delete', 'Episode::delete/$1/$2', [
'as' => 'episode-delete',
'filter' => 'permission:podcast_episodes-delete',
]);
$routes->get(
'delete',
'EpisodeController::delete/$1/$2',
[
'as' => 'episode-delete',
'filter' =>
'permission:podcast_episodes-delete',
],
);
$routes->get(
'transcript-delete',
'Episode::transcriptDelete/$1/$2',
'EpisodeController::transcriptDelete/$1/$2',
[
'as' => 'transcript-delete',
'filter' => 'permission:podcast_episodes-edit',
@ -311,7 +348,7 @@ $routes->group(
);
$routes->get(
'chapters-delete',
'Episode::chaptersDelete/$1/$2',
'EpisodeController::chaptersDelete/$1/$2',
[
'as' => 'chapters-delete',
'filter' => 'permission:podcast_episodes-edit',
@ -319,7 +356,7 @@ $routes->group(
);
$routes->get(
'soundbites',
'Episode::soundbitesEdit/$1/$2',
'EpisodeController::soundbitesEdit/$1/$2',
[
'as' => 'soundbites-edit',
'filter' => 'permission:podcast_episodes-edit',
@ -327,14 +364,14 @@ $routes->group(
);
$routes->post(
'soundbites',
'Episode::soundbitesAttemptEdit/$1/$2',
'EpisodeController::soundbitesAttemptEdit/$1/$2',
[
'filter' => 'permission:podcast_episodes-edit',
],
);
$routes->get(
'soundbites/(:num)/delete',
'Episode::soundbiteDelete/$1/$2/$3',
'EpisodeController::soundbiteDelete/$1/$2/$3',
[
'as' => 'soundbite-delete',
'filter' => 'permission:podcast_episodes-edit',
@ -342,7 +379,7 @@ $routes->group(
);
$routes->get(
'embeddable-player',
'Episode::embeddablePlayer/$1/$2',
'EpisodeController::embeddablePlayer/$1/$2',
[
'as' => 'embeddable-player-add',
'filter' => 'permission:podcast_episodes-edit',
@ -350,13 +387,13 @@ $routes->group(
);
$routes->group('persons', function ($routes): void {
$routes->get('/', 'EpisodePerson/$1/$2', [
$routes->get('/', 'EpisodePersonController/$1/$2', [
'as' => 'episode-person-manage',
'filter' => 'permission:podcast_episodes-edit',
]);
$routes->post(
'/',
'EpisodePerson::attemptAdd/$1/$2',
'EpisodePersonController::attemptAdd/$1/$2',
[
'filter' =>
'permission:podcast_episodes-edit',
@ -364,7 +401,7 @@ $routes->group(
);
$routes->get(
'(:num)/remove',
'EpisodePerson::remove/$1/$2/$3',
'EpisodePersonController::remove/$1/$2/$3',
[
'as' => 'episode-person-remove',
'filter' =>
@ -377,51 +414,64 @@ $routes->group(
// Podcast contributors
$routes->group('contributors', function ($routes): void {
$routes->get('/', 'Contributor::list/$1', [
$routes->get('/', 'ContributorController::list/$1', [
'as' => 'contributor-list',
'filter' =>
'permission:podcasts-view,podcast-manage_contributors',
]);
$routes->get('add', 'Contributor::add/$1', [
$routes->get('add', 'ContributorController::add/$1', [
'as' => 'contributor-add',
'filter' => 'permission:podcast-manage_contributors',
]);
$routes->post('add', 'Contributor::attemptAdd/$1', [
'filter' => 'permission:podcast-manage_contributors',
]);
$routes->post(
'add',
'ContributorController::attemptAdd/$1',
[
'filter' =>
'permission:podcast-manage_contributors',
],
);
// Contributor
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'Contributor::view/$1/$2', [
$routes->get('/', 'ContributorController::view/$1/$2', [
'as' => 'contributor-view',
'filter' =>
'permission:podcast-manage_contributors',
]);
$routes->get('edit', 'Contributor::edit/$1/$2', [
'as' => 'contributor-edit',
'filter' =>
'permission:podcast-manage_contributors',
]);
$routes->get(
'edit',
'ContributorController::edit/$1/$2',
[
'as' => 'contributor-edit',
'filter' =>
'permission:podcast-manage_contributors',
],
);
$routes->post(
'edit',
'Contributor::attemptEdit/$1/$2',
'ContributorController::attemptEdit/$1/$2',
[
'filter' =>
'permission:podcast-manage_contributors',
],
);
$routes->get('remove', 'Contributor::remove/$1/$2', [
'as' => 'contributor-remove',
'filter' =>
'permission:podcast-manage_contributors',
]);
$routes->get(
'remove',
'ContributorController::remove/$1/$2',
[
'as' => 'contributor-remove',
'filter' =>
'permission:podcast-manage_contributors',
],
);
});
});
$routes->group('platforms', function ($routes): void {
$routes->get(
'/',
'PodcastPlatform::platforms/$1/podcasting',
'PodcastPlatformController::platforms/$1/podcasting',
[
'as' => 'platforms-podcasting',
'filter' => 'permission:podcast-manage_platforms',
@ -429,7 +479,7 @@ $routes->group(
);
$routes->get(
'social',
'PodcastPlatform::platforms/$1/social',
'PodcastPlatformController::platforms/$1/social',
[
'as' => 'platforms-social',
'filter' => 'permission:podcast-manage_platforms',
@ -437,7 +487,7 @@ $routes->group(
);
$routes->get(
'funding',
'PodcastPlatform::platforms/$1/funding',
'PodcastPlatformController::platforms/$1/funding',
[
'as' => 'platforms-funding',
'filter' => 'permission:podcast-manage_platforms',
@ -445,7 +495,7 @@ $routes->group(
);
$routes->post(
'save/(:platformType)',
'PodcastPlatform::attemptPlatformsUpdate/$1/$2',
'PodcastPlatformController::attemptPlatformsUpdate/$1/$2',
[
'as' => 'platforms-save',
'filter' => 'permission:podcast-manage_platforms',
@ -453,7 +503,7 @@ $routes->group(
);
$routes->get(
'(:slug)/podcast-platform-remove',
'PodcastPlatform::removePodcastPlatform/$1/$2',
'PodcastPlatformController::removePodcastPlatform/$1/$2',
[
'as' => 'podcast-platform-remove',
'filter' => 'permission:podcast-manage_platforms',
@ -465,41 +515,51 @@ $routes->group(
// Instance wide Fediverse config
$routes->group('fediverse', function ($routes): void {
$routes->get('/', 'Fediverse::dashboard', [
$routes->get('/', 'FediverseController::dashboard', [
'as' => 'fediverse-dashboard',
]);
$routes->get('blocked-actors', 'Fediverse::blockedActors', [
'as' => 'fediverse-blocked-actors',
'filter' => 'permission:fediverse-block_actors',
]);
$routes->get('blocked-domains', 'Fediverse::blockedDomains', [
'as' => 'fediverse-blocked-domains',
'filter' => 'permission:fediverse-block_domains',
]);
$routes->get(
'blocked-actors',
'FediverseController::blockedActors',
[
'as' => 'fediverse-blocked-actors',
'filter' => 'permission:fediverse-block_actors',
],
);
$routes->get(
'blocked-domains',
'FediverseController::blockedDomains',
[
'as' => 'fediverse-blocked-domains',
'filter' => 'permission:fediverse-block_domains',
],
);
});
// Pages
$routes->group('pages', function ($routes): void {
$routes->get('/', 'Page::list', ['as' => 'page-list']);
$routes->get('new', 'Page::create', [
$routes->get('/', 'PageController::list', ['as' => 'page-list']);
$routes->get('new', 'PageController::create', [
'as' => 'page-create',
'filter' => 'permission:pages-manage',
]);
$routes->post('new', 'Page::attemptCreate', [
$routes->post('new', 'PageController::attemptCreate', [
'filter' => 'permission:pages-manage',
]);
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'Page::view/$1', ['as' => 'page-view']);
$routes->get('edit', 'Page::edit/$1', [
$routes->get('/', 'PageController::view/$1', [
'as' => 'page-view',
]);
$routes->get('edit', 'PageController::edit/$1', [
'as' => 'page-edit',
'filter' => 'permission:pages-manage',
]);
$routes->post('edit', 'Page::attemptEdit/$1', [
$routes->post('edit', 'PageController::attemptEdit/$1', [
'filter' => 'permission:pages-manage',
]);
$routes->get('delete', 'Page::delete/$1', [
$routes->get('delete', 'PageController::delete/$1', [
'as' => 'page-delete',
'filter' => 'permission:pages-manage',
]);
@ -508,44 +568,48 @@ $routes->group(
// Users
$routes->group('users', function ($routes): void {
$routes->get('/', 'User::list', [
$routes->get('/', 'UserController::list', [
'as' => 'user-list',
'filter' => 'permission:users-list',
]);
$routes->get('new', 'User::create', [
$routes->get('new', 'UserController::create', [
'as' => 'user-create',
'filter' => 'permission:users-create',
]);
$routes->post('new', 'User::attemptCreate', [
$routes->post('new', 'UserController::attemptCreate', [
'filter' => 'permission:users-create',
]);
// User
$routes->group('(:num)', function ($routes): void {
$routes->get('/', 'User::view/$1', [
$routes->get('/', 'UserController::view/$1', [
'as' => 'user-view',
'filter' => 'permission:users-view',
]);
$routes->get('edit', 'User::edit/$1', [
$routes->get('edit', 'UserController::edit/$1', [
'as' => 'user-edit',
'filter' => 'permission:users-manage_authorizations',
]);
$routes->post('edit', 'User::attemptEdit/$1', [
$routes->post('edit', 'UserController::attemptEdit/$1', [
'filter' => 'permission:users-manage_authorizations',
]);
$routes->get('ban', 'User::ban/$1', [
$routes->get('ban', 'UserController::ban/$1', [
'as' => 'user-ban',
'filter' => 'permission:users-manage_bans',
]);
$routes->get('unban', 'User::unBan/$1', [
$routes->get('unban', 'UserController::unBan/$1', [
'as' => 'user-unban',
'filter' => 'permission:users-manage_bans',
]);
$routes->get('force-pass-reset', 'User::forcePassReset/$1', [
'as' => 'user-force_pass_reset',
'filter' => 'permission:users-force_pass_reset',
]);
$routes->get('delete', 'User::delete/$1', [
$routes->get(
'force-pass-reset',
'UserController::forcePassReset/$1',
[
'as' => 'user-force_pass_reset',
'filter' => 'permission:users-force_pass_reset',
],
);
$routes->get('delete', 'UserController::delete/$1', [
'as' => 'user-delete',
'filter' => 'permission:users-delete',
]);
@ -554,13 +618,20 @@ $routes->group(
// My account
$routes->group('my-account', function ($routes): void {
$routes->get('/', 'MyAccount', [
$routes->get('/', 'MyAccountController', [
'as' => 'my-account',
]);
$routes->get('change-password', 'MyAccount::changePassword/$1', [
'as' => 'change-password',
]);
$routes->post('change-password', 'MyAccount::attemptChange/$1');
$routes->get(
'change-password',
'MyAccountController::changePassword/$1',
[
'as' => 'change-password',
],
);
$routes->post(
'change-password',
'MyAccountController::attemptChange/$1',
);
});
},
);
@ -570,42 +641,48 @@ $routes->group(
*/
$routes->group(config('App')->authGateway, function ($routes): void {
// Login/out
$routes->get('login', 'Auth::login', ['as' => 'login']);
$routes->post('login', 'Auth::attemptLogin');
$routes->get('logout', 'Auth::logout', ['as' => 'logout']);
$routes->get('login', 'AuthController::login', ['as' => 'login']);
$routes->post('login', 'AuthController::attemptLogin');
$routes->get('logout', 'AuthController::logout', [
'as' => 'logout',
]);
// Registration
$routes->get('register', 'Auth::register', [
$routes->get('register', 'AuthController::register', [
'as' => 'register',
]);
$routes->post('register', 'Auth::attemptRegister');
$routes->post('register', 'AuthController::attemptRegister');
// Activation
$routes->get('activate-account', 'Auth::activateAccount', [
$routes->get('activate-account', 'AuthController::activateAccount', [
'as' => 'activate-account',
]);
$routes->get('resend-activate-account', 'Auth::resendActivateAccount', [
'as' => 'resend-activate-account',
]);
$routes->get(
'resend-activate-account',
'AuthController::resendActivateAccount',
[
'as' => 'resend-activate-account',
],
);
// Forgot/Resets
$routes->get('forgot', 'Auth::forgotPassword', [
$routes->get('forgot', 'AuthController::forgotPassword', [
'as' => 'forgot',
]);
$routes->post('forgot', 'Auth::attemptForgot');
$routes->get('reset-password', 'Auth::resetPassword', [
$routes->post('forgot', 'AuthController::attemptForgot');
$routes->get('reset-password', 'AuthController::resetPassword', [
'as' => 'reset-password',
]);
$routes->post('reset-password', 'Auth::attemptReset');
$routes->post('reset-password', 'AuthController::attemptReset');
});
// Podcast's Public routes
$routes->group('@(:podcastName)', function ($routes): void {
$routes->get('/', 'Podcast::activity/$1', [
$routes->get('/', 'PodcastController::activity/$1', [
'as' => 'podcast-activity',
]);
// override default ActivityPub Library's actor route
$routes->get('/', 'Podcast::activity/$1', [
$routes->get('/', 'PodcastController::activity/$1', [
'as' => 'actor',
'alternate-content' => [
'application/activity+json' => [
@ -618,26 +695,26 @@ $routes->group('@(:podcastName)', function ($routes): void {
],
],
]);
$routes->get('episodes', 'Podcast::episodes/$1', [
$routes->get('episodes', 'PodcastController::episodes/$1', [
'as' => 'podcast-episodes',
]);
$routes->group('episodes/(:slug)', function ($routes): void {
$routes->get('/', 'Episode/$1/$2', [
$routes->get('/', 'EpisodeController/$1/$2', [
'as' => 'episode',
]);
$routes->get('oembed.json', 'Episode::oembedJSON/$1/$2', [
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
'as' => 'episode-oembed-json',
]);
$routes->get('oembed.xml', 'Episode::oembedXML/$1/$2', [
$routes->get('oembed.xml', 'EpisodeController::oembedXML/$1/$2', [
'as' => 'episode-oembed-xml',
]);
$routes->group('embeddable-player', function ($routes): void {
$routes->get('/', 'Episode::embeddablePlayer/$1/$2', [
$routes->get('/', 'EpisodeController::embeddablePlayer/$1/$2', [
'as' => 'embeddable-player',
]);
$routes->get(
'(:embeddablePlayerTheme)',
'Episode::embeddablePlayer/$1/$2/$3',
'EpisodeController::embeddablePlayer/$1/$2/$3',
[
'as' => 'embeddable-player-theme',
],
@ -645,16 +722,16 @@ $routes->group('@(:podcastName)', function ($routes): void {
});
});
$routes->head('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->head('feed.xml', 'FeedController/$1', ['as' => 'podcast_feed']);
$routes->get('feed.xml', 'FeedController/$1', ['as' => 'podcast_feed']);
});
// Other pages
$routes->get('/credits', 'Page::credits', ['as' => 'credits']);
$routes->get('/credits', 'PageController::credits', ['as' => 'credits']);
$routes->get('/pages/(:slug)', 'Page/$1', ['as' => 'page']);
// interacting as an actor
$routes->post('interact-as-actor', 'Auth::attemptInteractAsActor', [
$routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [
'as' => 'interact-as-actor',
]);
@ -662,13 +739,13 @@ $routes->post('interact-as-actor', 'Auth::attemptInteractAsActor', [
* Overwriting ActivityPub routes file
*/
$routes->group('@(:podcastName)', function ($routes): void {
$routes->post('notes/new', 'Note::attemptCreate/$1', [
$routes->post('notes/new', 'NoteController::attemptCreate/$1', [
'as' => 'note-attempt-create',
'filter' => 'permission:podcast-manage_publications',
]);
// Note
$routes->group('notes/(:uuid)', function ($routes): void {
$routes->get('/', 'Note/$1/$2', [
$routes->get('/', 'NoteController::view/$1/$2', [
'as' => 'note',
'alternate-content' => [
'application/activity+json' => [
@ -681,7 +758,7 @@ $routes->group('@(:podcastName)', function ($routes): void {
],
],
]);
$routes->get('replies', 'Note/$1/$2', [
$routes->get('replies', 'NoteController/$1/$2', [
'as' => 'note-replies',
'alternate-content' => [
'application/activity+json' => [
@ -696,33 +773,45 @@ $routes->group('@(:podcastName)', function ($routes): void {
]);
// Actions
$routes->post('action', 'Note::attemptAction/$1/$2', [
$routes->post('action', 'NoteController::attemptAction/$1/$2', [
'as' => 'note-attempt-action',
'filter' => 'permission:podcast-interact_as',
]);
$routes->post('block-actor', 'Note::attemptBlockActor/$1/$2', [
'as' => 'note-attempt-block-actor',
'filter' => 'permission:fediverse-block_actors',
]);
$routes->post('block-domain', 'Note::attemptBlockDomain/$1/$2', [
'as' => 'note-attempt-block-domain',
'filter' => 'permission:fediverse-block_domains',
]);
$routes->post('delete', 'Note::attemptDelete/$1/$2', [
$routes->post(
'block-actor',
'NoteController::attemptBlockActor/$1/$2',
[
'as' => 'note-attempt-block-actor',
'filter' => 'permission:fediverse-block_actors',
],
);
$routes->post(
'block-domain',
'NoteController::attemptBlockDomain/$1/$2',
[
'as' => 'note-attempt-block-domain',
'filter' => 'permission:fediverse-block_domains',
],
);
$routes->post('delete', 'NoteController::attemptDelete/$1/$2', [
'as' => 'note-attempt-delete',
'filter' => 'permission:podcast-manage_publications',
]);
$routes->get('remote/(:noteAction)', 'Note::remoteAction/$1/$2/$3', [
'as' => 'note-remote-action',
]);
$routes->get(
'remote/(:noteAction)',
'NoteController::remoteAction/$1/$2/$3',
[
'as' => 'note-remote-action',
],
);
});
$routes->get('follow', 'Actor::follow/$1', [
$routes->get('follow', 'ActorController::follow/$1', [
'as' => 'follow',
]);
$routes->get('outbox', 'Actor::outbox/$1', [
$routes->get('outbox', 'ActorController::outbox/$1', [
'as' => 'outbox',
'filter' => 'activity-pub:verify-activitystream',
]);

View File

@ -101,11 +101,11 @@ class Services extends BaseService
$instance = new $class($config);
if (empty($userModel)) {
if ($userModel === null) {
$userModel = new UserModel();
}
if (empty($loginModel)) {
if ($loginModel === null) {
$loginModel = new LoginModel();
}

View File

@ -8,9 +8,10 @@
namespace App\Controllers;
use ActivityPub\Controllers\ActorController as ActivityPubActorController;
use Analytics\AnalyticsTrait;
class Actor extends \ActivityPub\Controllers\ActorController
class ActorController extends ActivityPubActorController
{
use AnalyticsTrait;

View File

@ -16,7 +16,7 @@ use App\Authorization\GroupModel;
use App\Models\PodcastModel;
use App\Models\UserModel;
class Contributor extends BaseController
class ContributorController extends BaseController
{
/**
* @var Podcast
@ -172,7 +172,7 @@ class Contributor extends BaseController
public function remove()
{
if ($this->podcast->created_by == $this->user->id) {
if ($this->podcast->created_by === $this->user->id) {
return redirect()
->back()
->with('errors', [

View File

@ -8,15 +8,16 @@
namespace App\Controllers\Admin;
use App\Entities\Episode as EpisodeEntity;
use App\Entities\Episode;
use App\Entities\Note;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\NoteModel;
use App\Models\PodcastModel;
use App\Models\SoundbiteModel;
use CodeIgniter\I18n\Time;
class Episode extends BaseController
class EpisodeController extends BaseController
{
/**
* @var Podcast
@ -28,12 +29,7 @@ class Episode extends BaseController
*/
protected $episode;
/**
* @var Soundbite|null
*/
protected $soundbites;
public function _remap($method, ...$params)
public function _remap(string $method, ...$params)
{
if (
!($this->podcast = (new PodcastModel())->getPodcastById($params[0]))
@ -124,7 +120,7 @@ class Episode extends BaseController
->with('errors', $this->validator->getErrors());
}
$newEpisode = new EpisodeEntity([
$newEpisode = new Episode([
'podcast_id' => $this->podcast->id,
'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'),
@ -148,8 +144,8 @@ class Episode extends BaseController
'type' => $this->request->getPost('type'),
'is_blocked' => $this->request->getPost('block') == 'yes',
'custom_rss_string' => $this->request->getPost('custom_rss'),
'created_by' => user()->id,
'updated_by' => user()->id,
'created_by' => user_id(),
'updated_by' => user_id(),
'published_at' => null,
]);
@ -265,14 +261,15 @@ class Episode extends BaseController
'custom_rss',
);
$this->episode->updated_by = user()->id;
$this->episode->updated_by = user_id();
$audioFile = $this->request->getFile('audio_file');
if ($audioFile) {
if ($audioFile !== null && $audioFile->isValid()) {
$this->episode->audio_file = $audioFile;
}
$image = $this->request->getFile('image');
if ($image) {
if ($image !== null && $image->isValid()) {
$this->episode->image = $image;
}
@ -291,7 +288,7 @@ class Episode extends BaseController
) {
if (
($transcriptFile = $this->episode->transcript_file) &&
!empty($transcriptFile)
$transcriptFile !== null
) {
unlink($transcriptFile);
$this->episode->transcript_file_path = null;
@ -315,7 +312,7 @@ class Episode extends BaseController
) {
if (
($chaptersFile = $this->episode->chapters_file) &&
!empty($chaptersFile)
$chaptersFile !== null
) {
unlink($chaptersFile);
$this->episode->chapters_file_path = null;
@ -700,8 +697,8 @@ class Episode extends BaseController
foreach ($soundbites_array as $soundbite_id => $soundbite) {
if (
!empty($soundbite['start_time']) &&
!empty($soundbite['duration'])
$soundbite['start_time'] !== null &&
$soundbite['duration'] !== null
) {
$data = [
'podcast_id' => $this->podcast->id,
@ -709,10 +706,10 @@ class Episode extends BaseController
'start_time' => $soundbite['start_time'],
'duration' => $soundbite['duration'],
'label' => $soundbite['label'],
'updated_by' => user()->id,
'updated_by' => user_id(),
];
if ($soundbite_id == 0) {
$data += ['created_by' => user()->id];
$data += ['created_by' => user_id()];
} else {
$data += ['id' => $soundbite_id];
}

View File

@ -16,7 +16,7 @@ use App\Models\PodcastModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
class EpisodePerson extends BaseController
class EpisodePersonController extends BaseController
{
/**
* @var Podcast

View File

@ -8,7 +8,7 @@
namespace App\Controllers\Admin;
class Fediverse extends BaseController
class FediverseController extends BaseController
{
public function dashboard()
{

View File

@ -8,7 +8,7 @@
namespace App\Controllers\Admin;
class Home extends BaseController
class HomeController extends BaseController
{
public function index()
{

View File

@ -11,7 +11,7 @@ namespace App\Controllers\Admin;
use Config\Services;
use App\Models\UserModel;
class MyAccount extends BaseController
class MyAccountController extends BaseController
{
public function index()
{
@ -58,7 +58,7 @@ class MyAccount extends BaseController
user()->password = $this->request->getPost('new_password');
if (!$userModel->update(user()->id, user())) {
if (!$userModel->update(user_id(), user())) {
return redirect()
->back()
->withInput()

View File

@ -8,11 +8,11 @@
namespace App\Controllers\Admin;
use App\Entities\Page as EntitiesPage;
use App\Entities\Page;
use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PageModel;
class Page extends BaseController
class PageController extends BaseController
{
/**
* @var Page|null
@ -55,10 +55,10 @@ class Page extends BaseController
function attemptCreate()
{
$page = new EntitiesPage([
$page = new Page([
'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'),
'content' => $this->request->getPost('content'),
'content_markdown' => $this->request->getPost('content'),
]);
$pageModel = new PageModel();
@ -92,7 +92,7 @@ class Page extends BaseController
{
$this->page->title = $this->request->getPost('title');
$this->page->slug = $this->request->getPost('slug');
$this->page->content = $this->request->getPost('content');
$this->page->content_markdown = $this->request->getPost('content');
$pageModel = new PageModel();

View File

@ -8,11 +8,11 @@
namespace App\Controllers\Admin;
use App\Entities\Person as EntitiesPerson;
use App\Entities\Person;
use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PersonModel;
class Person extends BaseController
class PersonController extends BaseController
{
/**
* @var Person|null
@ -68,13 +68,13 @@ class Person extends BaseController
->with('errors', $this->validator->getErrors());
}
$person = new EntitiesPerson([
$person = new Person([
'full_name' => $this->request->getPost('full_name'),
'unique_name' => $this->request->getPost('unique_name'),
'information_url' => $this->request->getPost('information_url'),
'image' => $this->request->getFile('image'),
'created_by' => user()->id,
'updated_by' => user()->id,
'created_by' => user_id(),
'updated_by' => user_id(),
]);
$personModel = new PersonModel();
@ -125,7 +125,7 @@ class Person extends BaseController
$this->person->image = $image;
}
$this->person->updated_by = user()->id;
$this->person->updated_by = user_id();
$personModel = new PersonModel();
if (!$personModel->update($this->person->id, $this->person)) {

View File

@ -8,23 +8,30 @@
namespace App\Controllers\Admin;
use App\Entities\Podcast as EntitiesPodcast;
use App\Entities\Image;
use App\Entities\Podcast;
use CodeIgniter\Exceptions\PageNotFoundException;
use Config\Database;
use App\Models\CategoryModel;
use App\Models\LanguageModel;
use App\Models\PodcastModel;
use App\Models\EpisodeModel;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services;
class Podcast extends BaseController
class PodcastController extends BaseController
{
/**
* @var Podcast|null
* @var Podcast
*/
protected $podcast;
public function _remap($method, ...$params)
/**
*
* @param array<string> $params
* @return static|string
*/
public function _remap(string $method, ...$params)
{
if (count($params) === 0) {
return $this->$method();
@ -37,11 +44,13 @@ class Podcast extends BaseController
throw PageNotFoundException::forPageNotFound();
}
public function list()
public function list(): string
{
if (!has_permission('podcasts-list')) {
$data = [
'podcasts' => (new PodcastModel())->getUserPodcasts(user()->id),
'podcasts' => (new PodcastModel())->getUserPodcasts(
(int) user_id(),
),
];
} else {
$data = ['podcasts' => (new PodcastModel())->findAll()];
@ -50,7 +59,7 @@ class Podcast extends BaseController
return view('admin/podcast/list', $data);
}
public function view()
public function view(): string
{
$data = ['podcast' => $this->podcast];
@ -58,7 +67,7 @@ class Podcast extends BaseController
return view('admin/podcast/view', $data);
}
public function viewAnalytics()
public function viewAnalytics(): string
{
$data = ['podcast' => $this->podcast];
@ -66,7 +75,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/index', $data);
}
public function viewAnalyticsWebpages()
public function viewAnalyticsWebpages(): string
{
$data = ['podcast' => $this->podcast];
@ -74,7 +83,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/webpages', $data);
}
public function viewAnalyticsLocations()
public function viewAnalyticsLocations(): string
{
$data = ['podcast' => $this->podcast];
@ -82,7 +91,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/locations', $data);
}
public function viewAnalyticsUniqueListeners()
public function viewAnalyticsUniqueListeners(): string
{
$data = ['podcast' => $this->podcast];
@ -90,7 +99,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/unique_listeners', $data);
}
public function viewAnalyticsListeningTime()
public function viewAnalyticsListeningTime(): string
{
$data = ['podcast' => $this->podcast];
@ -98,7 +107,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/listening_time', $data);
}
public function viewAnalyticsTimePeriods()
public function viewAnalyticsTimePeriods(): string
{
$data = ['podcast' => $this->podcast];
@ -106,7 +115,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/time_periods', $data);
}
public function viewAnalyticsPlayers()
public function viewAnalyticsPlayers(): string
{
$data = ['podcast' => $this->podcast];
@ -114,7 +123,7 @@ class Podcast extends BaseController
return view('admin/podcast/analytics/players', $data);
}
public function create()
public function create(): string
{
helper(['form', 'misc']);
@ -132,7 +141,7 @@ class Podcast extends BaseController
return view('admin/podcast/create', $data);
}
public function attemptCreate()
public function attemptCreate(): RedirectResponse
{
$rules = [
'image' =>
@ -146,11 +155,11 @@ class Podcast extends BaseController
->with('errors', $this->validator->getErrors());
}
$podcast = new EntitiesPodcast([
$podcast = new Podcast([
'title' => $this->request->getPost('title'),
'name' => $this->request->getPost('name'),
'description_markdown' => $this->request->getPost('description'),
'image' => $this->request->getFile('image'),
'image' => new Image($this->request->getFile('image')),
'language_code' => $this->request->getPost('language'),
'category_id' => $this->request->getPost('category'),
'parental_advisory' =>
@ -171,8 +180,8 @@ class Podcast extends BaseController
'is_blocked' => $this->request->getPost('block') === 'yes',
'is_completed' => $this->request->getPost('complete') === 'yes',
'is_locked' => $this->request->getPost('lock') === 'yes',
'created_by' => user()->id,
'updated_by' => user()->id,
'created_by' => user_id(),
'updated_by' => user_id(),
]);
$podcastModel = new PodcastModel();
@ -192,14 +201,14 @@ class Podcast extends BaseController
$podcastAdminGroup = $authorize->group('podcast_admin');
$podcastModel->addPodcastContributor(
user()->id,
user_id(),
$newPodcastId,
$podcastAdminGroup->id,
);
// set Podcast categories
(new CategoryModel())->setPodcastCategories(
$newPodcastId,
(int) $newPodcastId,
$this->request->getPost('other_categories'),
);
@ -212,7 +221,7 @@ class Podcast extends BaseController
return redirect()->route('podcast-view', [$newPodcastId]);
}
public function edit()
public function edit(): string
{
helper('form');
@ -229,7 +238,7 @@ class Podcast extends BaseController
return view('admin/podcast/edit', $data);
}
public function attemptEdit()
public function attemptEdit(): RedirectResponse
{
$rules = [
'image' =>
@ -249,8 +258,8 @@ class Podcast extends BaseController
);
$image = $this->request->getFile('image');
if ($image->isValid()) {
$this->podcast->image = $image;
if ($image !== null && $image->isValid()) {
$this->podcast->image = new Image($image);
}
$this->podcast->language_code = $this->request->getPost('language');
$this->podcast->category_id = $this->request->getPost('category');
@ -281,7 +290,7 @@ class Podcast extends BaseController
$this->podcast->is_completed =
$this->request->getPost('complete') === 'yes';
$this->podcast->is_locked = $this->request->getPost('lock') === 'yes';
$this->podcast->updated_by = user()->id;
$this->podcast->updated_by = (int) user_id();
$db = Database::connect();
$db->transStart();
@ -306,7 +315,7 @@ class Podcast extends BaseController
return redirect()->route('podcast-view', [$this->podcast->id]);
}
public function latestEpisodes(int $limit, int $podcast_id)
public function latestEpisodes(int $limit, int $podcast_id): string
{
$episodes = (new EpisodeModel())
->where('podcast_id', $podcast_id)
@ -316,7 +325,7 @@ class Podcast extends BaseController
return view('admin/podcast/latest_episodes', ['episodes' => $episodes]);
}
public function delete()
public function delete(): RedirectResponse
{
(new PodcastModel())->delete($this->podcast->id);

View File

@ -15,6 +15,7 @@ use Config\Database;
use Podlibre\PodcastNamespace\ReversedTaxonomy;
use App\Entities\PodcastPerson;
use App\Entities\Episode;
use App\Entities\Image;
use App\Models\CategoryModel;
use App\Models\LanguageModel;
use App\Models\PodcastModel;
@ -26,14 +27,14 @@ use App\Models\EpisodePersonModel;
use Config\Services;
use League\HTMLToMarkdown\HtmlConverter;
class PodcastImport extends BaseController
class PodcastImportController extends BaseController
{
/**
* @var Podcast|null
*/
protected $podcast;
public function _remap($method, ...$params)
public function _remap(string $method, string ...$params)
{
if (count($params) === 0) {
return $this->$method();
@ -120,6 +121,19 @@ class PodcastImport extends BaseController
$channelDescriptionHtml = (string) $feed->channel[0]->description;
try {
if (
$nsItunes->image !== null &&
$nsItunes->image->attributes()['href'] !== null
) {
$imageFile = download_file(
(string) $nsItunes->image->attributes()['href'],
);
} else {
$imageFile = download_file(
(string) $feed->channel[0]->image->url,
);
}
$podcast = new Podcast([
'name' => $this->request->getPost('name'),
'imported_feed_url' => $this->request->getPost(
@ -133,50 +147,46 @@ class PodcastImport extends BaseController
$channelDescriptionHtml,
),
'description_html' => $channelDescriptionHtml,
'image' =>
$nsItunes->image && !empty($nsItunes->image->attributes())
? download_file((string) $nsItunes->image->attributes())
: ($feed->channel[0]->image &&
!empty($feed->channel[0]->image->url)
? download_file(
(string) $feed->channel[0]->image->url,
)
: null),
'image' => new Image($imageFile),
'language_code' => $this->request->getPost('language'),
'category_id' => $this->request->getPost('category'),
'parental_advisory' => empty($nsItunes->explicit)
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
'parental_advisory' =>
$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' => empty($nsItunes->type) ? 'episodic' : $nsItunes->type,
'type' =>
$nsItunes->type === null ? 'episodic' : $nsItunes->type,
'copyright' => (string) $feed->channel[0]->copyright,
'is_blocked' => empty($nsItunes->block)
? false
: $nsItunes->block === 'yes',
'is_completed' => empty($nsItunes->complete)
? false
: $nsItunes->complete === 'yes',
'is_blocked' =>
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
'is_completed' =>
$nsItunes->complete === null
? false
: $nsItunes->complete === 'yes',
'location_name' => $nsPodcast->location
? (string) $nsPodcast->location
: null,
'location_geo' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['geo'])
$nsPodcast->location->attributes()['geo'] === null
? null
: (string) $nsPodcast->location->attributes()['geo'],
'location_osmid' =>
'location_osm_id' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['osm'])
$nsPodcast->location->attributes()['osm'] === null
? null
: (string) $nsPodcast->location->attributes()['osm'],
'created_by' => user()->id,
'updated_by' => user()->id,
'created_by' => user_id(),
'updated_by' => user_id(),
]);
} catch (ErrorException $ex) {
return redirect()
@ -209,7 +219,7 @@ class PodcastImport extends BaseController
$podcastAdminGroup = $authorize->group('podcast_admin');
$podcastModel->addPodcastContributor(
user()->id,
user_id(),
$newPodcastId,
$podcastAdminGroup->id,
);
@ -236,6 +246,7 @@ class PodcastImport extends BaseController
}
}
}
if (count($podcastsPlatformsData) > 1) {
$platformModel->createPodcastPlatforms(
$newPodcastId,
@ -261,14 +272,15 @@ class PodcastImport extends BaseController
->with('errors', $personModel->errors());
}
$personGroup = empty($podcastPerson->attributes()['group'])
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
(string) $podcastPerson->attributes()['group']
];
$personGroup =
$podcastPerson->attributes()['group'] === null
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
(string) $podcastPerson->attributes()['group']
];
$personRole =
empty($podcastPerson->attributes()['role']) ||
empty($personGroup)
$podcastPerson->attributes()['role'] === null ||
$personGroup === null
? ['slug' => '']
: $personGroup['roles'][
strval($podcastPerson->attributes()['role'])
@ -291,7 +303,7 @@ class PodcastImport extends BaseController
$numberItems = $feed->channel[0]->item->count();
$lastItem =
!empty($this->request->getPost('max_episodes')) &&
$this->request->getPost('max_episodes') !== null &&
$this->request->getPost('max_episodes') < $numberItems
? $this->request->getPost('max_episodes')
: $numberItems;
@ -343,63 +355,71 @@ class PodcastImport extends BaseController
$itemDescriptionHtml = $item->description;
}
if (
$nsItunes->image !== null &&
$nsItunes->image->attributes()['href'] !== null
) {
$episodeImage = new Image(
download_file(
(string) $nsItunes->image->attributes()['href'],
),
);
} else {
$episodeImage = null;
}
$newEpisode = new Episode([
'podcast_id' => $newPodcastId,
'guid' => empty($item->guid) ? null : $item->guid,
'guid' => $item->guid ?? null,
'title' => $item->title,
'slug' => $slug,
'audio_file' => download_file($item->enclosure->attributes()),
'audio_file' => download_file(
$item->enclosure->attributes()['url'],
),
'description_markdown' => $converter->convert(
$itemDescriptionHtml,
),
'description_html' => $itemDescriptionHtml,
'image' =>
!$nsItunes->image || empty($nsItunes->image->attributes())
'image' => $episodeImage,
'parental_advisory' =>
$nsItunes->explicit === null
? null
: download_file(
(string) $nsItunes->image->attributes(),
),
'parental_advisory' => empty($nsItunes->explicit)
? null
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
: (in_array($nsItunes->explicit, ['yes', 'true'])
? 'explicit'
: (in_array($nsItunes->explicit, ['no', 'false'])
? 'clean'
: null)),
'number' =>
$this->request->getPost('force_renumber') === 'yes'
? $itemNumber
: (empty($nsItunes->episode)
? null
: $nsItunes->episode),
'season_number' => empty(
$this->request->getPost('season_number')
)
? (empty($nsItunes->season)
? null
: $nsItunes->season)
: $this->request->getPost('season_number'),
'type' => empty($nsItunes->episodeType)
? 'full'
: $nsItunes->episodeType,
'is_blocked' => empty($nsItunes->block)
? false
: $nsItunes->block === 'yes',
: $nsItunes->episode,
'season_number' =>
$this->request->getPost('season_number') === null
? $nsItunes->season
: $this->request->getPost('season_number'),
'type' =>
$nsItunes->episodeType === null
? 'full'
: $nsItunes->episodeType,
'is_blocked' =>
$nsItunes->block === null
? false
: $nsItunes->block === 'yes',
'location_name' => $nsPodcast->location
? $nsPodcast->location
: null,
'location_geo' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['geo'])
$nsPodcast->location->attributes()['geo'] === null
? null
: $nsPodcast->location->attributes()['geo'],
'location_osmid' =>
'location_osm_id' =>
!$nsPodcast->location ||
empty($nsPodcast->location->attributes()['osm'])
$nsPodcast->location->attributes()['osm'] === null
? null
: $nsPodcast->location->attributes()['osm'],
'created_by' => user()->id,
'updated_by' => user()->id,
'created_by' => user_id(),
'updated_by' => user_id(),
'published_at' => strtotime($item->pubDate),
]);
@ -431,14 +451,15 @@ class PodcastImport extends BaseController
->with('errors', $personModel->errors());
}
$personGroup = empty($episodePerson->attributes()['group'])
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
strval($episodePerson->attributes()['group'])
];
$personGroup =
$episodePerson->attributes()['group'] === null
? ['slug' => '']
: ReversedTaxonomy::$taxonomy[
strval($episodePerson->attributes()['group'])
];
$personRole =
empty($episodePerson->attributes()['role']) ||
empty($personGroup)
$episodePerson->attributes()['role'] === null ||
$personGroup === null
? ['slug' => '']
: $personGroup['roles'][
strval($episodePerson->attributes()['role'])

View File

@ -14,7 +14,7 @@ use App\Models\PodcastPersonModel;
use App\Models\PodcastModel;
use App\Models\PersonModel;
class PodcastPerson extends BaseController
class PodcastPersonController extends BaseController
{
/**
* @var Podcast

View File

@ -14,7 +14,7 @@ use App\Models\PlatformModel;
use App\Models\PodcastModel;
use Config\Services;
class PodcastPlatform extends BaseController
class PodcastPlatformController extends BaseController
{
/**
* @var Podcast|null
@ -69,7 +69,7 @@ class PodcastPlatform extends BaseController
as $platformSlug => $podcastPlatform
) {
$podcastPlatformUrl = $podcastPlatform['url'];
if (empty($podcastPlatformUrl)) {
if ($podcastPlatformUrl === null) {
continue;
}
if (!$validation->check($podcastPlatformUrl, 'validate_url')) {

View File

@ -10,11 +10,11 @@ namespace App\Controllers\Admin;
use CodeIgniter\Exceptions\PageNotFoundException;
use App\Authorization\GroupModel;
use App\Entities\User as EntitiesUser;
use App\Entities\User;
use App\Models\UserModel;
use Config\Services;
class User extends BaseController
class UserController extends BaseController
{
/**
* @var User|null
@ -82,7 +82,7 @@ class User extends BaseController
}
// Save the user
$user = new EntitiesUser($this->request->getPost());
$user = new User($this->request->getPost());
// Activate user
$user->activate();

View File

@ -8,11 +8,11 @@
namespace App\Controllers;
use Myth\Auth\Controllers\AuthController;
use Myth\Auth\Controllers\AuthController as MythAuthController;
use App\Entities\User;
use CodeIgniter\HTTP\RedirectResponse;
class Auth extends AuthController
class AuthController extends MythAuthController
{
/**
* An array of helpers to be automatically loaded
@ -66,7 +66,7 @@ class Auth extends AuthController
: $user->activate();
// Ensure default group gets assigned if set
if (!empty($this->config->defaultUserGroup)) {
if ($this->config->defaultUserGroup !== null) {
$users = $users->withGroup($this->config->defaultUserGroup);
}
@ -151,7 +151,7 @@ class Auth extends AuthController
// Reset token still valid?
if (
!empty($user->reset_expires) &&
$user->reset_expires !== null &&
time() > $user->reset_expires->getTimestamp()
) {
return redirect()

View File

@ -9,12 +9,14 @@
namespace App\Controllers;
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 Episode extends BaseController
class EpisodeController extends BaseController
{
use AnalyticsTrait;
@ -162,7 +164,7 @@ class Episode extends BaseController
'author_url' => $this->podcast->link,
'html' =>
'<iframe src="' .
$this->episode->embeddable_player .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
'width' => 600,
'height' => 200,
@ -192,12 +194,12 @@ class Episode extends BaseController
'html',
htmlentities(
'<iframe src="' .
$this->episode->embeddable_player .
$this->episode->embeddable_player_url .
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
),
);
$oembed->addChild('width', 600);
$oembed->addChild('height', 200);
$oembed->addChild('width', '600');
$oembed->addChild('height', '200');
return $this->response->setXML($oembed);
}

View File

@ -16,9 +16,9 @@ use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\Controller;
class Feed extends Controller
class FeedController extends Controller
{
public function index($podcastName): ResponseInterface
public function index(string $podcastName): ResponseInterface
{
helper('rss');
@ -27,17 +27,19 @@ class Feed extends Controller
throw PageNotFoundException::forPageNotFound();
}
$serviceSlug = '';
$service = null;
try {
$service = UserAgentsRSS::find($_SERVER['HTTP_USER_AGENT']);
if ($service) {
$serviceSlug = $service['slug'];
}
} catch (Exception $exception) {
// If things go wrong the show must go on and the user must be able to download the file
log_message('critical', $exception);
}
$serviceSlug = null;
if ($service) {
$serviceSlug = $service['slug'];
}
$cacheName =
"podcast#{$podcast->id}_feed" . ($service ? "_{$serviceSlug}" : '');
@ -57,6 +59,7 @@ class Feed extends Controller
: DECADE,
);
}
return $this->response->setXML($found);
}
}

View File

@ -10,7 +10,7 @@ namespace App\Controllers;
use App\Models\PodcastModel;
class Home extends BaseController
class HomeController extends BaseController
{
/**
* @return mixed

View File

@ -22,7 +22,7 @@ use CodeIgniter\Controller;
use Config\Services;
use Dotenv\Dotenv;
class Install extends Controller
class InstallController extends Controller
{
/**
* @var string[]
@ -50,15 +50,14 @@ class Install extends Controller
*/
public function index(): string
{
try {
// Check if .env is created and has all required fields
$dotenv = Dotenv::createUnsafeImmutable(ROOTPATH);
$dotenv->load();
} catch (Throwable $e) {
if (!file_exists(ROOTPATH . '.env')) {
$this->createEnv();
}
// Check if .env has all required fields
$dotenv = Dotenv::createUnsafeImmutable(ROOTPATH);
$dotenv->load();
// Check if the created .env file is writable to continue install process
if (is_really_writable(ROOTPATH . '.env')) {
try {
@ -171,8 +170,9 @@ class Install extends Controller
if (!$this->validate($rules)) {
return redirect()
->to(
(empty(host_url()) ? config('App')->baseURL : host_url()) .
config('App')->installGateway,
(host_url() === null
? config('App')->baseURL
: host_url()) . config('App')->installGateway,
)
->withInput()
->with('errors', $this->validator->getErrors());
@ -182,9 +182,8 @@ class Install extends Controller
$mediaBaseUrl = $this->request->getPost('media_base_url');
self::writeEnv([
'app.baseURL' => $baseUrl,
'app.mediaBaseURL' => empty($mediaBaseUrl)
? $baseUrl
: $mediaBaseUrl,
'app.mediaBaseURL' =>
$mediaBaseUrl === null ? $baseUrl : $mediaBaseUrl,
'app.adminGateway' => $this->request->getPost('admin_gateway'),
'app.authGateway' => $this->request->getPost('auth_gateway'),
]);
@ -192,7 +191,7 @@ class Install extends Controller
helper('text');
// redirect to full install url with new baseUrl input
return redirect(0)->to(
return redirect()->to(
reduce_double_slashes(
$baseUrl . '/' . config('App')->installGateway,
),
@ -357,9 +356,9 @@ class Install extends Controller
* writes config values in .env file
* overwrites any existing key and appends new ones
*
* @param array $data key/value config pairs
* @param array $configData key/value config pairs
*/
public static function writeEnv($configData): void
public static function writeEnv(array $configData): void
{
$envData = file(ROOTPATH . '.env'); // reads an array of lines

View File

@ -8,17 +8,19 @@
namespace App\Controllers;
use ActivityPub\Controllers\NoteController;
use ActivityPub\Controllers\NoteController as ActivityPubNoteController;
use ActivityPub\Entities\Note as ActivityPubNote;
use Analytics\AnalyticsTrait;
use App\Entities\Actor;
use App\Entities\Note as CastopodNote;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time;
class Note extends NoteController
class NoteController extends ActivityPubNoteController
{
use AnalyticsTrait;
@ -27,6 +29,11 @@ class Note extends NoteController
*/
protected $podcast;
/**
* @var Actor
*/
protected $actor;
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
public function _remap($method, ...$params)
@ -52,7 +59,7 @@ class Note extends NoteController
return $this->$method(...$params);
}
public function index(): RedirectResponse
public function view(): string
{
// Prevent analytics hit when authenticated
if (!can_user_interact()) {
@ -96,7 +103,7 @@ class Note extends NoteController
return $cachedView;
}
public function attemptCreate()
public function attemptCreate(): RedirectResponse
{
$rules = [
'message' => 'required|max_length[500]',
@ -153,7 +160,7 @@ class Note extends NoteController
return redirect()->back();
}
public function attemptReply()
public function attemptReply(): RedirectResponse
{
$rules = [
'message' => 'required|max_length[500]',
@ -185,7 +192,7 @@ class Note extends NoteController
return redirect()->back();
}
public function attemptFavourite()
public function attemptFavourite(): RedirectResponse
{
model('FavouriteModel')->toggleFavourite(
interact_as_actor(),
@ -195,14 +202,14 @@ class Note extends NoteController
return redirect()->back();
}
public function attemptReblog()
public function attemptReblog(): RedirectResponse
{
model('NoteModel')->toggleReblog(interact_as_actor(), $this->note);
return redirect()->back();
}
public function attemptAction()
public function attemptAction(): RedirectResponse
{
$rules = [
'action' => 'required|in_list[favourite,reblog,reply]',
@ -215,17 +222,23 @@ class Note extends NoteController
->with('errors', $this->validator->getErrors());
}
switch ($this->request->getPost('action')) {
$action = $this->request->getPost('action');
switch ($action) {
case 'favourite':
return $this->attemptFavourite();
case 'reblog':
return $this->attemptReblog();
case 'reply':
return $this->attemptReply();
default:
return redirect()
->back()
->withInput()
->with('errors', 'error');
}
}
public function remoteAction($action)
public function remoteAction(string $action): string
{
// Prevent analytics hit when authenticated
if (!can_user_interact()) {
@ -258,6 +271,6 @@ class Note extends NoteController
]);
}
return $cachedView;
return (string) $cachedView;
}
}

View File

@ -8,13 +8,13 @@
namespace App\Controllers;
use App\Entities\Page as PageEntity;
use App\Entities\Page;
use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PageModel;
use App\Models\CreditModel;
use App\Models\PodcastModel;
class Page extends BaseController
class PageController extends BaseController
{
/**
* @var Page|null
@ -60,10 +60,10 @@ class Page extends BaseController
$cacheName = "page_credits_{$locale}";
if (!($found = cache($cacheName))) {
$page = new PageEntity([
$page = new Page([
'title' => lang('Person.credits', [], $locale),
'slug' => 'credits',
'content' => '',
'content_markdown' => '',
]);
$allCredits = (new CreditModel())->findAll();

View File

@ -15,7 +15,7 @@ use CodeIgniter\Controller;
/*
* Provide public access to all platforms so that they can be exported
*/
class Platform extends Controller
class PlatformController extends Controller
{
public function index(): ResponseInterface
{

View File

@ -9,11 +9,12 @@
namespace App\Controllers;
use Analytics\AnalyticsTrait;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
use App\Models\NoteModel;
class Podcast extends BaseController
class PodcastController extends BaseController
{
use AnalyticsTrait;

View File

@ -142,7 +142,7 @@ class AddPodcasts extends Migration
'constraint' => 32,
'null' => true,
],
'location_osmid' => [
'location_osm_id' => [
'type' => 'VARCHAR',
'constraint' => 12,
'null' => true,
@ -194,7 +194,7 @@ class AddPodcasts extends Migration
'actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey('category_id', 'categories', 'id');

View File

@ -44,7 +44,8 @@ class AddEpisodes extends Migration
'constraint' => 255,
],
'audio_file_duration' => [
'type' => 'INT',
// exact value for duration with max 99999,999 ~ 27.7 hours
'type' => 'DECIMAL(8,3)',
'unsigned' => true,
'comment' => 'Playtime in seconds',
],
@ -136,7 +137,7 @@ class AddEpisodes extends Migration
'constraint' => 32,
'null' => true,
],
'location_osmid' => [
'location_osm_id' => [
'type' => 'VARCHAR',
'constraint' => 12,
'null' => true,
@ -189,7 +190,7 @@ class AddEpisodes extends Migration
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey('created_by', 'users', 'id');

View File

@ -32,10 +32,10 @@ class AddSoundbites extends Migration
'unsigned' => true,
],
'start_time' => [
'type' => 'FLOAT',
'type' => 'DECIMAL(8,3)',
],
'duration' => [
'type' => 'FLOAT',
'type' => 'DECIMAL(8,3)',
],
'label' => [
'type' => 'VARCHAR',
@ -67,14 +67,14 @@ class AddSoundbites extends Migration
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'episode_id',
'episodes',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey('created_by', 'users', 'id');

View File

@ -32,19 +32,19 @@ class AddPodcastsUsers extends Migration
],
]);
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
$this->forge->addForeignKey('user_id', 'users', 'id', false, 'CASCADE');
$this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE');
$this->forge->addForeignKey(
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'group_id',
'auth_groups',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('podcasts_users');

View File

@ -32,7 +32,10 @@ class AddPages extends Migration
'constraint' => 191,
'unique' => true,
],
'content' => [
'content_markdown' => [
'type' => 'TEXT',
],
'content_html' => [
'type' => 'TEXT',
],
'created_at' => [

View File

@ -32,14 +32,14 @@ class AddPodcastsCategories extends Migration
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'category_id',
'categories',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('podcasts_categories');

View File

@ -51,14 +51,14 @@ class AddPodcastsPersons extends Migration
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'person_id',
'persons',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('podcasts_persons');

View File

@ -56,21 +56,21 @@ class AddEpisodesPersons extends Migration
'podcast_id',
'podcasts',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'episode_id',
'episodes',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'person_id',
'persons',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('episodes_persons');

View File

@ -307,13 +307,14 @@ class AuthSeeder extends Seeder
/**
* @param array<string, string|int>[] $dataGroups
*/
static function getGroupIdByName(string $name, array $dataGroups): int
static function getGroupIdByName(string $name, array $dataGroups): ?int
{
foreach ($dataGroups as $group) {
if ($group['name'] === $name) {
return $group['id'];
}
}
return null;
}
}

View File

@ -69,14 +69,14 @@ class FakePodcastsAnalyticsSeeder extends Seeder
$age = floor(
($date - strtotime($episode->published_at)) / 86400,
);
$proba1 = floor(exp(3 - $age / 40)) + 1;
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
for (
$num_line = 0;
$num_line < rand(1, $proba1);
$num_line < rand(1, $probability1);
++$num_line
) {
$proba2 = floor(exp(6 - $age / 20)) + 10;
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
$player =
$jsonUserAgents[
@ -127,7 +127,7 @@ class FakePodcastsAnalyticsSeeder extends Seeder
//Bad luck, bad IP, nothing to do.
}
$hits = rand(0, $proba2);
$hits = rand(0, $probability2);
$analytics_podcasts[] = [
'podcast_id' => $podcast->id,

View File

@ -206,14 +206,14 @@ class FakeWebsiteAnalyticsSeeder extends Seeder
$age = floor(
($date - strtotime($episode->published_at)) / 86400,
);
$proba1 = floor(exp(3 - $age / 40)) + 1;
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
for (
$num_line = 0;
$num_line < rand(1, $proba1);
$num_line < rand(1, $probability1);
++$num_line
) {
$proba2 = floor(exp(6 - $age / 20)) + 10;
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
$domain =
$this->domains[rand(0, count($this->domains) - 1)];
@ -226,7 +226,7 @@ class FakeWebsiteAnalyticsSeeder extends Seeder
rand(0, count($this->browsers) - 1)
];
$hits = rand(0, $proba2);
$hits = rand(0, $probability2);
$website_by_browser[] = [
'podcast_id' => $podcast->id,

View File

@ -8,13 +8,18 @@
namespace App\Entities;
use ActivityPub\Entities\Actor as ActivityPubActor;
use App\Models\PodcastModel;
use RuntimeException;
class Actor extends \ActivityPub\Entities\Actor
/**
* @property Podcast|null $podcast
* @property boolean $is_podcast
*/
class Actor extends ActivityPubActor
{
/**
* @var App\Entities\Podcast|null
* @var Podcast|null
*/
protected $podcast;
@ -23,20 +28,20 @@ class Actor extends \ActivityPub\Entities\Actor
*/
protected $is_podcast;
public function getIsPodcast()
public function getIsPodcast(): bool
{
return !empty($this->podcast);
return $this->podcast !== null;
}
public function getPodcast()
public function getPodcast(): ?Podcast
{
if (empty($this->id)) {
if ($this->id === null) {
throw new RuntimeException(
'Actor must be created before getting associated podcast.',
'Podcast id must be set before getting associated podcast.',
);
}
if (empty($this->podcast)) {
if ($this->podcast === null) {
$this->podcast = (new PodcastModel())->getPodcastByActorId(
$this->id,
);

View File

@ -11,6 +11,14 @@ namespace App\Entities;
use App\Models\CategoryModel;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property int $parent_id
* @property Category|null $parent
* @property string $code
* @property string $apple_category
* @property string $google_category
*/
class Category extends Entity
{
/**
@ -23,7 +31,7 @@ class Category extends Entity
*/
protected $casts = [
'id' => 'integer',
'parent_id' => 'integer',
'parent_id' => '?integer',
'code' => 'string',
'apple_category' => 'string',
'google_category' => 'string',

View File

@ -14,6 +14,19 @@ use App\Models\PodcastModel;
use App\Models\EpisodeModel;
use CodeIgniter\Entity\Entity;
/**
* @property int $podcast_id
* @property Podcast $podcast
* @property int|null $episode_id
* @property Episode|null $episode
* @property string $full_name
* @property string $person_group
* @property string $group_label
* @property string $person_role
* @property string $role_label
* @property int $person_id
* @property Person $person
*/
class Credit extends Entity
{
/**
@ -45,30 +58,57 @@ class Credit extends Entity
* @var array<string, string>
*/
protected $casts = [
'person_group' => 'string',
'person_role' => 'string',
'person_id' => 'integer',
'full_name' => 'integer',
'podcast_id' => 'integer',
'episode_id' => '?integer',
'person_id' => 'integer',
'full_name' => 'string',
'person_group' => 'string',
'person_role' => 'string',
];
public function getPerson(): Person
{
if ($this->person_id === null) {
throw new RuntimeException(
'Credit must have person_id before getting person.',
);
}
if ($this->person === null) {
$this->person = (new PersonModel())->getPersonById(
$this->person_id,
);
}
return $this->person;
}
public function getPodcast(): Podcast
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id'],
);
if ($this->podcast_id === null) {
throw new RuntimeException(
'Credit must have podcast_id before getting podcast.',
);
}
if ($this->podcast === null) {
$this->podcast = (new PodcastModel())->getPodcastById(
$this->podcast_id,
);
}
return $this->podcast;
}
public function getEpisode(): ?Episode
{
if (empty($this->episode_id)) {
if ($this->episode_id === null) {
throw new RuntimeException(
'Credit must have episode_id before getting episode.',
);
}
if (empty($this->episode)) {
if ($this->episode === null) {
$this->episode = (new EpisodeModel())->getPublishedEpisodeById(
$this->podcast_id,
$this->episode_id,
@ -78,40 +118,23 @@ class Credit extends Entity
return $this->episode;
}
public function getPerson(): Person
public function getGroupLabel(): string
{
if (empty($this->person_id)) {
throw new RuntimeException(
'Credit must have person_id before getting person.',
);
}
if (empty($this->person)) {
$this->person = (new PersonModel())->getPersonById(
$this->person_id,
);
}
return $this->person;
}
public function getGroupLabel(): ?string
{
if (empty($this->person_group)) {
return null;
if ($this->person_group === null) {
return '';
}
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
}
public function getRoleLabel(): ?string
public function getRoleLabel(): string
{
if (empty($this->person_group)) {
return null;
if ($this->person_group === '') {
return '';
}
if (empty($this->person_role)) {
return null;
if ($this->person_role === '') {
return '';
}
return lang(

View File

@ -8,21 +8,77 @@
namespace App\Entities;
use App\Libraries\Image;
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 CodeIgniter\Entity\Entity;
use CodeIgniter\Files\Exceptions\FileNotFoundException;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter;
use RuntimeException;
/**
* @property int $id
* @property int $podcast_id
* @property Podcast $podcast
* @property string $link
* @property string $guid
* @property string $slug
* @property string $title
* @property File $audio_file
* @property string $audio_file_url
* @property string $audio_file_analytics_url
* @property string $audio_file_web_url
* @property string $audio_file_opengraph_url
* @property string $audio_file_path
* @property double $audio_file_duration
* @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 $description_markdown
* @property string $description_html
* @property Image $image
* @property string|null $image_path
* @property string|null $image_mimetype
* @property File|null $transcript_file
* @property string|null $transcript_file_url
* @property string|null $transcript_file_path
* @property string|null $transcript_file_remote_url
* @property File|null $chapters_file
* @property string|null $chapters_file_url
* @property string|null $chapters_file_path
* @property string|null $chapters_file_remote_url
* @property string|null $parental_advisory
* @property int $number
* @property int $season_number
* @property string $type
* @property bool $is_blocked
* @property Location $location
* @property string|null $location_name
* @property string|null $location_geo
* @property string|null $location_osm_id
* @property array|null $custom_rss
* @property string $custom_rss_string
* @property int $favourites_total
* @property int $reblogs_total
* @property int $notes_total
* @property int $created_by
* @property int $updated_by
* @property string $publication_status;
* @property Time|null $published_at;
* @property Time $created_at;
* @property Time $updated_at;
* @property Time|null $deleted_at;
*
* @property EpisodePerson[] $persons;
* @property Soundbite[] $soundbites;
* @property string $embeddable_player_url;
*/
class Episode extends Entity
{
/**
@ -35,25 +91,10 @@ class Episode extends Entity
*/
protected $link;
/**
* @var Image
*/
protected $image;
/**
* @var File
*/
protected $audioFile;
/**
* @var File
*/
protected $transcript_file;
/**
* @var File
*/
protected $chapters_file;
protected $audio_file;
/**
* @var string
@ -75,6 +116,31 @@ class Episode extends Entity
*/
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[]
*/
@ -91,18 +157,14 @@ class Episode extends Entity
protected $notes;
/**
* Holds text only description, striped of any markdown or html special characters
*
* @var string
* @var Location|null
*/
protected $description;
protected $location;
/**
* The embeddable player URL
*
* @var string
*/
protected $embeddable_player;
protected $custom_rss_string;
/**
* @var string
@ -110,12 +172,8 @@ class Episode extends Entity
protected $publication_status;
/**
* Return custom rss as string
*
* @var string
* @var string[]
*/
protected $custom_rss_string;
protected $dates = [
'published_at',
'created_at',
@ -123,6 +181,9 @@ class Episode extends Entity
'deleted_at',
];
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'podcast_id' => 'integer',
@ -130,7 +191,7 @@ class Episode extends Entity
'slug' => 'string',
'title' => 'string',
'audio_file_path' => 'string',
'audio_file_duration' => 'integer',
'audio_file_duration' => 'double',
'audio_file_mimetype' => 'string',
'audio_file_size' => 'integer',
'audio_file_header_size' => 'integer',
@ -149,7 +210,7 @@ class Episode extends Entity
'is_blocked' => 'boolean',
'location_name' => '?string',
'location_geo' => '?string',
'location_osmid' => '?string',
'location_osm_id' => '?string',
'custom_rss' => '?json-array',
'favourites_total' => 'integer',
'reblogs_total' => 'integer',
@ -161,96 +222,79 @@ class Episode extends Entity
/**
* Saves an episode image
*
* @param UploadedFile|File $image
* @param Image|null $image
*/
public function setImage($image)
public function setImage($image = null): self
{
if (
!empty($image) &&
(!($image instanceof UploadedFile) || $image->isValid())
) {
helper('media');
// check whether the user has inputted an image and store
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_path'] = save_media(
$image,
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->image = new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
if ($image === null) {
return $this;
}
// Save image
$image->saveImage(
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = $image->path;
return $this;
}
public function getImage(): Image
{
if ($imagePath = $this->attributes['image_path']) {
return new Image($imagePath, $this->attributes['image_mimetype']);
return new Image(
null,
$imagePath,
$this->attributes['image_mimetype'],
);
}
return $this->getPodcast()->image;
return $this->podcast->image;
}
/**
* Saves an audio file
*
* @param UploadedFile|File $audioFile
*
*/
public function setAudioFile($audioFile = null)
public function setAudioFile($audioFile)
{
if (
!empty($audioFile) &&
(!($audioFile instanceof UploadedFile) || $audioFile->isValid())
) {
helper(['media', 'id3']);
helper(['media', 'id3']);
$audio_metadata = get_file_tags($audioFile);
$audio_metadata = get_file_tags($audioFile);
$this->attributes['audio_file_path'] = save_media(
$audioFile,
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->attributes['audio_file_duration'] = round(
$audio_metadata['playtime_seconds'],
);
$this->attributes['audio_file_mimetype'] =
$audio_metadata['mime_type'];
$this->attributes['audio_file_size'] = $audio_metadata['filesize'];
$this->attributes['audio_file_header_size'] =
$audio_metadata['avdataoffset'];
$this->attributes['audio_file_path'] = save_media(
$audioFile,
'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'],
);
$this->attributes['audio_file_duration'] =
$audio_metadata['playtime_seconds'];
$this->attributes['audio_file_mimetype'] = $audio_metadata['mime_type'];
$this->attributes['audio_file_size'] = $audio_metadata['filesize'];
$this->attributes['audio_file_header_size'] =
$audio_metadata['avdataoffset'];
return $this;
}
return $this;
}
/**
* Saves an episode transcript file
*
* @param UploadedFile|File $transcriptFile
*
*/
public function setTranscriptFile($transcriptFile)
{
if (
!empty($transcriptFile) &&
(!($transcriptFile instanceof UploadedFile) ||
$transcriptFile->isValid())
) {
helper('media');
helper('media');
$this->attributes['transcript_file_path'] = save_media(
$transcriptFile,
$this->getPodcast()->name,
$this->attributes['slug'] . '-transcript',
);
}
$this->attributes['transcript_file_path'] = save_media(
$transcriptFile,
$this->getPodcast()->name,
$this->attributes['slug'] . '-transcript',
);
return $this;
}
@ -259,35 +303,28 @@ class Episode extends Entity
* Saves an episode chapters file
*
* @param UploadedFile|File $chaptersFile
*
*/
public function setChaptersFile($chaptersFile)
{
if (
!empty($chaptersFile) &&
(!($chaptersFile instanceof UploadedFile) ||
$chaptersFile->isValid())
) {
helper('media');
helper('media');
$this->attributes['chapters_file_path'] = save_media(
$chaptersFile,
$this->getPodcast()->name,
$this->attributes['slug'] . '-chapters',
);
}
$this->attributes['chapters_file_path'] = save_media(
$chaptersFile,
$this->getPodcast()->name,
$this->attributes['slug'] . '-chapters',
);
return $this;
}
public function getAudioFile()
public function getAudioFile(): File
{
helper('media');
return new File(media_path($this->audio_file_path));
}
public function getTranscriptFile()
public function getTranscriptFile(): ?File
{
if ($this->attributes['transcript_file_path']) {
helper('media');
@ -300,7 +337,7 @@ class Episode extends Entity
return null;
}
public function getChaptersFile()
public function getChaptersFile(): ?File
{
if ($this->attributes['chapters_file_path']) {
helper('media');
@ -313,14 +350,14 @@ class Episode extends Entity
return null;
}
public function getAudioFileUrl()
public function getAudioFileUrl(): string
{
helper('media');
return media_base_url($this->audio_file_path);
}
public function getAudioFileAnalyticsUrl()
public function getAudioFileAnalyticsUrl(): string
{
helper('analytics');
@ -335,12 +372,12 @@ class Episode extends Entity
);
}
public function getAudioFileWebUrl()
public function getAudioFileWebUrl(): string
{
return $this->getAudioFileAnalyticsUrl() . '?_from=-+Website+-';
}
public function getAudioFileOpengraphUrl()
public function getAudioFileOpengraphUrl(): string
{
return $this->getAudioFileAnalyticsUrl() . '?_from=-+Open+Graph+-';
}
@ -348,12 +385,8 @@ class Episode extends Entity
/**
* Gets transcript url from transcript file uri if it exists
* or returns the transcript_file_remote_url which can be null.
*
* @return string|null
* @throws FileNotFoundException
* @throws HTTPException
*/
public function getTranscriptFileUrl()
public function getTranscriptFileUrl(): ?string
{
if ($this->attributes['transcript_file_path']) {
return media_base_url($this->attributes['transcript_file_path']);
@ -378,9 +411,9 @@ class Episode extends Entity
/**
* Returns the episode's persons
*
* @return \App\Entities\EpisodePerson[]
* @return EpisodePerson[]
*/
public function getPersons()
public function getPersons(): array
{
if (empty($this->id)) {
throw new RuntimeException(
@ -401,9 +434,9 @@ class Episode extends Entity
/**
* Returns the episodes soundbites
*
* @return \App\Entities\Episode[]
* @return Soundbite[]
*/
public function getSoundbites()
public function getSoundbites(): array
{
if (empty($this->id)) {
throw new RuntimeException(
@ -421,7 +454,10 @@ class Episode extends Entity
return $this->soundbites;
}
public function getNotes()
/**
* @return Note[]
*/
public function getNotes(): array
{
if (empty($this->id)) {
throw new RuntimeException(
@ -436,7 +472,7 @@ class Episode extends Entity
return $this->notes;
}
public function getLink()
public function getLink(): string
{
return base_url(
route_to(
@ -447,7 +483,7 @@ class Episode extends Entity
);
}
public function getEmbeddablePlayer($theme = null)
public function getEmbeddablePlayerUrl($theme = null): string
{
return base_url(
$theme
@ -465,14 +501,18 @@ class Episode extends Entity
);
}
public function setGuid(string $guid)
public function setGuid(?string $guid = null)
{
return $this->attributes['guid'] = empty($guid)
? $this->getLink()
: $guid;
if ($guid === null) {
$this->attributes['guid'] = $this->getLink();
} else {
$this->attributes['guid'] = $guid;
}
return $this;
}
public function getPodcast()
public function getPodcast(): Podcast
{
return (new PodcastModel())->getPodcastById(
$this->attributes['podcast_id'],
@ -494,39 +534,46 @@ class Episode extends Entity
return $this;
}
public function getDescriptionHtml($serviceSlug = null)
public function getDescriptionHtml(?string $serviceSlug = null): string
{
return (empty($this->getPodcast()->partner_id) ||
empty($this->getPodcast()->partner_link_url) ||
empty($this->getPodcast()->partner_image_url)
? ''
: "<div><a href=\"{$this->getPartnerLink(
$descriptionHtml = '';
if (
$this->getPodcast()->partner_id !== null &&
$this->getPodcast()->partner_link_url !== null &&
$this->getPodcast()->partner_image_url !== null
) {
$descriptionHtml .= "<div><a href=\"{$this->getPartnerLink(
$serviceSlug,
)}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImage(
)}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImageUrl(
$serviceSlug,
)}\" alt=\"Partner image\" /></a></div>") .
$this->attributes['description_html'] .
(empty($this->getPodcast()->episode_description_footer_html)
? ''
: "<footer>{$this->getPodcast()->episode_description_footer_html}</footer>");
}
public function getDescription()
{
if ($this->description) {
return $this->description;
)}\" alt=\"Partner image\" /></a></div>";
}
return trim(
preg_replace(
'/\s+/',
' ',
strip_tags($this->attributes['description_html']),
),
);
$descriptionHtml .= $this->attributes['description_html'];
if ($this->getPodcast()->episode_description_footer_html) {
$descriptionHtml .= "<footer>{$this->getPodcast()->episode_description_footer_html}</footer>";
}
return $descriptionHtml;
}
public function getPublicationStatus()
public function getDescription(): string
{
if ($this->description === null) {
$this->description = trim(
preg_replace(
'/\s+/',
' ',
strip_tags($this->attributes['description_html']),
),
);
}
return $this->description;
}
public function getPublicationStatus(): string
{
if ($this->publication_status) {
return $this->publication_status;
@ -546,41 +593,57 @@ class Episode extends Entity
/**
* Saves the location name and fetches OpenStreetMap info
*
* @param string $locationName
*
*/
public function setLocation($locationName = null)
public function setLocation(?string $newLocationName = null)
{
helper('location');
if (
$locationName &&
(empty($this->attributes['location_name']) ||
$this->attributes['location_name'] != $locationName)
) {
$this->attributes['location_name'] = $locationName;
if ($location = fetch_osm_location($locationName)) {
$this->attributes['location_geo'] = $location['geo'];
$this->attributes['location_osmid'] = $location['osmid'];
}
} elseif (empty($locationName)) {
if ($newLocationName === null) {
$this->attributes['location_name'] = null;
$this->attributes['location_geo'] = null;
$this->attributes['location_osmid'] = null;
$this->attributes['location_osm_id'] = null;
}
helper('location');
$oldLocationName = $this->attributes['location_name'];
if (
$oldLocationName === null ||
$oldLocationName !== $newLocationName
) {
$this->attributes['location_name'] = $newLocationName;
if ($location = fetch_osm_location($newLocationName)) {
$this->attributes['location_geo'] = $location['geo'];
$this->attributes['location_osm_id'] = $location['osm_id'];
}
}
return $this;
}
public function getLocation(): ?Location
{
if ($this->location_name === null) {
return null;
}
if ($this->location === null) {
$this->location = new Location([
'name' => $this->location_name,
'geo' => $this->location_geo,
'osm_id' => $this->location_osm_id,
]);
}
return $this->location;
}
/**
* Get custom rss tag as XML String
*
* @return string
*
*/
function getCustomRssString()
function getCustomRssString(): string
{
if (empty($this->custom_rss)) {
if ($this->custom_rss === null) {
return '';
}
@ -597,18 +660,16 @@ class Episode extends Entity
],
$xmlNode,
);
return str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
}
/**
* Saves custom rss tag into json
*
* @param string $customRssString
*
*/
function setCustomRssString($customRssString)
function setCustomRssString(?string $customRssString = null)
{
if (empty($customRssString)) {
if ($customRssString === null) {
return $this;
}
@ -632,23 +693,35 @@ class Episode extends Entity
return $this;
}
function getPartnerLink($serviceSlug = null)
function getPartnerLink(?string $serviceSlug = null): string
{
return rtrim($this->getPodcast()->partner_link_url, '/') .
$partnerLink =
rtrim($this->getPodcast()->partner_link_url, '/') .
'?pid=' .
$this->getPodcast()->partner_id .
'&guid=' .
urlencode($this->attributes['guid']) .
(empty($serviceSlug) ? '' : '&_from=' . $serviceSlug);
urlencode($this->attributes['guid']);
if ($serviceSlug !== null) {
$partnerLink .= '&_from=' . $serviceSlug;
}
return $partnerLink;
}
function getPartnerImage($serviceSlug = null)
function getPartnerImageUrl($serviceSlug = null): string
{
return rtrim($this->getPodcast()->partner_image_url, '/') .
$partnerImageUrl =
rtrim($this->getPodcast()->partner_image_url, '/') .
'?pid=' .
$this->getPodcast()->partner_id .
'&guid=' .
urlencode($this->attributes['guid']) .
(empty($serviceSlug) ? '' : '&_from=' . $serviceSlug);
urlencode($this->attributes['guid']);
if ($serviceSlug !== null) {
$partnerImageUrl = '&_from=' . $serviceSlug;
}
return $partnerImageUrl;
}
}

View File

@ -11,6 +11,15 @@ 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
{
/**
@ -30,7 +39,7 @@ class EpisodePerson extends Entity
'person_role' => '?string',
];
public function getPerson()
public function getPerson(): Person
{
return (new PersonModel())->getPersonById(
$this->attributes['person_id'],

250
app/Entities/Image.php Normal file
View File

@ -0,0 +1,250 @@
<?php
/**
* @copyright 2021 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 CodeIgniter\Files\File;
use Config\Images as ImagesConfig;
use Config\Services;
use RuntimeException;
/**
* @property File|null $file
* @property string $dirname
* @property string $filename
* @property string $extension
* @property string $mimetype
* @property string $path
* @property string $url
* @property string $thumbnail_path
* @property string $thumbnail_url
* @property string $medium_path
* @property string $medium_url
* @property string $large_path
* @property string $large_url
* @property string $feed_path
* @property string $feed_url
* @property string $id3_path
* @property string $id3_url
*/
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;
public function __construct(
?File $file,
string $path = '',
string $mimetype = ''
) {
if ($file === null && $path === '') {
throw new RuntimeException(
'File or path must be set to create an Image.',
);
}
$this->config = config('Images');
$dirname = '';
$filename = '';
$extension = '';
if ($file !== null) {
$dirname = $file->getPath();
$filename = $file->getBasename();
$extension = $file->getExtension();
$mimetype = $file->getMimeType();
}
if ($path !== '') {
[
'filename' => $filename,
'dirname' => $dirname,
'extension' => $extension,
] = pathinfo($path);
}
$this->file = $file;
$this->dirname = $dirname;
$this->filename = $filename;
$this->extension = $extension;
$this->mimetype = $mimetype;
}
function getFile(): File
{
if ($this->file === null) {
$this->file = new File($this->path);
}
return $this->file;
}
function getPath(): string
{
return $this->dirname . '/' . $this->filename . '.' . $this->extension;
}
function getUrl(): string
{
helper('media');
return media_base_url($this->path);
}
function getThumbnailPath(): string
{
return $this->dirname .
'/' .
$this->filename .
$this->config->thumbnailSuffix .
'.' .
$this->extension;
}
function getThumbnailUrl(): string
{
helper('media');
return media_base_url($this->thumbnail_path);
}
function getMediumPath(): string
{
return $this->dirname .
'/' .
$this->filename .
$this->config->mediumSuffix .
'.' .
$this->extension;
}
function getMediumUrl(): string
{
helper('media');
return media_base_url($this->medium_path);
}
function getLargePath(): string
{
return $this->dirname .
'/' .
$this->filename .
$this->config->largeSuffix .
'.' .
$this->extension;
}
function getLargeUrl(): string
{
helper('media');
return media_base_url($this->large_path);
}
function getFeedPath(): string
{
return $this->dirname .
'/' .
$this->filename .
$this->config->feedSuffix .
'.' .
$this->extension;
}
function getFeedUrl(): string
{
helper('media');
return media_base_url($this->feed_path);
}
function getId3Path(): string
{
return $this->dirname .
'/' .
$this->filename .
$this->config->id3Suffix .
'.' .
$this->extension;
}
function getId3Url(): string
{
helper('media');
return media_base_url($this->id3_path);
}
public function saveImage(string $dirname, string $filename): void
{
helper('media');
$this->dirname = $dirname;
$this->filename = $filename;
save_media($this->file, $this->dirname, $this->filename);
$imageService = Services::image();
$thumbnailSize = $this->config->thumbnailSize;
$mediumSize = $this->config->mediumSize;
$largeSize = $this->config->largeSize;
$feedSize = $this->config->feedSize;
$id3Size = $this->config->id3Size;
$imageService
->withFile(media_path($this->path))
->resize($thumbnailSize, $thumbnailSize)
->save(media_path($this->thumbnail_path));
$imageService
->withFile(media_path($this->path))
->resize($mediumSize, $mediumSize)
->save(media_path($this->medium_path));
$imageService
->withFile(media_path($this->path))
->resize($largeSize, $largeSize)
->save(media_path($this->large_path));
$imageService
->withFile(media_path($this->path))
->resize($feedSize, $feedSize)
->save(media_path($this->feed_path));
$imageService
->withFile(media_path($this->path))
->resize($id3Size, $id3Size)
->save(media_path($this->id3_path));
}
}

View File

@ -10,6 +10,10 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property string $code
* @property string $native_name
*/
class Language extends Entity
{
/**

45
app/Entities/Location.php Normal file
View File

@ -0,0 +1,45 @@
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property string $url
* @property string $name
* @property string|null $geo
* @property string|null $osm_id
*/
class Location extends Entity
{
/**
* @var string
*/
const OSM_URL = 'https://www.openstreetmap.org/';
public function getUrl(): string
{
if ($this->osm_id !== null) {
return self::OSM_URL .
['N' => 'node', 'W' => 'way', 'R' => 'relation'][
substr($this->osm_id, 0, 1)
] .
'/' .
substr($this->osm_id, 1);
}
if ($this->geo !== null) {
return self::OSM_URL .
'#map=17/' .
str_replace(',', '/', substr($this->geo, 4));
}
return self::OSM_URL . 'search?query=' . urlencode($this->name);
}
}

View File

@ -8,10 +8,16 @@
namespace App\Entities;
use ActivityPub\Entities\Note as ActivityPubNote;
use App\Models\ActorModel;
use App\Models\EpisodeModel;
use RuntimeException;
class Note extends \ActivityPub\Entities\Note
/**
* @property int|null $episode_id
* @property Episode|null $episode
*/
class Note extends ActivityPubNote
{
/**
* @var Episode|null
@ -40,13 +46,13 @@ class Note extends \ActivityPub\Entities\Note
*/
public function getEpisode()
{
if (empty($this->episode_id)) {
if ($this->episode_id === null) {
throw new RuntimeException(
'Note must have an episode_id before getting episode.',
);
}
if (empty($this->episode)) {
if ($this->episode === null) {
$this->episode = (new EpisodeModel())->getEpisodeById(
$this->episode_id,
);

View File

@ -9,8 +9,20 @@
namespace App\Entities;
use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter;
/**
* @property int $id
* @property string $title
* @property string $link
* @property string $slug
* @property string $content_markdown
* @property string $content_html
* @property Time $created_at
* @property Time $updated_at
* @property Time|null $delete_at
*/
class Page extends Entity
{
/**
@ -30,21 +42,27 @@ class Page extends Entity
'id' => 'integer',
'title' => 'string',
'slug' => 'string',
'content' => 'string',
'content_markdown' => 'string',
'content_html' => 'string',
];
public function getLink()
public function getLink(): string
{
return url_to('page', $this->attributes['slug']);
}
public function getContentHtml(): string
public function setContentMarkdown(string $contentMarkdown): self
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($this->attributes['content']);
$this->attributes['content_markdown'] = $contentMarkdown;
$this->attributes['content_html'] = $converter->convertToHtml(
$contentMarkdown,
);
return $this;
}
}

View File

@ -8,11 +8,19 @@
namespace App\Entities;
use App\Libraries\Image;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\Files\File;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property string $full_name
* @property string $unique_name
* @property string|null $information_url
* @property Image $image
* @property string $image_path
* @property string $image_mimetype
* @property int $created_by
* @property int $updated_by
*/
class Person extends Entity
{
/**
@ -36,26 +44,16 @@ class Person extends Entity
/**
* Saves a picture in `public/media/persons/`
*
* @param UploadedFile|File|null $image
*/
public function setImage($image = null): self
public function setImage(Image $image): self
{
if ($image !== null) {
helper('media');
helper('media');
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_path'] = save_media(
$image,
'persons',
$this->attributes['unique_name'],
);
$this->image = new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
// Save image
$image->saveImage('persons', $this->attributes['unique_name']);
$this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = $image->path;
return $this;
}
@ -63,6 +61,7 @@ class Person extends Entity
public function getImage(): Image
{
return new Image(
null,
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);

View File

@ -10,6 +10,17 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property string $slug
* @property string $type
* @property string $label
* @property string $home_url
* @property string|null $submit_url
* @property string|null $link_url
* @property string|null $link_content
* @property bool|null $is_visible
* @property bool|null $is_on_embeddable_player
*/
class Platform extends Entity
{
/**

View File

@ -8,7 +8,6 @@
namespace App\Entities;
use App\Libraries\Image;
use App\Libraries\SimpleRSSElement;
use App\Models\CategoryModel;
use App\Models\EpisodeModel;
@ -16,11 +15,66 @@ use App\Models\PlatformModel;
use App\Models\PodcastPersonModel;
use CodeIgniter\Entity\Entity;
use App\Models\UserModel;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter;
use RuntimeException;
/**
* @property int $id
* @property int $actor_id
* @property Actor $actor
* @property string $name
* @property string $link
* @property string $feed_url
* @property string $title
* @property string $description Holds text only description, striped of any markdown or html special characters
* @property string $description_markdown
* @property string $description_html
* @property Image $image
* @property string $image_path
* @property string $image_mimetype
* @property string $language_code
* @property int $category_id
* @property Category $category
* @property int[] $other_categories_ids
* @property Category[] $other_categories
* @property string|null $parental_advisory
* @property string|null $publisher
* @property string $owner_name
* @property string $owner_email
* @property string $type
* @property string|null $copyright
* @property string|null $episode_description_footer_markdown
* @property string|null $episode_description_footer_html
* @property bool $is_blocked
* @property bool $is_completed
* @property bool $is_locked
* @property string|null $imported_feed_url
* @property string|null $new_feed_url
* @property Location $location
* @property string|null $location_name
* @property string|null $location_geo
* @property string|null $location_osm_id
* @property string|null $payment_pointer
* @property array|null $custom_rss
* @property string $custom_rss_string
* @property string|null $partner_id
* @property string|null $partner_link_url
* @property string|null $partner_image_url
* @property int $created_by
* @property int $updated_by
* @property Time $created_at;
* @property Time $updated_at;
* @property Time|null $deleted_at;
*
* @property Episode[] $episodes
* @property PodcastPerson[] $persons
* @property User[] $contributors
* @property Platform[] $podcasting_platforms
* @property Platform[] $social_platforms
* @property Platform[] $funding_platforms
*
*/
class Podcast extends Entity
{
/**
@ -39,14 +93,9 @@ class Podcast extends Entity
protected $image;
/**
* @var Episode[]
* @var string
*/
protected $episodes;
/**
* @var PodcastPerson[]
*/
protected $persons;
protected $description;
/**
* @var Category
@ -59,44 +108,53 @@ class Podcast extends Entity
protected $other_categories;
/**
* @var integer[]
* @var string[]
*/
protected $other_categories_ids;
/**
* @var Episode[]
*/
protected $episodes;
/**
* @var PodcastPerson[]
*/
protected $persons;
/**
* @var User[]
*/
protected $contributors;
/**
* @var Platform
* @var Platform[]
*/
protected $podcastingPlatforms;
protected $podcasting_platforms;
/**
* @var Platform
* @var Platform[]
*/
protected $socialPlatforms;
protected $social_platforms;
/**
* @var Platform
* @var Platform[]
*/
protected $fundingPlatforms;
protected $funding_platforms;
/**
* Holds text only description, striped of any markdown or html special characters
*
* @var string
* @var Location|null
*/
protected $description;
protected $location;
/**
* Return custom rss as string
*
* @var string
*/
protected $custom_rss_string;
/**
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'actor_id' => 'integer',
@ -123,7 +181,7 @@ class Podcast extends Entity
'new_feed_url' => '?string',
'location_name' => '?string',
'location_geo' => '?string',
'location_osmid' => '?string',
'location_osm_id' => '?string',
'payment_pointer' => '?string',
'custom_rss' => '?json-array',
'partner_id' => '?string',
@ -133,20 +191,15 @@ class Podcast extends Entity
'updated_by' => 'integer',
];
/**
* Returns the podcast actor
*
* @return Actor
*/
public function getActor(): Actor
{
if (!$this->attributes['actor_id']) {
if (!$this->actor_id) {
throw new RuntimeException(
'Podcast must have an actor_id before getting actor.',
);
}
if (empty($this->actor)) {
if ($this->actor === null) {
$this->actor = model('ActorModel')->getActorById($this->actor_id);
}
@ -156,36 +209,22 @@ class Podcast extends Entity
/**
* Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/`
*
* @param UploadedFile|File $image
* @param Image $image
*/
public function setImage($image = null): self
public function setImage($image): self
{
if ($image) {
helper('media');
// Save image
$image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
$this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_path'] = save_media(
$image,
'podcasts/' . $this->attributes['name'],
'cover',
);
$this->image = new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
$this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = $image->path;
return $this;
}
public function getImage(): Image
{
return new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
return new Image(null, $this->image_path, $this->image_mimetype);
}
public function getLink(): string
@ -350,14 +389,14 @@ class Podcast extends Entity
);
}
if (empty($this->podcastingPlatforms)) {
$this->podcastingPlatforms = (new PlatformModel())->getPodcastPlatforms(
if (empty($this->podcasting_platforms)) {
$this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id,
'podcasting',
);
}
return $this->podcastingPlatforms;
return $this->podcasting_platforms;
}
/**
@ -373,14 +412,14 @@ class Podcast extends Entity
);
}
if (empty($this->socialPlatforms)) {
$this->socialPlatforms = (new PlatformModel())->getPodcastPlatforms(
if (empty($this->social_platforms)) {
$this->social_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id,
'social',
);
}
return $this->socialPlatforms;
return $this->social_platforms;
}
/**
@ -396,14 +435,14 @@ class Podcast extends Entity
);
}
if (empty($this->fundingPlatforms)) {
$this->fundingPlatforms = (new PlatformModel())->getPodcastPlatforms(
if (empty($this->funding_platforms)) {
$this->funding_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id,
'funding',
);
}
return $this->fundingPlatforms;
return $this->funding_platforms;
}
/**
@ -444,29 +483,50 @@ class Podcast extends Entity
/**
* Saves the location name and fetches OpenStreetMap info
*/
public function setLocation(?string $locationName = null): self
public function setLocation(?string $newLocationName = null)
{
helper('location');
if (
$locationName &&
(empty($this->attributes['location_name']) ||
$this->attributes['location_name'] != $locationName)
) {
$this->attributes['location_name'] = $locationName;
if ($location = fetch_osm_location($locationName)) {
$this->attributes['location_geo'] = $location['geo'];
$this->attributes['location_osmid'] = $location['osmid'];
}
} elseif (empty($locationName)) {
if ($newLocationName === null) {
$this->attributes['location_name'] = null;
$this->attributes['location_geo'] = null;
$this->attributes['location_osmid'] = null;
$this->attributes['location_osm_id'] = null;
}
helper('location');
$oldLocationName = $this->attributes['location_name'];
if (
$oldLocationName === null ||
$oldLocationName !== $newLocationName
) {
$this->attributes['location_name'] = $newLocationName;
if ($location = fetch_osm_location($newLocationName)) {
$this->attributes['location_geo'] = $location['geo'];
$this->attributes['location_osm_id'] = $location['osm_id'];
}
}
return $this;
}
public function getLocation(): ?Location
{
if ($this->location_name === null) {
return null;
}
if ($this->location === null) {
$this->location = new Location([
'name' => $this->location_name,
'geo' => $this->location_geo,
'osm_id' => $this->location_osm_id,
]);
}
return $this->location;
}
/**
* Get custom rss tag as XML String
*

View File

@ -11,6 +11,14 @@ 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
{
/**
@ -29,7 +37,7 @@ class PodcastPerson extends Entity
'person_role' => '?string',
];
public function getPerson()
public function getPerson(): ?Person
{
return (new PersonModel())->getPersonById(
$this->attributes['person_id'],

View File

@ -10,6 +10,16 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property int $podcast_id
* @property int $episode_id
* @property double $start_time
* @property double $duration
* @property string|null $label
* @property int $created_by
* @property int $updated_by
*/
class Soundbite extends Entity
{
/**
@ -19,17 +29,10 @@ class Soundbite extends Entity
'id' => 'integer',
'podcast_id' => 'integer',
'episode_id' => 'integer',
'start_time' => 'float',
'duration' => 'float',
'start_time' => 'double',
'duration' => 'double',
'label' => '?string',
'created_by' => 'integer',
'updated_by' => 'integer',
];
public function setUpdatedBy(User $user): self
{
$this->attributes['updated_by'] = $user->id;
return $this;
}
}

View File

@ -10,22 +10,27 @@ namespace App\Entities;
use RuntimeException;
use App\Models\PodcastModel;
use Myth\Auth\Entities\User as MythAuthUser;
class User extends \Myth\Auth\Entities\User
/**
* @property int $id
* @property string $username
* @property string $email
* @property string $password
* @property bool $active
* @property bool $force_pass_reset
* @property int|null $podcast_id
* @property string|null $podcast_role
*
* @property Podcast[] $podcasts All podcasts the user is contributing to
*/
class User extends MythAuthUser
{
/**
* Per-user podcasts
* @var Podcast[]
*/
protected $podcasts = [];
/**
* The podcast the user is contributing to
*
* @var Podcast|null
*/
protected $podcast;
/**
* Array of field names and the type of value to cast them as
* when they are accessed.
@ -36,8 +41,8 @@ class User extends \Myth\Auth\Entities\User
'id' => 'integer',
'active' => 'boolean',
'force_pass_reset' => 'boolean',
'podcast_role' => '?string',
'podcast_id' => '?integer',
'podcast_role' => '?string',
];
/**
@ -47,36 +52,16 @@ class User extends \Myth\Auth\Entities\User
*/
public function getPodcasts(): array
{
if (empty($this->id)) {
if ($this->id === null) {
throw new RuntimeException(
'Users must be created before getting podcasts.',
);
}
if (empty($this->podcasts)) {
if ($this->podcasts === null) {
$this->podcasts = (new PodcastModel())->getUserPodcasts($this->id);
}
return $this->podcasts;
}
/**
* Returns a podcast the user is contributing to
*/
public function getPodcast(): Podcast
{
if (empty($this->podcast_id)) {
throw new RuntimeException(
'Podcast_id must be set before getting podcast.',
);
}
if (empty($this->podcast)) {
$this->podcast = (new PodcastModel())->getPodcastById(
$this->podcast_id,
);
}
return $this->podcast;
}
}

View File

@ -26,9 +26,7 @@ class PermissionFilter implements FilterInterface
*/
public function before(RequestInterface $request, $params = null)
{
if (!function_exists('logged_in')) {
helper('auth');
}
helper('auth');
if (empty($params)) {
return;

View File

@ -7,9 +7,22 @@
*/
use ActivityPub\Entities\Actor;
use App\Entities\User;
use CodeIgniter\Database\Exceptions\DataException;
use Config\Services;
if (!function_exists('user')) {
/**
* Returns the User instance for the current logged in user.
*/
function user(): ?User
{
$authenticate = Services::authentication();
$authenticate->check();
return $authenticate->user();
}
}
if (!function_exists('set_interact_as_actor')) {
/**
* Sets the actor id of which the user is acting as

View File

@ -6,6 +6,7 @@
* @link https://castopod.org/
*/
use App\Entities\Location;
use CodeIgniter\View\Table;
use CodeIgniter\I18n\Time;
@ -127,8 +128,8 @@ if (!function_exists('icon_button')) {
*
* Abstracts the `button()` helper to create a stylized icon button
*
* @param string $label The button label
* @param string $uri URI string or array of URI segments
* @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
*
@ -252,15 +253,11 @@ if (!function_exists('publication_pill')) {
*
* Shows the stylized publication datetime in regards to current datetime.
*
* @param Time $publicationDate publication datetime of the episode
* @param boolean $isPublished whether or not the episode has been published
* @param string $customClass css class to add to the component
*
* @return string
*/
function publication_pill(
?Time $publicationDate,
$publicationStatus,
string $publicationStatus,
string $customClass = ''
): string {
if ($publicationDate === null) {
@ -336,6 +333,12 @@ if (!function_exists('publication_button')) {
$variant = 'danger';
$iconLeft = 'cloud-off';
break;
default:
$label = '';
$route = '';
$variant = '';
$iconLeft = '';
break;
}
return button($label, $route, [
@ -351,16 +354,13 @@ if (!function_exists('episode_numbering')) {
/**
* Returns relevant translated episode numbering.
*
* @param string $class styling classes
* @param string $is_abbr component will show abbreviated numbering if true
*
* @return string|null
* @param bool $isAbbr component will show abbreviated numbering if true
*/
function episode_numbering(
?int $episodeNumber = null,
?int $seasonNumber = null,
string $class = '',
$isAbbr = false
bool $isAbbr = false
): string {
if (!$episodeNumber && !$seasonNumber) {
return '';
@ -368,22 +368,20 @@ if (!function_exists('episode_numbering')) {
$transKey = '';
$args = [];
if ($episodeNumber && $seasonNumber) {
if ($episodeNumber !== null) {
$args['episodeNumber'] = $episodeNumber;
}
if ($seasonNumber !== null) {
$args['seasonNumber'] = $seasonNumber;
}
if ($episodeNumber !== null && $seasonNumber !== null) {
$transKey = 'Episode.season_episode';
$args = [
'seasonNumber' => $seasonNumber,
'episodeNumber' => $episodeNumber,
];
} elseif ($episodeNumber && !$seasonNumber) {
} elseif ($episodeNumber !== null && $seasonNumber === null) {
$transKey = 'Episode.number';
$args = [
'episodeNumber' => $episodeNumber,
];
} elseif (!$episodeNumber && $seasonNumber) {
} elseif ($episodeNumber === null && $seasonNumber !== null) {
$transKey = 'Episode.season';
$args = [
'seasonNumber' => $seasonNumber,
];
}
if ($isAbbr) {
@ -408,19 +406,15 @@ if (!function_exists('location_link')) {
/**
* Returns link to display from location info
*/
function location_link(
?string $locationName,
?string $locationGeo,
?string $locationOsmid,
$class = ''
): string {
if (empty($locationName)) {
function location_link(?Location $location, string $class = ''): string
{
if ($location === null) {
return '';
}
return anchor(
location_url($locationName, $locationGeo, $locationOsmid),
icon('map-pin', 'mr-2') . $locationName,
$location->url,
icon('map-pin', 'mr-2') . $location->name,
[
'class' =>
'inline-flex items-baseline hover:underline' .

View File

@ -15,9 +15,9 @@ if (!function_exists('get_file_tags')) {
/**
* Gets audio file metadata and ID3 info
*
* @param UploadedFile $file
* @return array<string, string|double|int>
*/
function get_file_tags($file): array
function get_file_tags(File $file): array
{
$getID3 = new GetID3();
$FileInfo = $getID3->analyze($file);
@ -34,8 +34,6 @@ if (!function_exists('get_file_tags')) {
if (!function_exists('write_audio_file_tags')) {
/**
* Write audio file metadata / ID3 tags
*
* @return UploadedFile
*/
function write_audio_file_tags(Episode $episode): void
{
@ -51,7 +49,7 @@ if (!function_exists('write_audio_file_tags')) {
$tagwriter->tagformats = ['id3v2.4'];
$tagwriter->tag_encoding = $TextEncoding;
$cover = new File($episode->image->id3_path);
$cover = new File(media_path($episode->image->id3_path));
$APICdata = file_get_contents($cover->getRealPath());

View File

@ -11,49 +11,46 @@ use Config\Services;
if (!function_exists('fetch_osm_location')) {
/**
* Fetches places from Nominatim OpenStreetMap
*
* @return array|null
*/
function fetch_osm_location(string $locationName): ?array
{
$osmObject = null;
if (!empty($locationName)) {
try {
$client = Services::curlrequest();
try {
$client = Services::curlrequest();
$response = $client->request(
'GET',
'https://nominatim.openstreetmap.org/search.php?q=' .
urlencode($locationName) .
'&polygon_geojson=1&format=jsonv2',
[
'headers' => [
'User-Agent' => 'Castopod/' . CP_VERSION,
'Accept' => 'application/json',
],
$response = $client->request(
'GET',
'https://nominatim.openstreetmap.org/search.php?q=' .
urlencode($locationName) .
'&polygon_geojson=1&format=jsonv2',
[
'headers' => [
'User-Agent' => 'Castopod/' . CP_VERSION,
'Accept' => 'application/json',
],
);
$places = json_decode(
$response->getBody(),
true,
512,
JSON_THROW_ON_ERROR,
);
$osmObject = [
'geo' =>
empty($places[0]['lat']) || empty($places[0]['lon'])
? null
: "geo:{$places[0]['lat']},{$places[0]['lon']}",
'osmid' => empty($places[0]['osm_type'])
],
);
$places = json_decode(
$response->getBody(),
true,
512,
JSON_THROW_ON_ERROR,
);
$osmObject = [
'geo' =>
empty($places[0]['lat']) || empty($places[0]['lon'])
? null
: strtoupper(substr($places[0]['osm_type'], 0, 1)) .
$places[0]['osm_id'],
];
} catch (Exception $exception) {
//If things go wrong the show must go on
log_message('critical', $exception);
}
: "geo:{$places[0]['lat']},{$places[0]['lon']}",
'osm_id' => empty($places[0]['osm_type'])
? null
: strtoupper(substr($places[0]['osm_type'], 0, 1)) .
$places[0]['osm_id'],
];
} catch (Exception $exception) {
//If things go wrong the show must go on
log_message('critical', $exception);
}
return $osmObject;

View File

@ -15,14 +15,16 @@ if (!function_exists('save_media')) {
/**
* Saves a file to the corresponding podcast folder in `public/media`
*
* @param File|UploadedFile $filePath
* @param File|UploadedFile $file
*/
function save_media(
File $filePath,
string $folder,
string $mediaName
File $file,
string $folder = '',
string $filename
): string {
$fileName = $mediaName . '.' . $filePath->getExtension();
if (($extension = $file->getExtension()) !== '') {
$filename = $filename . '.' . $extension;
}
$mediaRoot = config('App')->mediaRoot . '/' . $folder;
@ -31,10 +33,10 @@ if (!function_exists('save_media')) {
touch($mediaRoot . '/index.html');
}
// move to media folder and overwrite file if already existing
$filePath->move($mediaRoot . '/', $fileName, true);
// move to media folder, overwrite file if already existing
$file->move($mediaRoot . '/', $filename, true);
return $folder . '/' . $fileName;
return $folder . '/' . $filename;
}
}
@ -107,8 +109,7 @@ if (!function_exists('media_base_url')) {
/**
* Return the media base URL to use in views
*
* @param string|array $uri URI string or array of URI segments
* @param string $protocol
* @param string|string[] $uri URI string or array of URI segments
*/
function media_base_url($uri = ''): string
{

View File

@ -127,7 +127,7 @@ if (!function_exists('slugify')) {
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
// remove unwanted characters
$text = preg_replace('~[^\\-\w]+~', '', $text);
$text = preg_replace('~[^\-\w]+~', '', $text);
// trim
$text = trim($text, '-');

View File

@ -6,20 +6,24 @@
* @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 array &$personsArray
* @param Person[]|PodcastPerson[]|EpisodePerson[] $persons
*/
function construct_person_array(array $persons, &$personsArray): void
function construct_person_array(array $persons, array &$personsArray): void
{
foreach ($persons as $person) {
if (array_key_exists($person->person->id, $personsArray)) {
$personsArray[$person->person->id]['roles'] .=
if (array_key_exists($person->id, $personsArray)) {
$personsArray[$person->id]['roles'] .=
empty($person->person_group) || empty($person->person_role)
? ''
: (empty($personsArray[$person->person->id]['roles'])
: (empty($personsArray[$person->id]['roles'])
? ''
: ', ') .
lang(

View File

@ -16,10 +16,10 @@ if (!function_exists('get_rss_feed')) {
/**
* Generates the rss feed for a given podcast entity
*
* @param string $service The name of the service that fetches the RSS feed for future reference when the audio file is eventually downloaded
* @param string $serviceSlug The name of the service that fetches the RSS feed for future reference when the audio file is eventually downloaded
* @return string rss feed as xml
*/
function get_rss_feed(Podcast $podcast, $serviceSlug = ''): string
function get_rss_feed(Podcast $podcast, ?string $serviceSlug = null): string
{
$episodes = $podcast->episodes;
@ -43,7 +43,7 @@ if (!function_exists('get_rss_feed')) {
$atom_link->addAttribute('rel', 'self');
$atom_link->addAttribute('type', 'application/rss+xml');
if (!empty($podcast->new_feed_url)) {
if ($podcast->new_feed_url !== null) {
$channel->addChild(
'new-feed-url',
$podcast->new_feed_url,
@ -67,23 +67,27 @@ if (!function_exists('get_rss_feed')) {
$itunes_image = $channel->addChild('image', null, $itunes_namespace);
$itunes_image->addAttribute('href', $podcast->image->original_url);
// FIXME: This should be downsized to 1400x1400
$itunes_image->addAttribute('href', $podcast->image->url);
$channel->addChild('language', $podcast->language_code);
if (!empty($podcast->location_name)) {
if ($podcast->location !== null) {
$locationElement = $channel->addChild(
'location',
htmlspecialchars($podcast->location_name),
htmlspecialchars($podcast->location->name),
$podcast_namespace,
);
if (!empty($podcast->location_geo)) {
$locationElement->addAttribute('geo', $podcast->location_geo);
if ($podcast->location->geo !== null) {
$locationElement->addAttribute('geo', $podcast->location->geo);
}
if (!empty($podcast->location_osmid)) {
$locationElement->addAttribute('osm', $podcast->location_osmid);
if ($podcast->location->osm_id !== null) {
$locationElement->addAttribute(
'osm',
$podcast->location->osm_id,
);
}
}
if (!empty($podcast->payment_pointer)) {
if ($podcast->payment_pointer !== null) {
$valueElement = $channel->addChild(
'value',
null,
@ -103,7 +107,7 @@ if (!function_exists('get_rss_feed')) {
'address',
$podcast->payment_pointer,
);
$recipientElement->addAttribute('split', 100);
$recipientElement->addAttribute('split', '100');
}
$channel
->addChild(
@ -112,7 +116,7 @@ if (!function_exists('get_rss_feed')) {
$podcast_namespace,
)
->addAttribute('owner', $podcast->owner_email);
if (!empty($podcast->imported_feed_url)) {
if ($podcast->imported_feed_url !== null) {
$channel->addChild(
'previousUrl',
$podcast->imported_feed_url,
@ -120,7 +124,7 @@ if (!function_exists('get_rss_feed')) {
);
}
foreach ($podcast->podcastingPlatforms as $podcastingPlatform) {
foreach ($podcast->podcasting_platforms as $podcastingPlatform) {
$podcastingPlatformElement = $channel->addChild(
'id',
null,
@ -130,13 +134,13 @@ if (!function_exists('get_rss_feed')) {
'platform',
$podcastingPlatform->slug,
);
if (!empty($podcastingPlatform->link_content)) {
if ($podcastingPlatform->link_content !== null) {
$podcastingPlatformElement->addAttribute(
'id',
$podcastingPlatform->link_content,
);
}
if (!empty($podcastingPlatform->link_url)) {
if ($podcastingPlatform->link_url !== null) {
$podcastingPlatformElement->addAttribute(
'url',
htmlspecialchars($podcastingPlatform->link_url),
@ -144,7 +148,7 @@ if (!function_exists('get_rss_feed')) {
}
}
foreach ($podcast->socialPlatforms as $socialPlatform) {
foreach ($podcast->social_platforms as $socialPlatform) {
$socialPlatformElement = $channel->addChild(
'social',
$socialPlatform->link_content,
@ -154,7 +158,7 @@ if (!function_exists('get_rss_feed')) {
'platform',
$socialPlatform->slug,
);
if (!empty($socialPlatform->link_url)) {
if ($socialPlatform->link_url !== null) {
$socialPlatformElement->addAttribute(
'url',
htmlspecialchars($socialPlatform->link_url),
@ -162,7 +166,7 @@ if (!function_exists('get_rss_feed')) {
}
}
foreach ($podcast->fundingPlatforms as $fundingPlatform) {
foreach ($podcast->funding_platforms as $fundingPlatform) {
$fundingPlatformElement = $channel->addChild(
'funding',
$fundingPlatform->link_content,
@ -172,7 +176,7 @@ if (!function_exists('get_rss_feed')) {
'platform',
$fundingPlatform->slug,
);
if (!empty($socialPlatform->link_url)) {
if ($fundingPlatform->link_url !== null) {
$fundingPlatformElement->addAttribute(
'url',
htmlspecialchars($fundingPlatform->link_url),
@ -186,9 +190,10 @@ if (!function_exists('get_rss_feed')) {
htmlspecialchars($podcastPerson->person->full_name),
$podcast_namespace,
);
if (
!empty($podcastPerson->person_role) &&
!empty($podcastPerson->person_group)
$podcastPerson->person_role !== null &&
$podcastPerson->person_group !== null
) {
$podcastPersonElement->addAttribute(
'role',
@ -201,7 +206,8 @@ if (!function_exists('get_rss_feed')) {
),
);
}
if (!empty($podcastPerson->person_group)) {
if ($podcastPerson->person_group !== null) {
$podcastPersonElement->addAttribute(
'group',
htmlspecialchars(
@ -217,7 +223,8 @@ if (!function_exists('get_rss_feed')) {
'img',
$podcastPerson->person->image->large_url,
);
if (!empty($podcastPerson->person->information_url)) {
if ($podcastPerson->person->information_url !== null) {
$podcastPersonElement->addAttribute(
'href',
$podcastPerson->person->information_url,
@ -263,7 +270,7 @@ if (!function_exists('get_rss_feed')) {
$image->addChild('title', $podcast->title);
$image->addChild('link', $podcast->link);
if (!empty($podcast->custom_rss)) {
if ($podcast->custom_rss !== null) {
array_to_rss(
[
'elements' => $podcast->custom_rss,
@ -280,7 +287,7 @@ if (!function_exists('get_rss_feed')) {
$enclosure->addAttribute(
'url',
$episode->audio_file_analytics_url .
(empty($serviceSlug)
($serviceSlug === ''
? ''
: '?_from=' . urlencode($serviceSlug)),
);
@ -292,22 +299,22 @@ if (!function_exists('get_rss_feed')) {
'pubDate',
$episode->published_at->format(DATE_RFC1123),
);
if (!empty($episode->location_name)) {
if ($episode->location !== null) {
$locationElement = $item->addChild(
'location',
htmlspecialchars($episode->location_name),
htmlspecialchars($episode->location->name),
$podcast_namespace,
);
if (!empty($episode->location_geo)) {
if ($episode->location->geo !== null) {
$locationElement->addAttribute(
'geo',
$episode->location_geo,
$episode->location->geo,
);
}
if (!empty($episode->location_osmid)) {
if ($episode->location->osm_id !== null) {
$locationElement->addAttribute(
'osm',
$episode->location_osmid,
$episode->location->osm_id,
);
}
}
@ -477,7 +484,7 @@ if (!function_exists('add_category_tag')) {
{
$itunes_namespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
$itunes_category = $node->addChild('category', null, $itunes_namespace);
$itunes_category = $node->addChild('category', '', $itunes_namespace);
$itunes_category->addAttribute(
'text',
$category->parent !== null
@ -488,7 +495,7 @@ if (!function_exists('add_category_tag')) {
if ($category->parent !== null) {
$itunes_category_child = $itunes_category->addChild(
'category',
null,
'',
$itunes_namespace,
);
$itunes_category_child->addAttribute(

View File

@ -6,13 +6,13 @@
* @link https://castopod.org/
*/
use CodeIgniter\HTTP\URI;
if (!function_exists('host_url')) {
/**
* Return the host URL to use in views
*
* @return string|false
*/
function host_url()
function host_url(): ?string
{
if (isset($_SERVER['HTTP_HOST'])) {
$protocol =
@ -23,7 +23,7 @@ if (!function_exists('host_url')) {
return $protocol . $_SERVER['HTTP_HOST'] . '/';
}
return false;
return null;
}
}
@ -43,41 +43,13 @@ if (!function_exists('current_season_url')) {
}
}
if (!function_exists('location_url')) {
/**
* Returns URL to display from location info
*/
function location_url(
string $locationName,
?string $locationGeo = null,
?string $locationOsmid = null
): string {
if (!empty($locationOsmid)) {
return 'https://www.openstreetmap.org/' .
['N' => 'node', 'W' => 'way', 'R' => 'relation'][
substr($locationOsmid, 0, 1)
] .
'/' .
substr($locationOsmid, 1);
}
if (!empty($locationGeo)) {
return 'https://www.openstreetmap.org/#map=17/' .
str_replace(',', '/', substr($locationGeo, 4));
}
return 'https://www.openstreetmap.org/search?query=' .
urlencode($locationName);
}
}
//--------------------------------------------------------------------
if (!function_exists('extract_params_from_episode_uri')) {
/**
* Returns podcast name and episode slug from episode string uri
*
* @param URI $episodeUri
*/
function extract_params_from_episode_uri($episodeUri): ?array
function extract_params_from_episode_uri(URI $episodeUri): ?array
{
preg_match(
'~@(?P<podcastName>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~',

View File

@ -43,15 +43,11 @@ class ActivityRequest
],
];
/**
* @param string $uri
* @param string $activityPayload
*/
public function __construct($uri, $activityPayload = null)
public function __construct(string $uri, ?string $activityPayload = null)
{
$this->request = Services::curlrequest();
if ($activityPayload) {
if ($activityPayload !== null) {
$this->request->setBody($activityPayload);
}

View File

@ -8,15 +8,14 @@
namespace ActivityPub\Controllers;
use ActivityPub\Entities\Actor;
use ActivityPub\Config\ActivityPub;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Exceptions\PageNotFoundException;
use ActivityPub\Entities\Note;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use ActivityPub\Objects\OrderedCollectionObject;
use ActivityPub\Objects\OrderedCollectionPage;
use App\Entities\Actor;
use CodeIgniter\Controller;
use CodeIgniter\I18n\Time;
@ -325,13 +324,10 @@ class ActorController extends Controller
// parse activityPub id to get actor and domain
// check if actor and domain exist
try {
if ($parts = split_handle($this->request->getPost('handle'))) {
extract($parts);
$data = get_webfinger_data($username, $domain);
}
} catch (HTTPException $httpException) {
if (
!($parts = split_handle($this->request->getPost('handle'))) ||
!($data = get_webfinger_data($parts['username'], $parts['domain']))
) {
return redirect()
->back()
->withInput()

View File

@ -33,9 +33,12 @@ class BlockController extends Controller
$handle = $this->request->getPost('handle');
if ($parts = split_handle($handle)) {
extract($parts);
if (($actor = get_or_create_actor($username, $domain)) === null) {
if (
($actor = get_or_create_actor(
$parts['username'],
$parts['domain'],
)) === null
) {
return redirect()
->back()
->withInput()

View File

@ -12,8 +12,8 @@ use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Exceptions\PageNotFoundException;
use ActivityPub\Entities\Note;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use ActivityPub\Config\ActivityPub;
use ActivityPub\Models\NoteModel;
use ActivityPub\Objects\OrderedCollectionObject;
use ActivityPub\Objects\OrderedCollectionPage;
use CodeIgniter\Controller;
@ -27,7 +27,7 @@ class NoteController extends Controller
protected $helpers = ['activitypub'];
/**
* @var Note|null
* @var Note
*/
protected $note;
@ -41,7 +41,7 @@ class NoteController extends Controller
$this->config = config('ActivityPub');
}
public function _remap($method, ...$params)
public function _remap(string $method, string ...$params)
{
if (!($this->note = model('NoteModel')->getNoteById($params[0]))) {
throw PageNotFoundException::forPageNotFound();
@ -63,7 +63,8 @@ class NoteController extends Controller
public function replies(): RedirectResponse
{
// get note replies
/** get note replies
* @var NoteModel */
$noteReplies = model('NoteModel')
->where(
'in_reply_to_id',
@ -90,10 +91,14 @@ class NoteController extends Controller
$orderedItems = [];
$noteObjectClass = $this->config->noteObject;
foreach ($paginatedReplies as $reply) {
$replyObject = new $noteObjectClass($reply);
$orderedItems[] = $replyObject->toJSON();
if ($paginatedReplies !== null) {
foreach ($paginatedReplies as $reply) {
$replyObject = new $noteObjectClass($reply);
$orderedItems[] = $replyObject->toJSON();
}
}
$collection = new OrderedCollectionPage($pager, $orderedItems);
}
@ -102,7 +107,7 @@ class NoteController extends Controller
->setBody($collection->toJSON());
}
public function attemptCreate()
public function attemptCreate(): RedirectResponse
{
$rules = [
'actor_id' => 'required|is_natural_no_zero',
@ -134,7 +139,7 @@ class NoteController extends Controller
return redirect()->back();
}
public function attemptFavourite()
public function attemptFavourite(): RedirectResponse
{
$rules = [
'actor_id' => 'required|is_natural_no_zero',
@ -156,7 +161,7 @@ class NoteController extends Controller
return redirect()->back();
}
public function attemptReblog()
public function attemptReblog(): RedirectResponse
{
$rules = [
'actor_id' => 'required|is_natural_no_zero',
@ -178,7 +183,7 @@ class NoteController extends Controller
return redirect()->back();
}
public function attemptReply()
public function attemptReply(): RedirectResponse
{
$rules = [
'actor_id' => 'required|is_natural_no_zero',
@ -233,13 +238,10 @@ class NoteController extends Controller
// get webfinger data from actor
// parse activityPub id to get actor and domain
// check if actor and domain exist
try {
if ($parts = split_handle($this->request->getPost('handle'))) {
extract($parts);
$data = get_webfinger_data($username, $domain);
}
} catch (HTTPException $httpException) {
if (
!($parts = split_handle($this->request->getPost('handle'))) ||
!($data = get_webfinger_data($parts['username'], $parts['domain']))
) {
return redirect()
->back()
->withInput()
@ -266,21 +268,21 @@ class NoteController extends Controller
);
}
public function attemptBlockActor()
public function attemptBlockActor(): RedirectResponse
{
model('ActorModel')->blockActor($this->note->actor->id);
return redirect()->back();
}
public function attemptBlockDomain()
public function attemptBlockDomain(): RedirectResponse
{
model('BlockedDomainModel')->blockDomain($this->note->actor->domain);
return redirect()->back();
}
public function attemptDelete()
public function attemptDelete(): RedirectResponse
{
model('NoteModel', false)->removeNote($this->note);

View File

@ -15,7 +15,10 @@ namespace ActivityPub\Core;
abstract class AbstractObject
{
public function set($property, $value): self
/**
* @param mixed $value
*/
public function set(string $property, $value): self
{
$this->$property = $value;
@ -35,7 +38,7 @@ abstract class AbstractObject
}
$array[$key] =
is_object($value) && $value instanceof self
is_object($value) && is_subclass_of($value, self::class)
? $value->toArray()
: $value;
}

View File

@ -81,21 +81,21 @@ class AddNotes extends Migration
'actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'in_reply_to_id',
'activitypub_notes',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'reblog_of_id',
'activitypub_notes',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('activitypub_notes');

View File

@ -63,21 +63,21 @@ class AddActivities extends Migration
'actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'target_actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'note_id',
'activitypub_notes',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('activitypub_activities');

View File

@ -35,14 +35,14 @@ class AddFavourites extends Migration
'actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'note_id',
'activitypub_notes',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('activitypub_favourites');

View File

@ -37,14 +37,14 @@ class AddFollowers extends Migration
'actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'target_actor_id',
'activitypub_actors',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('activitypub_follows');

View File

@ -33,14 +33,14 @@ class AddNotesPreviewCards extends Migration
'note_id',
'activitypub_notes',
'id',
false,
'',
'CASCADE',
);
$this->forge->addForeignKey(
'preview_card_id',
'activitypub_preview_cards',
'id',
false,
'',
'CASCADE',
);
$this->forge->createTable('activitypub_notes_preview_cards');

View File

@ -11,28 +11,42 @@ namespace ActivityPub\Entities;
use RuntimeException;
use Michalsn\Uuid\UuidEntity;
/**
* @property string $id
* @property int $actor_id
* @property Actor $actor
* @property int|null $target_actor_id
* @property Actor|null $target_actor
* @property string|null $note_id
* @property Note|null $note
* @property string $type
* @property object $payload
* @property string|null $status
* @property Time|null $scheduled_at
* @property Time $created_at
*/
class Activity extends UuidEntity
{
/**
* @var string[]
*/
protected $uuids = ['id', 'note_id'];
/**
* @var Actor
*/
protected $actor;
/**
* @var Actor
* @var Actor|null
*/
protected $target_actor;
/**
* @var Note
* @var Note|null
*/
protected $note;
/**
* @var string[]
*/
protected $uuids = ['id', 'note_id'];
/**
* @var string[]
*/

View File

@ -11,12 +11,36 @@ namespace ActivityPub\Entities;
use RuntimeException;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property string $uri
* @property string $username
* @property string $domain
* @property string $display_name
* @property string|null $summary
* @property string|null $private_key
* @property string|null $public_key
* @property string|null $public_key_id
* @property string|null $avatar_image_url
* @property string|null $avatar_image_mimetype
* @property string|null $cover_image_url
* @property string|null $cover_image_mimetype
* @property string $inbox_url
* @property string|null $outbox_url
* @property string|null $followers_url
* @property int $followers_count
* @property int $notes_count
* @property bool $is_blocked
*
* @property Actor[] $followers
* @property bool $is_local
*/
class Actor extends Entity
{
/**
* @var string
*/
protected $key_id;
protected $public_key_id;
/**
* @var Actor[]
@ -52,7 +76,7 @@ class Actor extends Entity
'is_blocked' => 'boolean',
];
public function getKeyId(): string
public function getPublicKeyId(): string
{
return $this->uri . '#main-key';
}
@ -72,7 +96,7 @@ class Actor extends Entity
}
/**
* @return Follower[]
* @return Actor[]
*/
public function getFollowers(): array
{

View File

@ -10,6 +10,9 @@ namespace ActivityPub\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property string $name
*/
class BlockedDomain extends Entity
{
/**

View File

@ -10,6 +10,10 @@ namespace ActivityPub\Entities;
use Michalsn\Uuid\UuidEntity;
/**
* @property int $actor_id
* @property string $note_id
*/
class Favourite extends UuidEntity
{
/**
@ -22,6 +26,6 @@ class Favourite extends UuidEntity
*/
protected $casts = [
'actor_id' => 'integer',
'note_id' => 'integer',
'note_id' => 'string',
];
}

View File

@ -10,6 +10,10 @@ namespace ActivityPub\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $actor_id
* @property int $target_actor_id
*/
class Follow extends Entity
{
/**

View File

@ -8,16 +8,38 @@
namespace ActivityPub\Entities;
use CodeIgniter\I18n\Time;
use RuntimeException;
use Michalsn\Uuid\UuidEntity;
/**
* @property string $id
* @property string $uri
* @property int $actor_id
* @property Actor $actor
* @property string|null $in_reply_to_id
* @property bool $is_reply
* @property Note|null $reply_to_note
* @property string|null $reblog_of_id
* @property bool $is_reblog
* @property Note|null $reblog_of_note
* @property string $message
* @property string $message_html
* @property int $favourites_count
* @property int $reblogs_count
* @property int $replies_count
* @property Time $published_at
* @property Time $created_at
*
* @property bool $has_preview_card
* @property PreviewCard|null $preview_card
*
* @property bool $has_replies
* @property Note[] $replies
* @property Note[] $reblogs
*/
class Note extends UuidEntity
{
/**
* @var string[]
*/
protected $uuids = ['id', 'in_reply_to_id', 'reblog_of_id'];
/**
* @var Actor
*/
@ -68,6 +90,11 @@ class Note extends UuidEntity
*/
protected $reblogs = [];
/**
* @var string[]
*/
protected $uuids = ['id', 'in_reply_to_id', 'reblog_of_id'];
/**
* @var string[]
*/

View File

@ -10,6 +10,20 @@ namespace ActivityPub\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $id
* @property string $note_id
* @property string $url
* @property string $title
* @property string $description
* @property string $type
* @property string|null $author_name
* @property string|null $author_url
* @property string|null $provider_name
* @property string|null $provider_url
* @property string|null $image
* @property string|null $html
*/
class PreviewCard extends Entity
{
/**

View File

@ -26,7 +26,6 @@ if (!function_exists('get_webfinger_data')) {
$webfingerUri = new URI();
$webfingerUri->setScheme('https');
$webfingerUri->setHost($domain);
isset($port) && $webfingerUri->setPort((int) $port);
$webfingerUri->setPath('/.well-known/webfinger');
$webfingerUri->setQuery("resource=acct:{$username}@{$domain}");
@ -35,7 +34,7 @@ if (!function_exists('get_webfinger_data')) {
return json_decode(
$webfingerResponse->getBody(),
null,
false,
512,
JSON_THROW_ON_ERROR,
);
@ -106,7 +105,7 @@ if (!function_exists('accept_follow')) {
$targetActor->inbox_url,
$acceptActivity->toJSON(),
);
$acceptRequest->sign($actor->key_id, $actor->private_key);
$acceptRequest->sign($actor->public_key_id, $actor->private_key);
$acceptRequest->post();
} catch (Exception $exception) {
$db->transRollback();
@ -119,18 +118,21 @@ if (!function_exists('accept_follow')) {
if (!function_exists('send_activity_to_followers')) {
/**
* Sends an activity to all actor followers
*
* @param string $activity
*/
function send_activity_to_followers(Actor $actor, $activityPayload): void
{
function send_activity_to_followers(
Actor $actor,
string $activityPayload
): void {
foreach ($actor->followers as $follower) {
try {
$acceptRequest = new ActivityRequest(
$follower->inbox_url,
$activityPayload,
);
$acceptRequest->sign($actor->key_id, $actor->private_key);
$acceptRequest->sign(
$actor->public_key_id,
$actor->private_key,
);
$acceptRequest->post();
} catch (Exception $e) {
// log error
@ -299,7 +301,7 @@ if (!function_exists('create_actor_from_uri')) {
$actorResponse = $activityRequest->get();
$actorPayload = json_decode(
$actorResponse->getBody(),
null,
false,
512,
JSON_THROW_ON_ERROR,
);
@ -351,9 +353,9 @@ if (!function_exists('extract_text_from_html')) {
/**
* Extracts the text from html content
*
* @return string|string[]|null
* @return string|null
*/
function extract_text_from_html(string $content)
function extract_text_from_html(string $content): ?string
{
return preg_replace('~\s+~', ' ', strip_tags($content));
}
@ -364,12 +366,12 @@ if (!function_exists('linkify')) {
* Turn all link elements in clickable links.
* Transforms urls and handles
*
* @param string $value
* @param array $protocols http/https, ftp, mail, twitter
* @param array $attributes
* @param string[] $protocols http/https, twitter
*/
function linkify($text, array $protocols = ['http', 'handle']): string
{
function linkify(
string $text,
array $protocols = ['http', 'handle']
): string {
$links = [];
// Extract text links for each protocol

View File

@ -97,15 +97,17 @@ class HttpSignature
throw new Exception('Malformed signature string.');
}
// extract parts as $keyId, $headers and $signature variables
extract($parts);
// set $keyId, $headers and $signature variables
$keyId = $parts['keyId'];
$headers = $parts['headers'];
$signature = $parts['signature'];
// Fetch the public key linked from keyId
$actorRequest = new ActivityRequest($keyId);
$actorResponse = $actorRequest->get();
$actor = json_decode(
$actorResponse->getBody(),
null,
false,
512,
JSON_THROW_ON_ERROR,
);

View File

@ -9,6 +9,7 @@
namespace ActivityPub\Models;
use ActivityPub\Entities\Activity;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\I18n\Time;
use DateTimeInterface;
use Michalsn\Uuid\UuidModel;
@ -76,7 +77,7 @@ class ActivityModel extends UuidModel
*
* @param Time $scheduledAt
*
* @return Michalsn\Uuid\BaseResult|int|string|false
* @return BaseResult|int|string|false
*/
public function newActivity(
string $type,

View File

@ -9,6 +9,7 @@
namespace ActivityPub\Models;
use ActivityPub\Entities\Actor;
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Events\Events;
use CodeIgniter\Model;
@ -57,7 +58,7 @@ class ActorModel extends Model
*/
protected $useTimestamps = true;
public function getActorById($id)
public function getActorById($id): Actor
{
$cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}";
if (!($found = cache($cacheName))) {

View File

@ -49,8 +49,6 @@ class BlockedDomainModel extends Model
/**
* Retrieves instance or podcast domain blocks depending on whether or not $podcastId param is set.
*
* @param integer|null $podcastId
*/
public function getBlockedDomains()
{

View File

@ -137,7 +137,8 @@ class FavouriteModel extends UuidModel
);
}
$this->table('activitypub_favourites')
$this->db
->table('activitypub_favourites')
->where([
'actor_id' => $actor->id,
'note_id' => service('uuid')

View File

@ -17,6 +17,7 @@ use ActivityPub\Activities\CreateActivity;
use ActivityPub\Activities\DeleteActivity;
use ActivityPub\Activities\UndoActivity;
use ActivityPub\Objects\TombstoneObject;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Events\Events;
use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time;
@ -489,7 +490,7 @@ class NoteModel extends UuidModel
}
/**
* @return ActivityPub\Models\BaseResult|int|string|false
* @return BaseResult|int|string|false
*/
public function reblog(Actor $actor, Note $note, $registerActivity = true)
{

View File

@ -72,9 +72,9 @@ class ActorObject extends ObjectType
protected $icon = [];
/**
* @var object
* @var array<string, string>
*/
protected $publicKey;
protected $publicKey = [];
public function __construct(Actor $actor)
{
@ -101,10 +101,12 @@ class ActorObject extends ObjectType
'url' => $actor->avatar_image_url,
];
$this->publicKey = [
'id' => $actor->key_id,
'owner' => $actor->uri,
'publicKeyPem' => $actor->public_key,
];
if ($actor->public_key !== null) {
$this->publicKey = [
'id' => $actor->public_key_id,
'owner' => $actor->uri,
'publicKeyPem' => $actor->public_key,
];
}
}
}

View File

@ -34,9 +34,9 @@ class NoteObject extends ObjectType
protected $inReplyTo;
/**
* @var array
* @var string
*/
protected $replies = [];
protected $replies;
/**
* @param Note $note

View File

@ -11,7 +11,6 @@
namespace ActivityPub\Objects;
use ActivityPub\Core\Activity;
use CodeIgniter\Pager\Pager;
use ActivityPub\Core\ObjectType;
@ -28,17 +27,17 @@ class OrderedCollectionObject extends ObjectType
protected $totalItems;
/**
* @var integer|null
* @var string|null
*/
protected $first;
/**
* @var integer|null
* @var string|null
*/
protected $current;
/**
* @var integer|null
* @var string|null
*/
protected $last;

View File

@ -25,12 +25,12 @@ class OrderedCollectionPage extends OrderedCollectionObject
protected $partOf;
/**
* @var integer
* @var string|null
*/
protected $prev;
/**
* @var integer
* @var string|null
*/
protected $next;

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