123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- <?php
- declare(strict_types = 1);
- // {{{ License
- // This file is part of GNU social - https://www.gnu.org/software/social
- //
- // GNU social is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // GNU social is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
- // }}}
- /**
- * Convert a Form from our declarative to Symfony's representation
- *
- * @package GNUsocial
- * @category Wrapper
- *
- * @author Hugo Sales <hugo@hsal.es>
- * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
- * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
- */
- namespace App\Core;
- use App\Core\DB\DB;
- use App\Util\Exception\RedirectException;
- use App\Util\Exception\ServerException;
- use App\Util\Formatting;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
- use Symfony\Component\Form\Form as SymfForm;
- use Symfony\Component\Form\FormFactoryInterface;
- use Symfony\Component\Form\FormInterface as SymfFormInterface;
- use Symfony\Component\HttpFoundation\Request;
- /**
- * This class converts our own form representation to Symfony's
- *
- * Example:
- * ```
- * $form = Form::create([
- * ['content', TextareaType::class, ['label' => ' ', 'data' => '', 'attr' => ['placeholder' => _m($placeholder_string[$rand_key])]]],
- * ['attachments', FileType::class, ['label' => ' ', 'data' => null, 'multiple' => true, 'required' => false]],
- * ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'expanded' => true, 'data' => 'public', 'choices' => [_m('Public') => 'public', _m('Instance') => 'instance', _m('Private') => 'private']]],
- * ['to', ChoiceType::class, ['label' => _m('To:'), 'multiple' => true, 'expanded' => true, 'choices' => $to_tags]],
- * ['post', SubmitType::class, ['label' => _m('Post')]],
- * ]);
- * ```
- * turns into
- * ```
- * \Symfony\Component\Form\Form {
- * config: Symfony\Component\Form\FormBuilder { ... }
- * ...
- * children: Symfony\Component\Form\Util\OrderedHashMap {
- * elements: array:5 [
- * "content" => Symfony\Component\Form\Form { ... }
- * "attachments" => Symfony\Component\Form\Form { ... }
- * "visibility" => Symfony\Component\Form\Form { ... }
- * "to" => Symfony\Component\Form\Form { ... }
- * "post" => Symfony\Component\Form\SubmitButton { ... }
- * ]
- * ...
- * }
- * ...
- * }
- * ```
- */
- abstract class Form
- {
- private static ?FormFactoryInterface $form_factory;
- public static function setFactory($ff): void
- {
- self::$form_factory = $ff;
- }
- /**
- * Create a form with the given associative array $form as fields
- */
- public static function create(
- array $form,
- ?object $target = null,
- array $extra_data = [],
- string $type = 'Symfony\Component\Form\Extension\Core\Type\FormType',
- array $form_options = [],
- ): SymfFormInterface {
- $name = $form[array_key_last($form)][0];
- $fb = self::$form_factory->createNamedBuilder($name, $type, data: null, options: array_merge($form_options, ['translation_domain' => false]));
- foreach ($form as [$key, $class, $options]) {
- if ($class == SubmitType::class && \in_array($key, ['save', 'publish', 'post'])) {
- Log::critical($m = "It's generally a bad idea to use {$key} as a form name, because it can conflict with other forms in the same page");
- throw new ServerException($m);
- }
- if ($target != null && empty($options['data']) && (mb_strstr($key, 'password') == false) && $class != SubmitType::class) {
- if (isset($extra_data[$key])) {
- // @codeCoverageIgnoreStart
- $options['data'] = $extra_data[$key];
- // @codeCoverageIgnoreEnd
- } else {
- $method = 'get' . ucfirst(Formatting::snakeCaseToCamelCase($key));
- if (method_exists($target, $method)) {
- $options['data'] = $target->{$method}();
- }
- }
- }
- unset($options['hide']);
- if (isset($options['transformer'])) {
- $transformer = $options['transformer'];
- unset($options['transformer']);
- }
- $fb->add($key, $class, $options);
- if (isset($transformer)) {
- $fb->get($key)->addModelTransformer(new $transformer());
- unset($transformer);
- }
- }
- return $fb->getForm();
- }
- /**
- * Whether the given $field of $form has the `required` property
- * set, defaults to true
- */
- public static function isRequired(array $form, string $field): bool
- {
- return $form[$field][2]['required'] ?? true;
- }
- /**
- * Handle the full life cycle of a form. Creates it with @see
- * self::create and inserts the submitted values into the database
- *
- * @throws ServerException
- */
- public static function handle(array $form_definition, Request $request, ?object $target, array $extra_args = [], ?callable $extra_step = null, array $create_args = [], ?SymfForm $testing_only_form = null): mixed
- {
- $form = $testing_only_form ?? self::create($form_definition, $target, ...$create_args);
- $form->handleRequest($request);
- if ($request->getMethod() === 'POST' && $form->isSubmitted()) {
- if (!$form->isValid()) {
- $errors = [];
- foreach ($form->all() as $child) {
- if (!$child->isValid()) {
- $errors[$child->getName()] = (string) $form[$child->getName()]->getErrors();
- }
- }
- return $errors;
- } else {
- $data = $form->getData();
- if (\is_null($target)) {
- return $data;
- }
- unset($data['translation_domain'], $data['save']);
- foreach ($data as $key => $val) {
- $method = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($key));
- if (method_exists($target, $method)) {
- if (isset($extra_args[$key])) {
- // @codeCoverageIgnoreStart
- $target->{$method}($val, $extra_args[$key]);
- // @codeCoverageIgnoreEnd
- } else {
- $target->{$method}($val);
- }
- }
- }
- if (isset($extra_step)) {
- // @codeCoverageIgnoreStart
- $extra_step($data, $extra_args);
- // @codeCoverageIgnoreEnd
- }
- DB::merge($target);
- DB::flush();
- throw new RedirectException(url: $request->getPathInfo());
- }
- }
- return $form;
- }
- }
|