Completed
Push — 3.x ( 3e834f...38b337 )
by Grégoire
03:36
created

src/Action/RetrieveAutocompleteItemsAction.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Action;
15
16
use Sonata\AdminBundle\Admin\AdminInterface;
17
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
18
use Sonata\AdminBundle\Admin\Pool;
19
use Sonata\AdminBundle\Filter\FilterInterface;
20
use Symfony\Component\Form\Form;
21
use Symfony\Component\HttpFoundation\JsonResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
25
26
final class RetrieveAutocompleteItemsAction
27
{
28
    /**
29
     * @var Pool
30
     */
31
    private $pool;
32
33
    public function __construct(Pool $pool)
34
    {
35
        $this->pool = $pool;
36
    }
37
38
    /**
39
     * Retrieve list of items for autocomplete form field.
40
     *
41
     * @throws \RuntimeException
42
     * @throws AccessDeniedException
43
     */
44
    public function __invoke(Request $request): JsonResponse
45
    {
46
        $admin = $this->pool->getInstance($request->get('admin_code'));
47
        $admin->setRequest($request);
48
        $context = $request->get('_context', '');
49
50
        if ('filter' === $context) {
51
            $admin->checkAccess('list');
52
        } elseif (!$admin->hasAccess('create') && !$admin->hasAccess('edit')) {
53
            throw new AccessDeniedException();
54
        }
55
56
        // subject will be empty to avoid unnecessary database requests and keep autocomplete function fast
57
        $admin->setSubject($admin->getNewInstance());
58
59
        if ('filter' === $context) {
60
            // filter
61
            $fieldDescription = $this->retrieveFilterFieldDescription($admin, $request->get('field'));
62
            $filterAutocomplete = $admin->getDatagrid()->getFilter($fieldDescription->getName());
63
64
            $property = $filterAutocomplete->getFieldOption('property');
65
            $callback = $filterAutocomplete->getFieldOption('callback');
66
            $minimumInputLength = $filterAutocomplete->getFieldOption('minimum_input_length', 3);
67
            $itemsPerPage = $filterAutocomplete->getFieldOption('items_per_page', 10);
68
            $reqParamPageNumber = $filterAutocomplete->getFieldOption('req_param_name_page_number', '_page');
69
            $toStringCallback = $filterAutocomplete->getFieldOption('to_string_callback');
70
            $targetAdminAccessAction = $filterAutocomplete->getFieldOption('target_admin_access_action', 'list');
71
        } else {
72
            // create/edit form
73
            $fieldDescription = $this->retrieveFormFieldDescription($admin, $request->get('field'));
74
            $formAutocomplete = $admin->getForm()->get($fieldDescription->getName());
75
76
            $formAutocompleteConfig = $formAutocomplete->getConfig();
77
            if ($formAutocompleteConfig->getAttribute('disabled')) {
78
                throw new AccessDeniedException(
79
                    'Autocomplete list can`t be retrieved because the form element is disabled or read_only.'
80
                );
81
            }
82
83
            $property = $formAutocompleteConfig->getAttribute('property');
84
            $callback = $formAutocompleteConfig->getAttribute('callback');
85
            $minimumInputLength = $formAutocompleteConfig->getAttribute('minimum_input_length');
86
            $itemsPerPage = $formAutocompleteConfig->getAttribute('items_per_page');
87
            $reqParamPageNumber = $formAutocompleteConfig->getAttribute('req_param_name_page_number');
88
            $toStringCallback = $formAutocompleteConfig->getAttribute('to_string_callback');
89
            $targetAdminAccessAction = $formAutocompleteConfig->getAttribute('target_admin_access_action');
90
        }
91
92
        $searchText = $request->get('q');
93
94
        $targetAdmin = $fieldDescription->getAssociationAdmin();
95
96
        // check user permission
97
        $targetAdmin->checkAccess($targetAdminAccessAction);
98
99
        if (mb_strlen($searchText, 'UTF-8') < $minimumInputLength) {
100
            return new JsonResponse(['status' => 'KO', 'message' => 'Too short search string.'], Response::HTTP_FORBIDDEN);
101
        }
102
103
        $targetAdmin->setFilterPersister(null);
104
        $datagrid = $targetAdmin->getDatagrid();
105
106
        if (null !== $callback) {
107
            if (!\is_callable($callback)) {
108
                throw new \RuntimeException('Callback does not contain callable function.');
109
            }
110
111
            $callback($targetAdmin, $property, $searchText);
112
        } else {
113
            if (\is_array($property)) {
114
                // multiple properties
115
                foreach ($property as $prop) {
116
                    if (!$datagrid->hasFilter($prop)) {
117
                        throw new \RuntimeException(sprintf(
118
                            'To retrieve autocomplete items,'
119
                            .' you should add filter "%s" to "%s" in configureDatagridFilters() method.',
120
                            $prop,
121
                            \get_class($targetAdmin)
122
                        ));
123
                    }
124
125
                    $filter = $datagrid->getFilter($prop);
126
                    $filter->setCondition(FilterInterface::CONDITION_OR);
127
128
                    $datagrid->setValue($filter->getFormName(), null, $searchText);
129
                }
130
            } else {
131
                if (!$datagrid->hasFilter($property)) {
132
                    throw new \RuntimeException(sprintf(
133
                        'To retrieve autocomplete items,'
134
                        .' you should add filter "%s" to "%s" in configureDatagridFilters() method.',
135
                        $property,
136
                        \get_class($targetAdmin)
137
                    ));
138
                }
139
140
                $datagrid->setValue($datagrid->getFilter($property)->getFormName(), null, $searchText);
141
            }
142
        }
143
144
        $datagrid->setValue('_per_page', null, $itemsPerPage);
145
        $datagrid->setValue('_page', null, $request->query->get($reqParamPageNumber, 1));
146
        $datagrid->buildPager();
147
148
        $pager = $datagrid->getPager();
149
150
        $items = [];
151
        $results = $pager->getResults();
152
153
        foreach ($results as $model) {
154
            if (null !== $toStringCallback) {
155
                if (!\is_callable($toStringCallback)) {
156
                    throw new \RuntimeException('Option "to_string_callback" does not contain callable function.');
157
                }
158
159
                $label = $toStringCallback($model, $property);
160
            } else {
161
                $resultMetadata = $targetAdmin->getObjectMetadata($model);
162
                $label = $resultMetadata->getTitle();
163
            }
164
165
            $items[] = [
166
                'id' => $admin->id($model),
167
                'label' => $label,
168
            ];
169
        }
170
171
        return new JsonResponse([
172
            'status' => 'OK',
173
            'more' => !$pager->isLastPage(),
174
            'items' => $items,
175
        ]);
176
    }
177
178
    /**
179
     * Retrieve the filter field description given by field name.
180
     *
181
     * @throws \RuntimeException
182
     */
183
    private function retrieveFilterFieldDescription(
184
        AdminInterface $admin,
185
        string $field
186
    ): FieldDescriptionInterface {
187
        $fieldDescription = $admin->getFilterFieldDescription($field);
188
189
        if (!$fieldDescription) {
190
            throw new \RuntimeException(sprintf('The field "%s" does not exist.', $field));
191
        }
192
193
        if (null === $fieldDescription->getTargetEntity()) {
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...face::getTargetEntity() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getTargetModel()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
194
            throw new \RuntimeException(sprintf('No associated entity with field "%s".', $field));
195
        }
196
197
        return $fieldDescription;
198
    }
199
200
    /**
201
     * Retrieve the form field description given by field name.
202
     *
203
     * @throws \RuntimeException
204
     */
205
    private function retrieveFormFieldDescription(
206
        AdminInterface $admin,
207
        string $field
208
    ): FieldDescriptionInterface {
209
        $fieldDescription = $admin->getFormFieldDescription($field);
210
211
        if (!$fieldDescription) {
212
            throw new \RuntimeException(sprintf('The field "%s" does not exist.', $field));
213
        }
214
215
        if (null === $fieldDescription->getTargetEntity()) {
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...face::getTargetEntity() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getTargetModel()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
216
            throw new \RuntimeException(sprintf('No associated entity with field "%s".', $field));
217
        }
218
219
        return $fieldDescription;
220
    }
221
}
222