HandlerBase.php 26.4 KB
Newer Older
dawehner's avatar
dawehner committed
1
<?php
2

dawehner's avatar
dawehner committed
3
4
/**
 * @file
5
 * Definition of Drupal\views\Plugin\views\HandlerBase.
dawehner's avatar
dawehner committed
6
7
 */

8
namespace Drupal\views\Plugin\views;
dawehner's avatar
dawehner committed
9

10
use Drupal\Component\Utility\Unicode;
11
use Drupal\views\Plugin\views\display\DisplayPluginBase;
12
use Drupal\views\Plugin\views\PluginBase;
13
use Drupal\views\ViewExecutable;
14
use Drupal\Core\Database\Database;
15
use Drupal\views\Views;
dawehner's avatar
dawehner committed
16

17
abstract class HandlerBase extends PluginBase {
18

dawehner's avatar
dawehner committed
19
  /**
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
   * Where the $query object will reside:
   *
   * @var Drupal\views\Plugin\views\query\QueryPluginBase
   */
  public $query = NULL;

  /**
   * The table this handler is attached to.
   *
   * @var string
   */
  public $table;

  /**
   * The alias of the table of this handler which is used in the query.
   *
   * @var string
   */
38
  public $tableAlias;
39
40
41
42
43
44

  /**
   * When a table has been moved this property is set.
   *
   * @var string
   */
45
  public $actualTable;
46
47
48
49
50
51
52

  /**
   * The actual field in the database table, maybe different
   * on other kind of query plugins/special handlers.
   *
   * @var string
   */
53
  public $realField;
54
55

  /**
56
   * With field you can override the realField if the real field is not set.
57
58
59
60
61
62
63
64
65
66
   *
   * @var string
   */
  public $field;

  /**
   * When a field has been moved this property is set.
   *
   * @var string
   */
67
  public $actualField;
68
69
70
71
72
73
74
75

  /**
   * The relationship used for this field.
   *
   * @var string
   */
  public $relationship = NULL;

76
77
78
  /**
   * Constructs a Handler object.
   */
79
80
  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
81
82
83
    $this->is_handler = TRUE;
  }

84
  /**
85
   * Overrides \Drupal\views\Plugin\views\PluginBase::init().
dawehner's avatar
dawehner committed
86
   */
87
88
89
  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
    parent::init($view, $display, $options);

dawehner's avatar
dawehner committed
90
91
92
93
94
95
    $display_id = $this->view->current_display;
    // Check to see if this handler type is defaulted. Note that
    // we have to do a lookup because the type is singular but the
    // option is stored as the plural.

    // If the 'moved to' keyword moved our handler, let's fix that now.
96
97
    if (isset($this->actualTable)) {
      $options['table'] = $this->actualTable;
dawehner's avatar
dawehner committed
98
99
    }

100
101
    if (isset($this->actualField)) {
      $options['field'] = $this->actualField;
dawehner's avatar
dawehner committed
102
103
    }

104
105
106
    $plural = $this->definition['plugin_type'];
    if (isset($types[$plural]['plural'])) {
      $plural = $types[$plural]['plural'];
dawehner's avatar
dawehner committed
107
    }
108
    if ($this->view->display_handler->isDefaulted($plural)) {
dawehner's avatar
dawehner committed
109
110
111
      $display_id = 'default';
    }

112
    $this->unpackOptions($this->options, $options);
dawehner's avatar
dawehner committed
113
114
115
116
117
118

    // This exist on most handlers, but not all. So they are still optional.
    if (isset($options['table'])) {
      $this->table = $options['table'];
    }

119
120
121
122
123
    // Allow alliases on both fields and tables.
    if (isset($this->definition['real table'])) {
      $this->table = $this->definition['real table'];
    }

