Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (warning)
75.00%
3 / 4
CRAP
94.74% covered (success)
94.74%
36 / 38
Form
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (warning)
75.00%
3 / 4
22.07
94.74% covered (success)
94.74%
36 / 38
 setFactory
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 create
0.00% covered (danger)
0.00%
0 / 1
12.14
90.00% covered (success)
90.00%
18 / 20
 isRequired
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 handle
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
15 / 15
1<?php
2
3// {{{ License
4// This file is part of GNU social - https://www.gnu.org/software/social
5//
6// GNU social is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Affero General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// GNU social is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU Affero General Public License for more details.
15//
16// You should have received a copy of the GNU Affero General Public License
17// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
18// }}}
19
20/**
21 * Convert a Form from our declarative to Symfony's representation
22 *
23 * @package  GNUsocial
24 * @category Wrapper
25 *
26 * @author    Hugo Sales <hugo@hsal.es>
27 * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
28 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
29 */
30
31namespace App\Core;
32
33use App\Core\DB\DB;
34use App\Util\Exception\ServerException;
35use App\Util\Formatting;
36use Symfony\Component\Form\Extension\Core\Type\SubmitType;
37use Symfony\Component\Form\Form as SymfForm;
38use Symfony\Component\Form\FormFactoryInterface;
39use Symfony\Component\HttpFoundation\Request;
40
41/**
42 * This class converts our own form representation to Symfony's
43 *
44 * Example:
45 * ```
46 * $form = Form::create([
47 * ['content',     TextareaType::class, ['label' => ' ', 'data' => '', 'attr' => ['placeholder' => _m($placeholder_string[$rand_key])]]],
48 * ['attachments', FileType::class,     ['label' => ' ', 'data' => null, 'multiple' => true, 'required' => false]],
49 * ['visibility',  ChoiceType::class,   ['label' => _m('Visibility:'), 'expanded' => true, 'data' => 'public', 'choices' => [_m('Public') => 'public', _m('Instance') => 'instance', _m('Private') => 'private']]],
50 * ['to',          ChoiceType::class,   ['label' => _m('To:'), 'multiple' => true, 'expanded' => true, 'choices' => $to_tags]],
51 * ['post',        SubmitType::class,   ['label' => _m('Post')]],
52 * ]);
53 * ```
54 * turns into
55 * ```
56 * \Symfony\Component\Form\Form {
57 * config: Symfony\Component\Form\FormBuilder { ... }
58 * ...
59 * children: Symfony\Component\Form\Util\OrderedHashMap {
60 * elements: array:5 [
61 * "content" => Symfony\Component\Form\Form { ... }
62 * "attachments" => Symfony\Component\Form\Form { ... }
63 * "visibility" => Symfony\Component\Form\Form { ... }
64 * "to" => Symfony\Component\Form\Form { ... }
65 * "post" => Symfony\Component\Form\SubmitButton { ... }
66 * ]
67 * ...
68 * }
69 * ...
70 * }
71 * ```
72 */
73abstract class Form
74{
75    private static ?FormFactoryInterface $form_factory;
76
77    public static function setFactory($ff): void
78    {
79        self::$form_factory = $ff;
80    }
81
82    /**
83     * Create a form with the given associative array $form as fields
84     */
85    public static function create(array $form,
86                                  ?object $target = null,
87                                  array $extra_data = [],
88                                  string $type = 'Symfony\Component\Form\Extension\Core\Type\FormType',
89                                  array $form_options = []): SymfForm
90    {
91        $name = $form[array_key_last($form)][0];
92        $fb   = self::$form_factory->createNamedBuilder($name, $type, data: null, options: array_merge($form_options, ['translation_domain' => false]));
93        foreach ($form as [$key, $class, $options]) {
94            if ($class == SubmitType::class && in_array($key, ['save', 'publish', 'post'])) {
95                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");
96                throw new ServerException($m);
97            }
98            if ($target != null && empty($options['data']) && (strstr($key, 'password') == false) && $class != SubmitType::class) {
99                if (isset($extra_data[$key])) {
100                    // @codeCoverageIgnoreStart
101                    $options['data'] = $extra_data[$key];
102                // @codeCoverageIgnoreEnd
103                } else {
104                    $method = 'get' . ucfirst(Formatting::snakeCaseToCamelCase($key));
105                    if (method_exists($target, $method)) {
106                        $options['data'] = $target->{$method}();
107                    }
108                }
109            }
110            unset($options['hide']);
111            if (isset($options['transformer'])) {
112                $transformer = $options['transformer'];
113                unset($options['transformer']);
114            }
115            $fb->add($key, $class, $options);
116            if (isset($transformer)) {
117                $fb->get($key)->addModelTransformer(new $transformer());
118                unset($transformer);
119            }
120        }
121        return $fb->getForm();
122    }
123
124    /**
125     * Whether the given $field of $form has the `required` property
126     * set, defaults to true
127     */
128    public static function isRequired(array $form, string $field): bool
129    {
130        return $form[$field][2]['required'] ?? true;
131    }
132
133    /**
134     * Handle the full life cycle of a form. Creates it with @see
135     * self::create and inserts the submitted values into the database
136     */
137    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)
138    {
139        $form = $testing_only_form ?? self::create($form_definition, $target, ...$create_args);
140
141        $form->handleRequest($request);
142        if ($form->isSubmitted() && $form->isValid()) {
143            $data = $form->getData();
144            if ($target == null) {
145                return $data;
146            }
147            unset($data['translation_domain'], $data['save']);
148            foreach ($data as $key => $val) {
149                $method = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($key));
150                if (method_exists($target, $method)) {
151                    if (isset($extra_args[$key])) {
152                        // @codeCoverageIgnoreStart
153                        $target->{$method}($val, $extra_args[$key]);
154                    // @codeCoverageIgnoreEnd
155                    } else {
156                        $target->{$method}($val);
157                    }
158                }
159            }
160            if (isset($extra_step)) {
161                // @codeCoverageIgnoreStart
162                $extra_step($data, $extra_args);
163                // @codeCoverageIgnoreEnd
164            }
165            DB::flush();
166        }
167        return $form;
168    }
169}