[COMPONENT][Tag] Improve Note Tag Handling and start extracting Circles logic out of the plugin, various bug fixes
This commit is contained in:
parent
ee007befa4
commit
627d92b290
@ -48,7 +48,7 @@ class Search extends FeedController
|
|||||||
$language = !\is_null($actor) ? $actor->getTopLanguage()->getLocale() : null;
|
$language = !\is_null($actor) ? $actor->getTopLanguage()->getLocale() : null;
|
||||||
$q = $this->string('q');
|
$q = $this->string('q');
|
||||||
|
|
||||||
$data = $this->query(query: $q, language: $language);
|
$data = $this->query(query: $q, locale: $language);
|
||||||
$notes = $data['notes'];
|
$notes = $data['notes'];
|
||||||
$actors = $data['actors'];
|
$actors = $data['actors'];
|
||||||
|
|
||||||
|
@ -6,31 +6,34 @@ namespace Component\Tag\Controller;
|
|||||||
|
|
||||||
use App\Core\Cache;
|
use App\Core\Cache;
|
||||||
use App\Core\Controller;
|
use App\Core\Controller;
|
||||||
use App\Core\DB\DB;
|
|
||||||
use function App\Core\I18n\_m;
|
|
||||||
use App\Entity as E;
|
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\BugFoundException;
|
use Component\Language\Entity\Language;
|
||||||
use App\Util\Exception\ClientException;
|
|
||||||
use App\Util\Exception\RedirectException;
|
|
||||||
use App\Util\Formatting;
|
|
||||||
use Component\Tag\Form\SelfTagsForm;
|
|
||||||
use Component\Tag\Tag as CompTag;
|
use Component\Tag\Tag as CompTag;
|
||||||
use Symfony\Component\Form\SubmitButton;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
|
|
||||||
class Tag extends Controller
|
class Tag extends Controller
|
||||||
{
|
{
|
||||||
private function process(string|array $canon_single_or_multi, null|string|array $tag_single_or_multi, string $key, string $query, string $template)
|
// TODO: Use Feed::query
|
||||||
|
// TODO: If ?canonical=something, respect
|
||||||
|
// TODO: Allow to set locale of tag being selected
|
||||||
|
private function process(null|string|array $tag_single_or_multi, string $key, string $query, string $template, bool $include_locale = false)
|
||||||
{
|
{
|
||||||
$actor = Common::actor();
|
$actor = Common::actor();
|
||||||
$page = $this->int('page') ?: 1;
|
$page = $this->int('page') ?: 1;
|
||||||
$lang = $this->string('lang');
|
|
||||||
|
$query_args = ['tag' => $tag_single_or_multi];
|
||||||
|
|
||||||
|
if ($include_locale) {
|
||||||
|
if (!\is_null($locale = $this->string('locale'))) {
|
||||||
|
$query_args['language_id'] = Language::getByLocale($locale)->getId();
|
||||||
|
} else {
|
||||||
|
$query_args['language_id'] = Common::actor()->getTopLanguage()->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$results = Cache::pagedStream(
|
$results = Cache::pagedStream(
|
||||||
key: $key,
|
key: $key,
|
||||||
query: $query,
|
query: $query,
|
||||||
query_args: ['canon' => $canon_single_or_multi],
|
query_args: $query_args,
|
||||||
actor: $actor,
|
actor: $actor,
|
||||||
page: $page,
|
page: $page,
|
||||||
);
|
);
|
||||||
@ -43,179 +46,25 @@ class Tag extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function single_note_tag(string $canon)
|
public function single_note_tag(string $tag)
|
||||||
{
|
{
|
||||||
return $this->process(
|
return $this->process(
|
||||||
canon_single_or_multi: $canon,
|
tag_single_or_multi: $tag,
|
||||||
tag_single_or_multi: $this->string('tag'),
|
key: CompTag::cacheKeys($tag)['note_single'],
|
||||||
key: CompTag::cacheKeys($canon)['note_single'],
|
query: 'SELECT n FROM note AS n JOIN note_tag AS nt WITH n.id = nt.note_id WHERE nt.tag = :tag AND nt.language_id = :language_id ORDER BY nt.created DESC, nt.note_id DESC',
|
||||||
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',
|
template: 'note_tag_feed.html.twig',
|
||||||
|
include_locale: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function multi_note_tags(string $canons)
|
public function multi_note_tags(string $tags)
|
||||||
{
|
{
|
||||||
return $this->process(
|
return $this->process(
|
||||||
canon_single_or_multi: explode(',', $canons),
|
tag_single_or_multi: explode(',', $tags),
|
||||||
tag_single_or_multi: !\is_null($this->string('tags')) ? explode(',', $this->string('tags')) : null,
|
key: CompTag::cacheKeys(str_replace(',', '-', $tags))['note_multi'],
|
||||||
key: CompTag::cacheKeys(str_replace(',', '-', $canons))['note_multi'],
|
query: 'select n from note n join note_tag nt with n.id = nt.note_id where nt.tag in (:tag) AND nt.language_id = :language_id order by nt.created DESC, nt.note_id DESC',
|
||||||
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',
|
template: 'note_tag_feed.html.twig',
|
||||||
|
include_locale: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function single_actor_tag(string $canon)
|
|
||||||
{
|
|
||||||
return $this->process(
|
|
||||||
canon_single_or_multi: $canon,
|
|
||||||
tag_single_or_multi: $this->string('tag'),
|
|
||||||
key: CompTag::cacheKeys($canon)['actor_single'],
|
|
||||||
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 $canons)
|
|
||||||
{
|
|
||||||
return $this->process(
|
|
||||||
canon_single_or_multi: explode(',', $canons),
|
|
||||||
tag_single_or_multi: !\is_null($this->string('tags')) ? explode(',', $this->string('tags')) : null,
|
|
||||||
key: CompTag::cacheKeys(str_replace(',', '-', $canons))['actor_multi'],
|
|
||||||
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',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic settings page for an Actor's self tags
|
|
||||||
*/
|
|
||||||
public static function settingsSelfTags(Request $request, E\Actor $target, string $details_id)
|
|
||||||
{
|
|
||||||
$actor = Common::actor();
|
|
||||||
if (!$actor->canAdmin($target)) {
|
|
||||||
throw new ClientException(_m('You don\'t have enough permissions to edit {nickname}\'s settings', ['{nickname}' => $target->getNickname()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$actor_tags = $target->getSelfTags();
|
|
||||||
|
|
||||||
[$add_form, $existing_form] = SelfTagsForm::handleTags(
|
|
||||||
$request,
|
|
||||||
$actor_tags,
|
|
||||||
handle_new: /**
|
|
||||||
* Handle adding tags
|
|
||||||
*/
|
|
||||||
function ($form) use ($request, $target, $details_id) {
|
|
||||||
$data = $form->getData();
|
|
||||||
$tags = $data['new-tags'];
|
|
||||||
$language = $target->getTopLanguage()->getLocale();
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
$tag = CompTag::ensureValid($tag);
|
|
||||||
$canon_tag = CompTag::canonicalTag($tag, language: $language);
|
|
||||||
$use_canon = $data['new-tags-use-canon'];
|
|
||||||
|
|
||||||
[$actor_tag, $actor_tag_existed] = E\ActorTag::createOrUpdate([
|
|
||||||
'tagger' => $target->getId(),
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'tag' => $tag,
|
|
||||||
'canonical' => $canon_tag,
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
]);
|
|
||||||
DB::persist($actor_tag);
|
|
||||||
|
|
||||||
$actor_circle = DB::findBy(
|
|
||||||
'actor_circle',
|
|
||||||
[
|
|
||||||
'tagger' => null,
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'in' => ['tag' => [$tag, $canon_tag]],
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (empty($actor_circle)) {
|
|
||||||
if ($actor_tag_existed) {
|
|
||||||
throw new BugFoundException('Actor tag existed but generic actor circle did not');
|
|
||||||
}
|
|
||||||
DB::persist(E\ActorCircle::create([
|
|
||||||
'tagger' => null,
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'tag' => $use_canon ? $canon_tag : $tag,
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
'private' => false,
|
|
||||||
'description' => null,
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DB::flush();
|
|
||||||
Cache::delete(E\Actor::cacheKeys($target->getId(), $target->getId())['tags']);
|
|
||||||
throw new RedirectException($request->get('_route'), ['nickname' => $target->getNickname(), 'open' => $details_id]);
|
|
||||||
},
|
|
||||||
handle_existing: /**
|
|
||||||
* Handle changes to the existing tags
|
|
||||||
*/
|
|
||||||
function ($form, array $form_definition) use ($request, $target, $details_id) {
|
|
||||||
$data = $form->getData();
|
|
||||||
$changed = false;
|
|
||||||
$language = $target->getTopLanguage()->getLocale();
|
|
||||||
foreach (array_chunk($form_definition, 3) as $entry) {
|
|
||||||
$tag = Formatting::removePrefix($entry[0][2]['data'], '#');
|
|
||||||
$canon_tag = CompTag::canonicalTag($tag, language: $language);
|
|
||||||
$use_canon = $entry[1][2]['attr']['data'];
|
|
||||||
|
|
||||||
/** @var SubmitButton $remove */
|
|
||||||
$remove = $form->get($entry[2][0]);
|
|
||||||
if ($remove->isClicked()) {
|
|
||||||
$changed = true;
|
|
||||||
DB::removeBy(
|
|
||||||
'actor_tag',
|
|
||||||
[
|
|
||||||
'tagger' => $target->getId(),
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'tag' => $tag,
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
DB::removeBy(
|
|
||||||
'actor_circle',
|
|
||||||
[
|
|
||||||
'tagger' => null,
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'tag' => $use_canon ? $canon_tag : $tag,
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var SubmitButton $toggle_canon */
|
|
||||||
$toggle_canon = $form->get($entry[1][0]);
|
|
||||||
if ($toggle_canon->isSubmitted()) {
|
|
||||||
$changed = true;
|
|
||||||
$actor_tag = DB::find(
|
|
||||||
'actor_tag',
|
|
||||||
[
|
|
||||||
'tagger' => $target->getId(),
|
|
||||||
'tagged' => $target->getId(),
|
|
||||||
'tag' => $tag,
|
|
||||||
'use_canonical' => $use_canon,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
DB::persist($actor_tag->setUseCanonical(!$use_canon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($changed) {
|
|
||||||
DB::flush();
|
|
||||||
Cache::delete(E\Actor::cacheKeys($target->getId(), $target->getId())['tags']);
|
|
||||||
throw new RedirectException($request->get('_route'), ['nickname' => $target->getNickname(), 'open' => $details_id]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove_label: _m('Remove self tag'),
|
|
||||||
add_label: _m('Add self tag'),
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'_template' => 'self_tags_settings.fragment.html.twig',
|
|
||||||
'add_self_tags_form' => $add_form->createView(),
|
|
||||||
'existing_self_tags_form' => $existing_form?->createView(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,15 @@ declare(strict_types = 1);
|
|||||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace Component\Tag\Entity;
|
||||||
|
|
||||||
use App\Core\Cache;
|
use App\Core\Cache;
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use App\Core\Entity;
|
use App\Core\Entity;
|
||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
|
use App\Entity\Actor;
|
||||||
|
use App\Entity\Note;
|
||||||
|
use Component\Language\Entity\Language;
|
||||||
use Component\Tag\Tag;
|
use Component\Tag\Tag;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
|
||||||
@ -39,6 +42,7 @@ use DateTimeInterface;
|
|||||||
* @author Mikael Nordfeldth <mmn@hethane.se>
|
* @author Mikael Nordfeldth <mmn@hethane.se>
|
||||||
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
|
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @author Hugo Sales <hugo@hsal.es>
|
* @author Hugo Sales <hugo@hsal.es>
|
||||||
|
* @author Diogo Peralta Cordeiro <@diogo.site>
|
||||||
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
|
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
@ -51,11 +55,11 @@ class NoteTag extends Entity
|
|||||||
private int $note_id;
|
private int $note_id;
|
||||||
private bool $use_canonical;
|
private bool $use_canonical;
|
||||||
private ?int $language_id = null;
|
private ?int $language_id = null;
|
||||||
private \DateTimeInterface $created;
|
private DateTimeInterface $created;
|
||||||
|
|
||||||
public function setTag(string $tag): self
|
public function setTag(string $tag): self
|
||||||
{
|
{
|
||||||
$this->tag = \mb_substr($tag, 0, 64);
|
$this->tag = mb_substr($tag, 0, 64);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +70,7 @@ class NoteTag extends Entity
|
|||||||
|
|
||||||
public function setCanonical(string $canonical): self
|
public function setCanonical(string $canonical): self
|
||||||
{
|
{
|
||||||
$this->canonical = \mb_substr($canonical, 0, 64);
|
$this->canonical = mb_substr($canonical, 0, 64);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,13 +112,13 @@ class NoteTag extends Entity
|
|||||||
return $this->language_id;
|
return $this->language_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreated(\DateTimeInterface $created): self
|
public function setCreated(DateTimeInterface $created): self
|
||||||
{
|
{
|
||||||
$this->created = $created;
|
$this->created = $created;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreated(): \DateTimeInterface
|
public function getCreated(): DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->created;
|
return $this->created;
|
||||||
}
|
}
|
||||||
@ -132,15 +136,24 @@ class NoteTag extends Entity
|
|||||||
|
|
||||||
public static function getByNoteId(int $note_id): array
|
public static function getByNoteId(int $note_id): array
|
||||||
{
|
{
|
||||||
return Cache::getList(self::cacheKey($note_id), fn () => DB::dql('select nt from note_tag nt join note n with n.id = nt.note_id where n.id = :id', ['id' => $note_id]));
|
return Cache::getList(self::cacheKey($note_id), fn () => DB::dql('SELECT nt FROM note_tag AS nt JOIN note AS n WITH n.id = nt.note_id WHERE n.id = :id', ['id' => $note_id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl(?Actor $actor = null, int $type = Router::ABSOLUTE_PATH): string
|
public function getUrl(?Actor $actor = null, int $type = Router::ABSOLUTE_PATH): string
|
||||||
{
|
{
|
||||||
$params = ['canon' => $this->getCanonical(), 'tag' => $this->getTag()];
|
$params['tag'] = $this->getTag();
|
||||||
if (!\is_null($actor)) {
|
|
||||||
$params['lang'] = $actor->getTopLanguage()->getLocale();
|
if (\is_null($this->getLanguageId())) {
|
||||||
|
if (!\is_null($actor)) {
|
||||||
|
$params['locale'] = $actor->getTopLanguage()->getLocale();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$params['locale'] = Language::getById($this->getLanguageId())->getLocale();
|
||||||
}
|
}
|
||||||
|
if ($this->getUseCanonical()) {
|
||||||
|
$params['canonical'] = $this->getCanonical();
|
||||||
|
}
|
||||||
|
|
||||||
return Router::url(id: 'single_note_tag', args: $params, type: $type);
|
return Router::url(id: 'single_note_tag', args: $params, type: $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,18 +163,18 @@ class NoteTag extends Entity
|
|||||||
'name' => 'note_tag',
|
'name' => 'note_tag',
|
||||||
'description' => 'Hash tags on notes',
|
'description' => 'Hash tags on notes',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
|
'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to tagged note'],
|
||||||
'tag' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'hash tag associated with this note'],
|
'tag' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'hash tag associated with this note'],
|
||||||
'canonical' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'ascii slug of tag'],
|
'canonical' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'ascii slug of tag'],
|
||||||
'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to tagged note'],
|
|
||||||
'use_canonical' => ['type' => 'bool', 'not null' => true, 'description' => 'whether the user wanted to use canonical tags in this note. Separate for blocks'],
|
'use_canonical' => ['type' => 'bool', 'not null' => true, 'description' => 'whether the user wanted to use canonical tags in this note. Separate for blocks'],
|
||||||
'language_id' => ['type' => 'int', 'not null' => false, 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'many to many', 'description' => 'the language this entry refers to'],
|
'language_id' => ['type' => 'int', 'not null' => false, 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'many to many', 'description' => 'the language this entry refers to'],
|
||||||
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
|
||||||
],
|
],
|
||||||
'primary key' => ['tag', 'note_id'],
|
'primary key' => ['note_id', 'tag'], // No need to require language in this association because all the related tags will be in the note's language already
|
||||||
'indexes' => [
|
'indexes' => [
|
||||||
'note_tag_created_idx' => ['created'],
|
'note_tag_created_idx' => ['created'],
|
||||||
'note_tag_note_id_idx' => ['note_id'],
|
'note_tag_note_id_idx' => ['note_id'],
|
||||||
'note_tag_canonical_idx' => ['canonical'],
|
'note_tag_tag_language_id_idx' => ['tag', 'language_id'],
|
||||||
'note_tag_tag_created_note_id_idx' => ['tag', 'created', 'note_id'],
|
'note_tag_tag_created_note_id_idx' => ['tag', 'created', 'note_id'],
|
||||||
],
|
],
|
||||||
];
|
];
|
@ -19,7 +19,7 @@ declare(strict_types = 1);
|
|||||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace Component\Tag\Entity;
|
||||||
|
|
||||||
use App\Core\Cache;
|
use App\Core\Cache;
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
@ -46,7 +46,7 @@ class NoteTagBlock extends Entity
|
|||||||
private string $tag;
|
private string $tag;
|
||||||
private string $canonical;
|
private string $canonical;
|
||||||
private bool $use_canonical;
|
private bool $use_canonical;
|
||||||
private \DateTimeInterface $modified;
|
private DateTimeInterface $modified;
|
||||||
|
|
||||||
public function setBlocker(int $blocker): self
|
public function setBlocker(int $blocker): self
|
||||||
{
|
{
|
||||||
@ -61,7 +61,7 @@ class NoteTagBlock extends Entity
|
|||||||
|
|
||||||
public function setTag(string $tag): self
|
public function setTag(string $tag): self
|
||||||
{
|
{
|
||||||
$this->tag = \mb_substr($tag, 0, 64);
|
$this->tag = mb_substr($tag, 0, 64);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ class NoteTagBlock extends Entity
|
|||||||
|
|
||||||
public function setCanonical(string $canonical): self
|
public function setCanonical(string $canonical): self
|
||||||
{
|
{
|
||||||
$this->canonical = \mb_substr($canonical, 0, 64);
|
$this->canonical = mb_substr($canonical, 0, 64);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,13 +92,13 @@ class NoteTagBlock extends Entity
|
|||||||
return $this->use_canonical;
|
return $this->use_canonical;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setModified(\DateTimeInterface $modified): self
|
public function setModified(DateTimeInterface $modified): self
|
||||||
{
|
{
|
||||||
$this->modified = $modified;
|
$this->modified = $modified;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getModified(): \DateTimeInterface
|
public function getModified(): DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->modified;
|
return $this->modified;
|
||||||
}
|
}
|
@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace Component\Tag\Form;
|
|
||||||
|
|
||||||
use App\Core\Form;
|
|
||||||
use function App\Core\I18n\_m;
|
|
||||||
use App\Entity as E;
|
|
||||||
use App\Util\Form\ArrayTransformer;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
|
|
||||||
abstract class SelfTagsForm
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param E\ActorTag[]|E\ActorTagBlock[]|E\NoteTagBlock[] $tags
|
|
||||||
*
|
|
||||||
* @return array [Form (add), ?Form (existing)]
|
|
||||||
*/
|
|
||||||
public static function handleTags(
|
|
||||||
Request $request,
|
|
||||||
array $tags,
|
|
||||||
callable $handle_new,
|
|
||||||
callable $handle_existing,
|
|
||||||
string $remove_label,
|
|
||||||
string $add_label,
|
|
||||||
): array {
|
|
||||||
$form_definition = [];
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
$canon = $tag->getCanonical();
|
|
||||||
$form_definition[] = ["{$canon}:old-tag", TextType::class, ['data' => '#' . $tag->getTag(), 'label' => ' ', 'disabled' => true]];
|
|
||||||
$form_definition[] = ["{$canon}:toggle-canon", SubmitType::class, ['attr' => ['data' => $tag->getUseCanonical()], 'label' => $tag->getUseCanonical() ? _m('Set non-canonical') : _m('Set canonical')]];
|
|
||||||
$form_definition[] = [$existing_form_name = "{$canon}:remove", SubmitType::class, ['label' => $remove_label]];
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing_form = !empty($form_definition) ? Form::create($form_definition) : null;
|
|
||||||
|
|
||||||
$add_form = Form::create([
|
|
||||||
['new-tags', TextType::class, ['label' => ' ', 'data' => [], 'required' => false, 'help' => _m('Tags for yourself (letters, numbers, -, ., and _), comma- or space-separated.'), 'transformer' => ArrayTransformer::class]],
|
|
||||||
['new-tags-use-canon', CheckboxType::class, ['label' => _m('Use canonical'), 'help' => _m('Assume this tag is the same as similar tags'), 'required' => false, 'data' => true]],
|
|
||||||
[$add_form_name = 'new-tags-add', SubmitType::class, ['label' => $add_label]],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($request->getMethod() === 'POST' && $request->request->has($add_form_name)) {
|
|
||||||
$add_form->handleRequest($request);
|
|
||||||
if ($add_form->isSubmitted() && $add_form->isValid()) {
|
|
||||||
$handle_new($add_form);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!\is_null($existing_form) && $request->getMethod() === 'POST' && $request->request->has($existing_form_name ?? '')) {
|
|
||||||
$existing_form->handleRequest($request);
|
|
||||||
if ($existing_form->isSubmitted() && $existing_form->isValid()) {
|
|
||||||
$handle_existing($existing_form, $form_definition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$add_form, $existing_form];
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,18 +30,15 @@ use function App\Core\I18n\_m;
|
|||||||
use App\Core\Modules\Component;
|
use App\Core\Modules\Component;
|
||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
use App\Entity\Actor;
|
use App\Entity\Actor;
|
||||||
use App\Entity\ActorCircle;
|
|
||||||
use App\Entity\ActorTag;
|
|
||||||
use App\Entity\Note;
|
use App\Entity\Note;
|
||||||
use App\Entity\NoteTag;
|
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\ClientException;
|
use App\Util\Exception\ClientException;
|
||||||
use App\Util\Formatting;
|
use App\Util\Formatting;
|
||||||
use App\Util\Functional as GSF;
|
use App\Util\Functional as GSF;
|
||||||
use App\Util\HTML;
|
use App\Util\HTML;
|
||||||
use App\Util\Nickname;
|
use Component\Circle\Entity\ActorTag;
|
||||||
use Component\Language\Entity\Language;
|
use Component\Language\Entity\Language;
|
||||||
use Component\Tag\Controller as C;
|
use Component\Tag\Entity\NoteTag;
|
||||||
use Doctrine\Common\Collections\ExpressionBuilder;
|
use Doctrine\Common\Collections\ExpressionBuilder;
|
||||||
use Doctrine\ORM\Query\Expr;
|
use Doctrine\ORM\Query\Expr;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
@ -53,22 +50,20 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
* Component responsible for extracting tags from posted notes, as well as normalizing them
|
* Component responsible for extracting tags from posted notes, as well as normalizing them
|
||||||
*
|
*
|
||||||
* @author Hugo Sales <hugo@hsal.es>
|
* @author Hugo Sales <hugo@hsal.es>
|
||||||
|
* @author Diogo Peralta Cordeiro <@diogo.site>
|
||||||
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
|
||||||
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
*/
|
*/
|
||||||
class Tag extends Component
|
class Tag extends Component
|
||||||
{
|
{
|
||||||
public const MAX_TAG_LENGTH = 64;
|
public const MAX_TAG_LENGTH = 64;
|
||||||
public const TAG_REGEX = '/(^|\\s)(#[\\pL\\pN_\\-]{1,64})/u'; // Brion Vibber 2011-02-23 v2:classes/Notice.php:367 function saveTags
|
public const TAG_REGEX = '/(^|\\s)(#[\\pL\\pN_\\-]{1,64})/u'; // Brion Vibber 2011-02-23 v2:classes/Notice.php:367 function saveTags
|
||||||
public const TAG_CIRCLE_REGEX = '/' . Nickname::BEFORE_MENTIONS . '@#([\pL\pN_\-\.]{1,64})/';
|
public const TAG_SLUG_REGEX = '[A-Za-z0-9]{1,64}';
|
||||||
public const TAG_SLUG_REGEX = '[A-Za-z0-9]{1,64}';
|
|
||||||
|
|
||||||
public function onAddRoute($r): bool
|
public function onAddRoute($r): bool
|
||||||
{
|
{
|
||||||
$r->connect('single_note_tag', '/note-tag/{canon<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_note_tag']);
|
$r->connect('single_note_tag', '/note-tag/{tag<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_note_tag']);
|
||||||
$r->connect('multi_note_tags', '/note-tags/{canons<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_note_tags']);
|
$r->connect('multi_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/{canon<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_actor_tag']);
|
|
||||||
$r->connect('multi_actor_tags', '/actor-tags/{canons<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_actor_tags']);
|
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +81,10 @@ class Tag extends Component
|
|||||||
preg_match_all(self::TAG_REGEX, $content, $matched_tags, \PREG_SET_ORDER);
|
preg_match_all(self::TAG_REGEX, $content, $matched_tags, \PREG_SET_ORDER);
|
||||||
$matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2]));
|
$matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2]));
|
||||||
foreach ($matched_tags as $match) {
|
foreach ($matched_tags as $match) {
|
||||||
$tag = self::ensureValid($match);
|
$tag = self::extract($match);
|
||||||
|
if (!self::validate($tag)) {
|
||||||
|
continue; // Ignore invalid tag candidates
|
||||||
|
}
|
||||||
$canonical_tag = self::canonicalTag($tag, \is_null($lang_id = $note->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
|
$canonical_tag = self::canonicalTag($tag, \is_null($lang_id = $note->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
|
||||||
DB::persist(NoteTag::create([
|
DB::persist(NoteTag::create([
|
||||||
'tag' => $tag,
|
'tag' => $tag,
|
||||||
@ -103,38 +101,54 @@ class Tag extends Component
|
|||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onRenderPlainTextNoteContent(string &$text, ?string $language = null): bool
|
public function onRenderPlainTextNoteContent(string &$text, ?string $locale = null): bool
|
||||||
{
|
{
|
||||||
$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], $locale), $text);
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cacheKeys(string $canon_single_or_multi): array
|
public static function cacheKeys(string $tag_single_or_multi): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'note_single' => "note-tag-feed-{$canon_single_or_multi}",
|
'note_single' => "note-tag-feed-{$tag_single_or_multi}",
|
||||||
'note_multi' => "note-tags-feed-{$canon_single_or_multi}",
|
'note_multi' => "note-tags-feed-{$tag_single_or_multi}",
|
||||||
'actor_single' => "actor-tag-feed-{$canon_single_or_multi}",
|
'actor_single' => "actor-tag-feed-{$tag_single_or_multi}",
|
||||||
'actor_multi' => "actor-tags-feed-{$canon_single_or_multi}",
|
'actor_multi' => "actor-tags-feed-{$tag_single_or_multi}",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function tagLink(string $tag, ?string $language): string
|
private static function tagLink(string $tag, ?string $locale): string
|
||||||
{
|
{
|
||||||
$tag = self::ensureLength($tag);
|
$tag = self::extract($tag);
|
||||||
$canonical = self::canonicalTag($tag, $language);
|
$url = Router::url('single_note_tag', !\is_null($locale) ? ['tag' => $tag, 'locale' => $locale] : ['tag' => $tag]);
|
||||||
$url = Router::url('single_note_tag', !\is_null($language) ? ['canon' => $canonical, 'lang' => $language, 'tag' => $tag] : ['canon' => $canonical, 'tag' => $tag]);
|
return HTML::html(['span' => ['attrs' => ['class' => 'tag'],
|
||||||
return HTML::html(['a' => ['attrs' => ['href' => $url, 'title' => $tag, 'rel' => 'tag'], $tag]], options: ['indent' => false]);
|
'#' . HTML::html(['a' => [
|
||||||
|
'attrs' => [
|
||||||
|
'href' => $url,
|
||||||
|
'rel' => 'tag', // https://microformats.org/wiki/rel-tag
|
||||||
|
],
|
||||||
|
$tag,
|
||||||
|
]], options: ['indent' => false]),
|
||||||
|
]], options: ['indent' => false, 'raw' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ensureValid(string $tag)
|
public static function extract(string $tag): string
|
||||||
{
|
{
|
||||||
$tag = self::ensureLength(Formatting::removePrefix($tag, '#'));
|
return self::ensureLength(Formatting::removePrefix($tag, '#'));
|
||||||
if (preg_match(self::TAG_REGEX, '#' . $tag)) {
|
}
|
||||||
return $tag;
|
|
||||||
} else {
|
public static function validate(string $tag): bool
|
||||||
|
{
|
||||||
|
return preg_match(self::TAG_REGEX, '#' . $tag) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function sanitize(string $tag): string
|
||||||
|
{
|
||||||
|
$tag = self::extract($tag);
|
||||||
|
if (!self::validate($tag)) {
|
||||||
throw new ClientException(_m('Invalid tag given: {tag}', ['{tag}' => $tag]));
|
throw new ClientException(_m('Invalid tag given: {tag}', ['{tag}' => $tag]));
|
||||||
}
|
}
|
||||||
|
return $tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ensureLength(string $tag): string
|
public static function ensureLength(string $tag): string
|
||||||
@ -143,11 +157,11 @@ class Tag extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a tag to it's canonical representation, by splitting it
|
* Convert a tag to its canonical representation, by splitting it
|
||||||
* into words, stemming it in the given language (if enabled) and
|
* into words, stemming it in the given language (if enabled) and
|
||||||
* sluggifying it (turning it into an ASCII representation)
|
* sluggifying it (turning it into an ASCII representation)
|
||||||
*/
|
*/
|
||||||
public static function canonicalTag(string $tag, ?string $language): string
|
public static function canonicalTag(string $tag, ?string $language = null): string
|
||||||
{
|
{
|
||||||
$result = '';
|
$result = '';
|
||||||
foreach (Formatting::splitWords(str_replace('#', '', $tag)) as $word) {
|
foreach (Formatting::splitWords(str_replace('#', '', $tag)) as $word) {
|
||||||
@ -165,17 +179,20 @@ class Tag extends Component
|
|||||||
*
|
*
|
||||||
* $term /^(note|tag|people|actor)/ means we want to match only either a note or an actor
|
* $term /^(note|tag|people|actor)/ means we want to match only either a note or an actor
|
||||||
*/
|
*/
|
||||||
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $language, ?Actor $actor, &$note_expr, &$actor_expr): bool
|
public function onSearchCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): bool
|
||||||
{
|
{
|
||||||
if (!str_contains($term, ':')) {
|
if (!str_contains($term, ':')) {
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
if (\is_null($locale)) {
|
||||||
|
$locale = Common::currentLanguage();
|
||||||
|
}
|
||||||
[$search_type, $search_term] = explode(':', $term);
|
[$search_type, $search_term] = explode(':', $term);
|
||||||
if (str_starts_with($search_term, '#')) {
|
if (str_starts_with($search_term, '#')) {
|
||||||
$search_term = self::ensureValid($search_term);
|
$search_term = self::sanitize($search_term);
|
||||||
$canon_search_term = self::canonicalTag($search_term, $language);
|
$canonical_search_term = self::canonicalTag($search_term, $locale);
|
||||||
$temp_note_expr = $eb->eq('note_tag.canonical', $canon_search_term);
|
$temp_note_expr = $eb->eq('note_tag.canonical', $canonical_search_term);
|
||||||
$temp_actor_expr = $eb->eq('actor_tag.canonical', $canon_search_term);
|
$temp_actor_expr = $eb->eq('actor_tag.canonical', $canonical_search_term);
|
||||||
if (Formatting::startsWith($term, ['note:', 'tag:', 'people:'])) {
|
if (Formatting::startsWith($term, ['note:', 'tag:', 'people:'])) {
|
||||||
$note_expr = $temp_note_expr;
|
$note_expr = $temp_note_expr;
|
||||||
} elseif (Formatting::startsWith($term, ['people:', 'actor:'])) {
|
} elseif (Formatting::startsWith($term, ['people:', 'actor:'])) {
|
||||||
@ -183,7 +200,7 @@ class Tag extends Component
|
|||||||
} elseif (Formatting::startsWith($term, GSF::cartesianProduct([['people', 'actor'], ['circle', 'list'], [':']], separator: ['-', '_']))) {
|
} elseif (Formatting::startsWith($term, GSF::cartesianProduct([['people', 'actor'], ['circle', 'list'], [':']], separator: ['-', '_']))) {
|
||||||
$null_tagger_expr = $eb->isNull('actor_circle.tagger');
|
$null_tagger_expr = $eb->isNull('actor_circle.tagger');
|
||||||
$tagger_expr = \is_null($actor_expr) ? $null_tagger_expr : $eb->orX($null_tagger_expr, $eb->eq('actor_circle.tagger', $actor->getId()));
|
$tagger_expr = \is_null($actor_expr) ? $null_tagger_expr : $eb->orX($null_tagger_expr, $eb->eq('actor_circle.tagger', $actor->getId()));
|
||||||
$tags = array_unique([$search_term, $canon_search_term]);
|
$tags = array_unique([$search_term, $canonical_search_term]);
|
||||||
$tag_expr = \count($tags) === 1 ? $eb->eq('actor_circle.tag', $tags[0]) : $eb->in('actor_circle.tag', $tags);
|
$tag_expr = \count($tags) === 1 ? $eb->eq('actor_circle.tag', $tags[0]) : $eb->in('actor_circle.tag', $tags);
|
||||||
$search_expr = $eb->andX(
|
$search_expr = $eb->andX(
|
||||||
$tagger_expr,
|
$tagger_expr,
|
||||||
@ -202,52 +219,23 @@ class Tag extends Component
|
|||||||
|
|
||||||
public function onSearchQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb): bool
|
public function onSearchQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb): bool
|
||||||
{
|
{
|
||||||
$note_qb->leftJoin(NoteTag::class, 'note_tag', Expr\Join::WITH, 'note_tag.note_id = note.id')
|
$note_qb->leftJoin(NoteTag::class, 'note_tag', Expr\Join::WITH, 'note_tag.note_id = note.id');
|
||||||
->leftJoin(ActorCircle::class, 'actor_circle', Expr\Join::WITH, 'note_actor.id = actor_circle.tagged');
|
$actor_qb->leftJoin(ActorTag::class, 'actor_tag', Expr\Join::WITH, 'actor_tag.tagger = actor.id');
|
||||||
$actor_qb->leftJoin(ActorTag::class, 'actor_tag', Expr\Join::WITH, 'actor_tag.tagger = actor.id')
|
|
||||||
->leftJoin(ActorCircle::class, 'actor_circle', Expr\Join::WITH, 'actor.id = actor_circle.tagged');
|
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onPostingAddFormEntries(Request $request, Actor $actor, array &$form_params)
|
public function onPostingAddFormEntries(Request $request, Actor $actor, array &$form_params): bool
|
||||||
{
|
{
|
||||||
$form_params[] = ['tag_use_canonical', CheckboxType::class, ['required' => false, 'data' => true, 'label' => _m('Make note tags canonical'), 'help' => _m('Canonical tags will be treated as a version of an existing tag with the same root/stem (e.g. \'#great_tag\' will be considered as a version of \'#great\', if it already exists)')]];
|
$form_params[] = ['tag_use_canonical', CheckboxType::class, ['required' => false, 'data' => true, 'label' => _m('Make note tags canonical'), 'help' => _m('Canonical tags will be treated as a version of an existing tag with the same root/stem (e.g. \'#great_tag\' will be considered as a version of \'#great\', if it already exists)')]];
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onAddExtraArgsToNoteContent(Request $request, Actor $actor, array $data, array &$extra_args)
|
public function onAddExtraArgsToNoteContent(Request $request, Actor $actor, array $data, array &$extra_args): bool
|
||||||
{
|
{
|
||||||
if (!isset($data['tag_use_canonical'])) {
|
if (!isset($data['tag_use_canonical'])) {
|
||||||
throw new ClientException;
|
throw new ClientException(_m('Missing Use Canonical preference for Tags.'));
|
||||||
}
|
}
|
||||||
$extra_args['tag_use_canonical'] = $data['tag_use_canonical'];
|
$extra_args['tag_use_canonical'] = $data['tag_use_canonical'];
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onPopulateSettingsTabs(Request $request, string $section, array &$tabs)
|
|
||||||
{
|
|
||||||
if ($section === 'profile' && $request->get('_route') === 'settings') {
|
|
||||||
$tabs[] = [
|
|
||||||
'title' => 'Self tags',
|
|
||||||
'desc' => 'Add or remove tags on yourself',
|
|
||||||
'id' => 'settings-self-tags',
|
|
||||||
'controller' => C\Tag::settingsSelfTags($request, Common::actor(), 'settings-self-tags-details'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return Event::next;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onPostingFillTargetChoices(Request $request, Actor $actor, array &$targets)
|
|
||||||
{
|
|
||||||
$actor_id = $actor->getId();
|
|
||||||
$tags = Cache::get(
|
|
||||||
"actor-circle-{$actor_id}",
|
|
||||||
fn () => DB::dql('select c.tag from actor_circle c where c.tagger = :tagger', ['tagger' => $actor_id]),
|
|
||||||
);
|
|
||||||
foreach ($tags as $t) {
|
|
||||||
$t = '#' . $t['tag'];
|
|
||||||
$targets[$t] = $t;
|
|
||||||
}
|
|
||||||
return Event::next;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,6 @@ use App\Core\Log;
|
|||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
use App\Core\VisibilityScope;
|
use App\Core\VisibilityScope;
|
||||||
use App\Entity\Note as GSNote;
|
use App\Entity\Note as GSNote;
|
||||||
use App\Entity\NoteTag;
|
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\ClientException;
|
use App\Util\Exception\ClientException;
|
||||||
use App\Util\Exception\DuplicateFoundException;
|
use App\Util\Exception\DuplicateFoundException;
|
||||||
@ -57,6 +56,7 @@ use Component\Attachment\Entity\AttachmentToNote;
|
|||||||
use Component\Conversation\Conversation;
|
use Component\Conversation\Conversation;
|
||||||
use Component\FreeNetwork\FreeNetwork;
|
use Component\FreeNetwork\FreeNetwork;
|
||||||
use Component\Language\Entity\Language;
|
use Component\Language\Entity\Language;
|
||||||
|
use Component\Tag\Entity\NoteTag;
|
||||||
use Component\Tag\Tag;
|
use Component\Tag\Tag;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
@ -254,7 +254,7 @@ class Note extends Model
|
|||||||
break;
|
break;
|
||||||
case 'Hashtag':
|
case 'Hashtag':
|
||||||
$match = ltrim($ap_tag->get('name'), '#');
|
$match = ltrim($ap_tag->get('name'), '#');
|
||||||
$tag = Tag::ensureValid($match);
|
$tag = Tag::extract($match);
|
||||||
$canonical_tag = $ap_tag->get('canonical') ?? Tag::canonicalTag($tag, \is_null($lang_id = $obj->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
|
$canonical_tag = $ap_tag->get('canonical') ?? Tag::canonicalTag($tag, \is_null($lang_id = $obj->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
|
||||||
DB::persist(NoteTag::create([
|
DB::persist(NoteTag::create([
|
||||||
'tag' => $tag,
|
'tag' => $tag,
|
||||||
|
@ -25,9 +25,8 @@ use App\Core\Cache;
|
|||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use App\Core\Event;
|
use App\Core\Event;
|
||||||
use App\Core\Modules\Plugin;
|
use App\Core\Modules\Plugin;
|
||||||
use App\Entity\ActorTag;
|
use Component\Circle\Entity\ActorTag;
|
||||||
use App\Entity\NoteTag;
|
use Component\Tag\Entity\NoteTag;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
class RelatedTags extends Plugin
|
class RelatedTags extends Plugin
|
||||||
@ -37,21 +36,25 @@ class RelatedTags extends Plugin
|
|||||||
*/
|
*/
|
||||||
public function onAddPinnedFeedContent(Request $request, array &$pinned)
|
public function onAddPinnedFeedContent(Request $request, array &$pinned)
|
||||||
{
|
{
|
||||||
$tags = $request->attributes->get('canons');
|
// Lets not use language, probably wouldn't make it more helpful
|
||||||
$tags = !\is_null($tags) ? explode(',', $tags) : [$request->attributes->get('canon')];
|
//$locale = $request->attributes->get('locale');
|
||||||
|
//$language_id = !empty($locale) ? Language::getByLocale($locale)->getId() : Common::actor()->getTopLanguage()->getId();
|
||||||
|
$tags = $request->attributes->get('tags');
|
||||||
|
$tags = !\is_null($tags) ? explode(',', $tags) : [$request->attributes->get('tag')];
|
||||||
|
|
||||||
switch ($request->attributes->get('_route')) {
|
switch ($request->attributes->get('_route')) {
|
||||||
case 'single_note_tag':
|
case 'single_note_tag':
|
||||||
// fall-through
|
// fall-through
|
||||||
case 'multi_note_tags':
|
case 'multi_note_tags':
|
||||||
$related = Cache::getList(
|
$related = Cache::getList(
|
||||||
|
//"related-note-tags-{$language_id}-" . implode('-', $tags),
|
||||||
'related-note-tags-' . implode('-', $tags),
|
'related-note-tags-' . implode('-', $tags),
|
||||||
fn () => DB::sql(
|
fn () => DB::sql(
|
||||||
<<<'EOQ'
|
<<<'EOQ'
|
||||||
select distinct on (nt.canonical) canonical, nt.tag, nt.note_id, nt.created
|
select distinct on (nt.canonical) canonical, nt.tag, nt.note_id, nt.canonical, nt.use_canonical, nt.created
|
||||||
from note_tag nt
|
from note_tag nt
|
||||||
where nt.note_id in (select n.id from note n join note_tag nt on n.id = nt.note_id where nt.canonical in (:tags))
|
where nt.note_id in (select n.id from note n join note_tag nt on n.id = nt.note_id where nt.tag in (:tags))
|
||||||
and not nt.canonical in (:tags)
|
and not nt.tag in (:tags)
|
||||||
limit 5
|
limit 5
|
||||||
EOQ,
|
EOQ,
|
||||||
['tags' => $tags],
|
['tags' => $tags],
|
||||||
@ -68,10 +71,10 @@ class RelatedTags extends Plugin
|
|||||||
'related-actor-tags-' . implode('-', $tags),
|
'related-actor-tags-' . implode('-', $tags),
|
||||||
fn () => DB::sql(
|
fn () => DB::sql(
|
||||||
<<<'EOQ'
|
<<<'EOQ'
|
||||||
select distinct on (at.canonical) canonical, at.tagger, at.tagged, at.tag, at.use_canonical, at.modified
|
select distinct on (at.tag) tag, at.tagger, at.tagged, at.tag, at.modified
|
||||||
from actor_tag at
|
from actor_tag at
|
||||||
where at.tagged in (select at.tagged from actor_tag at where at.canonical in (:tags))
|
where at.tagged in (select at.tagged from actor_tag at where at.tag in (:tags))
|
||||||
and not at.canonical in (:tags)
|
and not at.tag in (:tags)
|
||||||
limit 5
|
limit 5
|
||||||
EOQ,
|
EOQ,
|
||||||
['tags' => $tags],
|
['tags' => $tags],
|
||||||
|
@ -29,14 +29,12 @@ use App\Core\DB\DB;
|
|||||||
use App\Core\Form;
|
use App\Core\Form;
|
||||||
use function App\Core\I18n\_m;
|
use function App\Core\I18n\_m;
|
||||||
use App\Entity\Actor;
|
use App\Entity\Actor;
|
||||||
use App\Entity\ActorTag;
|
|
||||||
use App\Entity\ActorTagBlock;
|
|
||||||
use App\Entity\Note;
|
use App\Entity\Note;
|
||||||
use App\Entity\NoteTag;
|
|
||||||
use App\Entity\NoteTagBlock;
|
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\RedirectException;
|
use App\Util\Exception\RedirectException;
|
||||||
use Component\Language\Entity\Language;
|
use Component\Language\Entity\Language;
|
||||||
|
use Component\Tag\Entity\NoteTag;
|
||||||
|
use Component\Tag\Entity\NoteTagBlock;
|
||||||
use Component\Tag\Tag;
|
use Component\Tag\Tag;
|
||||||
use Functional as F;
|
use Functional as F;
|
||||||
use Plugin\TagBasedFiltering\TagBasedFiltering as TagFilerPlugin;
|
use Plugin\TagBasedFiltering\TagBasedFiltering as TagFilerPlugin;
|
||||||
@ -67,10 +65,10 @@ class AddBlocked extends Controller
|
|||||||
|
|
||||||
$form_definition = [];
|
$form_definition = [];
|
||||||
foreach ($blockable_tags as $nt) {
|
foreach ($blockable_tags as $nt) {
|
||||||
$canon = $nt->getCanonical();
|
$canonical = $nt->getCanonical();
|
||||||
$form_definition[] = ["{$canon}:tag", TextType::class, ['data' => '#' . $nt->getTag(), 'label' => ' ']];
|
$form_definition[] = ["{$canonical}:tag", TextType::class, ['data' => '#' . $nt->getTag(), 'label' => ' ']];
|
||||||
$form_definition[] = ["{$canon}:use-canon", CheckboxType::class, ['label' => _m('Use canonical'), 'help' => _m('Block all similar tags'), 'required' => false, 'data' => true]];
|
$form_definition[] = ["{$canonical}:use-canonical", CheckboxType::class, ['label' => _m('Use canonical'), 'help' => _m('Block all similar tags'), 'required' => false, 'data' => true]];
|
||||||
$form_definition[] = ["{$canon}:add", SubmitType::class, ['label' => _m('Block')]];
|
$form_definition[] = ["{$canonical}:add", SubmitType::class, ['label' => _m('Block')]];
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = null;
|
$form = null;
|
||||||
@ -80,24 +78,24 @@ class AddBlocked extends Controller
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$data = $form->getData();
|
$data = $form->getData();
|
||||||
foreach ($form_definition as [$id, $_, $opts]) {
|
foreach ($form_definition as [$id, $_, $opts]) {
|
||||||
[$canon, $type] = explode(':', $id);
|
[$canonical, $type] = explode(':', $id);
|
||||||
if ($type === 'add') {
|
if ($type === 'add') {
|
||||||
/** @var SubmitButton $button */
|
/** @var SubmitButton $button */
|
||||||
$button = $form->get($id);
|
$button = $form->get($id);
|
||||||
if ($button->isClicked()) {
|
if ($button->isClicked()) {
|
||||||
Cache::delete($block_class::cacheKey($user->getId()));
|
Cache::delete($block_class::cacheKey($user->getId()));
|
||||||
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]);
|
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]);
|
||||||
$new_tag = Tag::ensureValid($data[$canon . ':tag']);
|
$new_tag = Tag::sanitize($data[$canonical . ':tag']);
|
||||||
$language = $target instanceof Note ? Language::getByNote($target)->getLocale() : $user->getActor()->getTopLanguage()->getLocale();
|
$language = $target instanceof Note ? Language::getByNote($target)->getLocale() : $user->getActor()->getTopLanguage()->getLocale();
|
||||||
$canonical_tag = Tag::canonicalTag($new_tag, $language);
|
$canonical_tag = Tag::canonicalTag($new_tag, $language);
|
||||||
DB::persist($block_class::create([
|
DB::persist($block_class::create([
|
||||||
'blocker' => $user->getId(),
|
'blocker' => $user->getId(),
|
||||||
'tag' => $new_tag,
|
'tag' => $new_tag,
|
||||||
'canonical' => $canonical_tag,
|
'canonical' => $canonical_tag,
|
||||||
'use_canonical' => $data[$canon . ':use-canon'],
|
'use_canonical' => $data[$canonical . ':use-canonical'],
|
||||||
]));
|
]));
|
||||||
DB::flush();
|
DB::flush();
|
||||||
throw new RedirectException;
|
throw new RedirectException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,20 +129,4 @@ class AddBlocked extends Controller
|
|||||||
block_class: NoteTagBlock::class,
|
block_class: NoteTagBlock::class,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addBlockedActorTags(Request $request, int $actor_id)
|
|
||||||
{
|
|
||||||
return self::addBlocked(
|
|
||||||
request: $request,
|
|
||||||
type_name: 'actor',
|
|
||||||
calculate_target: fn () => Actor::getById($actor_id),
|
|
||||||
calculate_blocks: fn ($user) => ActorTagBlock::getByActorId($user->getId()),
|
|
||||||
calculate_tags: fn ($blocks) => F\reject(
|
|
||||||
ActorTag::getByActorId($actor_id),
|
|
||||||
fn (ActorTag $nt) => ActorTagBlock::checkBlocksActorTag($nt, $blocks),
|
|
||||||
),
|
|
||||||
label: _m('Tags of the account above:'),
|
|
||||||
block_class: ActorTagBlock::class,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,9 @@ use App\Core\Controller;
|
|||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use App\Core\Form;
|
use App\Core\Form;
|
||||||
use function App\Core\I18n\_m;
|
use function App\Core\I18n\_m;
|
||||||
use App\Entity\ActorTagBlock;
|
|
||||||
use App\Entity\NoteTagBlock;
|
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\RedirectException;
|
use App\Util\Exception\RedirectException;
|
||||||
|
use Component\Tag\Entity\NoteTagBlock;
|
||||||
use Component\Tag\Tag;
|
use Component\Tag\Tag;
|
||||||
use Plugin\TagBasedFiltering\TagBasedFiltering as TagFilerPlugin;
|
use Plugin\TagBasedFiltering\TagBasedFiltering as TagFilerPlugin;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
@ -105,7 +104,7 @@ class EditBlocked extends Controller
|
|||||||
$data = $add_block_form->getData();
|
$data = $add_block_form->getData();
|
||||||
Cache::delete($block_class::cacheKey($user->getId()));
|
Cache::delete($block_class::cacheKey($user->getId()));
|
||||||
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]);
|
Cache::delete(TagFilerPlugin::cacheKeys($user->getId())[$type_name]);
|
||||||
$new_tag = Tag::ensureValid($data['tag']);
|
$new_tag = Tag::sanitize($data['tag']);
|
||||||
$language = $user->getActor()->getTopLanguage()->getLocale();
|
$language = $user->getActor()->getTopLanguage()->getLocale();
|
||||||
$canonical_tag = Tag::canonicalTag($new_tag, $language);
|
$canonical_tag = Tag::canonicalTag($new_tag, $language);
|
||||||
DB::persist($block_class::create([
|
DB::persist($block_class::create([
|
||||||
@ -137,15 +136,4 @@ class EditBlocked extends Controller
|
|||||||
block_class: NoteTagBlock::class,
|
block_class: NoteTagBlock::class,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function editBlockedActorTags(Request $request)
|
|
||||||
{
|
|
||||||
return self::editBlocked(
|
|
||||||
request: $request,
|
|
||||||
type_name: 'actor',
|
|
||||||
calculate_blocks: fn ($user) => ActorTagBlock::getByActorId($user->getId()),
|
|
||||||
label: _m('Add blocked people tag:'),
|
|
||||||
block_class: ActorTagBlock::class,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,10 @@ use App\Core\Modules\Plugin;
|
|||||||
use App\Core\Router\RouteLoader;
|
use App\Core\Router\RouteLoader;
|
||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
use App\Entity\Actor;
|
use App\Entity\Actor;
|
||||||
use App\Entity\ActorTag;
|
|
||||||
use App\Entity\ActorTagBlock;
|
|
||||||
use App\Entity\LocalUser;
|
use App\Entity\LocalUser;
|
||||||
use App\Entity\Note;
|
use App\Entity\Note;
|
||||||
use App\Entity\NoteTag;
|
use Component\Tag\Entity\NoteTag;
|
||||||
use App\Entity\NoteTagBlock;
|
use Component\Tag\Entity\NoteTagBlock;
|
||||||
use Functional as F;
|
use Functional as F;
|
||||||
use Plugin\TagBasedFiltering\Controller as C;
|
use Plugin\TagBasedFiltering\Controller as C;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -88,10 +86,6 @@ class TagBasedFiltering extends Plugin
|
|||||||
self::cacheKeys($actor)['note'],
|
self::cacheKeys($actor)['note'],
|
||||||
fn () => DB::dql('select ntb from note_tag_block ntb where ntb.blocker = :blocker', ['blocker' => $actor->getId()]),
|
fn () => DB::dql('select ntb from note_tag_block ntb where ntb.blocker = :blocker', ['blocker' => $actor->getId()]),
|
||||||
);
|
);
|
||||||
$blocked_actor_tags = Cache::get(
|
|
||||||
self::cacheKeys($actor)['actor'],
|
|
||||||
fn () => DB::dql('select atb from actor_tag_block atb where atb.blocker = :blocker', ['blocker' => $actor->getId()]),
|
|
||||||
);
|
|
||||||
|
|
||||||
$notes = F\reject(
|
$notes = F\reject(
|
||||||
$notes,
|
$notes,
|
||||||
@ -102,10 +96,6 @@ class TagBasedFiltering extends Plugin
|
|||||||
NoteTag::getByNoteId($n->getId()),
|
NoteTag::getByNoteId($n->getId()),
|
||||||
fn ($nt) => NoteTagBlock::checkBlocksNoteTag($nt, $blocked_note_tags),
|
fn ($nt) => NoteTagBlock::checkBlocksNoteTag($nt, $blocked_note_tags),
|
||||||
)
|
)
|
||||||
|| F\some(
|
|
||||||
ActorTag::getByActorId($n->getActor()->getId()),
|
|
||||||
fn ($at) => ActorTagBlock::checkBlocksActorTag($at, $blocked_actor_tags),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -122,12 +112,6 @@ class TagBasedFiltering extends Plugin
|
|||||||
'id' => 'settings-muting-note-tags',
|
'id' => 'settings-muting-note-tags',
|
||||||
'controller' => C\EditBlocked::editBlockedNoteTags($request),
|
'controller' => C\EditBlocked::editBlockedNoteTags($request),
|
||||||
];
|
];
|
||||||
$tabs[] = [
|
|
||||||
'title' => 'Muted people tags',
|
|
||||||
'desc' => 'Edit your muted people tags',
|
|
||||||
'id' => 'settings-muting-actor-tags',
|
|
||||||
'controller' => C\EditBlocked::editBlockedActorTags($request),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
@ -142,12 +142,6 @@ class Activity extends Entity
|
|||||||
return DB::findOneBy($this->getObjectType(), ['id' => $this->getObjectId()]);
|
return DB::findOneBy($this->getObjectType(), ['id' => $this->getObjectId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNotificationTargetIdsFromActorTags(): array
|
|
||||||
{
|
|
||||||
$actor_circles = $this->getActor()->getActorCircles();
|
|
||||||
return F\flat_map($actor_circles, fn ($circle) => $circle->getSubscribedActors());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Who should be notified about this object?
|
* Who should be notified about this object?
|
||||||
*
|
*
|
||||||
@ -157,13 +151,6 @@ class Activity extends Entity
|
|||||||
{
|
{
|
||||||
$target_ids = [];
|
$target_ids = [];
|
||||||
|
|
||||||
// Actor Circles
|
|
||||||
if (\array_key_exists('actor_circle', $ids_already_known)) {
|
|
||||||
array_push($target_ids, ...$ids_already_known['actor_circle']);
|
|
||||||
} else {
|
|
||||||
array_push($target_ids, ...$this->getNotificationTargetIdsFromActorTags());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
if (\array_key_exists('notification_activity', $ids_already_known)) {
|
if (\array_key_exists('notification_activity', $ids_already_known)) {
|
||||||
array_push($target_ids, ...$ids_already_known['notification_activity']);
|
array_push($target_ids, ...$ids_already_known['notification_activity']);
|
||||||
|
@ -257,7 +257,7 @@ class Actor extends Entity
|
|||||||
'id' => "actor-id-{$actor_id}",
|
'id' => "actor-id-{$actor_id}",
|
||||||
'nickname' => "actor-nickname-id-{$actor_id}",
|
'nickname' => "actor-nickname-id-{$actor_id}",
|
||||||
'fullname' => "actor-fullname-id-{$actor_id}",
|
'fullname' => "actor-fullname-id-{$actor_id}",
|
||||||
'tags' => \is_null($other) ? "actor-tags-{$actor_id}" : "actor-tags-{$actor_id}-by-{$other}", // $other is $context_id
|
'self-tags' => "actor-self-tags-{$actor_id}",
|
||||||
'circles' => "actor-circles-{$actor_id}",
|
'circles' => "actor-circles-{$actor_id}",
|
||||||
'subscriber' => "subscriber-{$actor_id}",
|
'subscriber' => "subscriber-{$actor_id}",
|
||||||
'subscribed' => "subscribed-{$actor_id}",
|
'subscribed' => "subscribed-{$actor_id}",
|
||||||
@ -309,62 +309,20 @@ class Actor extends Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags attributed to self, shortcut function for increased legibility
|
* @return array ActorTag[] Self Tag Circles of which this actor is a member
|
||||||
*
|
|
||||||
* @return ActorTag[] resulting lists
|
|
||||||
*/
|
*/
|
||||||
public function getSelfTags(bool $_test_force_recompute = false): array
|
public function getSelfTags(): array
|
||||||
{
|
{
|
||||||
return $this->getOtherTags(context: $this->getId(), _test_force_recompute: $_test_force_recompute);
|
return Cache::getList(
|
||||||
|
self::cacheKeys($this->getId())['self-tags'],
|
||||||
|
fn() => DB::findBy('actor_tag', ['tagger' => $this->getId(), 'tagged' => $this->getId()], order_by: ['modified' => 'DESC']),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get tags that other people put on this actor, in reverse-chron order
|
* @return array ActorCircle[]
|
||||||
*
|
|
||||||
* @param null|Actor|int $context 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 $context attributed to $this
|
|
||||||
* @param null|int $offset Offset from latest
|
|
||||||
* @param null|int $limit Max number to get
|
|
||||||
*
|
|
||||||
* @return ActorTag[] resulting lists
|
|
||||||
*/
|
*/
|
||||||
public function getOtherTags(self|int|null $context = null, ?int $offset = null, ?int $limit = null, bool $_test_force_recompute = false): array
|
public function getCircles(): array
|
||||||
{
|
|
||||||
if (\is_null($context)) {
|
|
||||||
return Cache::getList(
|
|
||||||
self::cacheKeys($this->getId())['tags'],
|
|
||||||
fn() => DB::dql(
|
|
||||||
<<< 'EOQ'
|
|
||||||
SELECT tag
|
|
||||||
FROM actor_tag tag
|
|
||||||
WHERE tag.tagged = :id
|
|
||||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
|
||||||
EOQ,
|
|
||||||
['id' => $this->getId()],
|
|
||||||
options: ['offset' => $offset, 'limit' => $limit],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$context_id = \is_int($context) ? $context : $context->getId();
|
|
||||||
return Cache::getList(
|
|
||||||
self::cacheKeys($this->getId(), $context_id)['tags'],
|
|
||||||
fn() => DB::dql(
|
|
||||||
<<< 'EOQ'
|
|
||||||
SELECT tag
|
|
||||||
FROM actor_tag tag
|
|
||||||
WHERE tag.tagged = :tagged_id AND tag.tagger = :tagger_id
|
|
||||||
ORDER BY tag.modified DESC, tag.tagged DESC
|
|
||||||
EOQ,
|
|
||||||
['tagged_id' => $this->getId(), 'tagger_id' => $context_id],
|
|
||||||
options: ['offset' => $offset, 'limit' => $limit],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getActorCircles()
|
|
||||||
{
|
{
|
||||||
return Cache::getList(
|
return Cache::getList(
|
||||||
self::cacheKeys($this->getId())['circles'],
|
self::cacheKeys($this->getId())['circles'],
|
||||||
@ -410,6 +368,24 @@ class Actor extends Entity
|
|||||||
EOF, ['self' => $this->getId()]);
|
EOF, ['self' => $this->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSubscriptionsUrl(): string
|
||||||
|
{
|
||||||
|
if ($this->getIsLocal()) {
|
||||||
|
return Router::url('actor_subscriptions_nickname', ['nickname' => $this->getNickname()]);
|
||||||
|
} else {
|
||||||
|
return Router::url('actor_subscriptions_id', ['id' => $this->getId()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubscribersUrl(): string
|
||||||
|
{
|
||||||
|
if ($this->getIsLocal()) {
|
||||||
|
return Router::url('actor_subscribers_nickname', ['nickname' => $this->getNickname()]);
|
||||||
|
} else {
|
||||||
|
return Router::url('actor_subscribers_id', ['id' => $this->getId()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve an ambiguous nickname reference, checking in following order:
|
* Resolve an ambiguous nickname reference, checking in following order:
|
||||||
* - Actors that $sender subscribes to
|
* - Actors that $sender subscribes to
|
||||||
@ -428,9 +404,9 @@ class Actor extends Entity
|
|||||||
self::cacheKeys($this->getId(), $nickname)['relative-nickname'],
|
self::cacheKeys($this->getId(), $nickname)['relative-nickname'],
|
||||||
fn () => DB::dql(
|
fn () => DB::dql(
|
||||||
<<<'EOF'
|
<<<'EOF'
|
||||||
select a from actor a where
|
SELECT a FROM actor AS a WHERE
|
||||||
a.id in (select fa.subscribed_id 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 sa.subscribed_id FROM subscription sa JOIN actor aa WITH sa.subscribed_id = aa.id WHERE sa.subscriber_id = :actor_id AND aa.nickname = :nickname) OR
|
||||||
a.id in (select fb.subscriber_id from subscription fb join actor ab with fb.subscriber = ab.id where fb.subscribed = :actor_id and ab.nickname = :nickname) or
|
a.id IN (SELECT sb.subscriber_id FROM subscription sb JOIN actor ab WITH sb.subscriber_id = ab.id WHERE sb.subscribed_id = :actor_id AND ab.nickname = :nickname) OR
|
||||||
a.nickname = :nickname
|
a.nickname = :nickname
|
||||||
EOF,
|
EOF,
|
||||||
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
['nickname' => $nickname, 'actor_id' => $this->getId()],
|
||||||
|
Loading…
Reference in New Issue
Block a user