diff --git a/components/Posting/Controller/Posting.php b/components/Posting/Controller/Posting.php new file mode 100644 index 0000000000..2da6960156 --- /dev/null +++ b/components/Posting/Controller/Posting.php @@ -0,0 +1,69 @@ +handleRequest($request); + if ($form->isSubmitted()) { + try { + if ($form->isValid()) { + $data = $form->getData(); + Event::handle('PostingModifyData', [$request, $actor, &$data, $form]); + + if (empty($data['content']) && empty($data['attachments'])) { + // TODO Display error: At least one of `content` and `attachments` must be provided + throw new ClientException(_m('You must enter content or provide at least one attachment to post a note.')); + } + + if (\is_null(VisibilityScope::tryFrom($data['visibility']))) { + throw new ClientException(_m('You have selected an impossible visibility.')); + } + + $extra_args = []; + Event::handle('AddExtraArgsToNoteContent', [$request, $actor, $data, &$extra_args, $form]); + + if (\array_key_exists('in', $data) && $data['in'] !== 'public') { + $target = $data['in']; + } + + \Component\Posting\Posting::storeLocalNote( + actor: $actor, + content: $data['content'], + content_type: $data['content_type'], + locale: $data['language'], + scope: VisibilityScope::from($data['visibility']), + targets: isset($target) ? [$target] : [], + reply_to: $data['reply_to_id'], + attachments: $data['attachments'], + process_note_content_extra_args: $extra_args, + ); + + return Core\Form::forceRedirect($form, $request); + } + } catch (FormSizeFileException $e) { + throw new ClientException(_m('Invalid file size given'), previous: $e); + } + } + throw new ClientException(_m('Invalid form submission')); + } +} diff --git a/components/Posting/Form/Posting.php b/components/Posting/Form/Posting.php new file mode 100644 index 0000000000..47a50b19ee --- /dev/null +++ b/components/Posting/Form/Posting.php @@ -0,0 +1,102 @@ + 'text/plain', + ]; + Event::handle('PostingAvailableContentTypes', [&$available_content_types]); + + $in_targets = []; + Event::handle('PostingFillTargetChoices', [$request, $actor, &$in_targets]); + + $context_actor = null; + Event::handle('PostingGetContextActor', [$request, $actor, &$context_actor]); + + $form_params = []; + if (!empty($in_targets)) { // @phpstan-ignore-line + // Add "none" option to the first of choices + $in_targets = array_merge([_m('Public') => 'public'], $in_targets); + // Make the context actor the first In target option + if (!\is_null($context_actor)) { + foreach ($in_targets as $it_nick => $it_id) { + if ($it_id === $context_actor->getId()) { + unset($in_targets[$it_nick]); + $in_targets = array_merge([$it_nick => $it_id], $in_targets); + break; + } + } + } + $form_params[] = ['in', ChoiceType::class, ['label' => _m('In:'), 'multiple' => false, 'expanded' => false, 'choices' => $in_targets]]; + } + + $visibility_options = [ + _m('Public') => VisibilityScope::EVERYWHERE->value, + _m('Local') => VisibilityScope::LOCAL->value, + _m('Addressee') => VisibilityScope::ADDRESSEE->value, + ]; + if (!\is_null($context_actor) && $context_actor->isGroup()) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234 + if ($actor->canModerate($context_actor)) { + if ($context_actor->getRoles() & ActorLocalRoles::PRIVATE_GROUP) { + $visibility_options = array_merge([_m('Group') => VisibilityScope::GROUP->value], $visibility_options); + } else { + $visibility_options[_m('Group')] = VisibilityScope::GROUP->value; + } + } + } + $form_params[] = ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'multiple' => false, 'expanded' => false, 'choices' => $visibility_options]]; + + $form_params[] = ['content', TextareaType::class, ['label' => _m('Content:'), 'data' => $initial_content, 'attr' => ['placeholder' => _m($placeholder)], 'constraints' => [new Length(['max' => Common::config('site', 'text_limit')])]]]; + $form_params[] = ['attachments', FileType::class, ['label' => _m('Attachments:'), 'multiple' => true, 'required' => false, 'invalid_message' => _m('Attachment not valid.')]]; + $form_params[] = FormFields::language($actor, $context_actor, label: _m('Note language'), help: _m('The selected language will be federated and added as a lang attribute, preferred language can be set up in settings')); + + if (\count($available_content_types) > 1) { + $form_params[] = ['content_type', ChoiceType::class, + [ + 'label' => _m('Text format:'), 'multiple' => false, 'expanded' => false, + 'data' => $available_content_types[array_key_first($available_content_types)], + 'choices' => $available_content_types, + ], + ]; + } else { + $form_params[] = ['content_type', HiddenType::class, ['data' => $available_content_types[array_key_first($available_content_types)]]]; + } + + Event::handle('PostingAddFormEntries', [$request, $actor, &$form_params]); + + $form_params[] = ['post_note', SubmitType::class, ['label' => _m('Post')]]; + + return Form::create($form_params, form_options: ['action' => Router::url(\Component\Posting\Posting::route)]); + } +} diff --git a/components/Posting/Posting.php b/components/Posting/Posting.php index faf63ba268..810fcfa18b 100644 --- a/components/Posting/Posting.php +++ b/components/Posting/Posting.php @@ -23,14 +23,13 @@ declare(strict_types = 1); namespace Component\Posting; -use App\Core\ActorLocalRoles; use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; -use App\Core\Form; use App\Core\GSFile; use function App\Core\I18n\_m; use App\Core\Modules\Component; +use App\Core\Router\RouteLoader; use App\Core\Router\Router; use App\Core\VisibilityScope; use App\Entity\Activity; @@ -42,7 +41,6 @@ use App\Util\Exception\ClientException; use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\RedirectException; use App\Util\Exception\ServerException; -use App\Util\Form\FormFields; use App\Util\Formatting; use App\Util\HTML; use Component\Attachment\Entity\ActorToAttachment; @@ -51,18 +49,19 @@ use Component\Conversation\Conversation; use Component\Language\Entity\Language; use Component\Notification\Entity\Attention; use Functional as F; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\FileType; -use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\Extension\Core\Type\TextareaType; -use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Validator\Constraints\Length; class Posting extends Component { + public const route = 'posting_form_action'; + + public function onAddRoute(RouteLoader $r): bool + { + $r->connect(self::route, '/form/posting', Controller\Posting::class); + return Event::next; + } + /** * HTML render event handler responsible for adding and handling * the result of adding the note submission form, only if a user is logged in @@ -79,133 +78,7 @@ class Posting extends Component return Event::next; } - $actor = $user->getActor(); - - $placeholder_strings = ['How are you feeling?', 'Have something to share?', 'How was your day?']; - Event::handle('PostingPlaceHolderString', [&$placeholder_strings]); - $placeholder = $placeholder_strings[array_rand($placeholder_strings)]; - - $initial_content = ''; - Event::handle('PostingInitialContent', [&$initial_content]); - - $available_content_types = [ - _m('Plain Text') => 'text/plain', - ]; - Event::handle('PostingAvailableContentTypes', [&$available_content_types]); - - $in_targets = []; - Event::handle('PostingFillTargetChoices', [$request, $actor, &$in_targets]); - - $context_actor = null; - Event::handle('PostingGetContextActor', [$request, $actor, &$context_actor]); - - $form_params = []; - if (!empty($in_targets)) { // @phpstan-ignore-line - // Add "none" option to the first of choices - $in_targets = array_merge([_m('Public') => 'public'], $in_targets); - // Make the context actor the first In target option - if (!\is_null($context_actor)) { - foreach ($in_targets as $it_nick => $it_id) { - if ($it_id === $context_actor->getId()) { - unset($in_targets[$it_nick]); - $in_targets = array_merge([$it_nick => $it_id], $in_targets); - break; - } - } - } - $form_params[] = ['in', ChoiceType::class, ['label' => _m('In:'), 'multiple' => false, 'expanded' => false, 'choices' => $in_targets]]; - } - - $visibility_options = [ - _m('Public') => VisibilityScope::EVERYWHERE->value, - _m('Local') => VisibilityScope::LOCAL->value, - _m('Addressee') => VisibilityScope::ADDRESSEE->value, - ]; - if (!\is_null($context_actor) && $context_actor->isGroup()) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234 - if ($actor->canModerate($context_actor)) { - if ($context_actor->getRoles() & ActorLocalRoles::PRIVATE_GROUP) { - $visibility_options = array_merge([_m('Group') => VisibilityScope::GROUP->value], $visibility_options); - } else { - $visibility_options[_m('Group')] = VisibilityScope::GROUP->value; - } - } - } - $form_params[] = ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'multiple' => false, 'expanded' => false, 'choices' => $visibility_options]]; - - $form_params[] = ['content', TextareaType::class, ['label' => _m('Content:'), 'data' => $initial_content, 'attr' => ['placeholder' => _m($placeholder)], 'constraints' => [new Length(['max' => Common::config('site', 'text_limit')])]]]; - $form_params[] = ['attachments', FileType::class, ['label' => _m('Attachments:'), 'multiple' => true, 'required' => false, 'invalid_message' => _m('Attachment not valid.')]]; - $form_params[] = FormFields::language($actor, $context_actor, label: _m('Note language'), help: _m('The selected language will be federated and added as a lang attribute, preferred language can be set up in settings')); - - if (\count($available_content_types) > 1) { - $form_params[] = ['content_type', ChoiceType::class, - [ - 'label' => _m('Text format:'), 'multiple' => false, 'expanded' => false, - 'data' => $available_content_types[array_key_first($available_content_types)], - 'choices' => $available_content_types, - ], - ]; - } - - Event::handle('PostingAddFormEntries', [$request, $actor, &$form_params]); - - $form_params[] = ['post_note', SubmitType::class, ['label' => _m('Post')]]; - $form = Form::create($form_params); - - $form->handleRequest($request); - if ($form->isSubmitted()) { - try { - if ($form->isValid()) { - $data = $form->getData(); - Event::handle('PostingModifyData', [$request, $actor, &$data, $form_params, $form]); - - if (empty($data['content']) && empty($data['attachments'])) { - // TODO Display error: At least one of `content` and `attachments` must be provided - throw new ClientException(_m('You must enter content or provide at least one attachment to post a note.')); - } - - if (\is_null(VisibilityScope::tryFrom($data['visibility']))) { - throw new ClientException(_m('You have selected an impossible visibility.')); - } - - $content_type = $data['content_type'] ?? $available_content_types[array_key_first($available_content_types)]; - $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']; - } - - self::storeLocalNote( - actor: $user->getActor(), - content: $data['content'], - content_type: $content_type, - locale: $data['language'], - scope: VisibilityScope::from($data['visibility']), - targets: isset($target) ? [$target] : [], - reply_to: $data['reply_to_id'], - attachments: $data['attachments'], - process_note_content_extra_args: $extra_args, - ); - - try { - if ($request->query->has('from')) { - $from = $request->query->get('from'); - if (str_contains($from, '#')) { - [$from, $fragment] = explode('#', $from); - } - Router::match($from); - throw new RedirectException(url: $from . (isset($fragment) ? '#' . $fragment : '')); - } - } catch (ResourceNotFoundException $e) { - // continue - } - throw new RedirectException(); - } - } catch (FormSizeFileException $e) { - throw new ClientException(_m('Invalid file size given'), previous: $e); - } - } - $res['post_form'] = $form->createView(); + $res['post_form'] = Form\Posting::create($request)->createView(); return Event::next; }