dawehner's avatar
dawehner committed
124
    if (isset($this->definition['real field'])) {
125
      $this->realField = $this->definition['real field'];
dawehner's avatar
dawehner committed
126
127
128
    }

    if (isset($this->definition['field'])) {
129
      $this->realField = $this->definition['field'];
dawehner's avatar
dawehner committed
130
131
132
133
    }

    if (isset($options['field'])) {
      $this->field = $options['field'];
134
135
      if (!isset($this->realField)) {
        $this->realField = $options['field'];
dawehner's avatar
dawehner committed
136
137
138
139
140
      }
    }

    $this->query = &$view->query;
  }
141

142
143
  protected function defineOptions() {
    $options = parent::defineOptions();
144
145
146
147
148
149

    $options['id'] = array('default' => '');
    $options['table'] = array('default' => '');
    $options['field'] = array('default' => '');
    $options['relationship'] = array('default' => 'none');
    $options['group_type'] = array('default' => 'group');
150
    $options['admin_label'] = array('default' => '', 'translatable' => TRUE);
151
152
153
154
155
156
157

    return $options;
  }

  /**
   * Return a string representing this handler's name in the UI.
   */
158
159
160
  public function adminLabel($short = FALSE) {
    if (!empty($this->options['admin_label'])) {
      $title = check_plain($this->options['admin_label']);
161
162
163
164
165
166
167
168
169
170
171
      return $title;
    }
    $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
  }

  /**
   * Shortcut to get a handler's raw field value.
   *
   * This should be overridden for handlers with formulae or other
   * non-standard fields. Because this takes an argument, fields
172
   * overriding this can just call return parent::getField($formula)
173
   */
174
  public function getField($field = NULL) {
175
176
    if (!isset($field)) {
      if (!empty($this->formula)) {
177
        $field = $this->getFormula();
178
179
      }
      else {
180
        $field = $this->tableAlias . '.' . $this->realField;
181
182
183
184
      }
    }

    // If grouping, check to see if the aggregation method needs to modify the field.
185
    if ($this->view->display_handler->useGroupBy()) {
186
      $this->view->initQuery();
187
      if ($this->query) {
188
        $info = $this->query->getAggregationInfo();
189
190
191
192
193
        if (!empty($info[$this->options['group_type']]['method'])) {
          $method = $info[$this->options['group_type']]['method'];
          if (method_exists($this->query, $method)) {
            return $this->query->$method($this->options['group_type'], $field);
          }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        }
      }
    }

    return $field;
  }

  /**
   * Sanitize the value for output.
   *
   * @param $value
   *   The value being rendered.
   * @param $type
   *   The type of sanitization needed. If not provided, check_plain() is used.
   *
   * @return string
   *   Returns the safe value.
   */
212
  public function sanitizeValue($value, $type = NULL) {
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    switch ($type) {
      case 'xss':
        $value = filter_xss($value);
        break;
      case 'xss_admin':
        $value = filter_xss_admin($value);
        break;
      case 'url':
        $value = check_url($value);
        break;
      default:
        $value = check_plain($value);
        break;
    }
    return $value;
  }

  /**
   * Transform a string by a certain method.
   *
   * @param $string
   *    The input you want to transform.
   * @param $option
   *    How do you want to transform it, possible values:
   *      - upper: Uppercase the string.
   *      - lower: lowercase the string.
   *      - ucfirst: Make the first char uppercase.
   *      - ucwords: Make each word in the string uppercase.
   *
   * @return string
   *    The transformed string.
   */
245
  protected function caseTransform($string, $option) {
246
247
248
249
250
251
252
253
254
255
    switch ($option) {
      default:
        return $string;
      case 'upper':
        return drupal_strtoupper($string);
      case 'lower':
        return drupal_strtolower($string);
      case 'ucfirst':
        return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
      case 'ucwords':
256
        if (Unicode::getStatus() == Unicode::STATUS_MULTIBYTE) {
257
258
259
260
261
262
263
264
265
266
267
          return mb_convert_case($string, MB_CASE_TITLE);
        }
        else {
          return ucwords($string);
        }
    }
  }

  /**
   * Validate the options form.
   */
