HandlerBase.php 26.5 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\Plugin\Discovery\DiscoveryInterface;
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;
dawehner's avatar
dawehner committed
15

16
abstract class HandlerBase extends PluginBase {
17

dawehner's avatar
dawehner committed
18
  /**
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   * 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
   */
37
  public $tableAlias;
38 39 40 41 42 43

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

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

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

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

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

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

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

dawehner's avatar
dawehner committed
89 90 91 92 93 94
    $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.
95 96
    if (isset($this->actualTable)) {
      $options['table'] = $this->actualTable;
dawehner's avatar
dawehner committed
97 98
    }

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

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

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

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

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

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

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

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

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

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

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

    return $options;
  }

  /**
   * Return a string representing this handler's name in the UI.
   */
157 158 159
  public function adminLabel($short = FALSE) {
    if (!empty($this->options['admin_label'])) {
      $title = check_plain($this->options['admin_label']);
160 161 162 163 164 165 166 167 168 169 170
      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
171
   * overriding this can just call return parent::getField($formula)
172
   */
173
  public function getField($field = NULL) {
174 175 176 177 178
    if (!isset($field)) {
      if (!empty($this->formula)) {
        $field = $this->get_formula();
      }
      else {
179
        $field = $this->tableAlias . '.' . $this->realField;
180 181 182 183
      }
    }

    // If grouping, check to see if the aggregation method needs to modify the field.
184
    if ($this->view->display_handler->useGroupBy()) {
185
      $this->view->initQuery();
186 187
      if ($this->query) {
        $info = $this->query->get_aggregation_info();
188 189 190 191 192
        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);
          }
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
        }
      }
    }

    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.
   */
211
  protected function sanitizeValue($value, $type = NULL) {
212 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
    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.
   */
244
  protected function caseTransform($string, $option) {
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    global $multibyte;

    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':
        if ($multibyte == UNICODE_MULTIBYTE) {
          return mb_convert_case($string, MB_CASE_TITLE);
        }
        else {
          return ucwords($string);
        }
    }
  }

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

  /**
   * Build the options form.
   */
274
  public function buildOptionsForm(&$form, &$form_state) {
275 276 277 278 279 280
    // 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';

281
    $form['admin_label'] = array(
282 283 284
      '#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.'),
285
      '#default_value' => $this->options['admin_label'],
286 287 288 289
      '#fieldset' => 'more',
    );

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

  /**
   * 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 325
    $this->view->initQuery();
    $info = $this->view->query->get_aggregation_info();
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 = drupal_container()->get('views.views_data')->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 675 676
      $join = drupal_container()->get('plugin.manager.views.join')->createInstance($id, $configuration);

      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 = drupal_container()->get('views.views_data')->get($this->view->relationship->table);
694 695
    }
    else {
696
      $views_data = drupal_container()->get('views.views_data')->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 838 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 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
  /**
   * 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);

    views_ui_cache_set($form_state['view']);
    $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();
      }
      $aggregate = $executable->query->get_aggregation_info();
      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.
    $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
    $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
    views_ui_cache_set($form_state['view']);
  }

dawehner's avatar
dawehner committed
903
}