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

View File

@ -2,8 +2,19 @@
namespace App\Authorization; 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. * 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 false;
} }
return (bool) $this->permissionModel->doesGroupHavePermission( return $this->permissionModel->doesGroupHavePermission(
$groupId, $groupId,
$permissionId, $permissionId,
); );
@ -27,14 +38,14 @@ class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
/** /**
* Makes user part of given groups. * 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 public function setUserGroups(int $userId, array $groups = []): bool
{ {
// remove user from all groups before resetting it in new groups // remove user from all groups before resetting it in new groups
$this->groupModel->removeUserFromAllGroups($userId); $this->groupModel->removeUserFromAllGroups($userId);
if ($groups = []) { if ($groups === []) {
return true; return true;
} }

View File

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

View File

@ -2,7 +2,9 @@
namespace App\Authorization; 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, * 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 * get the full audio file url
*
* @param string|string[] $audioFilePath
*/ */
public function getAudioFileUrl(string $audioFilePath): string public function getAudioFileUrl($audioFilePath): string
{ {
helper('media'); helper('media');

View File

@ -2,7 +2,9 @@
namespace Config; 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; namespace Config;
use App\Entities\Actor;
use App\Entities\Note;
use App\Entities\User;
use CodeIgniter\Events\Events; use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException; use CodeIgniter\Exceptions\FrameworkException;
@ -23,6 +26,7 @@ use CodeIgniter\Exceptions\FrameworkException;
*/ */
Events::on('pre_system', function () { Events::on('pre_system', function () {
// @phpstan-ignore-next-line
if (ENVIRONMENT !== 'testing') { if (ENVIRONMENT !== 'testing') {
if (ini_get('zlib.output_compression')) { if (ini_get('zlib.output_compression')) {
throw FrameworkException::forEnabledZlibOutputCompression(); throw FrameworkException::forEnabledZlibOutputCompression();
@ -42,6 +46,8 @@ Events::on('pre_system', function () {
* Debug Toolbar Listeners. * Debug Toolbar Listeners.
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* If you delete, they will no longer be collected. * If you delete, they will no longer be collected.
*
* @phpstan-ignore-next-line
*/ */
if (CI_DEBUG) { if (CI_DEBUG) {
Events::on( 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'); helper('auth');
// set interact_as_actor_id value // 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'); helper('auth');
// remove user's interact_as_actor session // remove user's interact_as_actor session
@ -75,7 +81,7 @@ Events::on('logout', function ($user): void {
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* Update episode metadata counts * 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) { if ($note->episode_id) {
model('EpisodeModel') model('EpisodeModel')
->where('id', $note->episode_id) ->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}*"); 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) { if ($note->episode_id) {
model('EpisodeModel') model('EpisodeModel')
->where('id', $note->episode_id) ->where('id', $note->episode_id)
@ -106,7 +112,7 @@ Events::on('on_note_remove', function ($note): void {
cache()->deleteMatching("page_note#{$note->id}*"); 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) { if ($episodeId = $note->episode_id) {
model('EpisodeModel') model('EpisodeModel')
->where('id', $episodeId) ->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; $note = $reblogNote->reblog_of_note;
if ($episodeId = $note->episode_id) { if ($episodeId = $note->episode_id) {
model('EpisodeModel') 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; $note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->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; $note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->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) { if ($note->episode_id) {
model('EpisodeModel') model('EpisodeModel')
->where('id', $note->episode_id) ->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) { if ($note->episode_id) {
model('EpisodeModel') model('EpisodeModel')
->where('id', $note->episode_id) ->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_podcast*');
cache()->deleteMatching('page_note*'); 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_podcast*');
cache()->deleteMatching('page_note*'); 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_podcast*');
cache()->deleteMatching('page_note*'); 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_podcast*');
cache()->deleteMatching('page_note*'); cache()->deleteMatching('page_note*');
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,17 +8,19 @@
namespace App\Controllers; namespace App\Controllers;
use ActivityPub\Controllers\NoteController; use ActivityPub\Controllers\NoteController as ActivityPubNoteController;
use ActivityPub\Entities\Note as ActivityPubNote; use ActivityPub\Entities\Note as ActivityPubNote;
use Analytics\AnalyticsTrait; use Analytics\AnalyticsTrait;
use App\Entities\Actor;
use App\Entities\Note as CastopodNote; use App\Entities\Note as CastopodNote;
use App\Entities\Podcast;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
class Note extends NoteController class NoteController extends ActivityPubNoteController
{ {
use AnalyticsTrait; use AnalyticsTrait;
@ -27,6 +29,11 @@ class Note extends NoteController
*/ */
protected $podcast; protected $podcast;
/**
* @var Actor
*/
protected $actor;
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc']; protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
public function _remap($method, ...$params) public function _remap($method, ...$params)
@ -52,7 +59,7 @@ class Note extends NoteController
return $this->$method(...$params); return $this->$method(...$params);
} }
public function index(): RedirectResponse public function view(): string
{ {
// Prevent analytics hit when authenticated // Prevent analytics hit when authenticated
if (!can_user_interact()) { if (!can_user_interact()) {
@ -96,7 +103,7 @@ class Note extends NoteController
return $cachedView; return $cachedView;
} }
public function attemptCreate() public function attemptCreate(): RedirectResponse
{ {
$rules = [ $rules = [
'message' => 'required|max_length[500]', 'message' => 'required|max_length[500]',
@ -153,7 +160,7 @@ class Note extends NoteController
return redirect()->back(); return redirect()->back();
} }
public function attemptReply() public function attemptReply(): RedirectResponse
{ {
$rules = [ $rules = [
'message' => 'required|max_length[500]', 'message' => 'required|max_length[500]',
@ -185,7 +192,7 @@ class Note extends NoteController
return redirect()->back(); return redirect()->back();
} }
public function attemptFavourite() public function attemptFavourite(): RedirectResponse
{ {
model('FavouriteModel')->toggleFavourite( model('FavouriteModel')->toggleFavourite(
interact_as_actor(), interact_as_actor(),
@ -195,14 +202,14 @@ class Note extends NoteController
return redirect()->back(); return redirect()->back();
} }
public function attemptReblog() public function attemptReblog(): RedirectResponse
{ {
model('NoteModel')->toggleReblog(interact_as_actor(), $this->note); model('NoteModel')->toggleReblog(interact_as_actor(), $this->note);
return redirect()->back(); return redirect()->back();
} }
public function attemptAction() public function attemptAction(): RedirectResponse
{ {
$rules = [ $rules = [
'action' => 'required|in_list[favourite,reblog,reply]', 'action' => 'required|in_list[favourite,reblog,reply]',
@ -215,17 +222,23 @@ class Note extends NoteController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
switch ($this->request->getPost('action')) { $action = $this->request->getPost('action');
switch ($action) {
case 'favourite': case 'favourite':
return $this->attemptFavourite(); return $this->attemptFavourite();
case 'reblog': case 'reblog':
return $this->attemptReblog(); return $this->attemptReblog();
case 'reply': case 'reply':
return $this->attemptReply(); 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 // Prevent analytics hit when authenticated
if (!can_user_interact()) { 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; namespace App\Controllers;
use App\Entities\Page as PageEntity; use App\Entities\Page;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use App\Models\PageModel; use App\Models\PageModel;
use App\Models\CreditModel; use App\Models\CreditModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
class Page extends BaseController class PageController extends BaseController
{ {
/** /**
* @var Page|null * @var Page|null
@ -60,10 +60,10 @@ class Page extends BaseController
$cacheName = "page_credits_{$locale}"; $cacheName = "page_credits_{$locale}";
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
$page = new PageEntity([ $page = new Page([
'title' => lang('Person.credits', [], $locale), 'title' => lang('Person.credits', [], $locale),
'slug' => 'credits', 'slug' => 'credits',
'content' => '', 'content_markdown' => '',
]); ]);
$allCredits = (new CreditModel())->findAll(); $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 * Provide public access to all platforms so that they can be exported
*/ */
class Platform extends Controller class PlatformController extends Controller
{ {
public function index(): ResponseInterface public function index(): ResponseInterface
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,13 +8,18 @@
namespace App\Entities; namespace App\Entities;
use ActivityPub\Entities\Actor as ActivityPubActor;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use RuntimeException; 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; protected $podcast;
@ -23,20 +28,20 @@ class Actor extends \ActivityPub\Entities\Actor
*/ */
protected $is_podcast; 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( 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->podcast = (new PodcastModel())->getPodcastByActorId(
$this->id, $this->id,
); );

View File

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

View File

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

View File

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

View File

@ -11,6 +11,15 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use App\Models\PersonModel; 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 class EpisodePerson extends Entity
{ {
/** /**
@ -30,7 +39,7 @@ class EpisodePerson extends Entity
'person_role' => '?string', 'person_role' => '?string',
]; ];
public function getPerson() public function getPerson(): Person
{ {
return (new PersonModel())->getPersonById( return (new PersonModel())->getPersonById(
$this->attributes['person_id'], $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; use CodeIgniter\Entity\Entity;
/**
* @property string $code
* @property string $native_name
*/
class Language extends Entity 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; namespace App\Entities;
use ActivityPub\Entities\Note as ActivityPubNote;
use App\Models\ActorModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use RuntimeException; use RuntimeException;
class Note extends \ActivityPub\Entities\Note /**
* @property int|null $episode_id
* @property Episode|null $episode
*/
class Note extends ActivityPubNote
{ {
/** /**
* @var Episode|null * @var Episode|null
@ -40,13 +46,13 @@ class Note extends \ActivityPub\Entities\Note
*/ */
public function getEpisode() public function getEpisode()
{ {
if (empty($this->episode_id)) { if ($this->episode_id === null) {
throw new RuntimeException( throw new RuntimeException(
'Note must have an episode_id before getting episode.', 'Note must have an episode_id before getting episode.',
); );
} }
if (empty($this->episode)) { if ($this->episode === null) {
$this->episode = (new EpisodeModel())->getEpisodeById( $this->episode = (new EpisodeModel())->getEpisodeById(
$this->episode_id, $this->episode_id,
); );

View File

@ -9,8 +9,20 @@
namespace App\Entities; namespace App\Entities;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; 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 class Page extends Entity
{ {
/** /**
@ -30,21 +42,27 @@ class Page extends Entity
'id' => 'integer', 'id' => 'integer',
'title' => 'string', 'title' => 'string',
'slug' => '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']); return url_to('page', $this->attributes['slug']);
} }
public function getContentHtml(): string public function setContentMarkdown(string $contentMarkdown): self
{ {
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',
'allow_unsafe_links' => false, '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; namespace App\Entities;
use App\Libraries\Image;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\Files\File;
use CodeIgniter\Entity\Entity; 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 class Person extends Entity
{ {
/** /**
@ -36,26 +44,16 @@ class Person extends Entity
/** /**
* Saves a picture in `public/media/persons/` * 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(); // Save image
$this->attributes['image_path'] = save_media( $image->saveImage('persons', $this->attributes['unique_name']);
$image,
'persons', $this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['unique_name'], $this->attributes['image_path'] = $image->path;
);
$this->image = new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
return $this; return $this;
} }
@ -63,6 +61,7 @@ class Person extends Entity
public function getImage(): Image public function getImage(): Image
{ {
return new Image( return new Image(
null,
$this->attributes['image_path'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );

View File

@ -10,6 +10,17 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity; 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 class Platform extends Entity
{ {
/** /**

View File

@ -8,7 +8,6 @@
namespace App\Entities; namespace App\Entities;
use App\Libraries\Image;
use App\Libraries\SimpleRSSElement; use App\Libraries\SimpleRSSElement;
use App\Models\CategoryModel; use App\Models\CategoryModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
@ -16,11 +15,66 @@ use App\Models\PlatformModel;
use App\Models\PodcastPersonModel; use App\Models\PodcastPersonModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\Files\File; use CodeIgniter\I18n\Time;
use CodeIgniter\HTTP\Files\UploadedFile;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
use RuntimeException; 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 class Podcast extends Entity
{ {
/** /**
@ -39,14 +93,9 @@ class Podcast extends Entity
protected $image; protected $image;
/** /**
* @var Episode[] * @var string
*/ */
protected $episodes; protected $description;
/**
* @var PodcastPerson[]
*/
protected $persons;
/** /**
* @var Category * @var Category
@ -59,44 +108,53 @@ class Podcast extends Entity
protected $other_categories; protected $other_categories;
/** /**
* @var integer[] * @var string[]
*/ */
protected $other_categories_ids; protected $other_categories_ids;
/**
* @var Episode[]
*/
protected $episodes;
/**
* @var PodcastPerson[]
*/
protected $persons;
/** /**
* @var User[] * @var User[]
*/ */
protected $contributors; 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 Location|null
*
* @var string
*/ */
protected $description; protected $location;
/** /**
* Return custom rss as string
*
* @var string * @var string
*/ */
protected $custom_rss_string; protected $custom_rss_string;
/**
* @var array<string, string>
*/
protected $casts = [ protected $casts = [
'id' => 'integer', 'id' => 'integer',
'actor_id' => 'integer', 'actor_id' => 'integer',
@ -123,7 +181,7 @@ class Podcast extends Entity
'new_feed_url' => '?string', 'new_feed_url' => '?string',
'location_name' => '?string', 'location_name' => '?string',
'location_geo' => '?string', 'location_geo' => '?string',
'location_osmid' => '?string', 'location_osm_id' => '?string',
'payment_pointer' => '?string', 'payment_pointer' => '?string',
'custom_rss' => '?json-array', 'custom_rss' => '?json-array',
'partner_id' => '?string', 'partner_id' => '?string',
@ -133,20 +191,15 @@ class Podcast extends Entity
'updated_by' => 'integer', 'updated_by' => 'integer',
]; ];
/**
* Returns the podcast actor
*
* @return Actor
*/
public function getActor(): Actor public function getActor(): Actor
{ {
if (!$this->attributes['actor_id']) { if (!$this->actor_id) {
throw new RuntimeException( throw new RuntimeException(
'Podcast must have an actor_id before getting actor.', '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); $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/` * 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) { // Save image
helper('media'); $image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
$this->attributes['image_mimetype'] = $image->getMimeType(); $this->attributes['image_mimetype'] = $image->mimetype;
$this->attributes['image_path'] = save_media( $this->attributes['image_path'] = $image->path;
$image,
'podcasts/' . $this->attributes['name'],
'cover',
);
$this->image = new Image(
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
$this->image->saveSizes();
}
return $this; return $this;
} }
public function getImage(): Image public function getImage(): Image
{ {
return new Image( return new Image(null, $this->image_path, $this->image_mimetype);
$this->attributes['image_path'],
$this->attributes['image_mimetype'],
);
} }
public function getLink(): string public function getLink(): string
@ -350,14 +389,14 @@ class Podcast extends Entity
); );
} }
if (empty($this->podcastingPlatforms)) { if (empty($this->podcasting_platforms)) {
$this->podcastingPlatforms = (new PlatformModel())->getPodcastPlatforms( $this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id, $this->id,
'podcasting', 'podcasting',
); );
} }
return $this->podcastingPlatforms; return $this->podcasting_platforms;
} }
/** /**
@ -373,14 +412,14 @@ class Podcast extends Entity
); );
} }
if (empty($this->socialPlatforms)) { if (empty($this->social_platforms)) {
$this->socialPlatforms = (new PlatformModel())->getPodcastPlatforms( $this->social_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id, $this->id,
'social', 'social',
); );
} }
return $this->socialPlatforms; return $this->social_platforms;
} }
/** /**
@ -396,14 +435,14 @@ class Podcast extends Entity
); );
} }
if (empty($this->fundingPlatforms)) { if (empty($this->funding_platforms)) {
$this->fundingPlatforms = (new PlatformModel())->getPodcastPlatforms( $this->funding_platforms = (new PlatformModel())->getPodcastPlatforms(
$this->id, $this->id,
'funding', 'funding',
); );
} }
return $this->fundingPlatforms; return $this->funding_platforms;
} }
/** /**
@ -444,29 +483,50 @@ class Podcast extends Entity
/** /**
* Saves the location name and fetches OpenStreetMap info * Saves the location name and fetches OpenStreetMap info
*/ */
public function setLocation(?string $locationName = null): self public function setLocation(?string $newLocationName = null)
{ {
helper('location'); if ($newLocationName === null) {
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)) {
$this->attributes['location_name'] = null; $this->attributes['location_name'] = null;
$this->attributes['location_geo'] = 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; 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 * Get custom rss tag as XML String
* *

View File

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

View File

@ -10,6 +10,16 @@ namespace App\Entities;
use CodeIgniter\Entity\Entity; 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 class Soundbite extends Entity
{ {
/** /**
@ -19,17 +29,10 @@ class Soundbite extends Entity
'id' => 'integer', 'id' => 'integer',
'podcast_id' => 'integer', 'podcast_id' => 'integer',
'episode_id' => 'integer', 'episode_id' => 'integer',
'start_time' => 'float', 'start_time' => 'double',
'duration' => 'float', 'duration' => 'double',
'label' => '?string', 'label' => '?string',
'created_by' => 'integer', 'created_by' => 'integer',
'updated_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 RuntimeException;
use App\Models\PodcastModel; 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[] * @var Podcast[]
*/ */
protected $podcasts = []; 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 * Array of field names and the type of value to cast them as
* when they are accessed. * when they are accessed.
@ -36,8 +41,8 @@ class User extends \Myth\Auth\Entities\User
'id' => 'integer', 'id' => 'integer',
'active' => 'boolean', 'active' => 'boolean',
'force_pass_reset' => 'boolean', 'force_pass_reset' => 'boolean',
'podcast_role' => '?string',
'podcast_id' => '?integer', 'podcast_id' => '?integer',
'podcast_role' => '?string',
]; ];
/** /**
@ -47,36 +52,16 @@ class User extends \Myth\Auth\Entities\User
*/ */
public function getPodcasts(): array public function getPodcasts(): array
{ {
if (empty($this->id)) { if ($this->id === null) {
throw new RuntimeException( throw new RuntimeException(
'Users must be created before getting podcasts.', 'Users must be created before getting podcasts.',
); );
} }
if (empty($this->podcasts)) { if ($this->podcasts === null) {
$this->podcasts = (new PodcastModel())->getUserPodcasts($this->id); $this->podcasts = (new PodcastModel())->getUserPodcasts($this->id);
} }
return $this->podcasts; 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) public function before(RequestInterface $request, $params = null)
{ {
if (!function_exists('logged_in')) {
helper('auth'); helper('auth');
}
if (empty($params)) { if (empty($params)) {
return; return;

View File

@ -7,9 +7,22 @@
*/ */
use ActivityPub\Entities\Actor; use ActivityPub\Entities\Actor;
use App\Entities\User;
use CodeIgniter\Database\Exceptions\DataException; use CodeIgniter\Database\Exceptions\DataException;
use Config\Services; 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')) { if (!function_exists('set_interact_as_actor')) {
/** /**
* Sets the actor id of which the user is acting as * Sets the actor id of which the user is acting as

View File

@ -6,6 +6,7 @@
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
use App\Entities\Location;
use CodeIgniter\View\Table; use CodeIgniter\View\Table;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
@ -127,8 +128,8 @@ if (!function_exists('icon_button')) {
* *
* Abstracts the `button()` helper to create a stylized icon button * Abstracts the `button()` helper to create a stylized icon button
* *
* @param string $label The button label * @param string $icon The button icon
* @param string $uri URI string or array of URI segments * @param string $title The button label
* @param array $customOptions button options: variant, size, iconLeft, iconRight * @param array $customOptions button options: variant, size, iconLeft, iconRight
* @param array $customAttributes Additional attributes * @param array $customAttributes Additional attributes
* *
@ -252,15 +253,11 @@ if (!function_exists('publication_pill')) {
* *
* Shows the stylized publication datetime in regards to current datetime. * 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 * @return string
*/ */
function publication_pill( function publication_pill(
?Time $publicationDate, ?Time $publicationDate,
$publicationStatus, string $publicationStatus,
string $customClass = '' string $customClass = ''
): string { ): string {
if ($publicationDate === null) { if ($publicationDate === null) {
@ -336,6 +333,12 @@ if (!function_exists('publication_button')) {
$variant = 'danger'; $variant = 'danger';
$iconLeft = 'cloud-off'; $iconLeft = 'cloud-off';
break; break;
default:
$label = '';
$route = '';
$variant = '';
$iconLeft = '';
break;
} }
return button($label, $route, [ return button($label, $route, [
@ -351,16 +354,13 @@ if (!function_exists('episode_numbering')) {
/** /**
* Returns relevant translated episode numbering. * Returns relevant translated episode numbering.
* *
* @param string $class styling classes * @param bool $isAbbr component will show abbreviated numbering if true
* @param string $is_abbr component will show abbreviated numbering if true
*
* @return string|null
*/ */
function episode_numbering( function episode_numbering(
?int $episodeNumber = null, ?int $episodeNumber = null,
?int $seasonNumber = null, ?int $seasonNumber = null,
string $class = '', string $class = '',
$isAbbr = false bool $isAbbr = false
): string { ): string {
if (!$episodeNumber && !$seasonNumber) { if (!$episodeNumber && !$seasonNumber) {
return ''; return '';
@ -368,22 +368,20 @@ if (!function_exists('episode_numbering')) {
$transKey = ''; $transKey = '';
$args = []; $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'; $transKey = 'Episode.season_episode';
$args = [ } elseif ($episodeNumber !== null && $seasonNumber === null) {
'seasonNumber' => $seasonNumber,
'episodeNumber' => $episodeNumber,
];
} elseif ($episodeNumber && !$seasonNumber) {
$transKey = 'Episode.number'; $transKey = 'Episode.number';
$args = [ } elseif ($episodeNumber === null && $seasonNumber !== null) {
'episodeNumber' => $episodeNumber,
];
} elseif (!$episodeNumber && $seasonNumber) {
$transKey = 'Episode.season'; $transKey = 'Episode.season';
$args = [
'seasonNumber' => $seasonNumber,
];
} }
if ($isAbbr) { if ($isAbbr) {
@ -408,19 +406,15 @@ if (!function_exists('location_link')) {
/** /**
* Returns link to display from location info * Returns link to display from location info
*/ */
function location_link( function location_link(?Location $location, string $class = ''): string
?string $locationName, {
?string $locationGeo, if ($location === null) {
?string $locationOsmid,
$class = ''
): string {
if (empty($locationName)) {
return ''; return '';
} }
return anchor( return anchor(
location_url($locationName, $locationGeo, $locationOsmid), $location->url,
icon('map-pin', 'mr-2') . $locationName, icon('map-pin', 'mr-2') . $location->name,
[ [
'class' => 'class' =>
'inline-flex items-baseline hover:underline' . '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 * 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(); $getID3 = new GetID3();
$FileInfo = $getID3->analyze($file); $FileInfo = $getID3->analyze($file);
@ -34,8 +34,6 @@ if (!function_exists('get_file_tags')) {
if (!function_exists('write_audio_file_tags')) { if (!function_exists('write_audio_file_tags')) {
/** /**
* Write audio file metadata / ID3 tags * Write audio file metadata / ID3 tags
*
* @return UploadedFile
*/ */
function write_audio_file_tags(Episode $episode): void function write_audio_file_tags(Episode $episode): void
{ {
@ -51,7 +49,7 @@ if (!function_exists('write_audio_file_tags')) {
$tagwriter->tagformats = ['id3v2.4']; $tagwriter->tagformats = ['id3v2.4'];
$tagwriter->tag_encoding = $TextEncoding; $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()); $APICdata = file_get_contents($cover->getRealPath());

View File

@ -11,14 +11,11 @@ use Config\Services;
if (!function_exists('fetch_osm_location')) { if (!function_exists('fetch_osm_location')) {
/** /**
* Fetches places from Nominatim OpenStreetMap * Fetches places from Nominatim OpenStreetMap
*
* @return array|null
*/ */
function fetch_osm_location(string $locationName): ?array function fetch_osm_location(string $locationName): ?array
{ {
$osmObject = null; $osmObject = null;
if (!empty($locationName)) {
try { try {
$client = Services::curlrequest(); $client = Services::curlrequest();
@ -40,12 +37,13 @@ if (!function_exists('fetch_osm_location')) {
512, 512,
JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR,
); );
$osmObject = [ $osmObject = [
'geo' => 'geo' =>
empty($places[0]['lat']) || empty($places[0]['lon']) empty($places[0]['lat']) || empty($places[0]['lon'])
? null ? null
: "geo:{$places[0]['lat']},{$places[0]['lon']}", : "geo:{$places[0]['lat']},{$places[0]['lon']}",
'osmid' => empty($places[0]['osm_type']) 'osm_id' => empty($places[0]['osm_type'])
? null ? null
: strtoupper(substr($places[0]['osm_type'], 0, 1)) . : strtoupper(substr($places[0]['osm_type'], 0, 1)) .
$places[0]['osm_id'], $places[0]['osm_id'],
@ -54,7 +52,6 @@ if (!function_exists('fetch_osm_location')) {
//If things go wrong the show must go on //If things go wrong the show must go on
log_message('critical', $exception); log_message('critical', $exception);
} }
}
return $osmObject; return $osmObject;
} }

View File

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

View File

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

View File

@ -6,20 +6,24 @@
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
use App\Entities\Person;
use App\Entities\EpisodePerson;
use App\Entities\PodcastPerson;
if (!function_exists('construct_person_array')) { if (!function_exists('construct_person_array')) {
/** /**
* Fetches persons from an episode * 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) { foreach ($persons as $person) {
if (array_key_exists($person->person->id, $personsArray)) { if (array_key_exists($person->id, $personsArray)) {
$personsArray[$person->person->id]['roles'] .= $personsArray[$person->id]['roles'] .=
empty($person->person_group) || empty($person->person_role) empty($person->person_group) || empty($person->person_role)
? '' ? ''
: (empty($personsArray[$person->person->id]['roles']) : (empty($personsArray[$person->id]['roles'])
? '' ? ''
: ', ') . : ', ') .
lang( lang(

View File

@ -16,10 +16,10 @@ if (!function_exists('get_rss_feed')) {
/** /**
* Generates the rss feed for a given podcast entity * 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 * @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; $episodes = $podcast->episodes;
@ -43,7 +43,7 @@ if (!function_exists('get_rss_feed')) {
$atom_link->addAttribute('rel', 'self'); $atom_link->addAttribute('rel', 'self');
$atom_link->addAttribute('type', 'application/rss+xml'); $atom_link->addAttribute('type', 'application/rss+xml');
if (!empty($podcast->new_feed_url)) { if ($podcast->new_feed_url !== null) {
$channel->addChild( $channel->addChild(
'new-feed-url', 'new-feed-url',
$podcast->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 = $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); $channel->addChild('language', $podcast->language_code);
if (!empty($podcast->location_name)) { if ($podcast->location !== null) {
$locationElement = $channel->addChild( $locationElement = $channel->addChild(
'location', 'location',
htmlspecialchars($podcast->location_name), htmlspecialchars($podcast->location->name),
$podcast_namespace, $podcast_namespace,
); );
if (!empty($podcast->location_geo)) { if ($podcast->location->geo !== null) {
$locationElement->addAttribute('geo', $podcast->location_geo); $locationElement->addAttribute('geo', $podcast->location->geo);
} }
if (!empty($podcast->location_osmid)) { if ($podcast->location->osm_id !== null) {
$locationElement->addAttribute('osm', $podcast->location_osmid); $locationElement->addAttribute(
'osm',
$podcast->location->osm_id,
);
} }
} }
if (!empty($podcast->payment_pointer)) { if ($podcast->payment_pointer !== null) {
$valueElement = $channel->addChild( $valueElement = $channel->addChild(
'value', 'value',
null, null,
@ -103,7 +107,7 @@ if (!function_exists('get_rss_feed')) {
'address', 'address',
$podcast->payment_pointer, $podcast->payment_pointer,
); );
$recipientElement->addAttribute('split', 100); $recipientElement->addAttribute('split', '100');
} }
$channel $channel
->addChild( ->addChild(
@ -112,7 +116,7 @@ if (!function_exists('get_rss_feed')) {
$podcast_namespace, $podcast_namespace,
) )
->addAttribute('owner', $podcast->owner_email); ->addAttribute('owner', $podcast->owner_email);
if (!empty($podcast->imported_feed_url)) { if ($podcast->imported_feed_url !== null) {
$channel->addChild( $channel->addChild(
'previousUrl', 'previousUrl',
$podcast->imported_feed_url, $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( $podcastingPlatformElement = $channel->addChild(
'id', 'id',
null, null,
@ -130,13 +134,13 @@ if (!function_exists('get_rss_feed')) {
'platform', 'platform',
$podcastingPlatform->slug, $podcastingPlatform->slug,
); );
if (!empty($podcastingPlatform->link_content)) { if ($podcastingPlatform->link_content !== null) {
$podcastingPlatformElement->addAttribute( $podcastingPlatformElement->addAttribute(
'id', 'id',
$podcastingPlatform->link_content, $podcastingPlatform->link_content,
); );
} }
if (!empty($podcastingPlatform->link_url)) { if ($podcastingPlatform->link_url !== null) {
$podcastingPlatformElement->addAttribute( $podcastingPlatformElement->addAttribute(
'url', 'url',
htmlspecialchars($podcastingPlatform->link_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( $socialPlatformElement = $channel->addChild(
'social', 'social',
$socialPlatform->link_content, $socialPlatform->link_content,
@ -154,7 +158,7 @@ if (!function_exists('get_rss_feed')) {
'platform', 'platform',
$socialPlatform->slug, $socialPlatform->slug,
); );
if (!empty($socialPlatform->link_url)) { if ($socialPlatform->link_url !== null) {
$socialPlatformElement->addAttribute( $socialPlatformElement->addAttribute(
'url', 'url',
htmlspecialchars($socialPlatform->link_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( $fundingPlatformElement = $channel->addChild(
'funding', 'funding',
$fundingPlatform->link_content, $fundingPlatform->link_content,
@ -172,7 +176,7 @@ if (!function_exists('get_rss_feed')) {
'platform', 'platform',
$fundingPlatform->slug, $fundingPlatform->slug,
); );
if (!empty($socialPlatform->link_url)) { if ($fundingPlatform->link_url !== null) {
$fundingPlatformElement->addAttribute( $fundingPlatformElement->addAttribute(
'url', 'url',
htmlspecialchars($fundingPlatform->link_url), htmlspecialchars($fundingPlatform->link_url),
@ -186,9 +190,10 @@ if (!function_exists('get_rss_feed')) {
htmlspecialchars($podcastPerson->person->full_name), htmlspecialchars($podcastPerson->person->full_name),
$podcast_namespace, $podcast_namespace,
); );
if ( if (
!empty($podcastPerson->person_role) && $podcastPerson->person_role !== null &&
!empty($podcastPerson->person_group) $podcastPerson->person_group !== null
) { ) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'role', 'role',
@ -201,7 +206,8 @@ if (!function_exists('get_rss_feed')) {
), ),
); );
} }
if (!empty($podcastPerson->person_group)) {
if ($podcastPerson->person_group !== null) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'group', 'group',
htmlspecialchars( htmlspecialchars(
@ -217,7 +223,8 @@ if (!function_exists('get_rss_feed')) {
'img', 'img',
$podcastPerson->person->image->large_url, $podcastPerson->person->image->large_url,
); );
if (!empty($podcastPerson->person->information_url)) {
if ($podcastPerson->person->information_url !== null) {
$podcastPersonElement->addAttribute( $podcastPersonElement->addAttribute(
'href', 'href',
$podcastPerson->person->information_url, $podcastPerson->person->information_url,
@ -263,7 +270,7 @@ if (!function_exists('get_rss_feed')) {
$image->addChild('title', $podcast->title); $image->addChild('title', $podcast->title);
$image->addChild('link', $podcast->link); $image->addChild('link', $podcast->link);
if (!empty($podcast->custom_rss)) { if ($podcast->custom_rss !== null) {
array_to_rss( array_to_rss(
[ [
'elements' => $podcast->custom_rss, 'elements' => $podcast->custom_rss,
@ -280,7 +287,7 @@ if (!function_exists('get_rss_feed')) {
$enclosure->addAttribute( $enclosure->addAttribute(
'url', 'url',
$episode->audio_file_analytics_url . $episode->audio_file_analytics_url .
(empty($serviceSlug) ($serviceSlug === ''
? '' ? ''
: '?_from=' . urlencode($serviceSlug)), : '?_from=' . urlencode($serviceSlug)),
); );
@ -292,22 +299,22 @@ if (!function_exists('get_rss_feed')) {
'pubDate', 'pubDate',
$episode->published_at->format(DATE_RFC1123), $episode->published_at->format(DATE_RFC1123),
); );
if (!empty($episode->location_name)) { if ($episode->location !== null) {
$locationElement = $item->addChild( $locationElement = $item->addChild(
'location', 'location',
htmlspecialchars($episode->location_name), htmlspecialchars($episode->location->name),
$podcast_namespace, $podcast_namespace,
); );
if (!empty($episode->location_geo)) { if ($episode->location->geo !== null) {
$locationElement->addAttribute( $locationElement->addAttribute(
'geo', 'geo',
$episode->location_geo, $episode->location->geo,
); );
} }
if (!empty($episode->location_osmid)) { if ($episode->location->osm_id !== null) {
$locationElement->addAttribute( $locationElement->addAttribute(
'osm', '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_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( $itunes_category->addAttribute(
'text', 'text',
$category->parent !== null $category->parent !== null
@ -488,7 +495,7 @@ if (!function_exists('add_category_tag')) {
if ($category->parent !== null) { if ($category->parent !== null) {
$itunes_category_child = $itunes_category->addChild( $itunes_category_child = $itunes_category->addChild(
'category', 'category',
null, '',
$itunes_namespace, $itunes_namespace,
); );
$itunes_category_child->addAttribute( $itunes_category_child->addAttribute(

View File

@ -6,13 +6,13 @@
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
use CodeIgniter\HTTP\URI;
if (!function_exists('host_url')) { if (!function_exists('host_url')) {
/** /**
* Return the host URL to use in views * Return the host URL to use in views
*
* @return string|false
*/ */
function host_url() function host_url(): ?string
{ {
if (isset($_SERVER['HTTP_HOST'])) { if (isset($_SERVER['HTTP_HOST'])) {
$protocol = $protocol =
@ -23,7 +23,7 @@ if (!function_exists('host_url')) {
return $protocol . $_SERVER['HTTP_HOST'] . '/'; 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')) { if (!function_exists('extract_params_from_episode_uri')) {
/** /**
* Returns podcast name and episode slug from episode string uri * Returns podcast name and episode slug from episode string uri
*
* @param URI $episodeUri
*/ */
function extract_params_from_episode_uri($episodeUri): ?array function extract_params_from_episode_uri(URI $episodeUri): ?array
{ {
preg_match( preg_match(
'~@(?P<podcastName>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~', '~@(?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
], ],
]; ];
/** public function __construct(string $uri, ?string $activityPayload = null)
* @param string $uri
* @param string $activityPayload
*/
public function __construct($uri, $activityPayload = null)
{ {
$this->request = Services::curlrequest(); $this->request = Services::curlrequest();
if ($activityPayload) { if ($activityPayload !== null) {
$this->request->setBody($activityPayload); $this->request->setBody($activityPayload);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,28 +11,42 @@ namespace ActivityPub\Entities;
use RuntimeException; use RuntimeException;
use Michalsn\Uuid\UuidEntity; 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 class Activity extends UuidEntity
{ {
/**
* @var string[]
*/
protected $uuids = ['id', 'note_id'];
/** /**
* @var Actor * @var Actor
*/ */
protected $actor; protected $actor;
/** /**
* @var Actor * @var Actor|null
*/ */
protected $target_actor; protected $target_actor;
/** /**
* @var Note * @var Note|null
*/ */
protected $note; protected $note;
/**
* @var string[]
*/
protected $uuids = ['id', 'note_id'];
/** /**
* @var string[] * @var string[]
*/ */

View File

@ -11,12 +11,36 @@ namespace ActivityPub\Entities;
use RuntimeException; use RuntimeException;
use CodeIgniter\Entity\Entity; 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 class Actor extends Entity
{ {
/** /**
* @var string * @var string
*/ */
protected $key_id; protected $public_key_id;
/** /**
* @var Actor[] * @var Actor[]
@ -52,7 +76,7 @@ class Actor extends Entity
'is_blocked' => 'boolean', 'is_blocked' => 'boolean',
]; ];
public function getKeyId(): string public function getPublicKeyId(): string
{ {
return $this->uri . '#main-key'; return $this->uri . '#main-key';
} }
@ -72,7 +96,7 @@ class Actor extends Entity
} }
/** /**
* @return Follower[] * @return Actor[]
*/ */
public function getFollowers(): array public function getFollowers(): array
{ {

View File

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

View File

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

View File

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

View File

@ -8,16 +8,38 @@
namespace ActivityPub\Entities; namespace ActivityPub\Entities;
use CodeIgniter\I18n\Time;
use RuntimeException; use RuntimeException;
use Michalsn\Uuid\UuidEntity; 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 class Note extends UuidEntity
{ {
/**
* @var string[]
*/
protected $uuids = ['id', 'in_reply_to_id', 'reblog_of_id'];
/** /**
* @var Actor * @var Actor
*/ */
@ -68,6 +90,11 @@ class Note extends UuidEntity
*/ */
protected $reblogs = []; protected $reblogs = [];
/**
* @var string[]
*/
protected $uuids = ['id', 'in_reply_to_id', 'reblog_of_id'];
/** /**
* @var string[] * @var string[]
*/ */

View File

@ -10,6 +10,20 @@ namespace ActivityPub\Entities;
use CodeIgniter\Entity\Entity; 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 class PreviewCard extends Entity
{ {
/** /**

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
namespace ActivityPub\Models; namespace ActivityPub\Models;
use ActivityPub\Entities\Actor; use ActivityPub\Entities\Actor;
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Events\Events; use CodeIgniter\Events\Events;
use CodeIgniter\Model; use CodeIgniter\Model;
@ -57,7 +58,7 @@ class ActorModel extends Model
*/ */
protected $useTimestamps = true; protected $useTimestamps = true;
public function getActorById($id) public function getActorById($id): Actor
{ {
$cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}"; $cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}";
if (!($found = cache($cacheName))) { 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. * Retrieves instance or podcast domain blocks depending on whether or not $podcastId param is set.
*
* @param integer|null $podcastId
*/ */
public function getBlockedDomains() public function getBlockedDomains()
{ {

View File

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

View File

@ -17,6 +17,7 @@ use ActivityPub\Activities\CreateActivity;
use ActivityPub\Activities\DeleteActivity; use ActivityPub\Activities\DeleteActivity;
use ActivityPub\Activities\UndoActivity; use ActivityPub\Activities\UndoActivity;
use ActivityPub\Objects\TombstoneObject; use ActivityPub\Objects\TombstoneObject;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Events\Events; use CodeIgniter\Events\Events;
use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
@ -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) public function reblog(Actor $actor, Note $note, $registerActivity = true)
{ {

View File

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

View File

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

View File

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

View File

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

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