From 2d5fac7a89bacd2d1ad5182b62fabfbbdb2cd7ee Mon Sep 17 00:00:00 2001 From: Diogo Peralta Cordeiro Date: Wed, 16 Feb 2022 06:56:59 +0000 Subject: [PATCH] [COMPONENT][Notification] Re-introduce the concept of note attention Minor refactoring and bug fixing --- components/Blog/Controller/Post.php | 11 ++- components/Notification/Entity/Attention.php | 84 ++++++++++++++++++++ components/Posting/Posting.php | 61 +++++++------- plugins/RepeatNote/RepeatNote.php | 2 +- src/Entity/Note.php | 2 +- 5 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 components/Notification/Entity/Attention.php diff --git a/components/Blog/Controller/Post.php b/components/Blog/Controller/Post.php index ca5c655049..6a4056c3ff 100644 --- a/components/Blog/Controller/Post.php +++ b/components/Blog/Controller/Post.php @@ -72,6 +72,9 @@ class Post extends Controller ]; Event::handle('PostingAvailableContentTypes', [&$available_content_types]); + if (!is_int($this->int('in'))) { + throw new \InvalidArgumentException('You must specify an In group/org.'); + } $context_actor = Actor::getById($this->int('in')); if (!$context_actor->isGroup()) { throw new \InvalidArgumentException('Only group blog posts are supported for now.'); @@ -135,18 +138,14 @@ class Post extends Controller $extra_args = []; Event::handle('AddExtraArgsToNoteContent', [$request, $actor, $data, &$extra_args, $form_params, $form]); - if (\array_key_exists('in', $data) && $data['in'] !== 'public') { - $target = $data['in']; - } - Posting::storeLocalNote( actor: $actor, content: $data['content'], content_type: $content_type, locale: $data['language'], scope: VisibilityScope::from($data['visibility']), - target: $target ?? null, - reply_to_id: $data['reply_to_id'], + targets: [(int)$data['in']], + reply_to: $data['reply_to_id'], attachments: $data['attachments'], process_note_content_extra_args: $extra_args, ); diff --git a/components/Notification/Entity/Attention.php b/components/Notification/Entity/Attention.php new file mode 100644 index 0000000000..9f05f7841c --- /dev/null +++ b/components/Notification/Entity/Attention.php @@ -0,0 +1,84 @@ +. +// }}} + +namespace Component\Notification\Entity; + +use App\Core\Entity; + +/** + * Entity for note attentions + * + * @category DB + * @package GNUsocial + * + * @author Diogo Peralta Cordeiro <@diogo.site> + * @copyright 2022 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class Attention extends Entity +{ + // {{{ Autocode + // @codeCoverageIgnoreStart + private int $note_id; + private int $target_id; + + public function setNoteId(int $note_id): self + { + $this->note_id = $note_id; + return $this; + } + + public function getNoteId(): int + { + return $this->note_id; + } + + public function setTargetId(int $target_id): self + { + $this->target_id = $target_id; + return $this; + } + + public function getTargetId(): int + { + return $this->target_id; + } + + // @codeCoverageIgnoreEnd + // }}} Autocode + + public static function schemaDef(): array + { + return [ + 'name' => 'note_attention', + 'description' => 'Note attentions to actors (that are not a mention)', + 'fields' => [ + 'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'note_id to give attention'], + 'target_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'actor_id for feed receiver'], + ], + 'primary key' => ['note_id', 'target_id'], + 'indexes' => [ + 'attention_note_id_idx' => ['note_id'], + 'attention_target_id_idx' => ['target_id'], + ], + ]; + } +} diff --git a/components/Posting/Posting.php b/components/Posting/Posting.php index 3a76381e4f..a5d1ac8acd 100644 --- a/components/Posting/Posting.php +++ b/components/Posting/Posting.php @@ -28,6 +28,8 @@ use App\Core\DB\DB; use App\Core\Event; use App\Core\Form; use App\Core\GSFile; +use App\Entity\NoteType; +use Component\Notification\Entity\Attention; use function App\Core\I18n\_m; use App\Core\Modules\Component; use App\Core\Router\Router; @@ -179,8 +181,8 @@ class Posting extends Component content_type: $content_type, locale: $data['language'], scope: VisibilityScope::from($data['visibility']), - target: $target ?? null, - reply_to_id: $data['reply_to_id'], + targets: isset($target) ? [$target] : [], + reply_to: $data['reply_to_id'], attachments: $data['attachments'], process_note_content_extra_args: $extra_args, ); @@ -213,39 +215,41 @@ class Posting extends Component * $actor_id, possibly as a reply to note $reply_to and with flag * $is_local. Sanitizes $content and $attachments * - * @param Actor $actor The Actor responsible for the creation of this Note - * @param null|string $content The raw text content sent via Posting form - * @param string $content_type Indicating one of the various supported text format (Plain Text, Markdown, LaTeX...) - * @param null|string $locale Note's written text language, set by the default Actor language or upon filling Posting's form - * @param null|VisibilityScope $scope The scope of this Note - * @param null|Actor|int $target Filled by PostingFillTargetChoices, representing an Actor in its many forms to be targeted by this Note - * @param null|int $reply_to_id The soon-to-be Note parent's id, if it's a Reply itself - * @param array $attachments Array of UploadedFile to be stored as GSFiles associated to this note - * @param array $processed_attachments Array of [Attachment, Attachment's name] to be associated to this $actor and Note - * @param array $process_note_content_extra_args Extra arguments for the event ProcessNoteContent - * @param bool $notify True if the newly created Note activity should be passed on as a Notification - * @param null|string $rendered The Note's content post RenderNoteContent event, which sanitizes and processes the raw content sent - * + * @param Actor $actor The Actor responsible for the creation of this Note + * @param null|string $content The raw text content + * @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...) + * @param null|string $locale Note's written text language, set by the default Actor language or upon filling + * @param null|VisibilityScope $scope The visibility of this Note + * @param array $targets Actor|int[]: In Group/To Person or Bot, registers an attention between note and target + * @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself + * @param array $attachments UploadedFile[] to be stored as GSFiles associated to this note + * @param array $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note + * @param array $process_note_content_extra_args Extra arguments for the event ProcessNoteContent + * @param bool $notify True if the newly created Note activity should be passed on as a Notification + * @param null|string $rendered The Note's content post RenderNoteContent event, which sanitizes and processes the raw content sent + * @param string $source The source of this Note + * @return Note * @throws ClientException * @throws DuplicateFoundException * @throws ServerException */ public static function storeLocalNote( - Actor $actor, - ?string $content, - string $content_type, - ?string $locale = null, + Actor $actor, + ?string $content, + string $content_type, + ?string $locale = null, ?VisibilityScope $scope = null, - null|Actor|int $target = null, - ?int $reply_to_id = null, - array $attachments = [], - array $processed_attachments = [], - array $process_note_content_extra_args = [], - bool $notify = true, - ?string $rendered = null, - string $source = 'web', + array $targets = [], + null|int|Note $reply_to = null, + array $attachments = [], + array $processed_attachments = [], + array $process_note_content_extra_args = [], + bool $notify = true, + ?string $rendered = null, + string $source = 'web', ): Note { $scope ??= VisibilityScope::EVERYWHERE; // TODO: If site is private, default to LOCAL + $reply_to_id = is_null($reply_to) ? null : (is_int($reply_to) ? $reply_to : $reply_to->getId()); $mentions = []; if (\is_null($rendered) && !empty($content)) { Event::handle('RenderNoteContent', [$content, $content_type, &$rendered, $actor, $locale, &$mentions]); @@ -304,8 +308,9 @@ class Posting extends Component ]); DB::persist($activity); - if (!\is_null($target)) { + foreach ($targets as $target) { $target = \is_int($target) ? Actor::getById($target) : $target; + DB::persist(Attention::create(['note_id' => $note->getId(), 'target_id' => $target->getId()])); $mentions[] = [ 'mentioned' => [$target], 'type' => match ($target->getType()) { diff --git a/plugins/RepeatNote/RepeatNote.php b/plugins/RepeatNote/RepeatNote.php index 7e223f26c9..ace7e8ff26 100644 --- a/plugins/RepeatNote/RepeatNote.php +++ b/plugins/RepeatNote/RepeatNote.php @@ -86,7 +86,7 @@ class RepeatNote extends NoteHandlerPlugin content_type: $note->getContentType(), locale: \is_null($lang_id = $note->getLanguageId()) ? null : Language::getById($lang_id)->getLocale(), // If it's a repeat, the reply_to should be to the original, conversation ought to be the same - reply_to_id: $note->getReplyTo(), + reply_to: $note->getReplyTo(), processed_attachments: $note->getAttachmentsWithTitle(), notify: false, rendered: $note->getRendered(), diff --git a/src/Entity/Note.php b/src/Entity/Note.php index 3203f71d4c..e1881fb764 100644 --- a/src/Entity/Note.php +++ b/src/Entity/Note.php @@ -210,7 +210,7 @@ class Note extends Entity return $this->language_id; } - public function setType(VisibilityScope|int $type): self + public function setType(NoteType|int $type): self { $this->type = is_int($type) ? $type : $type->value; return $this;