268
  public function validateOptionsForm(&$form, &$form_state) { }
269
270
271
272

  /**
   * Build the options form.
   */
273
  public function buildOptionsForm(&$form, &$form_state) {
274
275
276
277
278
279
    // Some form elements belong in a fieldset for presentation, but can't
    // be moved into one because of the form_state['values'] hierarchy. Those
    // elements can add a #fieldset => 'fieldset_name' property, and they'll
    // be moved to their fieldset during pre_render.
    $form['#pre_render'][] = 'views_ui_pre_render_add_fieldset_markup';

280
    $form['admin_label'] = array(
281
282
283
      '#type' => 'textfield',
      '#title' => t('Administrative title'),
      '#description' => t('This title will be displayed on the views edit page instead of the default one. This might be useful if you have the same item twice.'),
284
      '#default_value' => $this->options['admin_label'],
285
286
287
288
      '#fieldset' => 'more',
    );

    // This form is long and messy enough that the "Administrative title" option
289
    // belongs in "more options" details at the bottom of the form.
290
    $form['more'] = array(
291
      '#type' => 'details',
292
      '#title' => t('More'),
293
      '#collapsed' => TRUE,
294
295
296
      '#weight' => 150,
    );
    // Allow to alter the default values brought into the form.
297
298
    // @todo Do we really want to keep this hook.
    \Drupal::moduleHandler()->alter('views_handler_options', $this->options, $this->view);
299
300
301
302
303
304
  }

  /**
   * Perform any necessary changes to the form values prior to storage.
   * There is no need for this function to actually store the data.
   */
305
  public function submitOptionsForm(&$form, &$form_state) { }
306
307
308
309

  /**
   * Provides the handler some groupby.
   */
310
  public function usesGroupBy() {
311
312
313
314
315
    return TRUE;
  }
  /**
   * Provide a form for aggregation settings.
   */
316
  public function buildGroupByForm(&$form, &$form_state) {
317
    $display_id = $form_state['display_id'];
318
    $types = ViewExecutable::viewsHandlerTypes();
319
320
321
322
323
    $type = $form_state['type'];
    $id = $form_state['id'];

    $form['#section'] = $display_id . '-' . $type . '-' . $id;

324
    $this->view->initQuery();
325
    $info = $this->view->query->getAggregationInfo();
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    foreach ($info as $id => $aggregate) {
      $group_types[$id] = $aggregate['title'];
    }

    $form['group_type'] = array(
      '#type' => 'select',
      '#title' => t('Aggregation type'),
      '#default_value' => $this->options['group_type'],
      '#description' => t('Select the aggregation function to use on this field.'),
      '#options' => $group_types,
    );
  }

  /**
   * Perform any necessary changes to the form values prior to storage.
   * There is no need for this function to actually store the data.
   */
343
  public function submitGroupByForm(&$form, &$form_state) {
344
345
346
347
348
349
350
351
352
    $item =& $form_state['handler']->options;

    $item['group_type'] = $form_state['values']['options']['group_type'];
  }

  /**
   * If a handler has 'extra options' it will get a little settings widget and
   * another form called extra_options.
   */
353
  public function hasExtraOptions() { return FALSE; }
354
355
356
357

  /**
   * Provide defaults for the handler.
   */
358
  public function defineExtraOptions(&$option) { }
359
360
361
362

  /**
   * Provide a form for setting options.
   */
363
  public function buildExtraOptionsForm(&$form, &$form_state) { }
364
365
366
367

  /**
   * Validate the options form.
   */
368
  public function validateExtraOptionsForm($form, &$form_state) { }
369
370
371
372
373

  /**
   * Perform any necessary changes to the form values prior to storage.
   * There is no need for this function to actually store the data.
   */
374
  public function submitExtraOptionsForm($form, &$form_state) { }
375
376
377
378

  /**
   * Determine if a handler can be exposed.
   */
