@@ -137,28 +137,6 @@ class Conversation extends Component | |||
return Event::next; | |||
} | |||
/** | |||
* Posting event to add extra information to Component\Posting form data | |||
* | |||
* @param array $data Transport data to be filled with reply_to_id | |||
* | |||
* @throws \App\Util\Exception\ClientException | |||
* @throws \App\Util\Exception\NoSuchNoteException | |||
* | |||
* @return bool EventHook | |||
*/ | |||
public function onPostingModifyData(Request $request, Actor $actor, array &$data): bool | |||
{ | |||
$data['reply_to_id'] = $request->get('_route') === 'conversation_reply_to' && $request->query->has('reply_to_id') | |||
? $request->query->getInt('reply_to_id') | |||
: null; | |||
if (!\is_null($data['reply_to_id'])) { | |||
Note::ensureCanInteract(Note::getById($data['reply_to_id']), $actor); | |||
} | |||
return Event::next; | |||
} | |||
/** | |||
* Append on note information about user actions. | |||
* | |||
@@ -194,6 +172,22 @@ class Conversation extends Component | |||
return Event::next; | |||
} | |||
private function getReplyToIdFromRequest(Request $request): ?int | |||
{ | |||
if (!\is_array($request->get('post_note')) || !\array_key_exists('_next', $request->get('post_note'))) { | |||
return null; | |||
} | |||
$next = parse_url($request->get('post_note')['_next']); | |||
if (!\array_key_exists('query', $next)) { | |||
return null; | |||
} | |||
parse_str($next['query'], $query); | |||
if (!\array_key_exists('reply_to_id', $query)) { | |||
return null; | |||
} | |||
return (int) $query['reply_to_id']; | |||
} | |||
/** | |||
* Informs **\App\Component\Posting::onAppendRightPostingBlock**, of the **current page context** in which the given | |||
* Actor is in. This is valuable when posting within a group route, allowing \App\Component\Posting to create a | |||
@@ -206,7 +200,7 @@ class Conversation extends Component | |||
*/ | |||
public function onPostingGetContextActor(Request $request, Actor $actor, ?Actor &$context_actor): bool | |||
{ | |||
$to_note_id = $request->query->get('reply_to_id'); | |||
$to_note_id = $this->getReplyToIdFromRequest($request); | |||
if (!\is_null($to_note_id)) { | |||
// Getting the actor itself | |||
$context_actor = Actor::getById(Note::getById((int) $to_note_id)->getActorId()); | |||
@@ -215,6 +209,27 @@ class Conversation extends Component | |||
return Event::next; | |||
} | |||
/** | |||
* Posting event to add extra information to Component\Posting form data | |||
* | |||
* @param array $data Transport data to be filled with reply_to_id | |||
* | |||
* @throws \App\Util\Exception\ClientException | |||
* @throws \App\Util\Exception\NoSuchNoteException | |||
* | |||
* @return bool EventHook | |||
*/ | |||
public function onPostingModifyData(Request $request, Actor $actor, array &$data): bool | |||
{ | |||
$to_note_id = $this->getReplyToIdFromRequest($request); | |||
if (!\is_null($to_note_id)) { | |||
Note::ensureCanInteract(Note::getById($to_note_id), $actor); | |||
$data['reply_to_id'] = $to_note_id; | |||
} | |||
return Event::next; | |||
} | |||
/** | |||
* Add minimal Note card to RightPanel template | |||
*/ | |||
@@ -85,13 +85,21 @@ class Group extends Component | |||
*/ | |||
private function getGroupFromContext(Request $request): ?Actor | |||
{ | |||
if (str_starts_with($request->get('_route'), 'group_actor_view_')) { | |||
if (!\is_null($id = $request->get('id'))) { | |||
return Actor::getById((int) $id); | |||
} | |||
if (!\is_null($nickname = $request->get('nickname'))) { | |||
return LocalGroup::getActorByNickname($nickname); | |||
if (\is_array($request->get('post_note')) && \array_key_exists('_next', $request->get('post_note'))) { | |||
$next = parse_url($request->get('post_note')['_next']); | |||
$match = Router::match($next['path']); | |||
$route = $match['_route']; | |||
$identifier = $match['id'] ?? $match['nickname'] ?? null; | |||
} else { | |||
$route = $request->get('_route'); | |||
$identifier = $request->get('id') ?? $request->get('nickname'); | |||
} | |||
if (str_starts_with($route, 'group_actor_view_')) { | |||
switch ($route) { | |||
case 'group_actor_view_nickname': | |||
return LocalGroup::getActorByNickname($identifier); | |||
case 'group_actor_view_id': | |||
return Actor::getById($identifier); | |||
} | |||
} | |||
return null; | |||
@@ -53,7 +53,7 @@ class Posting extends Controller | |||
locale: $data['language'], | |||
scope: VisibilityScope::from($data['visibility']), | |||
targets: isset($target) ? [$target] : [], | |||
reply_to: $data['reply_to_id'], | |||
reply_to: \array_key_exists('reply_to_id', $data) ? $data['reply_to_id'] : null, | |||
attachments: $data['attachments'], | |||
process_note_content_extra_args: $extra_args, | |||
); | |||
@@ -37,7 +37,6 @@ use function App\Core\I18n\_m; | |||
use App\Core\Router\Router; | |||
use App\Util\Common; | |||
use App\Util\Exception\ClientException; | |||
use App\Util\Exception\RedirectException; | |||
use App\Util\Exception\ServerException; | |||
use App\Util\Formatting; | |||
use Symfony\Component\Form\Extension\Core\Type\HiddenType; | |||
@@ -92,7 +91,7 @@ abstract class Form | |||
* Create a form with the given associative array $form as fields | |||
* | |||
* If the current request has a GET parameter `next`, adds `_next` hidden. Default values gotten | |||
* from the request, but can be overriden by `$extra_data['_next']` | |||
* from the request, but can be overridden by `$extra_data['_next']` | |||
*/ | |||
public static function create( | |||
array $form, | |||
@@ -147,15 +146,16 @@ abstract class Form | |||
} | |||
/** | |||
* Handle the full life cycle of a form. Creates it with @param array $form_definition | |||
* Handle the full life cycle of a form | |||
* | |||
* @param array $extra_args If specified, is used as $target->set($data[$key], $extra_args[$key]) | |||
* @param array $form_definition Creates it with a definition | |||
* @param null|object $target Must be a persistent DB object | |||
* @param array $extra_args If specified, is used as $target->set($data[$key], $extra_args[$key]) | |||
* | |||
* @throws RedirectException | |||
* @throws \Doctrine\ORM\ORMException | |||
* @throws ServerException | |||
* | |||
* @see | |||
* self::create and inserts the submitted values into the database | |||
* @see self::create and inserts the submitted values into the database | |||
*/ | |||
public static function handle(array $form_definition, Request $request, ?object $target, array $extra_args = [], ?callable $before_step = null, ?callable $after_step = null, array $create_args = [], ?SymfForm $testing_only_form = null): mixed | |||
{ | |||
@@ -204,7 +204,6 @@ abstract class Form | |||
// @codeCoverageIgnoreEnd | |||
} | |||
DB::merge($target); | |||
DB::flush(); | |||
} | |||
} | |||
@@ -216,6 +215,9 @@ abstract class Form | |||
* Force a redirect to the `_next` form field, which is either a page to go after filling a | |||
* form, or the page where the form was (given we use form actions). This prevents the browser | |||
* from attempting to resubmit the form when the user merely ment to refresh the page | |||
* | |||
* @throws ClientException | |||
* @throws ServerException | |||
*/ | |||
public static function forceRedirect(SymfFormInterface $form, Request $request): RedirectResponse | |||
{ | |||
@@ -227,7 +229,7 @@ abstract class Form | |||
$user = Common::user(); | |||
$user_id = !\is_null($user) ? $user->getId() : '(not logged in)'; | |||
Log::warning("Suspicious activity: User with ID {$user_id} submitted a form where the `_next` parameter is not a valid local URL ({$next})"); | |||
throw new ClientException(_m('Invalid form submission'), previous: $e); | |||
throw new ClientException(_m('Invalid form submission')); | |||
} | |||
} | |||
} |