From 2d297f45b3d7ef6e8711875a0b9b908e878115fa Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Thu, 22 Apr 2021 17:20:28 +0000 Subject: [PATCH] feat: add cache to ActivityPub sql queries + cache activity and note pages - authenticated pages are not cached - add AnalyticsTrait to register a podcast webpage hit across mutliple controllers - set actor_id as unique in podcasts table - fix issues with preview card not appearing - update codeigniter4-uuid --- app/Config/Events.php | 74 +++++- app/Controllers/Actor.php | 27 +- app/Controllers/BaseController.php | 12 +- app/Controllers/Episode.php | 27 +- app/Controllers/Note.php | 95 +++++-- app/Controllers/Page.php | 2 +- app/Controllers/Podcast.php | 85 +++--- .../2020-05-30-101500_add_podcasts.php | 2 + app/Entities/Actor.php | 46 ++++ app/Entities/Category.php | 2 +- app/Entities/Podcast.php | 6 +- app/Helpers/auth_helper.php | 3 +- app/Helpers/media_helper.php | 3 - .../ActivityPub/Config/ActivityPub.php | 7 + app/Libraries/ActivityPub/Entities/Note.php | 34 ++- .../ActivityPub/Models/ActivityModel.php | 12 +- .../ActivityPub/Models/ActorModel.php | 75 +++++- .../ActivityPub/Models/BlockedDomainModel.php | 39 ++- .../ActivityPub/Models/FavouriteModel.php | 27 ++ .../ActivityPub/Models/FollowModel.php | 25 +- .../ActivityPub/Models/NoteModel.php | 243 +++++++++++++----- .../ActivityPub/Models/PreviewCardModel.php | 52 +++- .../ActivityPub/Models/UuidModel.php | 206 --------------- app/Libraries/Analytics/AnalyticsTrait.php | 51 ++++ .../Analytics/Helpers/analytics_helper.php | 34 --- app/Models/ActorModel.php | 14 + app/Models/CategoryModel.php | 4 +- app/Models/EpisodeModel.php | 30 ++- app/Models/EpisodePersonModel.php | 5 +- app/Models/NoteModel.php | 2 +- app/Models/PodcastModel.php | 70 +++-- app/Models/SoundbiteModel.php | 6 +- app/Views/podcast/_partials/note.php | 2 +- .../podcast/_partials/note_authenticated.php | 2 +- .../podcast/_partials/note_with_replies.php | 8 +- .../note_with_replies_authenticated.php | 10 +- app/Views/podcast/_partials/reblog.php | 2 +- .../_partials/reblog_authenticated.php | 2 +- app/Views/podcast/_partials/reply.php | 2 +- .../podcast/_partials/reply_authenticated.php | 2 +- composer.json | 4 +- composer.lock | 61 +++-- 42 files changed, 879 insertions(+), 536 deletions(-) create mode 100644 app/Entities/Actor.php delete mode 100644 app/Libraries/ActivityPub/Models/UuidModel.php create mode 100644 app/Libraries/Analytics/AnalyticsTrait.php create mode 100644 app/Models/ActorModel.php diff --git a/app/Config/Events.php b/app/Config/Events.php index 1771383790..da21d27df3 100644 --- a/app/Config/Events.php +++ b/app/Config/Events.php @@ -81,6 +81,10 @@ Events::on('on_note_add', function ($note) { ->where('id', $note->episode_id) ->increment('notes_total'); } + + // Removing all of the podcast pages is a bit overkill, but works perfectly + // same for other events below + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); }); Events::on('on_note_remove', function ($note) { @@ -97,6 +101,9 @@ Events::on('on_note_remove', function ($note) { ->where('id', $note->episode_id) ->decrement('favourites_total', $note->favourites_count); } + + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); }); Events::on('on_note_reblog', function ($actor, $note) { @@ -109,10 +116,18 @@ Events::on('on_note_reblog', function ($actor, $note) { ->where('id', $episodeId) ->increment('notes_total'); } + + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); + + if ($actor->is_podcast) { + cache()->deleteMatching("page_podcast#{$actor->podcast->id}*"); + } }); Events::on('on_note_undo_reblog', function ($reblogNote) { - if ($episodeId = $reblogNote->reblog_of_note->episode_id) { + $note = $reblogNote->reblog_of_note; + if ($episodeId = $note->episode_id) { model('EpisodeModel') ->where('id', $episodeId) ->decrement('reblogs_total'); @@ -121,6 +136,29 @@ Events::on('on_note_undo_reblog', function ($reblogNote) { ->where('id', $episodeId) ->decrement('notes_total'); } + + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); + + if ($reblogNote->actor->is_podcast) { + cache()->deleteMatching( + "page_podcast#{$reblogNote->actor->podcast->id}*", + ); + } +}); + +Events::on('on_note_reply', function ($reply) { + $note = $reply->reply_to_note; + + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); +}); + +Events::on('on_reply_remove', function ($reply) { + $note = $reply->reply_to_note; + + cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); }); Events::on('on_note_favourite', function ($actor, $note) { @@ -129,6 +167,13 @@ Events::on('on_note_favourite', function ($actor, $note) { ->where('id', $note->episode_id) ->increment('favourites_total'); } + + cache()->deleteMatching("page_podcast#{$actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); + + if ($note->in_reply_to_id) { + cache()->deleteMatching("page_note#{$note->in_reply_to_id}*"); + } }); Events::on('on_note_undo_favourite', function ($actor, $note) { @@ -137,4 +182,31 @@ Events::on('on_note_undo_favourite', function ($actor, $note) { ->where('id', $note->episode_id) ->decrement('favourites_total'); } + + cache()->deleteMatching("page_podcast#{$actor->podcast->id}*"); + cache()->deleteMatching("page_note#{$note->id}*"); + + if ($note->in_reply_to_id) { + cache()->deleteMatching("page_note#{$note->in_reply_to_id}*"); + } +}); + +Events::on('on_block_actor', function ($actorId) { + cache()->deleteMatching('page_podcast*'); + cache()->deleteMatching('page_note*'); +}); + +Events::on('on_unblock_actor', function ($actorId) { + cache()->deleteMatching('page_podcast*'); + cache()->deleteMatching('page_note*'); +}); + +Events::on('on_block_domain', function ($domainName) { + cache()->deleteMatching('page_podcast*'); + cache()->deleteMatching('page_note*'); +}); + +Events::on('on_unblock_domain', function ($domainName) { + cache()->deleteMatching('page_podcast*'); + cache()->deleteMatching('page_note*'); }); diff --git a/app/Controllers/Actor.php b/app/Controllers/Actor.php index 6c91eda9c9..34951ad282 100644 --- a/app/Controllers/Actor.php +++ b/app/Controllers/Actor.php @@ -8,15 +8,32 @@ namespace App\Controllers; +use Analytics\AnalyticsTrait; + class Actor extends \ActivityPub\Controllers\ActorController { + use AnalyticsTrait; + public function follow() { - helper(['form', 'components', 'svg']); - $data = [ - 'actor' => $this->actor, - ]; + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->actor->podcast->id); + } + + $cacheName = "page_podcast@{$this->actor->username}_follow"; + if (!($cachedView = cache($cacheName))) { + helper(['form', 'components', 'svg']); + $data = [ + 'actor' => $this->actor, + ]; + + return view('podcast/follow', $data, [ + 'cache' => DECADE, + 'cache_name' => $cacheName, + ]); + } - return view('podcast/follow', $data); + return $cachedView; } } diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index 126b71009d..df816124ae 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -26,7 +26,7 @@ class BaseController extends Controller * * @var array */ - protected $helpers = ['auth', 'analytics', 'svg', 'components', 'misc']; + protected $helpers = ['auth', 'svg', 'components', 'misc']; /** * Constructor. @@ -47,15 +47,5 @@ class BaseController extends Controller // Preload any models, libraries, etc, here. //-------------------------------------------------------------------- // E.g.: $this->session = \Config\Services::session(); - - set_user_session_deny_list_ip(); - set_user_session_browser(); - set_user_session_referer(); - set_user_session_entry_page(); - } - - protected static function triggerWebpageHit($podcastId) - { - webpage_hit($podcastId); } } diff --git a/app/Controllers/Episode.php b/app/Controllers/Episode.php index 8448e7565f..f0533e764b 100644 --- a/app/Controllers/Episode.php +++ b/app/Controllers/Episode.php @@ -8,12 +8,15 @@ namespace App\Controllers; +use Analytics\AnalyticsTrait; use App\Models\EpisodeModel; use App\Models\PodcastModel; use SimpleXMLElement; class Episode extends BaseController { + use AnalyticsTrait; + /** * @var \App\Entities\Podcast */ @@ -44,10 +47,15 @@ class Episode extends BaseController public function index() { - self::triggerWebpageHit($this->podcast->id); + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->episode->podcast_id); + } $locale = service('request')->getLocale(); - $cacheName = "page_podcast#{$this->podcast->id}_episode{$this->episode->id}_{$locale}"; + $cacheName = + "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" . + (can_user_interact() ? '_authenticated' : ''); if (!($cachedView = cache($cacheName))) { helper('persons'); @@ -69,13 +77,7 @@ class Episode extends BaseController if (can_user_interact()) { helper('form'); - // The page cache is set to a decade so it is deleted manually upon podcast update - return view('podcast/episode_authenticated', $data, [ - 'cache' => $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - 'cache_name' => $cacheName . '_authenticated', - ]); + return view('podcast/episode_authenticated', $data); } else { // The page cache is set to a decade so it is deleted manually upon podcast update return view('podcast/episode', $data, [ @@ -94,7 +96,10 @@ class Episode extends BaseController { header('Content-Security-Policy: frame-ancestors https://* http://*'); - self::triggerWebpageHit($this->episode->podcast_id); + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->episode->podcast_id); + } $session = \Config\Services::session(); $session->start(); @@ -107,7 +112,7 @@ class Episode extends BaseController $locale = service('request')->getLocale(); - $cacheName = "page_podcast#{$this->podcast->id}_episode{$this->episode->id}_embeddable_player_{$theme}_{$locale}"; + $cacheName = "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_embeddable_player_{$theme}_{$locale}"; if (!($cachedView = cache($cacheName))) { $theme = EpisodeModel::$themes[$theme]; diff --git a/app/Controllers/Note.php b/app/Controllers/Note.php index c685a690e0..509c78746a 100644 --- a/app/Controllers/Note.php +++ b/app/Controllers/Note.php @@ -8,6 +8,7 @@ namespace App\Controllers; +use Analytics\AnalyticsTrait; use App\Models\EpisodeModel; use App\Models\PodcastModel; use CodeIgniter\HTTP\URI; @@ -15,6 +16,8 @@ use CodeIgniter\I18n\Time; class Note extends \ActivityPub\Controllers\NoteController { + use AnalyticsTrait; + /** * @var \App\Entities\Podcast */ @@ -47,24 +50,46 @@ class Note extends \ActivityPub\Controllers\NoteController public function index() { - helper('persons'); - $persons = []; - construct_person_array($this->podcast->persons, $persons); - - $data = [ - 'podcast' => $this->podcast, - 'actor' => $this->actor, - 'note' => $this->note, - 'persons' => $persons, - ]; + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->podcast->id); + } - // if user is logged in then send to the authenticated activity view - if (can_user_interact()) { - helper('form'); - return view('podcast/note_authenticated', $data); - } else { - return view('podcast/note', $data); + $cacheName = implode( + '_', + array_filter([ + 'page', + "note#{$this->note->id}", + service('request')->getLocale(), + can_user_interact() ? '_authenticated' : null, + ]), + ); + + if (!($cachedView = cache($cacheName))) { + helper('persons'); + $persons = []; + construct_person_array($this->podcast->persons, $persons); + + $data = [ + 'podcast' => $this->podcast, + 'actor' => $this->actor, + 'note' => $this->note, + 'persons' => $persons, + ]; + + // if user is logged in then send to the authenticated activity view + if (can_user_interact()) { + helper('form'); + return view('podcast/note_authenticated', $data); + } else { + return view('podcast/note', $data, [ + 'cache' => DECADE, + 'cache_name' => $cacheName, + ]); + } } + + return $cachedView; } public function attemptCreate() @@ -198,15 +223,37 @@ class Note extends \ActivityPub\Controllers\NoteController public function remoteAction($action) { - $data = [ - 'podcast' => $this->podcast, - 'actor' => $this->actor, - 'note' => $this->note, - 'action' => $action, - ]; + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->podcast->id); + } + + $cacheName = implode( + '_', + array_filter([ + 'page', + "note#{$this->note->id}", + "remote_{$action}", + service('request')->getLocale(), + ]), + ); - helper('form'); + if (!($cachedView = cache($cacheName))) { + $data = [ + 'podcast' => $this->podcast, + 'actor' => $this->actor, + 'note' => $this->note, + 'action' => $action, + ]; + + helper('form'); + + return view('podcast/note_remote_action', $data, [ + 'cache' => DECADE, + 'cache_name' => $cacheName, + ]); + } - return view('podcast/note_remote_action', $data); + return $cachedView; } } diff --git a/app/Controllers/Page.php b/app/Controllers/Page.php index 8f97b5de99..73c452dfe9 100644 --- a/app/Controllers/Page.php +++ b/app/Controllers/Page.php @@ -56,7 +56,7 @@ class Page extends BaseController $locale = service('request')->getLocale(); $allPodcasts = (new PodcastModel())->findAll(); - $cacheName = "paÄe_credits_{$locale}"; + $cacheName = "page_credits_{$locale}"; if (!($found = cache($cacheName))) { $page = new \App\Entities\Page([ 'title' => lang('Person.credits', [], $locale), diff --git a/app/Controllers/Podcast.php b/app/Controllers/Podcast.php index 2b351e73d2..c9e735805c 100644 --- a/app/Controllers/Podcast.php +++ b/app/Controllers/Podcast.php @@ -8,12 +8,15 @@ namespace App\Controllers; +use Analytics\AnalyticsTrait; use App\Models\EpisodeModel; use App\Models\PodcastModel; use App\Models\NoteModel; class Podcast extends BaseController { + use AnalyticsTrait; + /** * @var \App\Entities\Podcast|null */ @@ -37,32 +40,56 @@ class Podcast extends BaseController public function activity() { - self::triggerWebpageHit($this->podcast->id); - - helper('persons'); - $persons = []; - construct_person_array($this->podcast->persons, $persons); - - $data = [ - 'podcast' => $this->podcast, - 'notes' => (new NoteModel())->getActorNotes( - $this->podcast->actor_id, - ), - 'persons' => $persons, - ]; - - // if user is logged in then send to the authenticated activity view - if (can_user_interact()) { - helper('form'); - return view('podcast/activity_authenticated', $data); - } else { - return view('podcast/activity', $data); + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->podcast->id); + } + + $cacheName = implode( + '_', + array_filter([ + 'page', + "podcast#{$this->podcast->id}", + 'activity', + service('request')->getLocale(), + can_user_interact() ? '_authenticated' : null, + ]), + ); + + if (!($cachedView = cache($cacheName))) { + helper('persons'); + $persons = []; + construct_person_array($this->podcast->persons, $persons); + + $data = [ + 'podcast' => $this->podcast, + 'notes' => (new NoteModel())->getActorPublishedNotes( + $this->podcast->actor_id, + ), + 'persons' => $persons, + ]; + + // if user is logged in then send to the authenticated activity view + if (can_user_interact()) { + helper('form'); + return view('podcast/activity_authenticated', $data); + } else { + return view('podcast/activity', $data, [ + 'cache' => DECADE, + 'cache_name' => $cacheName, + ]); + } } + + return $cachedView; } public function episodes() { - self::triggerWebpageHit($this->podcast->id); + // Prevent analytics hit when authenticated + if (!can_user_interact()) { + $this->registerPodcastWebpageHit($this->podcast->id); + } $yearQuery = $this->request->getGet('year'); $seasonQuery = $this->request->getGet('season'); @@ -85,14 +112,15 @@ class Podcast extends BaseController array_filter([ 'page', "podcast#{$this->podcast->id}", + 'episodes', $yearQuery ? 'year' . $yearQuery : null, $seasonQuery ? 'season' . $seasonQuery : null, service('request')->getLocale(), - can_user_interact() ? '_interact' : '', + can_user_interact() ? '_authenticated' : null, ]), ); - if (!($found = cache($cacheName))) { + if (!($cachedView = cache($cacheName))) { // Build navigation array $podcastModel = new PodcastModel(); $years = $podcastModel->getYears($this->podcast->id); @@ -171,14 +199,9 @@ class Podcast extends BaseController // if user is logged in then send to the authenticated episodes view if (can_user_interact()) { - $found = view('podcast/episodes_authenticated', $data, [ - 'cache' => $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - 'cache_name' => $cacheName, - ]); + return view('podcast/episodes_authenticated', $data); } else { - $found = view('podcast/episodes', $data, [ + return view('podcast/episodes', $data, [ 'cache' => $secondsToNextUnpublishedEpisode ? $secondsToNextUnpublishedEpisode : DECADE, @@ -187,6 +210,6 @@ class Podcast extends BaseController } } - return $found; + return $cachedView; } } diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index 0eddb82f05..d052c278af 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -187,7 +187,9 @@ class AddPodcasts extends Migration ]); $this->forge->addPrimaryKey('id'); + // TODO: remove name in favor of username from actor $this->forge->addUniqueKey('name'); + $this->forge->addUniqueKey('actor_id'); $this->forge->addForeignKey( 'actor_id', 'activitypub_actors', diff --git a/app/Entities/Actor.php b/app/Entities/Actor.php new file mode 100644 index 0000000000..78515a706f --- /dev/null +++ b/app/Entities/Actor.php @@ -0,0 +1,46 @@ +<?php + +/** + * @copyright 2020 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Entities; + +use App\Models\PodcastModel; + +class Actor extends \ActivityPub\Entities\Actor +{ + /** + * @var App\Entities\Podcast|null + */ + protected $podcast; + + /** + * @var boolean + */ + protected $is_podcast; + + public function getIsPodcast() + { + return !empty($this->podcast); + } + + public function getPodcast() + { + if (empty($this->id)) { + throw new \RuntimeException( + 'Actor must be created before getting associated podcast.', + ); + } + + if (empty($this->podcast)) { + $this->podcast = (new PodcastModel())->getPodcastByActorId( + $this->id, + ); + } + + return $this->podcast; + } +} diff --git a/app/Entities/Category.php b/app/Entities/Category.php index e233f5a5e0..d58e143b48 100644 --- a/app/Entities/Category.php +++ b/app/Entities/Category.php @@ -31,7 +31,7 @@ class Category extends Entity $parentId = $this->attributes['parent_id']; return $parentId != 0 - ? (new CategoryModel())->findParent($parentId) + ? (new CategoryModel())->getCategoryById($parentId) : null; } } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 8bce314e5a..db7af33717 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -143,7 +143,7 @@ class Podcast extends Entity } if (empty($this->actor)) { - $this->actor = (new ActorModel())->getActorById($this->actor_id); + $this->actor = model('ActorModel')->getActorById($this->actor_id); } return $this->actor; @@ -254,7 +254,9 @@ class Podcast extends Entity } if (empty($this->category)) { - $this->category = (new CategoryModel())->find($this->category_id); + $this->category = (new CategoryModel())->getCategoryById( + $this->category_id, + ); } return $this->category; diff --git a/app/Helpers/auth_helper.php b/app/Helpers/auth_helper.php index d1d095baaf..4408c0aa7c 100644 --- a/app/Helpers/auth_helper.php +++ b/app/Helpers/auth_helper.php @@ -6,7 +6,6 @@ * @link https://castopod.org/ */ -use ActivityPub\Models\ActorModel; use CodeIgniter\Database\Exceptions\DataException; use Config\Services; @@ -68,7 +67,7 @@ if (!function_exists('interact_as_actor')) { $session = session(); if ($session->has('interact_as_actor_id')) { - return (new ActorModel())->getActorById( + return model('ActorModel')->getActorById( $session->get('interact_as_actor_id'), ); } diff --git a/app/Helpers/media_helper.php b/app/Helpers/media_helper.php index d82720a072..a7e2eb6926 100644 --- a/app/Helpers/media_helper.php +++ b/app/Helpers/media_helper.php @@ -7,7 +7,6 @@ */ use CodeIgniter\Files\File; -use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\ResponseInterface; /** @@ -42,8 +41,6 @@ function save_media($file, $folder, $mediaName) */ function download_file($fileUrl) { - var_dump($fileUrl); - $client = \Config\Services::curlrequest(); $response = $client->get($fileUrl, [ diff --git a/app/Libraries/ActivityPub/Config/ActivityPub.php b/app/Libraries/ActivityPub/Config/ActivityPub.php index 0feb9f162f..e96460d47d 100644 --- a/app/Libraries/ActivityPub/Config/ActivityPub.php +++ b/app/Libraries/ActivityPub/Config/ActivityPub.php @@ -30,4 +30,11 @@ class ActivityPub extends BaseConfig public $defaultCoverImagePath = 'assets/images/cover-default.jpg'; public $defaultCoverImageMimetype = 'image/jpeg'; + + /** + * -------------------------------------------------------------------- + * Cache options + * -------------------------------------------------------------------- + */ + public $cachePrefix = 'ap_'; } diff --git a/app/Libraries/ActivityPub/Entities/Note.php b/app/Libraries/ActivityPub/Entities/Note.php index 9e1736d0c2..af278d4b91 100644 --- a/app/Libraries/ActivityPub/Entities/Note.php +++ b/app/Libraries/ActivityPub/Entities/Note.php @@ -44,11 +44,21 @@ class Note extends UuidEntity */ protected $preview_card; + /** + * @var boolean + */ + protected $has_preview_card; + /** * @var \ActivityPub\Entities\Note[] */ protected $replies; + /** + * @var boolean + */ + protected $has_replies; + /** * @var \ActivityPub\Entities\Note[] */ @@ -106,6 +116,18 @@ class Note extends UuidEntity return $this->preview_card; } + public function getHasPreviewCard() + { + return !empty($this->getPreviewCard()) ? true : false; + } + + public function getIsReply() + { + $this->is_reply = $this->in_reply_to_id !== null; + + return $this->is_reply; + } + public function getReplies() { if (empty($this->id)) { @@ -121,11 +143,9 @@ class Note extends UuidEntity return $this->replies; } - public function getIsReply() + public function getHasReplies() { - $this->is_reply = $this->in_reply_to_id !== null; - - return $this->is_reply; + return !empty($this->getReplies()) ? true : false; } public function getReplyToNote() @@ -152,11 +172,7 @@ class Note extends UuidEntity } if (empty($this->reblogs)) { - $this->reblogs = model('NoteModel')->getNoteReblogs( - service('uuid') - ->fromString($this->id) - ->getBytes(), - ); + $this->reblogs = model('NoteModel')->getNoteReblogs($this->id); } return $this->reblogs; diff --git a/app/Libraries/ActivityPub/Models/ActivityModel.php b/app/Libraries/ActivityPub/Models/ActivityModel.php index 33c7f45e41..0d7a82ad5d 100644 --- a/app/Libraries/ActivityPub/Models/ActivityModel.php +++ b/app/Libraries/ActivityPub/Models/ActivityModel.php @@ -8,6 +8,8 @@ namespace ActivityPub\Models; +use Michalsn\Uuid\UuidModel; + class ActivityModel extends UuidModel { protected $table = 'activitypub_activities'; @@ -34,7 +36,15 @@ class ActivityModel extends UuidModel public function getActivityById($activityId) { - return $this->find($activityId); + $cacheName = + config('ActivityPub')->cachePrefix . "activity#{$activityId}"; + if (!($found = cache($cacheName))) { + $found = $this->find($activityId); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** diff --git a/app/Libraries/ActivityPub/Models/ActorModel.php b/app/Libraries/ActivityPub/Models/ActorModel.php index deed724ef5..a82ee94e73 100644 --- a/app/Libraries/ActivityPub/Models/ActorModel.php +++ b/app/Libraries/ActivityPub/Models/ActorModel.php @@ -8,6 +8,7 @@ namespace ActivityPub\Models; +use CodeIgniter\Events\Events; use CodeIgniter\Model; class ActorModel extends Model @@ -42,7 +43,14 @@ class ActorModel extends Model public function getActorById($id) { - return $this->find($id); + $cacheName = config('ActivityPub')->cachePrefix . "actor#{$id}"; + if (!($found = cache($cacheName))) { + $found = $this->find($id); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** @@ -76,31 +84,51 @@ class ActorModel extends Model public function getActorByUri($actorUri) { - return $this->where('uri', $actorUri)->first(); + $hashedActorUri = md5($actorUri); + $cacheName = + config('ActivityPub')->cachePrefix . "actor@{$hashedActorUri}"; + if (!($found = cache($cacheName))) { + $found = $this->where('uri', $actorUri)->first(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } public function getFollowers($actorId) { - return $this->join( - 'activitypub_follows', - 'activitypub_follows.actor_id = id', - 'inner', - ) - ->where('activitypub_follows.target_actor_id', $actorId) - ->findAll(); + $cacheName = + config('ActivityPub')->cachePrefix . "actor#{$actorId}_followers"; + if (!($found = cache($cacheName))) { + $found = $this->join( + 'activitypub_follows', + 'activitypub_follows.actor_id = id', + 'inner', + ) + ->where('activitypub_follows.target_actor_id', $actorId) + ->findAll(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** - * Check if an actor is blocked using its uri + * Check if an existing actor is blocked using its uri. + * Returns FALSE if the actor doesn't exist * * @param mixed $actorUri * @return boolean */ public function isActorBlocked($actorUri) { - return $this->where(['uri' => $actorUri, 'is_blocked' => true])->first() - ? true - : false; + if ($actor = $this->getActorByUri($actorUri)) { + return $actor->is_blocked; + } + + return false; } /** @@ -110,16 +138,35 @@ class ActorModel extends Model */ public function getBlockedActors() { - return $this->where('is_blocked', 1)->findAll(); + $cacheName = config('ActivityPub')->cachePrefix . 'blocked_actors'; + if (!($found = cache($cacheName))) { + $found = $this->where('is_blocked', 1)->findAll(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } public function blockActor($actorId) { + $prefix = config('ActivityPub')->cachePrefix; + cache()->delete($prefix . 'blocked_actors'); + cache()->deleteMatching($prefix . '*replies'); + + Events::trigger('on_block_actor', $actorId); + $this->update($actorId, ['is_blocked' => 1]); } public function unblockActor($actorId) { + $prefix = config('ActivityPub')->cachePrefix; + cache()->delete($prefix . 'blocked_actors'); + cache()->deleteMatching($prefix . '*replies'); + + Events::trigger('on_unblock_actor', $actorId); + $this->update($actorId, ['is_blocked' => 0]); } } diff --git a/app/Libraries/ActivityPub/Models/BlockedDomainModel.php b/app/Libraries/ActivityPub/Models/BlockedDomainModel.php index 482ef77c49..d87e115749 100644 --- a/app/Libraries/ActivityPub/Models/BlockedDomainModel.php +++ b/app/Libraries/ActivityPub/Models/BlockedDomainModel.php @@ -8,6 +8,7 @@ namespace ActivityPub\Models; +use CodeIgniter\Events\Events; use CodeIgniter\Model; class BlockedDomainModel extends Model @@ -30,20 +31,41 @@ class BlockedDomainModel extends Model */ public function getBlockedDomains() { - return $this->findAll(); + $cacheName = config('ActivityPub')->cachePrefix . 'blocked_domains'; + if (!($found = cache($cacheName))) { + $found = $this->findAll(); + + cache()->save($cacheName, $found, DECADE); + } + return $found; } public function isDomainBlocked($domain) { - if ($this->find($domain)) { - return true; + $hashedDomain = md5($domain); + $cacheName = + config('ActivityPub')->cachePrefix . + "domain#{$hashedDomain}_isBlocked"; + if (!($found = cache($cacheName))) { + $found = $this->find($domain) ? true : false; + + cache()->save($cacheName, $found, DECADE); } - return false; + return $found; } public function blockDomain($name) { + $hashedDomain = md5($name); + $prefix = config('ActivityPub')->cachePrefix; + cache()->delete($prefix . "domain#{$hashedDomain}_isBlocked"); + cache()->delete($prefix . 'blocked_domains'); + + cache()->deleteMatching($prefix . '*replies'); + + Events::trigger('on_block_domain', $name); + $this->db->transStart(); // set all actors from the domain as blocked @@ -63,6 +85,15 @@ class BlockedDomainModel extends Model public function unblockDomain($name) { + $hashedDomain = md5($name); + $prefix = config('ActivityPub')->cachePrefix; + cache()->delete($prefix . "domain#{$hashedDomain}_isBlocked"); + cache()->delete($prefix . 'blocked_domains'); + + cache()->deleteMatching($prefix . '*replies'); + + Events::trigger('on_unblock_domain', $name); + $this->db->transStart(); // unblock all actors from the domain model('ActorModel') diff --git a/app/Libraries/ActivityPub/Models/FavouriteModel.php b/app/Libraries/ActivityPub/Models/FavouriteModel.php index 8a5d781e48..4aacdc9a2d 100644 --- a/app/Libraries/ActivityPub/Models/FavouriteModel.php +++ b/app/Libraries/ActivityPub/Models/FavouriteModel.php @@ -11,6 +11,7 @@ namespace ActivityPub\Models; use ActivityPub\Activities\LikeActivity; use ActivityPub\Activities\UndoActivity; use CodeIgniter\Events\Events; +use Michalsn\Uuid\UuidModel; class FavouriteModel extends UuidModel { @@ -49,6 +50,19 @@ class FavouriteModel extends UuidModel ) ->increment('favourites_count'); + $prefix = config('ActivityPub')->cachePrefix; + $hashedNoteUri = md5($note->uri); + cache()->delete($prefix . "note#{$note->id}"); + cache()->delete($prefix . "note@{$hashedNoteUri}"); + cache()->delete($prefix . "actor#{$actor->id}_published_notes"); + + if ($note->in_reply_to_id) { + cache()->delete($prefix . "note#{$note->in_reply_to_id}_replies"); + cache()->delete( + $prefix . "note#{$note->in_reply_to_id}_replies_withBlocked", + ); + } + Events::trigger('on_note_favourite', $actor, $note); if ($registerActivity) { @@ -91,6 +105,19 @@ class FavouriteModel extends UuidModel ) ->decrement('favourites_count'); + $prefix = config('ActivityPub')->cachePrefix; + $hashedNoteUri = md5($note->uri); + cache()->delete($prefix . "note#{$note->id}"); + cache()->delete($prefix . "note@{$hashedNoteUri}"); + cache()->delete($prefix . "actor#{$actor->id}_published_notes"); + + if ($note->in_reply_to_id) { + cache()->delete($prefix . "note#{$note->in_reply_to_id}_replies"); + cache()->delete( + $prefix . "note#{$note->in_reply_to_id}_replies_withBlocked", + ); + } + $this->table('activitypub_favourites') ->where([ 'actor_id' => $actor->id, diff --git a/app/Libraries/ActivityPub/Models/FollowModel.php b/app/Libraries/ActivityPub/Models/FollowModel.php index 89831855d4..ced37815ed 100644 --- a/app/Libraries/ActivityPub/Models/FollowModel.php +++ b/app/Libraries/ActivityPub/Models/FollowModel.php @@ -27,9 +27,8 @@ class FollowModel extends Model protected $updatedField = null; /** - * - * @param \ActivityPub\Entities\Actor $actor - * @param \ActivityPub\Entities\Actor $targetActor + * @param \ActivityPub\Entities\Actor $actor Actor that is following + * @param \ActivityPub\Entities\Actor $targetActor Actor that is being followed * @param bool $registerActivity * @return void * @throws DatabaseException @@ -49,6 +48,14 @@ class FollowModel extends Model ->where('id', $targetActor->id) ->increment('followers_count'); + cache()->delete( + config('ActivityPub')->cachePrefix . "actor#{$targetActor->id}", + ); + cache()->delete( + config('ActivityPub')->cachePrefix . + "actor#{$targetActor->id}_followers", + ); + if ($registerActivity) { $followActivity = new FollowActivity(); @@ -85,8 +92,8 @@ class FollowModel extends Model } /** - * @param \ActivityPub\Entities\Actor $actor - * @param \ActivityPub\Entities\Actor $targetActor + * @param \ActivityPub\Entities\Actor $actor Actor that is unfollowing + * @param \ActivityPub\Entities\Actor $targetActor Actor that is being unfollowed * @return void * @throws InvalidArgumentException * @throws DatabaseException @@ -108,6 +115,14 @@ class FollowModel extends Model ->where('id', $targetActor->id) ->decrement('followers_count'); + cache()->delete( + config('ActivityPub')->cachePrefix . "actor#{$targetActor->id}", + ); + cache()->delete( + config('ActivityPub')->cachePrefix . + "actor#{$targetActor->id}_followers", + ); + if ($registerActivity) { $undoActivity = new UndoActivity(); // get follow activity from database diff --git a/app/Libraries/ActivityPub/Models/NoteModel.php b/app/Libraries/ActivityPub/Models/NoteModel.php index f0914542d6..ed1600f3d1 100644 --- a/app/Libraries/ActivityPub/Models/NoteModel.php +++ b/app/Libraries/ActivityPub/Models/NoteModel.php @@ -17,6 +17,7 @@ use ActivityPub\Objects\TombstoneObject; use CodeIgniter\Events\Events; use CodeIgniter\HTTP\URI; use CodeIgniter\I18n\Time; +use Michalsn\Uuid\UuidModel; class NoteModel extends UuidModel { @@ -54,12 +55,28 @@ class NoteModel extends UuidModel public function getNoteById($noteId) { - return $this->find($noteId); + $cacheName = config('ActivityPub')->cachePrefix . "note#{$noteId}"; + if (!($found = cache($cacheName))) { + $found = $this->find($noteId); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } public function getNoteByUri($noteUri) { - return $this->where('uri', $noteUri)->first(); + $hashedNoteUri = md5($noteUri); + $cacheName = + config('ActivityPub')->cachePrefix . "note@{$hashedNoteUri}"; + if (!($found = cache($cacheName))) { + $found = $this->where('uri', $noteUri)->first(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** @@ -67,15 +84,24 @@ class NoteModel extends UuidModel * * @return \ActivityPub\Entities\Note[] */ - public function getActorNotes($actorId) + public function getActorPublishedNotes($actorId) { - return $this->where([ - 'actor_id' => $actorId, - 'in_reply_to_id' => null, - ]) - ->where('`published_at` <= NOW()', null, false) - ->orderBy('published_at', 'DESC') - ->findAll(); + $cacheName = + config('ActivityPub')->cachePrefix . + "actor#{$actorId}_published_notes"; + if (!($found = cache($cacheName))) { + $found = $this->where([ + 'actor_id' => $actorId, + 'in_reply_to_id' => null, + ]) + ->where('`published_at` <= NOW()', null, false) + ->orderBy('published_at', 'DESC') + ->findAll(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** @@ -88,26 +114,34 @@ class NoteModel extends UuidModel */ public function getNoteReplies($noteId, $withBlocked = false) { - if (!$withBlocked) { - $this->select('activitypub_notes.*') - ->join( - 'activitypub_actors', - 'activitypub_actors.id = activitypub_notes.actor_id', - 'inner', - ) - ->where('activitypub_actors.is_blocked', 0); - } + $cacheName = + config('ActivityPub')->cachePrefix . + "note#{$noteId}_replies" . + ($withBlocked ? '_withBlocked' : ''); + + if (!($found = cache($cacheName))) { + if (!$withBlocked) { + $this->select('activitypub_notes.*') + ->join( + 'activitypub_actors', + 'activitypub_actors.id = activitypub_notes.actor_id', + 'inner', + ) + ->where('activitypub_actors.is_blocked', 0); + } - $this->where( - 'in_reply_to_id', - service('uuid') - ->fromString($noteId) - ->getBytes(), - ) - ->where('`published_at` <= NOW()', null, false) - ->orderBy('published_at', 'ASC'); + $this->where( + 'in_reply_to_id', + $this->uuid->fromString($noteId)->getBytes(), + ) + ->where('`published_at` <= NOW()', null, false) + ->orderBy('published_at', 'ASC'); + $found = $this->findAll(); - return $this->findAll(); + cache()->save($cacheName, $found, DECADE); + } + + return $found; } /** @@ -115,16 +149,28 @@ class NoteModel extends UuidModel */ public function getNoteReblogs($noteId) { - return $this->where('reblog_of_id', $noteId) - ->where('`published_at` <= NOW()', null, false) - ->orderBy('published_at', 'ASC') - ->findAll(); + $cacheName = + config('ActivityPub')->cachePrefix . "note#{$noteId}_reblogs"; + + if (!($found = cache($cacheName))) { + $found = $this->where( + 'reblog_of_id', + $this->uuid->fromString($noteId)->getBytes(), + ) + ->where('`published_at` <= NOW()', null, false) + ->orderBy('published_at', 'ASC') + ->findAll(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; } public function addPreviewCard($noteId, $previewCardId) { return $this->db->table('activitypub_notes_preview_cards')->insert([ - 'note_id' => $noteId, + 'note_id' => $this->uuid->fromString($noteId)->getBytes(), 'preview_card_id' => $previewCardId, ]); } @@ -169,10 +215,6 @@ class NoteModel extends UuidModel // problem when linking note to preview card return false; } - - $this->db->transComplete(); - - return $newNoteId; } } @@ -180,17 +222,19 @@ class NoteModel extends UuidModel ->where('id', $note->actor_id) ->increment('notes_count'); + $cachePrefix = config('ActivityPub')->cachePrefix; + cache()->delete($cachePrefix . "actor#{$note->actor_id}"); + cache()->delete( + $cachePrefix . "actor#{$note->actor_id}_published_notes", + ); + Events::trigger('on_note_add', $note); if ($registerActivity) { - $noteUuid = service('uuid') - ->fromBytes($newNoteId) - ->toString(); - // set note id and uri to construct NoteObject - $note->id = $noteUuid; + $note->id = $newNoteId; $note->uri = base_url( - route_to('note', $note->actor->username, $noteUuid), + route_to('note', $note->actor->username, $newNoteId), ); $createActivity = new CreateActivity(); @@ -203,7 +247,7 @@ class NoteModel extends UuidModel 'Create', $note->actor_id, null, - $noteUuid, + $newNoteId, $createActivity->toJSON(), $note->published_at, 'queued', @@ -234,7 +278,7 @@ class NoteModel extends UuidModel $scheduledActivity = model('ActivityModel') ->where([ 'type' => 'Create', - 'note_id' => service('uuid') + 'note_id' => $this->uuid ->fromString($updatedNote->id) ->getBytes(), ]) @@ -253,6 +297,12 @@ class NoteModel extends UuidModel // update note $updateResult = $this->update($updatedNote->id, $updatedNote); + // Clear note cache + $prefix = config('ActivityPub')->cachePrefix; + $hashedNoteUri = md5($updatedNote->uri); + cache()->delete($prefix . "note#{$updatedNote->id}"); + cache()->delete($prefix . "note@{$hashedNoteUri}"); + $this->db->transComplete(); return $updateResult; @@ -268,32 +318,61 @@ class NoteModel extends UuidModel { $this->db->transStart(); + $cachePrefix = config('ActivityPub')->cachePrefix; + model('ActorModel') ->where('id', $note->actor_id) ->decrement('notes_count'); + cache()->delete($cachePrefix . "actor#{$note->actor_id}"); + cache()->delete( + $cachePrefix . "actor#{$note->actor_id}_published_notes", + ); if ($note->in_reply_to_id) { // Note to remove is a reply model('NoteModel') ->where( 'id', - service('uuid') - ->fromString($note->in_reply_to_id) - ->getBytes(), + $this->uuid->fromString($note->in_reply_to_id)->getBytes(), ) ->decrement('replies_count'); + + $replyToNote = $note->reply_to_note; + cache()->delete($cachePrefix . "note#{$replyToNote->id}"); + cache()->delete($cachePrefix . "note@{$replyToNote->uri}"); + cache()->delete($cachePrefix . "note#{$replyToNote->id}_replies"); + cache()->delete( + $cachePrefix . "note#{$replyToNote->id}_replies_withBlocked", + ); + + Events::trigger('on_reply_remove', $note); } - // remove all reblogs + // remove all note reblogs foreach ($note->reblogs as $reblog) { $this->removeNote($reblog); } - // remove all replies + // remove all note replies foreach ($note->replies as $reply) { $this->removeNote($reply); } + if ($note->preview_card) { + // check that preview card in no longer used elsewhere before deleting it + if ( + $this->db + ->table('activitypub_notes_preview_cards') + ->where('preview_card_id', $note->preview_card->id) + ->countAll() <= 1 + ) { + model('PreviewCardModel')->deletePreviewCard( + $note->preview_card->id, + $note->preview_card->url, + ); + } + } + Events::trigger('on_note_remove', $note); if ($registerActivity) { @@ -326,6 +405,15 @@ class NoteModel extends UuidModel ]); } + // clear note + replies / reblogs + actor and its published notes + $hashedNoteUri = md5($note->uri); + cache()->delete($cachePrefix . "note#{$note->id}"); + cache()->delete($cachePrefix . "note@{$hashedNoteUri}"); + cache()->delete($cachePrefix . "note#{$note->id}_replies"); + cache()->delete($cachePrefix . "note#{$note->id}_replies_withBlocked"); + cache()->delete($cachePrefix . "note#{$note->id}_reblogs"); + cache()->delete($cachePrefix . "note#{$note->id}_preview_card"); + $result = model('NoteModel', false)->delete($note->id); $this->db->transComplete(); @@ -349,12 +437,19 @@ class NoteModel extends UuidModel model('NoteModel') ->where( 'id', - service('uuid') - ->fromString($reply->in_reply_to_id) - ->getBytes(), + $this->uuid->fromString($reply->in_reply_to_id)->getBytes(), ) ->increment('replies_count'); + $prefix = config('ActivityPub')->cachePrefix; + $hashedNoteUri = md5($reply->reply_to_note->uri); + cache()->delete($prefix . "note#{$reply->in_reply_to_id}"); + cache()->delete($prefix . "note@{$hashedNoteUri}"); + cache()->delete($prefix . "note#{$reply->in_reply_to_id}_replies"); + cache()->delete( + $prefix . "note#{$reply->in_reply_to_id}_replies_withBlocked", + ); + Events::trigger('on_note_reply', $reply); $this->db->transComplete(); @@ -385,15 +480,19 @@ class NoteModel extends UuidModel ->where('id', $actor->id) ->increment('notes_count'); + $prefix = config('ActivityPub')->cachePrefix; + cache()->delete($prefix . "actor#{$note->actor_id}"); + cache()->delete($prefix . "actor#{$note->actor_id}_published_notes"); + model('NoteModel') - ->where( - 'id', - service('uuid') - ->fromString($note->id) - ->getBytes(), - ) + ->where('id', $this->uuid->fromString($note->id)->getBytes()) ->increment('reblogs_count'); + $hashedNoteUri = md5($note->uri); + cache()->delete($prefix . "note#{$note->id}"); + cache()->delete($prefix . "note@{$hashedNoteUri}"); + cache()->delete($prefix . "note#{$note->id}_reblogs"); + Events::trigger('on_note_reblog', $actor, $note); if ($registerActivity) { @@ -438,15 +537,26 @@ class NoteModel extends UuidModel ->where('id', $reblogNote->actor_id) ->decrement('notes_count'); + $cachePrefix = config('ActivityPub')->cachePrefix; + cache()->delete($cachePrefix . "actor#{$reblogNote->actor_id}"); + cache()->delete( + $cachePrefix . "actor#{$reblogNote->actor_id}_published_notes", + ); + model('NoteModel') ->where( 'id', - service('uuid') - ->fromString($reblogNote->reblog_of_id) - ->getBytes(), + $this->uuid->fromString($reblogNote->reblog_of_id)->getBytes(), ) ->decrement('reblogs_count'); + $hashedReblogNoteUri = md5($reblogNote->uri); + $hashedNoteUri = md5($reblogNote->reblog_of_note->uri); + cache()->delete($cachePrefix . "note#{$reblogNote->id}"); + cache()->delete($cachePrefix . "note@{$hashedReblogNoteUri}"); + cache()->delete($cachePrefix . "note#{$reblogNote->reblog_of_id}"); + cache()->delete($cachePrefix . "note@{$hashedNoteUri}"); + Events::trigger('on_note_undo_reblog', $reblogNote); if ($registerActivity) { @@ -456,7 +566,7 @@ class NoteModel extends UuidModel ->where([ 'type' => 'Announce', 'actor_id' => $reblogNote->actor_id, - 'note_id' => service('uuid') + 'note_id' => $this->uuid ->fromString($reblogNote->reblog_of_id) ->getBytes(), ]) @@ -516,7 +626,7 @@ class NoteModel extends UuidModel if ( !($reblogNote = $this->where([ 'actor_id' => $actor->id, - 'reblog_of_id' => service('uuid') + 'reblog_of_id' => $this->uuid ->fromString($note->id) ->getBytes(), ])->first()) @@ -529,9 +639,8 @@ class NoteModel extends UuidModel protected function setNoteId($data) { - $uuid4 = service('uuid')->uuid4(); - $data['id'] = $uuid4->toString(); - $data['data']['id'] = $uuid4->getBytes(); + $uuid4 = $this->uuid->{$this->uuidVersion}(); + $data['data']['id'] = $uuid4->toString(); if (!isset($data['data']['uri'])) { $actor = model('ActorModel')->getActorById( diff --git a/app/Libraries/ActivityPub/Models/PreviewCardModel.php b/app/Libraries/ActivityPub/Models/PreviewCardModel.php index 874fe11518..f7dc02aea6 100644 --- a/app/Libraries/ActivityPub/Models/PreviewCardModel.php +++ b/app/Libraries/ActivityPub/Models/PreviewCardModel.php @@ -35,22 +35,50 @@ class PreviewCardModel extends Model public function getPreviewCardFromUrl($url) { - return $this->where('url', $url)->first(); + $hashedPreviewCardUrl = md5($url); + $cacheName = + config('ActivityPub')->cachePrefix . + "preview_card@{$hashedPreviewCardUrl}"; + if (!($found = cache($cacheName))) { + $found = $this->where('url', $url)->first(); + cache()->save($cacheName, $found, DECADE); + } + + return $found; } public function getNotePreviewCard($noteId) { - return $this->join( - 'activitypub_notes_preview_cards', - 'activitypub_notes_preview_cards.preview_card_id = id', - 'inner', - ) - ->where( - 'note_id', - service('uuid') - ->fromString($noteId) - ->getBytes(), + $cacheName = + config('ActivityPub')->cachePrefix . "note#{$noteId}_preview_card"; + if (!($found = cache($cacheName))) { + $found = $this->join( + 'activitypub_notes_preview_cards', + 'activitypub_notes_preview_cards.preview_card_id = id', + 'inner', ) - ->first(); + ->where( + 'note_id', + service('uuid') + ->fromString($noteId) + ->getBytes(), + ) + ->first(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; + } + + public function deletePreviewCard($id, $url) + { + $hashedPreviewCardUrl = md5($url); + cache()->delete( + config('ActivityPub')->cachePrefix . + "preview_card@{$hashedPreviewCardUrl}", + ); + + return $this->delete($id); } } diff --git a/app/Libraries/ActivityPub/Models/UuidModel.php b/app/Libraries/ActivityPub/Models/UuidModel.php deleted file mode 100644 index 2029a846f7..0000000000 --- a/app/Libraries/ActivityPub/Models/UuidModel.php +++ /dev/null @@ -1,206 +0,0 @@ -<?php - -/** - * @copyright 2021 Podlibre - * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 - * @link https://castopod.org/ - */ - -namespace ActivityPub\Models; - -use CodeIgniter\Database\Exceptions\DataException; -use stdClass; - -class UuidModel extends \Michalsn\Uuid\UuidModel -{ - /** - * This insert overwrite is added as a means to FIX some bugs - * from the extended Uuid package. See: https://github.com/michalsn/codeigniter4-uuid/issues/2 - * - * Inserts data into the current table. If an object is provided, - * it will attempt to convert it to an array. - * - * @param array|object $data - * @param boolean $returnID Whether insert ID should be returned or not. - * - * @return BaseResult|integer|string|false - * @throws \ReflectionException - */ - public function insert($data = null, bool $returnID = true) - { - $escape = null; - - $this->insertID = 0; - - if (empty($data)) { - $data = $this->tempData['data'] ?? null; - $escape = $this->tempData['escape'] ?? null; - $this->tempData = []; - } - - if (empty($data)) { - throw DataException::forEmptyDataset('insert'); - } - - // If $data is using a custom class with public or protected - // properties representing the table elements, we need to grab - // them as an array. - if (is_object($data) && !$data instanceof stdClass) { - $data = static::classToArray( - $data, - $this->primaryKey, - $this->dateFormat, - false, - ); - } - - // If it's still a stdClass, go ahead and convert to - // an array so doProtectFields and other model methods - // don't have to do special checks. - if (is_object($data)) { - $data = (array) $data; - } - - if (empty($data)) { - throw DataException::forEmptyDataset('insert'); - } - - // Validate data before saving. - if ($this->skipValidation === false) { - if ($this->cleanRules()->validate($data) === false) { - return false; - } - } - - // Must be called first so we don't - // strip out created_at values. - $data = $this->doProtectFields($data); - - // Set created_at and updated_at with same time - $date = $this->setDate(); - - if ( - $this->useTimestamps && - !empty($this->createdField) && - !array_key_exists($this->createdField, $data) - ) { - $data[$this->createdField] = $date; - } - - if ( - $this->useTimestamps && - !empty($this->updatedField) && - !array_key_exists($this->updatedField, $data) - ) { - $data[$this->updatedField] = $date; - } - - $eventData = ['data' => $data]; - if ($this->tempAllowCallbacks) { - $eventData = $this->trigger('beforeInsert', $eventData); - } - - // Require non empty primaryKey when - // not using auto-increment feature - if ( - !$this->useAutoIncrement && - empty($eventData['data'][$this->primaryKey]) - ) { - throw DataException::forEmptyPrimaryKey('insert'); - } - - if (!empty($this->uuidFields)) { - foreach ($this->uuidFields as $field) { - if ($field === $this->primaryKey) { - $this->uuidTempData[ - $field - ] = $this->uuid->{$this->uuidVersion}(); - - if ($this->uuidUseBytes === true) { - $this->builder()->set( - $field, - $this->uuidTempData[$field]->getBytes(), - ); - } else { - $this->builder()->set( - $field, - $this->uuidTempData[$field]->toString(), - ); - } - } else { - if ( - $this->uuidUseBytes === true && - !empty($eventData['data'][$field]) - ) { - $this->uuidTempData[$field] = $this->uuid->fromString( - $eventData['data'][$field], - ); - $this->builder()->set( - $field, - $this->uuidTempData[$field]->getBytes(), - ); - unset($eventData['data'][$field]); - } - } - } - } - - // Must use the set() method to ensure objects get converted to arrays - $result = $this->builder() - ->set($eventData['data'], '', $escape) - ->insert(); - - // If insertion succeeded then save the insert ID - if ($result) { - if ( - !$this->useAutoIncrement || - isset($eventData['data'][$this->primaryKey]) - ) { - $this->insertID = $eventData['data'][$this->primaryKey]; - } else { - if (in_array($this->primaryKey, $this->uuidFields)) { - $this->insertID = $this->uuidTempData[ - $this->primaryKey - ]->toString(); - } else { - $this->insertID = $this->db->insertID(); - } - } - } - - // Cleanup data before event trigger - if (!empty($this->uuidFields) && $this->uuidUseBytes === true) { - foreach ($this->uuidFields as $field) { - if ( - $field === $this->primaryKey || - empty($this->uuidTempData[$field]) - ) { - continue; - } - - $eventData['data'][$field] = $this->uuidTempData[ - $field - ]->toString(); - } - } - - $eventData = [ - 'id' => $this->insertID, - 'data' => $eventData['data'], - 'result' => $result, - ]; - if ($this->tempAllowCallbacks) { - // Trigger afterInsert events with the inserted data and new ID - $this->trigger('afterInsert', $eventData); - } - $this->tempAllowCallbacks = $this->allowCallbacks; - - // If insertion failed, get out of here - if (!$result) { - return $result; - } - - // otherwise return the insertID, if requested. - return $returnID ? $this->insertID : $result; - } -} diff --git a/app/Libraries/Analytics/AnalyticsTrait.php b/app/Libraries/Analytics/AnalyticsTrait.php new file mode 100644 index 0000000000..2b0a69e70c --- /dev/null +++ b/app/Libraries/Analytics/AnalyticsTrait.php @@ -0,0 +1,51 @@ +<?php + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace Analytics; + +trait AnalyticsTrait +{ + /** + * + * @param integer $podcastId + * @return void + */ + protected function registerPodcastWebpageHit($podcastId) + { + helper('analytics'); + + set_user_session_deny_list_ip(); + set_user_session_browser(); + set_user_session_referer(); + set_user_session_entry_page(); + + $session = \Config\Services::session(); + $session->start(); + + if (!$session->get('denyListIp')) { + $db = \Config\Database::connect(); + + $referer = $session->get('referer'); + $domain = empty(parse_url($referer, PHP_URL_HOST)) + ? '- Direct -' + : parse_url($referer, PHP_URL_HOST); + parse_str(parse_url($referer, PHP_URL_QUERY), $queries); + $keywords = empty($queries['q']) ? null : $queries['q']; + + $procedureName = $db->prefixTable('analytics_website'); + $db->query("call $procedureName(?,?,?,?,?,?)", [ + $podcastId, + $session->get('browser'), + $session->get('entryPage'), + $referer, + $domain, + $keywords, + ]); + } + } +} diff --git a/app/Libraries/Analytics/Helpers/analytics_helper.php b/app/Libraries/Analytics/Helpers/analytics_helper.php index 227a7ef6bf..a9fc7c5791 100644 --- a/app/Libraries/Analytics/Helpers/analytics_helper.php +++ b/app/Libraries/Analytics/Helpers/analytics_helper.php @@ -258,40 +258,6 @@ if (!function_exists('set_user_session_entry_page')) { } } -if (!function_exists('webpage_hit')) { - /** - * - * @param integer $podcastId - * @return void - */ - function webpage_hit($podcastId) - { - $session = \Config\Services::session(); - $session->start(); - - if (!$session->get('denyListIp')) { - $db = \Config\Database::connect(); - - $referer = $session->get('referer'); - $domain = empty(parse_url($referer, PHP_URL_HOST)) - ? '- Direct -' - : parse_url($referer, PHP_URL_HOST); - parse_str(parse_url($referer, PHP_URL_QUERY), $queries); - $keywords = empty($queries['q']) ? null : $queries['q']; - - $procedureName = $db->prefixTable('analytics_website'); - $db->query("call $procedureName(?,?,?,?,?,?)", [ - $podcastId, - $session->get('browser'), - $session->get('entryPage'), - $referer, - $domain, - $keywords, - ]); - } - } -} - if (!function_exists('podcast_hit')) { /** * Counting podcast episode downloads for analytic purposes diff --git a/app/Models/ActorModel.php b/app/Models/ActorModel.php new file mode 100644 index 0000000000..694c7db6d0 --- /dev/null +++ b/app/Models/ActorModel.php @@ -0,0 +1,14 @@ +<?php + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +namespace App\Models; + +class ActorModel extends \ActivityPub\Models\ActorModel +{ + protected $returnType = \App\Entities\Actor::class; +} diff --git a/app/Models/CategoryModel.php b/app/Models/CategoryModel.php index 6fc150c470..1116116b23 100644 --- a/app/Models/CategoryModel.php +++ b/app/Models/CategoryModel.php @@ -27,9 +27,9 @@ class CategoryModel extends Model protected $useTimestamps = false; - public function findParent($parentId) + public function getCategoryById($id) { - return $this->find($parentId); + return $this->find($id); } public function getCategoryOptions() diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 9360348286..8d36325bb0 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -139,6 +139,7 @@ class EpisodeModel extends Model public function getEpisodeById($episodeId) { + // TODO: episode id should be a composite key. The cache should include podcast_id. $cacheName = "podcast_episode#{$episodeId}"; if (!($found = cache($cacheName))) { $builder = $this->where([ @@ -153,19 +154,16 @@ class EpisodeModel extends Model return $found; } - public function getPublishedEpisodeById($episodeId, $podcastId = null) + public function getPublishedEpisodeById($podcastId, $episodeId) { - $cacheName = "podcast_episode#{$episodeId}_published"; + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_published"; if (!($found = cache($cacheName))) { - $builder = $this->where([ + $found = $this->where([ 'id' => $episodeId, - ])->where('`published_at` <= NOW()', null, false); - - if ($podcastId) { - $builder->where('podcast_id', $podcastId); - } - - $found = $builder->first(); + ]) + ->where('podcast_id', $podcastId) + ->where('`published_at` <= NOW()', null, false) + ->first(); cache()->save($cacheName, $found, DECADE); } @@ -287,11 +285,17 @@ class EpisodeModel extends Model // delete model requests cache cache()->delete("podcast#{$episode->podcast_id}_episodes"); - cache()->deleteMatching("podcast_episode#{$episode->id}*"); + cache()->delete("podcast_episode#{$episode->id}"); + cache()->deleteMatching( + "podcast#{$episode->podcast_id}_episode#{$episode->id}*", + ); cache()->delete( "podcast#{$episode->podcast_id}_episode@{$episode->slug}", ); + cache()->deleteMatching( + "page_podcast#{$episode->podcast_id}_activity*", + ); cache()->deleteMatching( "page_podcast#{$episode->podcast_id}_episode#{$episode->id}_*", ); @@ -300,12 +304,12 @@ class EpisodeModel extends Model if ($episode->season_number) { cache()->deleteMatching("podcast#{$episode->podcast_id}_season*"); cache()->deleteMatching( - "page_podcast#{$episode->podcast_id}_season*", + "page_podcast#{$episode->podcast_id}_episodes_season*", ); } else { cache()->deleteMatching("podcast#{$episode->podcast_id}_year*"); cache()->deleteMatching( - "page_podcast#{$episode->podcast_id}_year*", + "page_podcast#{$episode->podcast_id}_episodes_year*", ); } diff --git a/app/Models/EpisodePersonModel.php b/app/Models/EpisodePersonModel.php index 8df81e10e5..5e96171b6e 100644 --- a/app/Models/EpisodePersonModel.php +++ b/app/Models/EpisodePersonModel.php @@ -38,9 +38,9 @@ class EpisodePersonModel extends Model protected $afterInsert = ['clearCache']; protected $beforeDelete = ['clearCache']; - public function getEpisodePersons($episodeId) + public function getEpisodePersons($podcastId, $episodeId) { - $cacheName = "podcast_episode#{$episodeId}_persons"; + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_persons"; if (!($found = cache($cacheName))) { $found = $this->select('episodes_persons.*') ->where('episode_id', $episodeId) @@ -124,7 +124,6 @@ class EpisodePersonModel extends Model $episodeId = $person->episode_id; } - cache()->delete("podcast_episode#{$episodeId}_persons"); (new EpisodeModel())->clearCache(['id' => $episodeId]); return $data; diff --git a/app/Models/NoteModel.php b/app/Models/NoteModel.php index 5a95a16ecd..981e1591ba 100644 --- a/app/Models/NoteModel.php +++ b/app/Models/NoteModel.php @@ -1,7 +1,7 @@ <?php /** - * @copyright 2020 Podlibre + * @copyright 2021 Podlibre * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 4724385fbe..a332f763c5 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -8,7 +8,6 @@ namespace App\Models; -use ActivityPub\Models\ActorModel; use CodeIgniter\HTTP\URI; use CodeIgniter\Model; use phpseclib\Crypt\RSA; @@ -74,7 +73,7 @@ class PodcastModel extends Model protected $validationMessages = []; protected $beforeInsert = ['createPodcastActor']; - protected $afterInsert = ['setAvatarImageUrl']; + protected $afterInsert = ['setActorAvatar']; protected $afterUpdate = ['updatePodcastActor']; // clear cache before update if by any chance, the podcast name changes, so will the podcast link @@ -104,6 +103,18 @@ class PodcastModel extends Model return $found; } + public function getPodcastByActorId($actorId) + { + $cacheName = "podcast_actor#{$actorId}"; + if (!($found = cache($cacheName))) { + $found = $this->where('actor_id', $actorId)->first(); + + cache()->save($cacheName, $found, DECADE); + } + + return $found; + } + /** * Gets all the podcasts a given user is contributing to * @@ -300,25 +311,6 @@ class PodcastModel extends Model return $defaultQuery; } - public function clearCache(array $data) - { - $podcast = (new PodcastModel())->getPodcastById( - is_array($data['id']) ? $data['id'][0] : $data['id'], - ); - - // delete cache all podcast pages - cache()->deleteMatching("page_podcast#{$podcast->id}_*"); - - // delete model requests cache, includes feed / query / episode lists, etc. - cache()->deleteMatching("podcast#{$podcast->id}*"); - cache()->delete("podcast@{$podcast->name}"); - - // clear cache for every credit page - cache()->deleteMatching('page_credits_*'); - - return $data; - } - /** * Creates an actor linked to the podcast * (Triggered before insert) @@ -359,16 +351,18 @@ class PodcastModel extends Model return $data; } - protected function setAvatarImageUrl($data) + protected function setActorAvatar($data) { $podcast = (new PodcastModel())->getPodcastById( is_array($data['id']) ? $data['id'][0] : $data['id'], ); - $podcast->actor->avatar_image_url = $podcast->image->thumbnail_url; - $podcast->actor->avatar_image_mimetype = $podcast->image_mimetype; + $podcastActor = (new ActorModel())->find($podcast->actor_id); - (new ActorModel())->update($podcast->actor->id, $podcast->actor); + $podcastActor->avatar_image_url = $podcast->image->thumbnail_url; + $podcastActor->avatar_image_mimetype = $podcast->image_mimetype; + + (new ActorModel())->update($podcast->actor_id, $podcastActor); return $data; } @@ -380,7 +374,7 @@ class PodcastModel extends Model ); $actorModel = new ActorModel(); - $actor = $actorModel->find($podcast->actor_id); + $actor = $actorModel->getActorById($podcast->actor_id); // update values $actor->display_name = $podcast->title; @@ -394,4 +388,28 @@ class PodcastModel extends Model return $data; } + + public function clearCache(array $data) + { + $podcast = (new PodcastModel())->getPodcastById( + is_array($data['id']) ? $data['id'][0] : $data['id'], + ); + + // delete cache all podcast pages + cache()->deleteMatching("page_podcast#{$podcast->id}*"); + + // delete all cache for podcast actor + cache()->deleteMatching( + config('ActivityPub')->cachePrefix . "actor#{$podcast->actor_id}*", + ); + + // delete model requests cache, includes feed / query / episode lists, etc. + cache()->deleteMatching("podcast#{$podcast->id}*"); + cache()->delete("podcast@{$podcast->name}"); + + // clear cache for every credit page + cache()->deleteMatching('page_credits_*'); + + return $data; + } } diff --git a/app/Models/SoundbiteModel.php b/app/Models/SoundbiteModel.php index 102ab7bfaf..9f7bcbc6c1 100644 --- a/app/Models/SoundbiteModel.php +++ b/app/Models/SoundbiteModel.php @@ -56,7 +56,7 @@ class SoundbiteModel extends Model */ public function getEpisodeSoundbites(int $podcastId, int $episodeId): array { - $cacheName = "podcast_episode#{$episodeId}_soundbites"; + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_soundbites"; if (!($found = cache($cacheName))) { $found = $this->where([ 'episode_id' => $episodeId, @@ -77,7 +77,9 @@ class SoundbiteModel extends Model : $data['id']['episode_id'], ); - cache()->delete("podcast_episode#{$episode->id}_soundbites"); + cache()->delete( + "podcast#{$episode->podcast_id}_episode#{$episode->id}_soundbites", + ); // delete cache for rss feed cache()->deleteMatching("podcast#{$episode->podcast_id}_feed*"); diff --git a/app/Views/podcast/_partials/note.php b/app/Views/podcast/_partials/note.php index d94f7eb390..c14fbef75a 100644 --- a/app/Views/podcast/_partials/note.php +++ b/app/Views/podcast/_partials/note.php @@ -31,7 +31,7 @@ <?= view('podcast/_partials/episode_card', [ 'episode' => $note->episode, ]) ?> - <?php elseif ($note->preview_card_id): ?> + <?php elseif ($note->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $note->preview_card, ]) ?> diff --git a/app/Views/podcast/_partials/note_authenticated.php b/app/Views/podcast/_partials/note_authenticated.php index 01930eb245..baeb8794a4 100644 --- a/app/Views/podcast/_partials/note_authenticated.php +++ b/app/Views/podcast/_partials/note_authenticated.php @@ -31,7 +31,7 @@ <?= view('podcast/_partials/episode_card', [ 'episode' => $note->episode, ]) ?> - <?php elseif ($note->preview_card_id): ?> + <?php elseif ($note->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $note->preview_card, ]) ?> diff --git a/app/Views/podcast/_partials/note_with_replies.php b/app/Views/podcast/_partials/note_with_replies.php index fa9a769dbd..c33ed4cb62 100644 --- a/app/Views/podcast/_partials/note_with_replies.php +++ b/app/Views/podcast/_partials/note_with_replies.php @@ -15,7 +15,9 @@ </div> -<?php foreach ($note->replies as $reply): ?> - <?= view('podcast/_partials/reply', ['reply' => $reply]) ?> -<?php endforeach; ?> +<?php if ($note->has_replies): ?> + <?php foreach ($note->replies as $reply): ?> + <?= view('podcast/_partials/reply', ['reply' => $reply]) ?> + <?php endforeach; ?> +<?php endif; ?> </div> diff --git a/app/Views/podcast/_partials/note_with_replies_authenticated.php b/app/Views/podcast/_partials/note_with_replies_authenticated.php index 429d99f3db..95e7fe7258 100644 --- a/app/Views/podcast/_partials/note_with_replies_authenticated.php +++ b/app/Views/podcast/_partials/note_with_replies_authenticated.php @@ -39,7 +39,11 @@ </div> <?= form_close() ?> -<?php foreach ($note->replies as $reply): ?> - <?= view('podcast/_partials/reply_authenticated', ['reply' => $reply]) ?> -<?php endforeach; ?> +<?php if ($note->has_replies): ?> + <?php foreach ($note->replies as $reply): ?> + <?= view('podcast/_partials/reply_authenticated', [ + 'reply' => $reply, + ]) ?> + <?php endforeach; ?> +<?php endif; ?> </div> diff --git a/app/Views/podcast/_partials/reblog.php b/app/Views/podcast/_partials/reblog.php index fa014f1e91..461c76ca64 100644 --- a/app/Views/podcast/_partials/reblog.php +++ b/app/Views/podcast/_partials/reblog.php @@ -38,7 +38,7 @@ <?= view('podcast/_partials/episode_card', [ 'episode' => $note->episode, ]) ?> - <?php elseif ($note->preview_card_id): ?> + <?php elseif ($note->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $note->preview_card, ]) ?> diff --git a/app/Views/podcast/_partials/reblog_authenticated.php b/app/Views/podcast/_partials/reblog_authenticated.php index 43ecf46012..e9251e256a 100644 --- a/app/Views/podcast/_partials/reblog_authenticated.php +++ b/app/Views/podcast/_partials/reblog_authenticated.php @@ -38,7 +38,7 @@ <?= view('podcast/_partials/episode_card', [ 'episode' => $note->episode, ]) ?> - <?php elseif ($note->preview_card_id): ?> + <?php elseif ($note->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $note->preview_card, ]) ?> diff --git a/app/Views/podcast/_partials/reply.php b/app/Views/podcast/_partials/reply.php index c687a1e9c5..2a9018edf1 100644 --- a/app/Views/podcast/_partials/reply.php +++ b/app/Views/podcast/_partials/reply.php @@ -19,7 +19,7 @@ ><?= lang('Common.mediumDate', [$reply->published_at]) ?></time> </header> <p class="mb-2 note-content"><?= $reply->message_html ?></p> - <?php if ($reply->preview_card_id): ?> + <?php if ($reply->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $reply->preview_card, ]) ?> diff --git a/app/Views/podcast/_partials/reply_authenticated.php b/app/Views/podcast/_partials/reply_authenticated.php index e7dfebcfaa..f618dc0be6 100644 --- a/app/Views/podcast/_partials/reply_authenticated.php +++ b/app/Views/podcast/_partials/reply_authenticated.php @@ -19,7 +19,7 @@ ><?= lang('Common.mediumDate', [$reply->created_at]) ?></time> </header> <p class="mb-2 note-content"><?= $reply->message_html ?></p> - <?php if ($reply->preview_card_id): ?> + <?php if ($reply->has_preview_card): ?> <?= view('podcast/_partials/preview_card', [ 'preview_card' => $reply->preview_card, ]) ?> diff --git a/composer.json b/composer.json index d219a958df..447f16c778 100644 --- a/composer.json +++ b/composer.json @@ -12,14 +12,14 @@ "geoip2/geoip2": "^v2.11.0", "myth/auth": "dev-develop", "codeigniter4/codeigniter4": "dev-develop", - "league/commonmark": "^1.5.7", + "league/commonmark": "^1.6.0", "vlucas/phpdotenv": "^v5.3.0", "league/html-to-markdown": "^4.10", "opawg/user-agents-php": "^v1.0", "podlibre/ipcat": "^v1.0", "podlibre/podcast-namespace": "^v1.0.6", "phpseclib/phpseclib": "~2.0.30", - "michalsn/codeigniter4-uuid": "^1.0@beta", + "michalsn/codeigniter4-uuid": "^v1.0.0", "essence/essence": "^3.5.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index ed5ded5457..89b57d985e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b5d726bdc7252c80c0fd5a6f53de1948", + "content-hash": "f370b196462e2ca2ff3e2df9627f2ba4", "packages": [ { "name": "brick/math", @@ -68,12 +68,12 @@ "source": { "type": "git", "url": "https://github.com/codeigniter4/CodeIgniter4.git", - "reference": "dfbc85af9ef408a6654cce6a462c8fdde3ee2446" + "reference": "8b2e7c29043977fac378c37690cc951a715c2bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/dfbc85af9ef408a6654cce6a462c8fdde3ee2446", - "reference": "dfbc85af9ef408a6654cce6a462c8fdde3ee2446", + "url": "https://api.github.com/repos/codeigniter4/CodeIgniter4/zipball/8b2e7c29043977fac378c37690cc951a715c2bd5", + "reference": "8b2e7c29043977fac378c37690cc951a715c2bd5", "shasum": "" }, "require": { @@ -91,10 +91,10 @@ "fakerphp/faker": "^1.9", "mikey179/vfsstream": "^1.6", "nexusphp/tachycardia": "^1.0", - "phpstan/phpstan": "0.12.84", + "phpstan/phpstan": "0.12.85", "phpunit/phpunit": "^9.1", "predis/predis": "^1.1", - "rector/rector": "0.10.6", + "rector/rector": "0.10.17", "squizlabs/php_codesniffer": "^3.3" }, "suggest": { @@ -139,7 +139,7 @@ "slack": "https://codeigniterchat.slack.com", "issues": "https://github.com/codeigniter4/CodeIgniter4/issues" }, - "time": "2021-04-20T08:40:30+00:00" + "time": "2021-05-03T08:32:21+00:00" }, { "name": "composer/ca-bundle", @@ -823,16 +823,16 @@ }, { "name": "league/commonmark", - "version": "1.5.8", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "08fa59b8e4e34ea8a773d55139ae9ac0e0aecbaf" + "reference": "19a9673b833cc37770439097b381d86cd125bfe8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/08fa59b8e4e34ea8a773d55139ae9ac0e0aecbaf", - "reference": "08fa59b8e4e34ea8a773d55139ae9ac0e0aecbaf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/19a9673b833cc37770439097b381d86cd125bfe8", + "reference": "19a9673b833cc37770439097b381d86cd125bfe8", "shasum": "" }, "require": { @@ -920,7 +920,7 @@ "type": "tidelift" } ], - "time": "2021-03-28T18:51:39+00:00" + "time": "2021-05-01T19:00:49+00:00" }, { "name": "league/html-to-markdown", @@ -1125,16 +1125,16 @@ }, { "name": "michalsn/codeigniter4-uuid", - "version": "v1.0.0-beta3", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/michalsn/codeigniter4-uuid.git", - "reference": "568aba8f315199b6cc87e76b8441cd03a2bba5b4" + "reference": "c8bbd961401015307bc72f6f6aa93509ffac1d5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/michalsn/codeigniter4-uuid/zipball/568aba8f315199b6cc87e76b8441cd03a2bba5b4", - "reference": "568aba8f315199b6cc87e76b8441cd03a2bba5b4", + "url": "https://api.github.com/repos/michalsn/codeigniter4-uuid/zipball/c8bbd961401015307bc72f6f6aa93509ffac1d5f", + "reference": "c8bbd961401015307bc72f6f6aa93509ffac1d5f", "shasum": "" }, "require": { @@ -1172,9 +1172,9 @@ ], "support": { "issues": "https://github.com/michalsn/codeigniter4-uuid/issues", - "source": "https://github.com/michalsn/codeigniter4-uuid/tree/v1.0.0-beta3" + "source": "https://github.com/michalsn/codeigniter4-uuid/tree/v1.0.1" }, - "time": "2021-04-02T11:08:18+00:00" + "time": "2021-05-03T12:47:44+00:00" }, { "name": "myth/auth", @@ -1182,12 +1182,12 @@ "source": { "type": "git", "url": "https://github.com/lonnieezell/myth-auth.git", - "reference": "eff9805d7f1d27326f14875b53ff4b3d2a6b72ee" + "reference": "2b42da1884745eec22ac10f7941a4f9350576a86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/eff9805d7f1d27326f14875b53ff4b3d2a6b72ee", - "reference": "eff9805d7f1d27326f14875b53ff4b3d2a6b72ee", + "url": "https://api.github.com/repos/lonnieezell/myth-auth/zipball/2b42da1884745eec22ac10f7941a4f9350576a86", + "reference": "2b42da1884745eec22ac10f7941a4f9350576a86", "shasum": "" }, "require": { @@ -1248,7 +1248,7 @@ "type": "patreon" } ], - "time": "2021-04-12T22:34:12+00:00" + "time": "2021-05-02T05:32:03+00:00" }, { "name": "opawg/user-agents-php", @@ -1584,16 +1584,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -1617,7 +1617,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -1628,9 +1628,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ramsey/collection", @@ -4314,8 +4314,7 @@ "stability-flags": { "james-heinrich/getid3": 20, "myth/auth": 20, - "codeigniter4/codeigniter4": 20, - "michalsn/codeigniter4-uuid": 10 + "codeigniter4/codeigniter4": 20 }, "prefer-stable": true, "prefer-lowest": false, -- GitLab