379
  public function canExpose() { return FALSE; }
380
381
382
383
384

  /**
   * Set new exposed option defaults when exposed setting is flipped
   * on.
   */
385
  public function defaultExposeOptions() { }
386
387
388
389

  /**
   * Get information about the exposed form for the form renderer.
   */
390
  public function exposedInfo() { }
391
392
393
394

  /**
   * Render our chunk of the exposed handler form when selecting
   */
395
  public function buildExposedForm(&$form, &$form_state) { }
396
397
398
399

  /**
   * Validate the exposed handler form
   */
400
  public function validateExposed(&$form, &$form_state) { }
401
402
403
404

  /**
   * Submit the exposed handler form
   */
405
  public function submitExposed(&$form, &$form_state) { }
406
407
408
409

  /**
   * Form for exposed handler options.
   */
410
  public function buildExposeForm(&$form, &$form_state) { }
411
412
413
414

  /**
   * Validate the options form.
   */
415
  public function validateExposeForm($form, &$form_state) { }
416
417
418
419
420

  /**
   * Perform any necessary changes to the form exposes prior to storage.
   * There is no need for this function to actually store the data.
   */
421
  public function submitExposeForm($form, &$form_state) { }
422
423
424
425

  /**
   * Shortcut to display the expose/hide button.
   */
426
  public function showExposeButton(&$form, &$form_state) { }
427
428
429
430

  /**
   * Shortcut to display the exposed options form.
   */
431
  public function showExposeForm(&$form, &$form_state) {
432
433
434
435
    if (empty($this->options['exposed'])) {
      return;
    }

436
    $this->buildExposeForm($form, $form_state);
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455

    // When we click the expose button, we add new gadgets to the form but they
    // have no data in $_POST so their defaults get wiped out. This prevents
    // these defaults from getting wiped out. This setting will only be TRUE
    // during a 2nd pass rerender.
    if (!empty($form_state['force_expose_options'])) {
      foreach (element_children($form['expose']) as $id) {
        if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
          $form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
        }
      }
    }
  }

  /**
   * Check whether current user has access to this handler.
   *
   * @return boolean
   */
456
  public function access() {
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
    if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
      if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
        return call_user_func_array($this->definition['access callback'], $this->definition['access arguments']);
      }
      return $this->definition['access callback']();
    }

    return TRUE;
  }

  /**
   * Run before the view is built.
   *
   * This gives all the handlers some time to set up before any handler has
   * been fully run.
   */
473
  public function preQuery() { }
474
475
476
477
478
479
480
481

  /**
   * Run after the view is executed, before the result is cached.
   *
   * This gives all the handlers some time to modify values. This is primarily
   * used so that handlers that pull up secondary data can put it in the
   * $values so that the raw data can be utilized externally.
   */
482
  public function postExecute(&$values) { }
483
484
485

  /**
   * Provides a unique placeholders for handlers.
486
487
488
   *
   * @return string
   *   A placeholder which contains the table and the fieldname.
489
   */
490
  protected function placeholder() {
491
    return $this->query->placeholder($this->table . '_' . $this->field);
492
493
494
495
496
497
  }

  /**
   * Called just prior to query(), this lets a handler set up any relationship
   * it needs.
   */
498
  public function setRelationship() {
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
    // Ensure this gets set to something.
    $this->relationship = NULL;

    // Don't process non-existant relationships.
    if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
      return;
    }

    $relationship = $this->options['relationship'];

    // Ignore missing/broken relationships.
    if (empty($this->view->relationship[$relationship])) {
      return;
    }

    // Check to see if the relationship has already processed. If not, then we
    // cannot process it.
    if (empty($this->view->relationship[$relationship]->alias)) {
      return;
    }

    // Finally!
    $this->relationship = $this->view->relationship[$relationship]->alias;
  }

  /**
   * Ensure the main table for this handler is in the query. This is used
   * a lot.
   */
