[COMPONENTS][Tag] Refactor Tag and add self tag stream
This commit is contained in:
parent
6680772e47
commit
5c3d561a67
@ -12,7 +12,7 @@ use Functional as F;
|
||||
|
||||
class Tag extends Controller
|
||||
{
|
||||
private function process(string|array $tag_or_tags, callable $key, string $query)
|
||||
private function process(string|array $tag_or_tags, callable $key, string $query, string $template)
|
||||
{
|
||||
$actor = Common::actor();
|
||||
$page = $this->int('page') ?: 1;
|
||||
@ -26,7 +26,7 @@ class Tag extends Controller
|
||||
} else {
|
||||
$canonical = F\map($tag_or_tags, fn ($t) => CompTag::canonicalTag($t, $lang));
|
||||
}
|
||||
$notes = Cache::pagedStream(
|
||||
$results = Cache::pagedStream(
|
||||
key: $key($canonical),
|
||||
query: $query,
|
||||
query_args: ['canon' => $canonical],
|
||||
@ -35,28 +35,51 @@ class Tag extends Controller
|
||||
);
|
||||
|
||||
return [
|
||||
'_template' => 'tag_stream.html.twig',
|
||||
'notes' => $notes,
|
||||
'_template' => $template,
|
||||
'results' => $results,
|
||||
'page' => $page,
|
||||
];
|
||||
}
|
||||
|
||||
public function single_tag(string $tag)
|
||||
public function single_note_tag(string $tag)
|
||||
{
|
||||
return $this->process(
|
||||
tag_or_tags: $tag,
|
||||
key: fn ($canonical) => "tag-{$canonical}",
|
||||
key: fn ($canonical) => "note-tag-feed-{$canonical}",
|
||||
query: 'select n from note n join note_tag nt with n.id = nt.note_id where nt.canonical = :canon order by nt.created DESC, nt.note_id DESC',
|
||||
template: 'note_tag_feed.html.twig',
|
||||
);
|
||||
}
|
||||
|
||||
public function multi_tags(string $tags)
|
||||
public function multi_note_tags(string $tags)
|
||||
{
|
||||
$tags = explode(',', $tags);
|
||||
return $this->process(
|
||||
tag_or_tags: $tags,
|
||||
key: fn ($canonical) => 'tags-' . implode('-', $canonical),
|
||||
key: fn ($canonical) => 'note-tags-feed-' . implode('-', $canonical),
|
||||
query: 'select n from note n join note_tag nt with n.id = nt.note_id where nt.canonical in (:canon) order by nt.created DESC, nt.note_id DESC',
|
||||
template: 'note_tag_feed.html.twig',
|
||||
);
|
||||
}
|
||||
|
||||
public function single_actor_tag(string $tag)
|
||||
{
|
||||
return $this->process(
|
||||
tag_or_tags: $tag,
|
||||
key: fn ($canonical) => "actor-tag-feed-{$canonical}",
|
||||
query: 'select a from actor a join actor_tag at with a.id = at.tagged where at.canonical = :canon order by at.modified DESC',
|
||||
template: 'actor_tag_feed.html.twig',
|
||||
);
|
||||
}
|
||||
|
||||
public function multi_actor_tag(string $tag)
|
||||
{
|
||||
$tags = explode(',', $tags);
|
||||
return $this->process(
|
||||
tag_or_tags: $tag,
|
||||
key: fn ($canonical) => 'actor-tags-feed-' . implode('-', $canonical),
|
||||
query: 'select a from actor a join actor_tag at with a.id = at.tagged where at.canonical = :canon order by at.modified DESC',
|
||||
template: 'actor_tag_feed.html.twig',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +52,10 @@ class Tag extends Component
|
||||
|
||||
public function onAddRoute($r): bool
|
||||
{
|
||||
$r->connect('single_tag', '/tag/{tag<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_tag']);
|
||||
$r->connect('multiple_tags', '/tags/{tags<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_tags']);
|
||||
$r->connect('single_note_tag', '/note-tag/{tag<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_note_tag']);
|
||||
$r->connect('multiple_note_tags', '/note-tags/{tags<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_note_tags']);
|
||||
$r->connect('single_actor_tag', '/actor-tag/{tag<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_actor_tag']);
|
||||
$r->connect('multiple_actor_tags', '/actor-tags/{tags<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_actor_tags']);
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
@ -80,17 +82,15 @@ class Tag extends Component
|
||||
|
||||
public function onRenderPlainTextNoteContent(string &$text, ?string $language = null): bool
|
||||
{
|
||||
if (!is_null($language)) {
|
||||
$text = preg_replace_callback(self::TAG_REGEX, fn ($m) => $m[1] . self::tagLink($m[2], $language), $text);
|
||||
}
|
||||
$text = preg_replace_callback(self::TAG_REGEX, fn ($m) => $m[1] . self::tagLink($m[2], $language), $text);
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
private static function tagLink(string $tag, string $language): string
|
||||
private static function tagLink(string $tag, ?string $language): string
|
||||
{
|
||||
$tag = self::ensureLength($tag);
|
||||
$canonical = self::canonicalTag($tag, $language);
|
||||
$url = Router::url('tag', ['tag' => $canonical, 'lang' => $language]);
|
||||
$url = Router::url('single_note_tag', !\is_null($language) ? ['tag' => $canonical, 'lang' => $language] : ['tag' => $canonical]);
|
||||
return HTML::html(['a' => ['attrs' => ['href' => $url, 'title' => $tag, 'rel' => 'tag'], $tag]], options: ['indent' => false]);
|
||||
}
|
||||
|
||||
@ -99,12 +99,12 @@ class Tag extends Component
|
||||
return mb_substr($tag, 0, self::MAX_TAG_LENGTH);
|
||||
}
|
||||
|
||||
public static function canonicalTag(string $tag, string $language): string
|
||||
public static function canonicalTag(string $tag, ?string $language): string
|
||||
{
|
||||
$result = '';
|
||||
foreach (Formatting::splitWords(str_replace('#', '', $tag)) as $word) {
|
||||
$temp_res = null;
|
||||
if (Event::handle('StemWord', [$language, $word, &$temp_res]) !== Event::stop) {
|
||||
if (\is_null($language) || Event::handle('StemWord', [$language, $word, &$temp_res]) !== Event::stop) {
|
||||
$temp_res = $word;
|
||||
}
|
||||
$result .= Formatting::slugify($temp_res);
|
||||
|
9
components/Tag/templates/actor_tag_feed.html.twig
Normal file
9
components/Tag/templates/actor_tag_feed.html.twig
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
{% for actor in results %}
|
||||
{% block profile_view %}{% include 'cards/profile/view.html.twig' %}{% endblock profile_view %}
|
||||
{% endfor %}
|
||||
|
||||
{{ "Page: " ~ page }}
|
||||
{% endblock %}
|
@ -2,7 +2,7 @@
|
||||
{% import '/cards/note/view.html.twig' as noteView %}
|
||||
|
||||
{% block body %}
|
||||
{% for note in notes %}
|
||||
{% for note in results %}
|
||||
{% block current_note %}
|
||||
{{ noteView.macro_note(note) }}
|
||||
{% endblock current_note %}
|
@ -372,7 +372,7 @@ abstract class Cache
|
||||
$per_page = Common::config('streams', 'notes_per_page');
|
||||
}
|
||||
|
||||
$filter_scope = fn (Note $n) => $n->isVisibleTo($actor);
|
||||
$filter_scope = fn (Note|Actor $o) => $o->isVisibleTo($actor);
|
||||
|
||||
$getter = fn (int $offset, int $length) => DB::dql($query, $query_args, options: ['offset' => $offset, 'limit' => $length]);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
declare(strict_types = 1);
|
||||
|
||||
// {{{ License
|
||||
|
||||
@ -29,15 +29,13 @@ use App\Core\Entity;
|
||||
use App\Core\Event;
|
||||
use App\Core\Router\Router;
|
||||
use App\Core\UserRoles;
|
||||
use App\Util\Common;
|
||||
use App\Util\Exception\NicknameException;
|
||||
use App\Util\Exception\NotFoundException;
|
||||
use App\Util\Nickname;
|
||||
use Component\Avatar\Avatar;
|
||||
use Component\Tag\Tag as TagComponent;
|
||||
use DateTimeInterface;
|
||||
use Functional as F;
|
||||
use function in_array;
|
||||
use function is_null;
|
||||
|
||||
/**
|
||||
* Entity for actors
|
||||
@ -60,7 +58,7 @@ class Actor extends Entity
|
||||
private int $id;
|
||||
private string $nickname;
|
||||
private ?string $fullname = null;
|
||||
private int $roles = 4;
|
||||
private int $roles = 4;
|
||||
private ?string $homepage;
|
||||
private ?string $bio;
|
||||
private ?string $location;
|
||||
@ -102,7 +100,7 @@ class Actor extends Entity
|
||||
|
||||
public function getFullname(): ?string
|
||||
{
|
||||
if (is_null($this->fullname)) {
|
||||
if (\is_null($this->fullname)) {
|
||||
return null;
|
||||
}
|
||||
return $this->fullname;
|
||||
@ -254,17 +252,17 @@ class Actor extends Entity
|
||||
|
||||
public static function getById(int $id): ?self
|
||||
{
|
||||
return Cache::get('actor-id-' . $id, fn() => DB::find('actor', ['id' => $id]));
|
||||
return Cache::get('actor-id-' . $id, fn () => DB::find('actor', ['id' => $id]));
|
||||
}
|
||||
|
||||
public static function getNicknameById(int $id): string
|
||||
{
|
||||
return Cache::get('actor-nickname-id-' . $id, fn() => self::getById($id)->getNickname());
|
||||
return Cache::get('actor-nickname-id-' . $id, fn () => self::getById($id)->getNickname());
|
||||
}
|
||||
|
||||
public static function getFullnameById(int $id): ?string
|
||||
{
|
||||
return Cache::get('actor-fullname-id-' . $id, fn() => self::getById($id)->getFullname());
|
||||
return Cache::get('actor-fullname-id-' . $id, fn () => self::getById($id)->getFullname());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,91 +278,92 @@ class Actor extends Entity
|
||||
/**
|
||||
* Get tags that other people put on this actor, in reverse-chron order
|
||||
*
|
||||
* @param Actor|int|null $scoped Actor we are requesting as:
|
||||
* @param null|Actor|int $scoped Actor we are requesting as:
|
||||
* - If null = All tags attributed to self by other actors (excludes self tags)
|
||||
* - If self = Same as getSelfTags
|
||||
* - otherwise = Tags that $scoped attributed to $this
|
||||
* @param int|null $offset Offset from latest
|
||||
* @param int|null $limit Max number to get
|
||||
* @param bool $_test_force_recompute
|
||||
* @param null|int $offset Offset from latest
|
||||
* @param null|int $limit Max number to get
|
||||
*
|
||||
* @return [ActorCircle] resulting lists
|
||||
*/
|
||||
public function getOtherTags(Actor|int|null $scoped = null, ?int $offset = null, ?int $limit = null, bool $_test_force_recompute = false): array
|
||||
public function getOtherTags(self|int|null $scoped = null, ?int $offset = null, ?int $limit = null, bool $_test_force_recompute = false): array
|
||||
{
|
||||
if (is_null($scoped)) {
|
||||
if (\is_null($scoped)) {
|
||||
return Cache::get(
|
||||
"othertags-{$this->getId()}",
|
||||
fn() => DB::dql(
|
||||
<<< EOQ
|
||||
SELECT circle
|
||||
FROM App\Entity\ActorTag tag
|
||||
JOIN App\Entity\ActorCircle circle
|
||||
WITH
|
||||
tag.tagger = circle.tagger
|
||||
AND tag.tag = circle.tag
|
||||
WHERE tag.tagged = :id
|
||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
||||
EOQ,
|
||||
fn () => DB::dql(
|
||||
<<< 'EOQ'
|
||||
SELECT circle
|
||||
FROM App\Entity\ActorTag tag
|
||||
JOIN App\Entity\ActorCircle circle
|
||||
WITH
|
||||
tag.tagger = circle.tagger
|
||||
AND tag.tag = circle.tag
|
||||
WHERE tag.tagged = :id
|
||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
||||
EOQ,
|
||||
['id' => $this->getId()],
|
||||
['offset' => $offset,
|
||||
'limit' => $limit])
|
||||
['offset' => $offset,
|
||||
'limit' => $limit, ],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
$scoped_id = is_int($scoped) ? $scoped : $scoped->getId();
|
||||
$scoped_id = \is_int($scoped) ? $scoped : $scoped->getId();
|
||||
return Cache::get(
|
||||
"othertags-{$this->getId()}-by-{$scoped_id}",
|
||||
fn() => DB::dql(
|
||||
<<< EOQ
|
||||
SELECT circle
|
||||
FROM App\Entity\ActorTag tag
|
||||
JOIN App\Entity\ActorCircle circle
|
||||
WITH
|
||||
tag.tagger = circle.tagger
|
||||
AND tag.tag = circle.tag
|
||||
WHERE
|
||||
tag.tagged = :id
|
||||
AND ( circle.private != true
|
||||
OR ( circle.tagger = :scoped
|
||||
AND circle.private = true
|
||||
)
|
||||
)
|
||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
||||
EOQ,
|
||||
['id' => $this->getId(),
|
||||
'scoped' => $scoped_id],
|
||||
['offset' => $offset,
|
||||
'limit' => $limit]
|
||||
)
|
||||
fn () => DB::dql(
|
||||
<<< 'EOQ'
|
||||
SELECT circle
|
||||
FROM App\Entity\ActorTag tag
|
||||
JOIN App\Entity\ActorCircle circle
|
||||
WITH
|
||||
tag.tagger = circle.tagger
|
||||
AND tag.tag = circle.tag
|
||||
WHERE
|
||||
tag.tagged = :id
|
||||
AND ( circle.private != true
|
||||
OR ( circle.tagger = :scoped
|
||||
AND circle.private = true
|
||||
)
|
||||
)
|
||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
||||
EOQ,
|
||||
['id' => $this->getId(),
|
||||
'scoped' => $scoped_id, ],
|
||||
['offset' => $offset,
|
||||
'limit' => $limit, ],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $tags array of strings to become self tags
|
||||
* @param array|null $existing array of existing self tags (actor_circle[])
|
||||
* @return $this
|
||||
* @throws NotFoundException
|
||||
* @param array $tags array of strings to become self tags
|
||||
* @param null|array $existing array of existing self tags (actor_circle[])
|
||||
*
|
||||
* @throws \App\Util\Exception\DuplicateFoundException
|
||||
* @throws NotFoundException
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSelfTags(array $tags, ?array $existing = null): self
|
||||
{
|
||||
if (is_null($existing)) {
|
||||
if (\is_null($existing)) {
|
||||
$existing = $this->getSelfTags();
|
||||
}
|
||||
$existing_actor_circles = F\map($existing, fn($actor_circle) => $actor_circle->getTag());
|
||||
$tags_to_add = array_diff($tags, $existing_actor_circles);
|
||||
$tags_to_remove = array_diff($existing_actor_circles, $tags);
|
||||
$actor_circles_to_remove = F\filter($existing, fn($actor_circle) => in_array($actor_circle->getTag(), $tags_to_remove));
|
||||
$existing_actor_circles = F\map($existing, fn ($actor_circle) => $actor_circle->getTag());
|
||||
$tags_to_add = array_diff($tags, $existing_actor_circles);
|
||||
$tags_to_remove = array_diff($existing_actor_circles, $tags);
|
||||
$actor_circles_to_remove = F\filter($existing, fn ($actor_circle) => \in_array($actor_circle->getTag(), $tags_to_remove));
|
||||
foreach ($tags_to_add as $tag) {
|
||||
$actor_circle = ActorCircle::create(['tagger' => $this->getId(), 'tag' => $tag, 'private' => false]);
|
||||
$actor_tag = ActorTag::create(['tagger' => $this->id, 'tagged' => $this->id, 'tag' => $tag]);
|
||||
DB::persist($actor_circle);
|
||||
DB::persist($actor_tag);
|
||||
$canonical_tag = TagComponent::canonicalTag($tag, $this->getTopLanguage()->getLocale());
|
||||
DB::persist(ActorCircle::create(['tagger' => $this->getId(), 'tag' => $canonical_tag, 'private' => false]));
|
||||
DB::persist(ActorTag::create(['tagger' => $this->id, 'tagged' => $this->id, 'tag' => $tag, 'canonical' => $canonical_tag]));
|
||||
}
|
||||
foreach ($actor_circles_to_remove as $actor_circle) {
|
||||
$actor_tag = DB::findOneBy('actor_tag', ['tagger' => $this->getId(), 'tagged' => $this->getId(), 'tag' => $actor_circle->getTag()]);
|
||||
DB::persist($actor_tag);
|
||||
DB::remove($actor_tag);
|
||||
$canonical_tag = TagComponent::canonicalTag($actor_circle->getTag(), $this->getTopLanguage()->getLocale());
|
||||
DB::removeBy('actor_tag', ['tagger' => $this->getId(), 'tagged' => $this->getId(), 'canonical' => $canonical_tag]);
|
||||
DB::removeBy('actor_circle', ['id' => $actor_circle->getId()]);
|
||||
}
|
||||
Cache::delete("selftags-{$this->getId()}");
|
||||
@ -378,9 +377,9 @@ class Actor extends Entity
|
||||
'subscribers-' . $this->id,
|
||||
function () {
|
||||
return DB::dql(
|
||||
'select count(f) from App\Entity\Subscription f where f.subscribed = :subscribed',
|
||||
['subscribed' => $this->id],
|
||||
)[0][1] - 1; // Remove self subscription
|
||||
'select count(f) from App\Entity\Subscription f where f.subscribed = :subscribed',
|
||||
['subscribed' => $this->id],
|
||||
)[0][1] - 1; // Remove self subscription
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -391,9 +390,9 @@ class Actor extends Entity
|
||||
'subscribed-' . $this->id,
|
||||
function () {
|
||||
return DB::dql(
|
||||
'select count(f) from App\Entity\Subscription f where f.subscriber = :subscriber',
|
||||
['subscriber' => $this->id],
|
||||
)[0][1] - 1; // Remove self subscription
|
||||
'select count(f) from App\Entity\Subscription f where f.subscriber = :subscriber',
|
||||
['subscriber' => $this->id],
|
||||
)[0][1] - 1; // Remove self subscription
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -419,16 +418,16 @@ class Actor extends Entity
|
||||
$nickname = Nickname::normalize($nickname, check_already_used: false);
|
||||
return Cache::get(
|
||||
'relative-nickname-' . $nickname . '-' . $this->getId(),
|
||||
fn() => DB::dql(
|
||||
<<<'EOF'
|
||||
fn () => DB::dql(
|
||||
<<<'EOF'
|
||||
select a from actor a where
|
||||
a.id in (select fa.subscribed from subscription fa join actor aa with fa.subscribed = aa.id where fa.subscriber = :actor_id and aa.nickname = :nickname) or
|
||||
a.id in (select fb.subscriber from subscription fb join actor ab with fb.subscriber = ab.id where fb.subscribed = :actor_id and ab.nickname = :nickname) or
|
||||
a.nickname = :nickname
|
||||
EOF,
|
||||
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
||||
['limit' => 1],
|
||||
)[0] ?? null,
|
||||
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
||||
['limit' => 1],
|
||||
)[0] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -471,6 +470,11 @@ class Actor extends Entity
|
||||
return $aliases;
|
||||
}
|
||||
|
||||
public function getTopLanguage(): Language
|
||||
{
|
||||
return ActorLanguage::getActorLanguages($this, context: null)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most appropriate language for $this to use when
|
||||
* referring to $context (a reply or a group, for instance)
|
||||
@ -479,43 +483,38 @@ class Actor extends Entity
|
||||
*/
|
||||
public function getPreferredLanguageChoices(?self $context = null): array
|
||||
{
|
||||
$id = $context?->getId() ?? $this->getId();
|
||||
$key = ActorLanguage::collectionCacheKey($this, $context);
|
||||
$langs = Cache::getList(
|
||||
$key,
|
||||
fn() => DB::dql(
|
||||
'select l from actor_language al join language l with al.language_id = l.id where al.actor_id = :id order by al.ordering ASC',
|
||||
['id' => $id],
|
||||
),
|
||||
) ?: [
|
||||
Language::getFromLocale(Common::config('site', 'language')),
|
||||
];
|
||||
return array_merge(...F\map($langs, fn($l) => $l->toChoiceFormat()));
|
||||
$langs = ActorLanguage::getActorLanguages($this, context: $context);
|
||||
return array_merge(...F\map($langs, fn ($l) => $l->toChoiceFormat()));
|
||||
}
|
||||
|
||||
public function isVisibleTo(self $other): bool
|
||||
{
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
public static function schemaDef(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'actor',
|
||||
'name' => 'actor',
|
||||
'description' => 'local and remote users, groups and bots are actors, for instance',
|
||||
'fields' => [
|
||||
'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'],
|
||||
'nickname' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username'],
|
||||
'fullname' => ['type' => 'text', 'description' => 'display name'],
|
||||
'roles' => ['type' => 'int', 'not null' => true, 'default' => UserRoles::USER, 'description' => 'Bitmap of permissions this actor has'],
|
||||
'homepage' => ['type' => 'text', 'description' => 'identifying URL'],
|
||||
'bio' => ['type' => 'text', 'description' => 'descriptive biography'],
|
||||
'location' => ['type' => 'text', 'description' => 'physical location'],
|
||||
'lat' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'],
|
||||
'lon' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'],
|
||||
'location_id' => ['type' => 'int', 'description' => 'location id if possible'],
|
||||
'fields' => [
|
||||
'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'],
|
||||
'nickname' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username'],
|
||||
'fullname' => ['type' => 'text', 'description' => 'display name'],
|
||||
'roles' => ['type' => 'int', 'not null' => true, 'default' => UserRoles::USER, 'description' => 'Bitmap of permissions this actor has'],
|
||||
'homepage' => ['type' => 'text', 'description' => 'identifying URL'],
|
||||
'bio' => ['type' => 'text', 'description' => 'descriptive biography'],
|
||||
'location' => ['type' => 'text', 'description' => 'physical location'],
|
||||
'lat' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'],
|
||||
'lon' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'],
|
||||
'location_id' => ['type' => 'int', 'description' => 'location id if possible'],
|
||||
'location_service' => ['type' => 'int', 'description' => 'service used to obtain location id'],
|
||||
'is_local' => ['type' => 'bool', 'not null' => true, 'description' => 'Does this actor have a LocalUser associated'],
|
||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
||||
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
||||
'is_local' => ['type' => 'bool', 'not null' => true, 'description' => 'Does this actor have a LocalUser associated'],
|
||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
||||
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
||||
],
|
||||
'primary key' => ['id'],
|
||||
'indexes' => [
|
||||
'indexes' => [
|
||||
'actor_nickname_idx' => ['nickname'],
|
||||
],
|
||||
'fulltext indexes' => [
|
||||
|
@ -53,7 +53,7 @@ class ActorCircle extends Entity
|
||||
private DateTimeInterface $created;
|
||||
private DateTimeInterface $modified;
|
||||
|
||||
public function setId(int $id): ActorCircle
|
||||
public function setId(int $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
@ -133,22 +133,29 @@ class ActorCircle extends Entity
|
||||
// @codeCoverageIgnoreEnd
|
||||
// }}} Autocode
|
||||
|
||||
public function getActorTag()
|
||||
{
|
||||
return Cache::get(
|
||||
"actor-tag-{$this->getTag()}",
|
||||
fn () => DB::findBy('actor_tag', ['tagger' => $this->getTagger(), 'canonical' => $this->getTag()], limit: 1)[0], // TODO jank
|
||||
);
|
||||
}
|
||||
|
||||
public function getSubscribedActors(?int $offset = null, ?int $limit = null): array
|
||||
{
|
||||
return Cache::get(
|
||||
"circle-{$this->getId()}",
|
||||
fn() => DB::dql(
|
||||
<<< EOQ
|
||||
fn () => DB::dql(
|
||||
<<< 'EOQ'
|
||||
SELECT a
|
||||
FROM App\Entity\Actor a
|
||||
JOIN App\Entity\ActorCircleSubscription s
|
||||
WITH a.id = s.actor_id
|
||||
ORDER BY s.created DESC, a.id DESC
|
||||
EOQ,
|
||||
options:
|
||||
['offset' => $offset,
|
||||
'limit' => $limit]
|
||||
)
|
||||
options: ['offset' => $offset,
|
||||
'limit' => $limit, ],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -160,7 +167,7 @@ class ActorCircle extends Entity
|
||||
'fields' => [
|
||||
'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'],
|
||||
'tagger' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'actor_list_tagger_fkey', 'not null' => true, 'description' => 'user making the tag'],
|
||||
'tag' => ['type' => 'varchar', 'length' => 64, 'foreign key' => true, 'target' => 'ActorTag.tag', 'multiplicity' => 'many to one', 'not null' => true, 'description' => 'actor tag'], // Join with ActorTag // // so, Doctrine doesn't like that the target is not unique, even though the pair is
|
||||
'tag' => ['type' => 'varchar', 'length' => 64, 'foreign key' => true, 'target' => 'ActorTag.canonical', 'multiplicity' => 'many to one', 'not null' => true, 'description' => 'actor tag'], // Join with ActorTag // // so, Doctrine doesn't like that the target is not unique, even though the pair is
|
||||
'description' => ['type' => 'text', 'description' => 'description of the people tag'],
|
||||
'private' => ['type' => 'bool', 'default' => false, 'description' => 'is this tag private'],
|
||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
|
||||
|
@ -23,8 +23,10 @@ declare(strict_types = 1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Core\Cache;
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Entity;
|
||||
use App\Util\Common;
|
||||
|
||||
/**
|
||||
* Entity for actor languages
|
||||
@ -79,9 +81,9 @@ class ActorLanguage extends Entity
|
||||
// @codeCoverageIgnoreEnd
|
||||
// }}} Autocode
|
||||
|
||||
public static function collectionCacheKey(LocalUser|Actor $actor, ?Actor $content = null)
|
||||
public static function collectionCacheKey(LocalUser|Actor $actor, ?Actor $context = null)
|
||||
{
|
||||
return 'actor-' . $actor->getId() . '-langs' . (!\is_null($content) ? '-cxt-' . $content->getId() : '');
|
||||
return 'actor-' . $actor->getId() . '-langs' . (!\is_null($context) ? '-cxt-' . $context->getId() : '');
|
||||
}
|
||||
|
||||
public static function normalizeOrdering(LocalUser|Actor $actor)
|
||||
@ -94,6 +96,21 @@ class ActorLanguage extends Entity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self[]
|
||||
*/
|
||||
public static function getActorLanguages(LocalUser|Actor $actor, ?Actor $context): array
|
||||
{
|
||||
$id = $context?->getId() ?? $actor->getId();
|
||||
return Cache::getList(
|
||||
self::collectionCacheKey($actor, context: $context),
|
||||
fn () => DB::dql(
|
||||
'select l from actor_language al join language l with al.language_id = l.id where al.actor_id = :id order by al.ordering ASC',
|
||||
['id' => $id],
|
||||
),
|
||||
) ?: [Language::getFromLocale(Common::config('site', 'language'))];
|
||||
}
|
||||
|
||||
public static function schemaDef(): array
|
||||
{
|
||||
return [
|
||||
|
@ -23,6 +23,7 @@ namespace App\Entity;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Entity;
|
||||
use App\Core\Router\Router;
|
||||
use Component\Tag\Tag;
|
||||
use DateTimeInterface;
|
||||
|
||||
@ -108,6 +109,15 @@ class ActorTag extends Entity
|
||||
// @codeCoverageIgnoreEnd
|
||||
// }}} Autocode
|
||||
|
||||
public function getUrl(?Actor $actor = null): string
|
||||
{
|
||||
$params = ['tag' => $this->getCanonical()];
|
||||
if (!\is_null($actor)) {
|
||||
$params['lang'] = $actor->getTopLanguage()->getLocale();
|
||||
}
|
||||
return Router::url('single_actor_tag', $params);
|
||||
}
|
||||
|
||||
public static function schemaDef(): array
|
||||
{
|
||||
return [
|
||||
@ -119,11 +129,11 @@ class ActorTag extends Entity
|
||||
'canonical' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'ascii slug of tag'],
|
||||
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
||||
],
|
||||
'primary key' => ['tagger', 'tagged', 'tag'],
|
||||
'primary key' => ['tagger', 'tagged', 'canonical'],
|
||||
'indexes' => [
|
||||
'actor_tag_modified_idx' => ['modified'],
|
||||
'actor_tag_tagger_tag_idx' => ['tagger', 'tag'], // For Circles
|
||||
'actor_tag_tagged_idx' => ['tagged'],
|
||||
'actor_tag_modified_idx' => ['modified'],
|
||||
'actor_tag_tagger_canonical_idx' => ['tagger', 'canonical'], // For Circles
|
||||
'actor_tag_tagged_idx' => ['tagged'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
<nav class="profile-info-tags">
|
||||
{% if actor_tags %}
|
||||
{% for tag in actor_tags %}
|
||||
<a href='#'><em>#{{ tag.getTag() }}</em></a>
|
||||
<a href="{{ tag.getActorTag().getUrl(actor) }}"><em>#{{ tag.getTag() }}</em></a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{ '(No tags)' | trans }}
|
||||
|
Loading…
Reference in New Issue
Block a user