fix: remove fediverse prefix to prevent migration error + load routes during podcast import

refactor migration queries to use forge functions
This commit is contained in:
Yassine Doghri 2023-08-26 13:03:01 +00:00
parent 072b3ff61d
commit 7ff1dbe903
63 changed files with 387 additions and 355 deletions

View File

@ -27,7 +27,8 @@ class View extends BaseView
*
* Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
*
* @var string[]
* @var array<string, string>
* @phpstan-var array<string, callable-string>
*/
public $filters = [];
@ -35,7 +36,8 @@ class View extends BaseView
* Parser Plugins provide a way to extend the functionality provided by the core Parser by creating aliases that
* will be replaced with any callable. Can be single or tag pair.
*
* @var string[]
* @var array<string, string>
* @phpstan-var array<string, callable-string>
*/
public $plugins = [];

View File

@ -290,7 +290,7 @@ class EpisodeController extends BaseController
$episodeComments = model(PostModel::class)
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
return $builder->select('id')
->from(config('Fediverse')->tablesPrefix . 'posts')
->from('fediverse_posts')
->where('episode_id', $this->episode->id);
})
->where('`published_at` <= UTC_TIMESTAMP()', null, false)

View File

@ -195,7 +195,7 @@ class AddPodcasts extends BaseMigration
$this->forge->addUniqueKey('handle');
$this->forge->addUniqueKey('guid');
$this->forge->addUniqueKey('actor_id');
$this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('cover_id', 'media', 'id');
$this->forge->addForeignKey('banner_id', 'media', 'id', '', 'SET NULL');
$this->forge->addForeignKey('category_id', 'categories', 'id');

View File

@ -164,10 +164,10 @@ class AddEpisodes extends BaseMigration
// Add Full-Text Search index on title and description_markdown
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}episodes
ADD FULLTEXT(title, description_markdown);
CODE_SAMPLE;
ADD FULLTEXT title (title, description_markdown);
SQL;
$this->db->query($createQuery);
}

View File

@ -64,12 +64,9 @@ class AddEpisodeComments extends BaseMigration
],
]);
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->createTable('episode_comments');
}

View File

@ -27,12 +27,9 @@ class AddLikes extends BaseMigration
],
]);
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
$this->forge->addPrimaryKey(['actor_id', 'comment_id']);
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
$this->forge->createTable('likes');
}

View File

@ -20,7 +20,7 @@ class AddCreditsView extends BaseMigration
$podcastPersonsTable = $this->db->prefixTable('podcasts_persons');
$episodePersonsTable = $this->db->prefixTable('episodes_persons');
$episodesTable = $this->db->prefixTable('episodes');
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
CREATE VIEW `{$viewName}` AS
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `{$podcastPersonsTable}`
INNER JOIN `{$personsTable}`
@ -33,7 +33,7 @@ class AddCreditsView extends BaseMigration
ON (`episode_id`=`{$episodesTable}`.`id`)
WHERE `{$episodesTable}`.published_at <= UTC_TIMESTAMP()
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
}

View File

@ -17,10 +17,8 @@ class AddEpisodeIdToPosts extends BaseMigration
public function up(): void
{
$prefix = $this->db->getPrefix();
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addColumn("{$fediverseTablesPrefix}posts", [
$this->forge->addColumn('fediverse_posts', [
'episode_id' => [
'type' => 'INT',
'unsigned' => true,
@ -29,22 +27,22 @@ class AddEpisodeIdToPosts extends BaseMigration
],
]);
$alterQuery = <<<CODE_SAMPLE
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
CODE_SAMPLE;
$this->db->query($alterQuery);
$this->forge->addForeignKey(
'episode_id',
'episodes',
'id',
'',
'CASCADE',
$prefix . 'fediverse_posts_episode_id_foreign'
);
$this->forge->processIndexes('fediverse_posts');
}
public function down(): void
{
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$prefix = $this->db->getPrefix();
$this->forge->dropForeignKey(
$fediverseTablesPrefix . 'posts',
$fediverseTablesPrefix . 'posts_episode_id_foreign'
);
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'episode_id');
$this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_episode_id_foreign');
$this->forge->dropColumn('fediverse_posts', 'episode_id');
}
}

View File

@ -17,10 +17,8 @@ class AddCreatedByToPosts extends BaseMigration
public function up(): void
{
$prefix = $this->db->getPrefix();
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addColumn("{$fediverseTablesPrefix}posts", [
$this->forge->addColumn('fediverse_posts', [
'created_by' => [
'type' => 'INT',
'unsigned' => true,
@ -29,22 +27,22 @@ class AddCreatedByToPosts extends BaseMigration
],
]);
$alterQuery = <<<CODE_SAMPLE
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
CODE_SAMPLE;
$this->db->query($alterQuery);
$this->forge->addForeignKey(
'created_by',
'users',
'id',
'',
'CASCADE',
$prefix . 'fediverse_posts_created_by_foreign'
);
$this->forge->processIndexes('fediverse_posts');
}
public function down(): void
{
$fediverseTablesPrefix = config('Fediverse')
->tablesPrefix;
$prefix = $this->db->getPrefix();
$this->forge->dropForeignKey(
$fediverseTablesPrefix . 'posts',
$fediverseTablesPrefix . 'posts_created_by_foreign'
);
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'created_by');
$this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_created_by_foreign');
$this->forge->dropColumn('fediverse_posts', 'created_by');
}
}

View File

@ -10,23 +10,23 @@ class AddFullTextSearchIndexes extends BaseMigration
{
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}episodes DROP INDEX IF EXISTS title;
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}episodes
ADD FULLTEXT episodes_search (title, description_markdown, slug, location_name);
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}podcasts
ADD FULLTEXT podcasts_search (title, description_markdown, handle, location_name);
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
}
@ -35,17 +35,17 @@ class AddFullTextSearchIndexes extends BaseMigration
{
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}episodes
DROP INDEX IF EXISTS episodes_search;
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
$createQuery = <<<CODE_SAMPLE
$createQuery = <<<SQL
ALTER TABLE {$prefix}podcasts
DROP INDEX IF EXISTS podcasts_search;
CODE_SAMPLE;
SQL;
$this->db->query($createQuery);
}