528
  public function ensureMyTable() {
529
530
    if (!isset($this->tableAlias)) {
      $this->tableAlias = $this->query->ensure_table($this->table, $this->relationship);
531
    }
532
    return $this->tableAlias;
533
534
535
536
537
  }

  /**
   * Provide text for the administrative summary
   */
538
  public function adminSummary() { }
539
540
541
542
543
544
545

  /**
   * Determine if this item is 'exposed', meaning it provides form elements
   * to let users modify the view.
   *
   * @return TRUE/FALSE
   */
546
  public function isExposed() {
547
548
549
    return !empty($this->options['exposed']);
  }

550
551
552
  /**
   * Returns TRUE if the exposed filter works like a grouped filter.
   */
553
  public function isAGroup() { return FALSE; }
554
555
556
557
558
559

  /**
   * Define if the exposed input has to be submitted multiple times.
   * This is TRUE when exposed filters grouped are using checkboxes as
   * widgets.
   */
560
  public function multipleExposedInput() { return FALSE; }
561

562
563
564
  /**
   * Take input from exposed handlers and assign to this handler, if necessary.
   */
565
  public function acceptExposedInput($input) { return TRUE; }
566
567
568
569

  /**
   * If set to remember exposed input in the session, store it there.
   */
570
  public function storeExposedInput($input, $status) { return TRUE; }
571
572
573
574
575
576
577
578

  /**
   * Get the join object that should be used for this handler.
   *
   * This method isn't used a great deal, but it's very handy for easily
   * getting the join if it is necessary to make some changes to it, such
   * as adding an 'extra'.
   */
579
  public function getJoin() {
580
581
582
    // get the join from this table that links back to the base table.
    // Determine the primary table to seek
    if (empty($this->query->relationships[$this->relationship])) {
583
      $base_table = $this->view->storage->get('base_table');
584
585
586
587
588
    }
    else {
      $base_table = $this->query->relationships[$this->relationship]['base'];
    }

589
    $join = $this->getTableJoin($this->table, $base_table);
590
591
592
593
594
595
596
597
598
    if ($join) {
      return clone $join;
    }
  }

  /**
   * Validates the handler against the complete View.
   *
   * This is called when the complete View is being validated. For validating
599
   * the handler options form use validateOptionsForm().
600
   *
601
   * @see views_handler::validateOptionsForm()
602
603
604
605
   *
   * @return
   *   Empty array if the handler is valid; an array of error strings if it is not.
   */
606
  public function validate() { return array(); }
607
608
609
610
611

  /**
   * Determine if the handler is considered 'broken', meaning it's a
   * a placeholder used when a handler can't be found.
   */
612
  public function broken() { }
613

614
615
616
617
618
619
620
621
622
  /**
   * Creates cross-database SQL date formatting.
   *
   * @param string $format
   *   A format string for the result, like 'Y-m-d H:i:s'.
   *
   * @return string
   *   An appropriate SQL string for the DB type and field type.
   */
623
624
  public function getDateFormat($format) {
    return $this->query->getDateFormat($this->getDateField(), $format);
625
626
627
628
629
630
631
632
  }

  /**
   * Creates cross-database SQL dates.
   *
   * @return string
   *   An appropriate SQL string for the db type and field type.
   */
