3 namespace Drupal\views_ui\Form\Ajax;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\views\ViewExecutable;
10 * Provides a rearrange form for Views filters.
14 class RearrangeFilter extends ViewsFormBase {
17 * Constructs a new RearrangeFilter object.
19 public function __construct($type = NULL) {
20 $this->setType($type);
26 public function getFormKey() {
27 return 'rearrange-filter';
33 public function getFormId() {
34 return 'views_ui_rearrange_filter_form';
40 public function buildForm(array $form, FormStateInterface $form_state) {
41 $view = $form_state->get('view');
42 $display_id = $form_state->get('display_id');
45 $types = ViewExecutable::getHandlerTypes();
46 $executable = $view->getExecutable();
47 if (!$executable->setDisplay($display_id)) {
48 $form['markup'] = ['#markup' => $this->t('Invalid display id @display', ['@display' => $display_id])];
51 $display = $executable->displayHandlers->get($display_id);
52 $form['#title'] = Html::escape($display->display['display_title']) . ': ';
53 $form['#title'] .= $this->t('Rearrange @type', ['@type' => $types[$type]['ltitle']]);
54 $form['#section'] = $display_id . 'rearrange-item';
56 if ($display->defaultableSections($types[$type]['plural'])) {
57 $section = $types[$type]['plural'];
58 $form_state->set('section', $section);
59 views_ui_standard_display_dropdown($form, $form_state, $section);
62 if (!empty($view->form_cache)) {
63 $groups = $view->form_cache['groups'];
64 $handlers = $view->form_cache['handlers'];
67 $groups = $display->getOption('filter_groups');
68 $handlers = $display->getOption($types[$type]['plural']);
72 // Get relationship labels
74 foreach ($display->getHandlers('relationship') as $id => $handler) {
75 $relationships[$id] = $handler->adminLabel();
81 * Filter groups is an array that contains:
83 * 'operator' => 'and' || 'or',
85 * $group_id => 'and' || 'or',
90 $grouping = count(array_keys($groups['groups'])) > 1;
92 $form['filter_groups']['#tree'] = TRUE;
93 $form['filter_groups']['operator'] = [
96 'AND' => $this->t('And'),
97 'OR' => $this->t('Or'),
99 '#default_value' => $groups['operator'],
101 'class' => ['warning-on-change'],
103 '#title' => $this->t('Operator to use on all groups'),
104 '#description' => $this->t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
105 '#access' => $grouping,
108 $form['remove_groups']['#tree'] = TRUE;
110 foreach ($groups['groups'] as $id => $group) {
111 $form['filter_groups']['groups'][$id] = [
112 '#title' => $this->t('Operator'),
115 'AND' => $this->t('And'),
116 'OR' => $this->t('Or'),
118 '#default_value' => $group,
120 'class' => ['warning-on-change'],
124 // To prevent a notice.
125 $form['remove_groups'][$id] = [];
127 $form['remove_groups'][$id] = [
129 '#value' => $this->t('Remove group @group', ['@group' => $id]),
130 '#id' => "views-remove-group-$id",
132 'class' => ['views-remove-group'],
135 '#ajax' => ['url' => NULL],
138 $group_options[$id] = $id == 1 ? $this->t('Default group') : $this->t('Group @group', ['@group' => $id]);
139 $form['#group_renders'][$id] = [];
142 $form['#group_options'] = $group_options;
143 $form['#groups'] = $groups;
144 // We don't use getHandlers() because we want items without handlers to
145 // appear and show up as 'broken' so that the user can see them.
146 $form['filters'] = ['#tree' => TRUE];
147 foreach ($handlers as $id => $field) {
148 // If the group does not exist, move the filters to the default group.
149 if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
153 $handler = $display->getHandler($type, $id);
154 if ($grouping && $handler && !$handler->canGroup()) {
155 $field['group'] = 'ungroupable';
158 // If not grouping and the handler is set ungroupable, move it back to
159 // the default group to prevent weird errors from having it be in its
161 if (!$grouping && $field['group'] == 'ungroupable') {
165 // Place this item into the proper group for rendering.
166 $form['#group_renders'][$field['group']][] = $id;
168 $form['filters'][$id]['weight'] = [
169 '#title' => t('Weight for @id', ['@id' => $id]),
170 '#title_display' => 'invisible',
171 '#type' => 'textfield',
172 '#default_value' => ++$count,
175 $form['filters'][$id]['group'] = [
176 '#title' => t('Group for @id', ['@id' => $id]),
177 '#title_display' => 'invisible',
179 '#options' => $group_options,
180 '#default_value' => $field['group'],
182 'class' => ['views-region-select', 'views-region-' . $id],
184 '#access' => $field['group'] !== 'ungroupable',
188 $name = $handler->adminLabel() . ' ' . $handler->adminSummary();
189 if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
190 $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
193 $form['filters'][$id]['name'] = [
198 $form['filters'][$id]['name'] = ['#markup' => $this->t('Broken field @id', ['@id' => $id])];
200 $form['filters'][$id]['removed'] = [
201 '#title' => t('Remove @id', ['@id' => $id]),
202 '#title_display' => 'invisible',
203 '#type' => 'checkbox',
204 '#id' => 'views-removed-' . $id,
205 '#attributes' => ['class' => ['views-remove-checkbox']],
206 '#default_value' => 0,
210 $view->getStandardButtons($form, $form_state, 'views_ui_rearrange_filter_form');
211 $form['actions']['add_group'] = [
213 '#value' => $this->t('Create new filter group'),
214 '#id' => 'views-add-group',
217 'class' => ['views-add-group'],
219 '#ajax' => ['url' => NULL],
228 public function submitForm(array &$form, FormStateInterface $form_state) {
229 $types = ViewExecutable::getHandlerTypes();
230 $view = $form_state->get('view');
231 $display = &$view->getExecutable()->displayHandlers->get($form_state->get('display_id'));
232 $remember_groups = [];
234 if (!empty($view->form_cache)) {
235 $old_fields = $view->form_cache['handlers'];
238 $old_fields = $display->getOption($types['filter']['plural']);
241 $groups = $form_state->getValue('filter_groups');
242 // Whatever button was clicked, re-calculate field information.
243 $new_fields = $order = [];
245 // Make an array with the weights
246 foreach ($form_state->getValue('filters') as $field => $info) {
247 // add each value that is a field with a weight to our list, but only if
248 // it has had its 'removed' checkbox checked.
249 if (is_array($info) && empty($info['removed'])) {
250 if (isset($info['weight'])) {
251 $order[$field] = $info['weight'];
254 if (isset($info['group'])) {
255 $old_fields[$field]['group'] = $info['group'];
256 $remember_groups[$info['group']][] = $field;
264 // Create a new list of fields in the new order.
265 foreach (array_keys($order) as $field) {
266 $new_fields[$field] = $old_fields[$field];
269 // If the #group property is set on the clicked button, that means we are
270 // either adding or removing a group, not actually updating the filters.
271 $triggering_element = $form_state->getTriggeringElement();
272 if (!empty($triggering_element['#group'])) {
273 if ($triggering_element['#group'] == 'add') {
275 $groups['groups'][] = 'AND';
278 // Renumber groups above the removed one down.
279 foreach (array_keys($groups['groups']) as $group_id) {
280 if ($group_id >= $triggering_element['#group']) {
281 $old_group = $group_id + 1;
282 if (isset($groups['groups'][$old_group])) {
283 $groups['groups'][$group_id] = $groups['groups'][$old_group];
284 if (isset($remember_groups[$old_group])) {
285 foreach ($remember_groups[$old_group] as $id) {
286 $new_fields[$id]['group'] = $group_id;
291 // If this is the last one, just unset it.
292 unset($groups['groups'][$group_id]);
297 // Update our cache with values so that cancel still works the way
299 $view->form_cache = [
300 'key' => 'rearrange-filter',
302 'handlers' => $new_fields,
305 // Return to this form except on actual Update.
306 $view->addFormToStack('rearrange-filter', $form_state->get('display_id'), 'filter');
309 // The actual update button was clicked. Remove the empty groups, and
310 // renumber them sequentially.
311 ksort($remember_groups);
312 $groups['groups'] = static::arrayKeyPlus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
313 // Change the 'group' key on each field to match. Here, $mapping is an
314 // array whose keys are the old group numbers and whose values are the new
315 // (sequentially numbered) ones.
316 $mapping = array_flip(static::arrayKeyPlus(array_keys($remember_groups)));
317 foreach ($new_fields as &$new_field) {
318 $new_field['group'] = $mapping[$new_field['group']];
321 // Write the changed handler values.
322 $display->setOption($types['filter']['plural'], $new_fields);
323 $display->setOption('filter_groups', $groups);
324 if (isset($view->form_cache)) {
325 unset($view->form_cache);
334 * Adds one to each key of an array.
336 * For example array(0 => 'foo') would be array(1 => 'foo').
338 * @param array $array
339 * The array to increment keys on.
342 * The array with incremented keys.
344 public static function arrayKeyPlus($array) {
345 $keys = array_keys($array);
346 // Sort the keys in reverse order so incrementing them doesn't overwrite any
349 foreach ($keys as $key) {
350 $array[$key + 1] = $array[$key];
353 // Sort the keys back to ascending order.