View File

@ -57,7 +57,8 @@ class BaseClip extends Entity
protected ?float $end_time = null;
/**
* @var string[]
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];

View File

@ -144,7 +144,8 @@ class Episode extends Entity
protected ?string $publication_status = null;
/**
* @var string[]
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['published_at', 'created_at', 'updated_at'];

View File

@ -51,7 +51,8 @@ class EpisodeComment extends UuidEntity
protected bool $has_replies = false;
/**
* @var string[]
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['created_at'];

View File

@ -166,7 +166,8 @@ class Podcast extends Entity
protected ?string $publication_status = null;
/**
* @var string[]
* @var array<int, string>
* @phpstan-var list<string>
*/
protected $dates = ['published_at', 'created_at', 'updated_at'];

View File

@ -10,11 +10,17 @@ use CodeIgniter\HTTP\ResponseInterface;
class AllowCorsFilter implements FilterInterface
{
/**
* @param string[]|null $arguments
*/
public function before(RequestInterface $request, $arguments = null): void
{
// Do something here
}
/**
* @param string[]|null $arguments
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
{
if (! $response->hasHeader('Cache-Control')) {

View File

@ -206,7 +206,7 @@ if (! function_exists('publication_status_banner')) {
$bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode');
$bannerText = lang('Podcast.publication_status_banner.scheduled', [
'publication_date' => local_datetime($publicationDate),
], null, false);
]);
$linkRoute = route_to('podcast-publish_edit', $podcastId);
$linkLabel = lang('Podcast.publish_edit');
break;
@ -492,10 +492,10 @@ if (! function_exists('category_label')) {
{
$categoryLabel = '';
if ($category->parent_id !== null) {
$categoryLabel .= lang('Podcast.category_options.' . $category->parent->code, [], null, false) . ' ';
$categoryLabel .= lang('Podcast.category_options.' . $category->parent->code) . ' ';
}
return $categoryLabel . lang('Podcast.category_options.' . $category->code, [], null, false);
return $categoryLabel . lang('Podcast.category_options.' . $category->code);
}
}

View File

@ -14,18 +14,25 @@ declare(strict_types=1);
namespace App\Libraries;
use Closure;
use CodeIgniter\Router\RouteCollection as CodeIgniterRouteCollection;
class RouteCollection extends CodeIgniterRouteCollection
{
/**
* The current hostname from $_SERVER['HTTP_HOST']
*/
private ?string $httpHost = null;
/**
* Does the heavy lifting of creating an actual route. You must specify
* the request method(s) that this route will work for. They can be separated
* by a pipe character "|" if there is more than one.
*
* @param array|Closure|string $to
* @param array<int, mixed>|Closure|string $to
* @param array<string, mixed> $options
*/
protected function create(string $verb, string $from, $to, ?array $options = null)
protected function create(string $verb, string $from, $to, ?array $options = null): void
{
$overwrite = false;
$prefix = $this->group === null ? '' : $this->group . '/';
@ -81,8 +88,8 @@ class RouteCollection extends CodeIgniterRouteCollection
// Get a constant string to work with.
$to = preg_replace('/(\$\d+)/', '$X', $to);
for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) {
$to = preg_replace_callback('/\$X/', static fn ($m) => '$' . $i, $to, 1);
for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; ++$i) {
$to = preg_replace_callback('/\$X/', static fn ($m): string => '$' . $i, $to, 1);
}
}
@ -97,7 +104,7 @@ class RouteCollection extends CodeIgniterRouteCollection
// If no namespace found, add the default namespace
if (strpos($to, '\\') === false || strpos($to, '\\') > 0) {
$namespace = $options['namespace'] ?? $this->defaultNamespace;
$to = trim($namespace, '\\') . '\\' . $to;
$to = trim((string) $namespace, '\\') . '\\' . $to;
}
// Always ensure that we escape our namespace so we're not pointing to
// \CodeIgniter\Routes\Controller::method.
@ -134,4 +141,140 @@ class RouteCollection extends CodeIgniterRouteCollection
$this->routes['*'][$name]['redirect'] = $options['redirect'];
}
}
/**
* Compares the hostname passed in against the current hostname
* on this page request.
*
* @param string $hostname Hostname in route options
*/
private function checkHostname($hostname): bool
{
// CLI calls can't be on hostname.
if ($this->httpHost === null) {
return false;
}
return strtolower($this->httpHost) === strtolower($hostname);
}
/**
* @param array<int, mixed> $to
*
* @return string|array<int, mixed>
*/
private function processArrayCallableSyntax(string $from, array $to): string | array
{
// [classname, method]
// eg, [Home::class, 'index']
if (is_callable($to, true, $callableName)) {
// If the route has placeholders, add params automatically.
$params = $this->getMethodParams($from);
return '\\' . $callableName . $params;
}
// [[classname, method], params]
// eg, [[Home::class, 'index'], '$1/$2']
if (
isset($to[0], $to[1])
&& is_callable($to[0], true, $callableName)
&& is_string($to[1])
) {
return '\\' . $callableName . '/' . $to[1];
}
return $to;
}
/**
* Compares the subdomain(s) passed in against the current subdomain
* on this page request.
*
* @param string|string[] $subdomains
*/
private function checkSubdomains($subdomains): bool
{
// CLI calls can't be on subdomain.
if ($this->httpHost === null) {
return false;
}
if ($this->currentSubdomain === null) {
$this->currentSubdomain = $this->determineCurrentSubdomain();
}
if (! is_array($subdomains)) {
$subdomains = [$subdomains];
}
// Routes can be limited to any sub-domain. In that case, though,
// it does require a sub-domain to be present.
if (! empty($this->currentSubdomain) && in_array('*', $subdomains, true)) {
return true;
}
return in_array($this->currentSubdomain, $subdomains, true);
}
/**
* Returns the method param string like `/$1/$2` for placeholders
*/
private function getMethodParams(string $from): string
{
preg_match_all('/\(.+?\)/', $from, $matches);
$count = is_countable($matches[0]) ? count($matches[0]) : 0;
$params = '';
for ($i = 1; $i <= $count; ++$i) {
$params .= '/$' . $i;
}
return $params;
}
/**
* Examines the HTTP_HOST to get the best match for the subdomain. It
* won't be perfect, but should work for our needs.
*
* It's especially not perfect since it's possible to register a domain
* with a period (.) as part of the domain name.
*
* @return false|string the subdomain
*/
private function determineCurrentSubdomain()
{
// We have to ensure that a scheme exists
// on the URL else parse_url will mis-interpret
// 'host' as the 'path'.
$url = $this->httpHost;
if (strpos($url, 'http') !== 0) {
$url = 'http://' . $url;
}
$parsedUrl = parse_url($url);
$host = explode('.', $parsedUrl['host']);
if ($host[0] === 'www') {
unset($host[0]);
}
// Get rid of any domains, which will be the last
unset($host[count($host) - 1]);
// Account for .co.uk, .co.nz, etc. domains
if (end($host) === 'co') {
$host = array_slice($host, 0, -1);
}
// If we only have 1 part left, then we don't have a sub-domain.
if (count($host) === 1) {
// Set it to false so we don't make it back here again.
return false;
}
return array_shift($host);
}
}