633
634
  public function getDateField() {
    return $this->query->getDateField("$this->tableAlias.$this->realField");
635
636
637
638
639
640
641
642
643
644
645
646
647
  }

  /**
   * Fetches a handler to join one table to a primary table from the data cache.
   *
   * @param string $table
   *   The table to join from.
   * @param string $base_table
   *   The table to join to.
   *
   * @return Drupal\views\Plugin\views\join\JoinPluginBase
   */
  public static function getTableJoin($table, $base_table) {
648
    $data = Views::viewsData()->get($table);
649
    if (isset($data['table']['join'][$base_table])) {
650
651
652
      $join_info = $data['table']['join'][$base_table];
      if (!empty($join_info['join_id'])) {
        $id = $join_info['join_id'];
653
654
655
656
657
      }
      else {
        $id = 'standard';
      }

658
659
660
661
      $configuration = $join_info;
      // Fill in some easy defaults.
      if (empty($configuration['table'])) {
        $configuration['table'] = $table;
662
663
      }
      // If this is empty, it's a direct link.
664
665
      if (empty($configuration['left_table'])) {
        $configuration['left_table'] = $base_table;
666
667
      }

668
669
670
671
      if (isset($join_info['arguments'])) {
        foreach ($join_info['arguments'] as $key => $argument) {
          $configuration[$key] = $argument;
        }
672
673
      }

674
      $join = Views::pluginManager('join')->createInstance($id, $configuration);
675
676

      return $join;
677
678
679
    }
  }

680
681
682
683
684
685
686
687
688
689
690
691
692
  /**
   * Determines the entity type used by this handler.
   *
   * If this handler uses a relationship, the base class of the relationship is
   * taken into account.
   *
   * @return string
   *   The machine name of the entity type.
   */
  public function getEntityType() {
    // If the user has configured a relationship on the handler take that into
    // account.
    if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
693
      $views_data = Views::viewsData()->get($this->view->relationship->table);
694
695
    }
    else {
696
      $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
697
698
699
700
701
702
    }

    if (isset($views_data['table']['entity type'])) {
      return $views_data['table']['entity type'];
    }
    else {
703
      throw new \Exception(format_string('No entity type for field @field on view @view', array('@field' => $this->options['id'], '@view' => $this->view->storage->id())));
704
705
706
    }
  }

707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
  /**
   * Breaks x,y,z and x+y+z into an array. Numeric only.
   *
   * @param string $str
   *   The string to parse.
   * @param Drupal\views\Plugin\views\HandlerBase|null $handler
   *   The handler object to use as a base. If not specified one will
   *   be created.
   *
   * @return Drupal\views\Plugin\views\HandlerBase|stdClass $handler
   *   The new handler object.
   */
  public static function breakPhrase($str, &$handler = NULL) {
    if (!$handler) {
      $handler = new \stdClass();
    }

    // Set up defaults:

    if (!isset($handler->value)) {
      $handler->value = array();
    }

    if (!isset($handler->operator)) {
      $handler->operator = 'or';
    }

    if (empty($str)) {
      return $handler;
    }

    if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
      // The '+' character in a query string may be parsed as ' '.
      $handler->operator = 'or';
      $handler->value = preg_split('/[+ ]/', $str);
    }
    elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
      $handler->operator = 'and';
      $handler->value = explode(',', $str);
    }

    // Keep an 'error' value if invalid strings were given.
    if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
      $handler->value = array(-1);
      return $handler;
    }

    // Doubly ensure that all values are numeric only.
    foreach ($handler->value as $id => $value) {
      $handler->value[$id] = intval($value);
    }

    return $handler;
  }

  /**
   * Breaks x,y,z and x+y+z into an array. Works for strings.
   *
   * @param string $str
   *   The string to parse.
   * @param Drupal\views\Plugin\views\HandlerBase|null $handler
   *   The object to use as a base. If not specified one will
   *   be created.
   *
   * @return Drupal\views\Plugin\views\HandlerBase|stdClass $handler
   *   The new handler object.
   */
  public static function breakPhraseString($str, &$handler = NULL) {
    if (!$handler) {
      $handler = new \stdClass();
    }

    // Set up defaults:
    if (!isset($handler->value)) {
      $handler->value = array();
    }

    if (!isset($handler->operator)) {
      $handler->operator = 'or';
    }

    if ($str == '') {
      return $handler;
    }

    // Determine if the string has 'or' operators (plus signs) or 'and' operators
    // (commas) and split the string accordingly. If we have an 'and' operator,
    // spaces are treated as part of the word being split, but otherwise they are
    // treated the same as a plus sign.
    $or_wildcard = '[^\s+,]';
    $and_wildcard = '[^+,]';
    if (preg_match("/^({$or_wildcard}+[+ ])+{$or_wildcard}+$/", $str)) {
      $handler->operator = 'or';
      $handler->value = preg_split('/[+ ]/', $str);
    }
    elseif (preg_match("/^({$and_wildcard}+,)*{$and_wildcard}+$/", $str)) {
      $handler->operator = 'and';
      $handler->value = explode(',', $str);
    }

    // Keep an 'error' value if invalid strings were given.
    if (!empty($str) && (empty($handler->value) || !is_array($handler->value))) {
      $handler->value = array(-1);
      return $handler;
    }

    // Doubly ensure that all values are strings only.
    foreach ($handler->value as $id => $value) {
      $handler->value[$id] = (string) $value;
    }

    return $handler;
  }

821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
  /**
   * Displays the Expose form.
   */
  public function displayExposedForm($form, &$form_state) {
    $item = &$this->options;
    // flip
    $item['exposed'] = empty($item['exposed']);

    // If necessary, set new defaults:
    if ($item['exposed']) {
      $this->defaultExposeOptions();
    }

    $form_state['view']->get('executable')->setItem($form_state['display_id'], $form_state['type'], $form_state['id'], $item);

    $form_state['view']->addFormToStack($form_state['form_key'], $form_state['display_id'], $form_state['type'], $form_state['id'], TRUE, TRUE);

838
    $form_state['view']->cacheSet();
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
    $form_state['rerender'] = TRUE;
    $form_state['rebuild'] = TRUE;
    $form_state['force_expose_options'] = TRUE;
  }

  /**
   * A submit handler that is used for storing temporary items when using
   * multi-step changes, such as ajax requests.
   */
  public function submitTemporaryForm($form, &$form_state) {
    // Run it through the handler's submit function.
    $this->submitOptionsForm($form['options'], $form_state);
    $item = $this->options;
    $types = ViewExecutable::viewsHandlerTypes();

    // For footer/header $handler_type is area but $type is footer/header.
    // For all other handle types it's the same.
    $handler_type = $type = $form_state['type'];
    if (!empty($types[$type]['type'])) {
      $handler_type = $types[$type]['type'];
    }

    $override = NULL;
    $executable = $form_state['view']->get('executable');
    if ($executable->display_handler->useGroupBy() && !empty($item['group_type'])) {
      if (empty($executable->query)) {
        $executable->initQuery();
      }
867
      $aggregate = $executable->query->getAggregationInfo();
868
869
870
871
872
873
874
      if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
        $override = $aggregate[$item['group_type']]['handler'][$type];
      }
    }

    // Create a new handler and unpack the options from the form onto it. We
    // can use that for storage.
875
    $handler = views_get_handler($item, $handler_type, $override);
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
    $handler->init($executable, $executable->display_handler, $item);

    // Add the incoming options to existing options because items using
    // the extra form may not have everything in the form here.
    $options = $form_state['values']['options'] + $this->options;

    // This unpacks only options that are in the definition, ensuring random
    // extra stuff on the form is not sent through.
    $handler->unpackOptions($handler->options, $options, NULL, FALSE);

    // Store the item back on the view.
    $executable = $form_state['view']->get('executable');
    $executable->temporary_options[$type][$form_state['id']] = $handler->options;

    // @todo Decide if \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() is
    //   perhaps the better place to fix the issue.
    // \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() drops the current
    // form from the stack, even if it's an #ajax. So add the item back to the top
    // of the stack.
    $form_state['view']->addFormToStack($form_state['form_key'], $form_state['display_id'], $type, $item['id'], TRUE);

    $form_state['rerender'] = TRUE;
    $form_state['rebuild'] = TRUE;
    // Write to cache
900
    $form_state['view']->cacheSet();
901
902
  }

dawehner's avatar
dawehner committed
903
}