View File

@ -82,7 +82,7 @@ class ComponentRenderer
$matches[name] = tag name
$matches[attributes] = array of attribute string (class="foo")
*/
return preg_replace_callback($pattern, function ($match): string {
return preg_replace_callback($pattern, function (array $match): string {
$view = $this->locateView($match['name']);
$attributes = $this->parseAttributes($match['attributes']);
@ -104,7 +104,7 @@ class ComponentRenderer
$matches[attributes] = string of tag attributes (class="foo")
$matches[slot] = the content inside the tags
*/
return preg_replace_callback($pattern, function ($match): string {
return preg_replace_callback($pattern, function (array $match): string {
$view = $this->locateView($match['name']);
$attributes = $this->parseAttributes($match['attributes']);
$attributes['slot'] = $match['slot'];

View File

@ -94,13 +94,13 @@ class Vite
private function getHtmlTag(string $assetUrl, string $type): string
{
return match ($type) {
'css' => <<<CODE_SAMPLE
'css' => <<<HTML
<link rel="stylesheet" href="{$assetUrl}"/>
CODE_SAMPLE
HTML
,
'js' => <<<CODE_SAMPLE
'js' => <<<HTML
<script type="module" src="{$assetUrl}"></script>
CODE_SAMPLE
HTML
,
default => '',
};

View File

@ -67,15 +67,10 @@ class CategoryModel extends Model
static function (array $result, Category $category): array {
$result[$category->id] = '';
if ($category->parent instanceof Category) {
$result[$category->id] = lang(
'Podcast.category_options.' . $category->parent->code,
[],
null,
false
) . ' ';
$result[$category->id] = lang('Podcast.category_options.' . $category->parent->code) . ' ';
}
$result[$category->id] .= lang('Podcast.category_options.' . $category->code, [], null, false);
$result[$category->id] .= lang('Podcast.category_options.' . $category->code);
return $result;
},
[],

View File

@ -216,7 +216,7 @@ class EpisodeCommentModel extends UuidModel
)
->whereIn('in_reply_to_id', static function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
return $builder->select('id')
->from(config('Fediverse')->tablesPrefix . 'posts')
->from('fediverse_posts')
->where([
'episode_id' => $episodeId,
'in_reply_to_id' => null,

View File

@ -382,13 +382,11 @@ class EpisodeModel extends UuidModel
->groupBy('episode_id')
->getCompiledSelect();
$postsTable = config('Fediverse')
->tablesPrefix . 'posts';
$episodePostsRepliesCount = (new PostModel())->builder()
->select($postsTable . '.episode_id as episode_id, COUNT(*) as `comments_count`')
->join($postsTable . ' as fp', $postsTable . '.id = fp.in_reply_to_id')
->where($postsTable . '.in_reply_to_id', null)
->groupBy($postsTable . '.episode_id')
->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`')
->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id')
->where('fediverse_posts.in_reply_to_id', null)
->groupBy('fediverse_posts.episode_id')
->getCompiledSelect();
/** @var BaseResult $query */
@ -409,11 +407,7 @@ class EpisodeModel extends UuidModel
{
$episodePostsCount = $this->builder()
->select('episodes.id, COUNT(*) as `posts_count`')
->join(
config('Fediverse')
->tablesPrefix . 'posts',
'episodes.id = ' . config('Fediverse')->tablesPrefix . 'posts.episode_id'
)
->join('fediverse_posts', 'episodes.id = fediverse_posts.episode_id')
->where('in_reply_to_id', null)
->groupBy('episodes.id')
->get()

View File

@ -145,7 +145,7 @@ class PersonModel extends Model
$this->select('`id`, `full_name`')
->orderBy('`full_name`', 'ASC')
->findAll(),
static function ($result, $person) {
static function (array $result, $person): array {
$result[$person->id] = $person->full_name;
return $result;
},

View File

@ -158,12 +158,12 @@ class PlatformModel extends Model
$podcastsPlatformsTable = $this->db->prefixTable('podcasts_platforms');
$platformsTable = $this->db->prefixTable('platforms');
$deleteJoinQuery = <<<CODE_SAMPLE
$deleteJoinQuery = <<<SQL
DELETE {$podcastsPlatformsTable}
FROM {$podcastsPlatformsTable}
INNER JOIN {$platformsTable} ON {$platformsTable}.slug = {$podcastsPlatformsTable}.platform_slug
WHERE `podcast_id` = ? AND `type` = ?
CODE_SAMPLE;
SQL;
$this->db->query($deleteJoinQuery, [$podcastId, $platformType]);

View File

@ -84,6 +84,7 @@ class PodcastModel extends Model
* @var array<string, string>
*/
protected $validationRules = [
'id' => 'permit_empty|is_natural_no_zero',
'title' => 'required',
'handle' => 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]',
'description_markdown' => 'required',
@ -174,23 +175,15 @@ class PodcastModel extends Model
$prefix = $this->db->getPrefix();
if ($orderBy === 'activity') {
$fediverseTablePrefix = $prefix . config('Fediverse')
->tablesPrefix;
$this->builder()
->select(
'podcasts.*, MAX(' . $fediverseTablePrefix . 'posts.published_at' . ') as max_published_at'
)
->join(
$fediverseTablePrefix . 'posts',
$fediverseTablePrefix . 'posts.actor_id = podcasts.actor_id',
'left'
)
->select('podcasts.*, MAX(`' . $prefix . 'fediverse_posts`.`published_at`) as max_published_at')
->join('fediverse_posts', 'fediverse_posts.actor_id = podcasts.actor_id', 'left')
->groupStart()
->where(
'`' . $fediverseTablePrefix . 'posts`.`published_at` <= UTC_TIMESTAMP()',
'`' . $prefix . 'fediverse_posts`.`published_at` <= UTC_TIMESTAMP()',
null,
false
)->orWhere($fediverseTablePrefix . 'posts.published_at', null)
)->orWhere('fediverse_posts.published_at', null)
->groupEnd()
->groupBy('podcasts.actor_id')
->orderBy('max_published_at', 'DESC');

View File

@ -58,8 +58,8 @@ class PostModel extends FediversePostModel
public function setEpisodeIdForRepliesOfEpisodePosts(): int | false
{
// make sure that posts in reply to episode activities have an episode id
$postsToUpdate = $this->db->table(config('Fediverse')->tablesPrefix . 'posts as p1')
->join(config('Fediverse')->tablesPrefix . 'posts as p2', 'p1.id = p2.in_reply_to_id')
$postsToUpdate = $this->db->table('fediverse_posts as p1')
->join('fediverse_posts as p2', 'p1.id = p2.in_reply_to_id')
->select('p2.id, p1.episode_id')
->where([
'p2.in_reply_to_id IS NOT' => null,

View File

@ -82,7 +82,7 @@ class RolesDoc extends BaseCommand
$pattern,
['role', 'description', 'permissions'],
$authGroups->instanceGroups,
static function ($table, $key, $value) use ($instanceMatrix): void {
static function ($table, $key, array $value) use ($instanceMatrix): void {
$table->addRow($value['title'], $value['description'], implode(', ', $instanceMatrix[$key]));
}
);
@ -109,7 +109,7 @@ class RolesDoc extends BaseCommand
$pattern,
['role', 'description', 'permissions'],
$authGroups->podcastGroups,
static function ($table, $key, $value) use ($podcastMatrix): void {
static function ($table, $key, array $value) use ($podcastMatrix): void {
$table->addRow($value['title'], $value['description'], implode(', ', $podcastMatrix[$key]));
}
);

View File

@ -83,7 +83,7 @@ class ContributorController extends BaseController
$users = (new UserModel())->findAll();
$contributorOptions = array_reduce(
$users,
static function ($result, $user) {
static function (array $result, $user): array {
$result[$user->id] = $user->username;
return $result;
},
@ -94,7 +94,7 @@ class ContributorController extends BaseController
$roleOptions = [];
array_walk(
$roles,
static function ($role, $key) use (&$roleOptions): array {
static function (string $role, $key) use (&$roleOptions): array {
$roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
return $roleOptions;
},
@ -137,7 +137,7 @@ class ContributorController extends BaseController
$roleOptions = [];
array_walk(
$roles,
static function ($role) use (&$roleOptions): array {
static function (string $role) use (&$roleOptions): array {
$roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
return $roleOptions;
},

View File

@ -66,7 +66,7 @@ class UserController extends BaseController
$roleOptions = [];
array_walk(
$roles,
static function ($role, $key) use (&$roleOptions): array {
static function (array $role, $key) use (&$roleOptions): array {
$roleOptions[$key] = $role['title'];
return $roleOptions;
},
@ -172,7 +172,7 @@ class UserController extends BaseController
$roleOptions = [];
array_walk(
$roles,
static function ($role, $key) use (&$roleOptions): array {
static function (array $role, $key) use (&$roleOptions): array {
$roleOptions[$key] = $role['title'];
return $roleOptions;
},

View File

@ -38,8 +38,6 @@ class Fediverse extends BaseConfig
public string $defaultCoverImageMimetype = 'image/jpeg';
public string $tablesPrefix = 'fediverse_';
/**
* --------------------------------------------------------------------
* Cache options

View File

@ -291,14 +291,11 @@ class ActorController extends Controller
public function followers(): ResponseInterface
{
$tablesPrefix = config('Fediverse')
->tablesPrefix;
// get followers for a specific actor
$followers = model('ActorModel', false)
->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
->where($tablesPrefix . 'follows.target_actor_id', $this->actor->id)
->orderBy($tablesPrefix . 'follows.created_at', 'DESC');
->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner')
->where('fediverse_follows.target_actor_id', $this->actor->id)
->orderBy('fediverse_follows.created_at', 'DESC');
$pageNumber = (int) $this->request->getGet('page');

View File

@ -113,11 +113,11 @@ class AddActors extends BaseMigration
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('uri');
$this->forge->addUniqueKey(['username', 'domain']);
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'actors');
$this->forge->createTable('fediverse_actors');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'actors');
$this->forge->dropTable('fediverse_actors');
}
}

View File

@ -75,21 +75,18 @@ class AddPosts extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('uri');
// FIXME: an actor must reblog a post only once
// $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']);
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('in_reply_to_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('reblog_of_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'posts');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('in_reply_to_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('reblog_of_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_posts');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts');
$this->forge->dropTable('fediverse_posts');
}
}

View File

@ -58,18 +58,15 @@ class AddActivities extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'activities');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_activities');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'activities');
$this->forge->dropTable('fediverse_activities');
}
}

View File

@ -29,18 +29,15 @@ class AddFavourites extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
$this->forge->addPrimaryKey(['actor_id', 'post_id']);
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'favourites');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_favourites');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'favourites');
$this->forge->dropTable('fediverse_favourites');
}
}

View File

@ -31,18 +31,15 @@ class AddFollowers extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
$this->forge->addPrimaryKey(['actor_id', 'target_actor_id']);
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'follows');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_follows');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'follows');
$this->forge->dropTable('fediverse_follows');
}
}

View File

@ -75,11 +75,11 @@ class AddPreviewCards extends BaseMigration
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey('url');
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'preview_cards');
$this->forge->createTable('fediverse_preview_cards');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'preview_cards');
$this->forge->dropTable('fediverse_preview_cards');
}
}

View File

@ -29,17 +29,14 @@ class AddPostsPreviewCards extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addPrimaryKey(['post_id', 'preview_card_id']);
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('preview_card_id', $tablesPrefix . 'preview_cards', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'posts_preview_cards');
$this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('preview_card_id', 'fediverse_preview_cards', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_posts_preview_cards');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts_preview_cards');
$this->forge->dropTable('fediverse_posts_preview_cards');
}
}

View File

@ -28,11 +28,11 @@ class AddBlockedDomains extends BaseMigration
],
]);
$this->forge->addPrimaryKey('name');
$this->forge->createTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
$this->forge->createTable('fediverse_blocked_domains');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
$this->forge->dropTable('fediverse_blocked_domains');
}
}

View File

@ -55,19 +55,16 @@ class AddNotifications extends BaseMigration
],
]);
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('activity_id', $tablesPrefix . 'activities', 'id', '', 'CASCADE');
$this->forge->createTable($tablesPrefix . 'notifications');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('activity_id', 'fediverse_activities', 'id', '', 'CASCADE');
$this->forge->createTable('fediverse_notifications');
}
public function down(): void
{
$this->forge->dropTable(config('Fediverse')->tablesPrefix . 'notifications');
$this->forge->dropTable('fediverse_notifications');
}
}

View File

@ -408,7 +408,7 @@ if (! function_exists('linkify')) {
),
'handle' => preg_replace_callback(
'~(?<!\w)@(?<username>\w++)(?:@(?<domain>(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]))?~',
static function ($match) use (&$links) {
static function (array $match) use (&$links) {
// check if host is set and look for actor in database
if (isset($match['host'])) {
if (
@ -489,7 +489,7 @@ if (! function_exists('linkify')) {
// Insert all links
return preg_replace_callback(
'~<(\d+)>~',
static function ($match) use (&$links) {
static function (array $match) use (&$links): string {
return $links[$match[1] - 1];
},
$text,

View File

@ -13,14 +13,15 @@ namespace Modules\Fediverse\Models;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\I18n\Time;
use DateTimeInterface;
use Michalsn\Uuid\UuidModel;
use Modules\Fediverse\Entities\Activity;
class ActivityModel extends BaseUuidModel
class ActivityModel extends UuidModel
{
/**
* @var string
*/
protected $table = 'activities';
protected $table = 'fediverse_activities';
/**
* @var string

View File

@ -11,14 +11,15 @@ declare(strict_types=1);
namespace Modules\Fediverse\Models;
use CodeIgniter\Events\Events;
use CodeIgniter\Model;
use Modules\Fediverse\Entities\Actor;
class ActorModel extends BaseModel
class ActorModel extends Model
{
/**
* @var string
*/
protected $table = 'actors';
protected $table = 'fediverse_actors';
/**
* @var string[]
@ -119,10 +120,8 @@ class ActorModel extends BaseModel
config('Fediverse')
->cachePrefix . "actor#{$actorId}_followers";
if (! ($found = cache($cacheName))) {
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$found = $this->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
->where($tablesPrefix . 'follows.target_actor_id', $actorId)
$found = $this->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner')
->where('fediverse_follows.target_actor_id', $actorId)
->findAll();
cache()
@ -225,28 +224,27 @@ class ActorModel extends BaseModel
->cachePrefix . 'blocked_actors';
if (! ($found = cache($cacheName))) {
$tablePrefix = config('Database')
->default['DBPrefix'] . config('Fediverse')
->tablesPrefix;
->default['DBPrefix'];
$result = $this->select('COUNT(DISTINCT `cp_fediverse_actors`.`id`) as `total_active_actors`', false)
->join(
$tablePrefix . 'posts',
$tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id',
$tablePrefix . 'fediverse_posts',
$tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_posts.actor_id',
'left outer'
)
->join(
$tablePrefix . 'favourites',
$tablePrefix . 'actors.id = ' . $tablePrefix . 'favourites.actor_id',
$tablePrefix . 'fediverse_favourites',
$tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_favourites.actor_id',
'left outer'
)
->where($tablePrefix . 'actors.domain', get_current_domain())
->groupStart()
->where(
"`{$tablePrefix}posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
"`{$tablePrefix}fediverse_posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
null,
false
)
->orWhere(
"`{$tablePrefix}favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
"`{$tablePrefix}fediverse_favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
null,
false
)
@ -265,12 +263,8 @@ class ActorModel extends BaseModel
public function resetFollowersCount(): int | false
{
$tablePrefix = config('Fediverse')
->tablesPrefix;
$actorsFollowersCount = $this->db->table($tablePrefix . 'follows')->select(
'target_actor_id as id, COUNT(*) as `followers_count`'
)
$actorsFollowersCount = $this->db->table('fediverse_follows')
->select('target_actor_id as id, COUNT(*) as `followers_count`')
->groupBy('id')
->get()
->getResultArray();
@ -284,10 +278,7 @@ class ActorModel extends BaseModel
public function resetPostsCount(): int | false
{
$tablePrefix = config('Fediverse')
->tablesPrefix;
$actorsFollowersCount = $this->db->table($tablePrefix . 'posts')->select(
$actorsFollowersCount = $this->db->table($tablePrefix . 'fediverse_posts')->select(
'actor_id as id, COUNT(*) as `posts_count`'
)
->where([

View File

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Modules\Fediverse\Models;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Model;
use CodeIgniter\Validation\ValidationInterface;
class BaseModel extends Model
{
/**
* Model constructor.
*
* @param ConnectionInterface|null $db DB Connection
* @param ValidationInterface|null $validation Validation
*/
public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
{
parent::__construct($db, $validation);
$this->table = config('Fediverse')
->tablesPrefix . $this->table;
}
}

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Modules\Fediverse\Models;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Validation\ValidationInterface;
use Michalsn\Uuid\UuidModel;
class BaseUuidModel extends UuidModel
{
public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
{
parent::__construct($db, $validation);
$this->table = config('Fediverse')
->tablesPrefix . $this->table;
}
}

View File

@ -12,14 +12,15 @@ namespace Modules\Fediverse\Models;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Events\Events;
use CodeIgniter\Model;
use Modules\Fediverse\Entities\BlockedDomain;
class BlockedDomainModel extends BaseModel
class BlockedDomainModel extends Model
{
/**
* @var string
*/
protected $table = 'blocked_domains';
protected $table = 'fediverse_blocked_domains';
/**
* @var string

View File

@ -11,18 +11,19 @@ declare(strict_types=1);
namespace Modules\Fediverse\Models;
use CodeIgniter\Events\Events;
use Michalsn\Uuid\UuidModel;
use Modules\Fediverse\Activities\LikeActivity;
use Modules\Fediverse\Activities\UndoActivity;
use Modules\Fediverse\Entities\Actor;
use Modules\Fediverse\Entities\Favourite;
use Modules\Fediverse\Entities\Post;
class FavouriteModel extends BaseUuidModel
class FavouriteModel extends UuidModel
{
/**
* @var string
*/
protected $table = 'favourites';
protected $table = 'fediverse_favourites';
/**
* @var string[]

View File

@ -12,18 +12,19 @@ namespace Modules\Fediverse\Models;
use CodeIgniter\Events\Events;
use CodeIgniter\I18n\Time;
use CodeIgniter\Model;
use Exception;
use Modules\Fediverse\Activities\FollowActivity;
use Modules\Fediverse\Activities\UndoActivity;
use Modules\Fediverse\Entities\Actor;
use Modules\Fediverse\Entities\Follow;
class FollowModel extends BaseModel
class FollowModel extends Model
{
/**
* @var string
*/
protected $table = 'follows';
protected $table = 'fediverse_follows';
/**
* @var string[]

View File

@ -10,14 +10,15 @@ declare(strict_types=1);
namespace Modules\Fediverse\Models;
use Michalsn\Uuid\UuidModel;
use Modules\Fediverse\Entities\Notification;
class NotificationModel extends BaseUuidModel
class NotificationModel extends UuidModel
{
/**
* @var string
*/
protected $table = 'notifications';
protected $table = 'fediverse_notifications';
/**
* @var string

View File

@ -15,6 +15,7 @@ use CodeIgniter\Events\Events;
use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time;
use Exception;
use Michalsn\Uuid\UuidModel;
use Modules\Fediverse\Activities\AnnounceActivity;
use Modules\Fediverse\Activities\CreateActivity;
use Modules\Fediverse\Activities\DeleteActivity;
@ -23,12 +24,12 @@ use Modules\Fediverse\Entities\Actor;
use Modules\Fediverse\Entities\Post;
use Modules\Fediverse\Objects\TombstoneObject;
class PostModel extends BaseUuidModel
class PostModel extends UuidModel
{
/**
* @var string
*/
protected $table = 'posts';
protected $table = 'fediverse_posts';
/**
* @var string
@ -172,16 +173,10 @@ class PostModel extends BaseUuidModel
($withBlocked ? '_withBlocked' : '');
if (! ($found = cache($cacheName))) {
$tablesPrefix = config('Fediverse')
->tablesPrefix;
if (! $withBlocked) {
$this->select($tablesPrefix . 'posts.*')
->join(
$tablesPrefix . 'actors',
$tablesPrefix . 'actors.id = ' . $tablesPrefix . 'posts.actor_id',
'inner'
)
->where($tablesPrefix . 'actors.is_blocked', 0);
$this->select('fediverse_posts.*')
->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id', 'inner')
->where('fediverse_actors.is_blocked', 0);
}
$this->where('in_reply_to_id', $this->uuid->fromString($postId) ->getBytes())
@ -222,7 +217,7 @@ class PostModel extends BaseUuidModel
public function addPreviewCard(string $postId, int $previewCardId): bool
{
return $this->db->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards')
return $this->db->table('fediverse_posts_preview_cards')
->insert([
'post_id' => $this->uuid->fromString($postId)
->getBytes(),
@ -370,7 +365,7 @@ class PostModel extends BaseUuidModel
if (
$post->preview_card &&
$this->db
->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards')
->table('fediverse_posts_preview_cards')
->where('preview_card_id', $post->preview_card->id)
->countAll() <= 1
) {
@ -599,11 +594,9 @@ class PostModel extends BaseUuidModel
$cacheName = config('Fediverse')
->cachePrefix . 'blocked_actors';
if (! ($found = cache($cacheName))) {
$tablePrefix = config('Fediverse')
->tablesPrefix;
$result = $this->select('COUNT(*) as total_local_posts')
->join($tablePrefix . 'actors', $tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id')
->where($tablePrefix . 'actors.domain', get_current_domain())
->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id')
->where('fediverse_actors.domain', get_current_domain())
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->get()
->getResultArray();
@ -619,12 +612,8 @@ class PostModel extends BaseUuidModel
public function resetFavouritesCount(): int | false
{
$tablePrefix = config('Fediverse')
->tablesPrefix;
$postsFavouritesCount = $this->db->table($tablePrefix . 'favourites')->select(
'post_id as id, COUNT(*) as `favourites_count`'
)
$postsFavouritesCount = $this->db->table('fediverse_favourites')
->select('post_id as id, COUNT(*) as `favourites_count`')
->groupBy('id')
->get()
->getResultArray();
@ -639,12 +628,9 @@ class PostModel extends BaseUuidModel
public function resetReblogsCount(): int | false
{
$tablePrefix = config('Fediverse')
->tablesPrefix;
$postsReblogsCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`')
->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.reblog_of_id')
->groupBy($tablePrefix . 'posts.id')
$postsReblogsCount = $this->select('fediverse_posts.id, COUNT(*) as `replies_count`')
->join('fediverse_posts as p2', 'fediverse_posts.id = p2.reblog_of_id')
->groupBy('fediverse_posts.id')
->get()
->getResultArray();
@ -658,12 +644,9 @@ class PostModel extends BaseUuidModel
public function resetRepliesCount(): int | false
{
$tablePrefix = config('Fediverse')
->tablesPrefix;
$postsRepliesCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`')
->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.in_reply_to_id')
->groupBy($tablePrefix . 'posts.id')
$postsRepliesCount = $this->select('fediverse_posts.id, COUNT(*) as `replies_count`')
->join('fediverse_posts as p2', 'fediverse_posts.id = p2.in_reply_to_id')
->groupBy('fediverse_posts.id')
->get()
->getResultArray();

View File

@ -11,14 +11,15 @@ declare(strict_types=1);
namespace Modules\Fediverse\Models;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Model;
use Modules\Fediverse\Entities\PreviewCard;
class PreviewCardModel extends BaseModel
class PreviewCardModel extends Model
{
/**
* @var string
*/
protected $table = 'preview_cards';
protected $table = 'fediverse_preview_cards';
/**
* @var string[]
@ -75,11 +76,9 @@ class PreviewCardModel extends BaseModel
config('Fediverse')
->cachePrefix . "post#{$postId}_preview_card";
if (! ($found = cache($cacheName))) {
$tablesPrefix = config('Fediverse')
->tablesPrefix;
$found = $this->join(
$tablesPrefix . 'posts_preview_cards',
$tablesPrefix . 'posts_preview_cards.preview_card_id = id',
'fediverse_posts_preview_cards',
'fediverse_posts_preview_cards.preview_card_id = id',
'inner',
)
->where('post_id', service('uuid') ->fromString($postId) ->getBytes())

View File

@ -243,8 +243,6 @@ class InstallController extends Controller
{
$migrate = Services::migrations();
$migrate->setNamespace('CodeIgniter\Settings')
->latest();
$migrate->setNamespace(null)
->latest();
}

View File

@ -18,7 +18,7 @@ if (! function_exists('media_url')) {
$relativePath = implode('/', $relativePath);
}
$uri = new URI(rtrim((string) config(Media::class)->baseURL, '/') . '/' . ltrim($relativePath));
$uri = new URI(rtrim(config(Media::class)->baseURL, '/') . '/' . ltrim($relativePath));
return URI::createURIString(
$scheme ?? $uri->getScheme(),

View File

@ -18,6 +18,7 @@ use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\I18n\Time;
use CodeIgniter\Shield\Entities\User;
use Config\Services;
use Exception;
use League\HTMLToMarkdown\HtmlConverter;
use Modules\Auth\Models\UserModel;
@ -95,6 +96,9 @@ class PodcastImport extends BaseCommand
public function run(array $params): void
{
// FIXME: getting named routes doesn't work from v4.3 anymore, so loading all routes before importing
Services::routes()->loadRoutes();
$this->init();
try {
@ -503,7 +507,7 @@ class PodcastImport extends BaseCommand
->get()
->getResultArray();
return array_map(static function ($element) {
return array_map(static function (array $element) {
return $element['guid'];
}, $result);
}

View File

@ -28,8 +28,6 @@ class DatabaseUpdate extends BaseCommand
{
$migrate = Services::migrations();
$migrate->setNamespace('CodeIgniter\Settings')
->latest();
$migrate->setNamespace(null)
->latest();
}

View File

@ -16,14 +16,14 @@ class AddIsPublishedOnHubsToPodcasts extends BaseMigration
{
public function up(): void
{
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
ALTER TABLE {$prefix}podcasts
ADD COLUMN `is_published_on_hubs` BOOLEAN NOT NULL DEFAULT 0 AFTER `custom_rss`;
CODE_SAMPLE;
$this->db->query($createQuery);
$this->forge->addColumn('podcasts', [
'is_published_on_hubs' => [
'type' => 'BOOLEAN',
'null' => false,
'default' => 0,
'after' => 'custom_rss',
],
]);
}
public function down(): void

View File

@ -16,14 +16,14 @@ class AddIsPublishedOnHubsToEpisodes extends BaseMigration
{
public function up(): void
{
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
ALTER TABLE {$prefix}episodes
ADD COLUMN `is_published_on_hubs` BOOLEAN NOT NULL DEFAULT 0 AFTER `custom_rss`;
CODE_SAMPLE;
$this->db->query($createQuery);
$this->forge->addColumn('episodes', [
'is_published_on_hubs' => [
'type' => 'BOOLEAN',
'null' => false,
'default' => 0,
'after' => 'custom_rss',
],
]);
}
public function down(): void

View File

@ -2,7 +2,6 @@
declare(strict_types=1);
use Rector\CodeQuality\Rector\PropertyFetch\ExplicitMethodCallOverMagicGetSetRector;
use Rector\CodingStyle\Rector\ClassMethod\UnSpreadOperatorRector;
use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
@ -13,7 +12,6 @@ use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector;
use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector;
use Rector\EarlyReturn\Rector\If_\ChangeAndIfToEarlyReturnRector;
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
use Rector\EarlyReturn\Rector\If_\ChangeOrIfReturnToEarlyReturnRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Set\ValueObject\SetList;
@ -49,11 +47,9 @@ return static function (RectorConfig $rectorConfig): void {
__DIR__ . '/modules/Admin/Language/*/PersonsTaxonomy.php',
// skip rules from used sets
ChangeOrIfReturnToEarlyReturnRector::class,
ChangeOrIfContinueToMultiContinueRector::class,
EncapsedStringsToSprintfRector::class,
UnSpreadOperatorRector::class,
ExplicitMethodCallOverMagicGetSetRector::class,
RemoveExtraParametersRector::class,
UnwrapFutureCompatibleIfPhpVersionRector::class,

View File

@ -31,9 +31,9 @@ $userPodcasts = get_podcasts_user_can_interact_with(auth()->user()); ?>
$items = [
[
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="px-4 my-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$notificationsTitle}</span>
CODE_SAMPLE),
HTML),
],
];
@ -45,7 +45,7 @@ if ($userPodcasts !== []) {
$items[] = [
'type' => 'link',
'title' => <<<CODE_SAMPLE
'title' => <<<HTML
<div class="inline-flex items-center flex-1 text-sm align-middle">
<div class="relative">
<img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" />
@ -53,7 +53,7 @@ if ($userPodcasts !== []) {
</div>
<span class="max-w-xs truncate">{$userPodcastTitle}</span>
</div>
CODE_SAMPLE
HTML
,
'uri' => route_to('notification-list', $userPodcast->id),
];
@ -62,9 +62,9 @@ if ($userPodcasts !== []) {
$noNotificationsText = lang('Notifications.no_notifications');
$items[] = [
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="mx-4 my-2 text-sm italic text-center text-skin-muted">{$noNotificationsText}</span>
CODE_SAMPLE),
HTML),
];
}
?>
@ -90,11 +90,11 @@ foreach ($userPodcasts as $userPodcast) {
$checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : '';
$userPodcastTitle = esc($userPodcast->title);
$interactButtons .= <<<CODE_SAMPLE
$interactButtons .= <<<HTML
<button class="inline-flex items-center w-full px-4 py-1 hover:bg-highlight" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
<div class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" /><span class="max-w-xs truncate">{$userPodcastTitle}</span>{$checkMark}</div>
</button>
CODE_SAMPLE;
HTML;
}
$interactAsText = lang('Common.choose_interact');
@ -126,7 +126,7 @@ if ($userPodcasts !== []) {
$menuItems = array_merge([
[
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<nav class="flex flex-col py-2 whitespace-nowrap">
<span class="px-4 mb-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$interactAsText}</span>
<form action="{$interactAsRoute}" method="POST" class="flex flex-col">
@ -134,7 +134,7 @@ if ($userPodcasts !== []) {
{$interactButtons}
</form>
</nav>
CODE_SAMPLE),
HTML),
],
[
'type' => 'separator',

View File

@ -66,9 +66,9 @@ if ($episode->published_at === null) {
$title = lang('Episode.messages.unpublishBeforeDeleteTip');
$items[] = [
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}{$label}</span>
CODE_SAMPLE),
HTML),
];
} ?>
<DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labelledby="more-dropdown-<?= $episode->id ?>" offsetY="-32" items="<?= esc(json_encode($items)) ?>" />

View File

@ -149,9 +149,9 @@ data_table(
$title = lang('Episode.messages.unpublishBeforeDeleteTip');
$items[] = [
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}<span class="ml-2">{$label}</span></span>
CODE_SAMPLE),
HTML),
];
}
return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 rounded-full focus:ring-accent" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' .

View File

@ -31,15 +31,15 @@
? ''
: '@' . esc($notification->actor->domain));
$actorUsernameHtml = <<<CODE_SAMPLE
$actorUsernameHtml = <<<HTML
<strong class="break-all">{$actorUsername}</strong>
CODE_SAMPLE;
HTML;
$targetActorUsername = '@' . esc($notification->target_actor->username);
$targetActorUsernameHtml = <<<CODE_SAMPLE
$targetActorUsernameHtml = <<<HTML
<strong class="break-all">{$targetActorUsername}</strong>
CODE_SAMPLE;
HTML;
$notificationTitle = match ($notification->type) {
'reply' => lang('Notifications.reply', [

View File

@ -26,9 +26,9 @@ $userPodcasts = get_podcasts_user_can_interact_with(auth()->user()); ?>
$items = [
[
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="px-4 my-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$notificationsTitle}</span>
CODE_SAMPLE),
HTML),
],
];
@ -40,7 +40,7 @@ if ($userPodcasts !== []) {
$items[] = [
'type' => 'link',
'title' => <<<CODE_SAMPLE
'title' => <<<HTML
<div class="inline-flex items-center flex-1 text-sm align-middle">
<div class="relative">
<img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" />
@ -48,7 +48,7 @@ if ($userPodcasts !== []) {
</div>
<span class="max-w-xs truncate">{$userPodcastTitle}</span>
</div>
CODE_SAMPLE
HTML
,
'uri' => route_to('notification-list', $userPodcast->id),
];
@ -57,9 +57,9 @@ if ($userPodcasts !== []) {
$noNotificationsText = lang('Notifications.no_notifications');
$items[] = [
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<span class="mx-4 my-2 text-sm italic text-center text-skin-muted">{$noNotificationsText}</span>
CODE_SAMPLE),
HTML),
];
}
?>
@ -90,11 +90,11 @@ foreach ($userPodcasts as $userPodcast) {
$checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : '';
$userPodcastTitle = esc($userPodcast->title);
$interactButtons .= <<<CODE_SAMPLE
$interactButtons .= <<<HTML
<button class="inline-flex items-center w-full px-4 py-1 hover:bg-highlight" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
<div class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" /><span class="max-w-xs truncate">{$userPodcastTitle}</span>{$checkMark}</div>
</button>
CODE_SAMPLE;
HTML;
}
}
@ -127,7 +127,7 @@ if ($userPodcasts !== []) {
$menuItems = array_merge([
[
'type' => 'html',
'content' => esc(<<<CODE_SAMPLE
'content' => esc(<<<HTML
<nav class="flex flex-col py-2 whitespace-nowrap">
<span class="px-4 mb-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$interactAsText}</span>
<form action="{$interactAsRoute}" method="POST" class="flex flex-col">
@ -135,7 +135,7 @@ if ($userPodcasts !== []) {
{$interactButtons}
</form>
</nav>
CODE_SAMPLE),
HTML),
],
[
'type' => 'separator',