HandlerBase.php 27 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 318
    $view = &$form_state['view'];
    $display_id = $form_state['display_id'];
319
    $types = ViewExecutable::viewsHandlerTypes();
320 321 322 323 324
    $type = $form_state['type'];
    $id = $form_state['id'];

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

325
    $view->initQuery();
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    $info = $view->query->get_aggregation_info();
    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.
   */
344
  public function submitGroupByForm(&$form, &$form_state) {
345 346 347 348 349 350 351 352 353
    $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.
   */
354
  public function hasExtraOptions() { return FALSE; }
355 356 357 358

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // 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
   */
457
  public function access() {
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    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.
   */
474
  public function preQuery() { }
475 476 477 478 479 480 481 482

  /**
   * 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.
   */
483
  public function postExecute(&$values) { }
484 485 486

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

  /**
   * Called just prior to query(), this lets a handler set up any relationship
   * it needs.
   */
499
  public function setRelationship() {
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 528
    // 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.
   */
529
  public function ensureMyTable() {
530 531
    if (!isset($this->tableAlias)) {
      $this->tableAlias = $this->query->ensure_table($this->table, $this->relationship);
532
    }
533
    return $this->tableAlias;
534 535 536 537 538
  }

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

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

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

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

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

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

  /**
   * 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'.
   */
580
  public function getJoin() {
581 582 583
    // 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])) {
584
      $base_table = $this->view->storage->get('base_table');
585 586 587 588 589
    }
    else {
      $base_table = $this->query->relationships[$this->relationship]['base'];
    }

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

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

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

615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
  /**
   * 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.
   */
  public function getSQLFormat($format) {
    $db_type = Database::getConnection()->databaseType();
    $field = $this->getSQLDateField();
    switch ($db_type) {
      case 'mysql':
        $replace = array(
          'Y' => '%Y',
          'y' => '%y',
          'M' => '%b',
          'm' => '%m',
          'n' => '%c',
          'F' => '%M',
          'D' => '%a',
          'd' => '%d',
          'l' => '%W',
          'j' => '%e',
          'W' => '%v',
          'H' => '%H',
          'h' => '%h',
          'i' => '%i',
          's' => '%s',
          'A' => '%p',
          );
        $format = strtr($format, $replace);
        return "DATE_FORMAT($field, '$format')";
      case 'pgsql':
        $replace = array(
          'Y' => 'YYYY',
          'y' => 'YY',
          'M' => 'Mon',
          'm' => 'MM',
          'n' => 'MM', // no format for Numeric representation of a month, without leading zeros
          'F' => 'Month',
          'D' => 'Dy',
          'd' => 'DD',
          'l' => 'Day',
          'j' => 'DD', // no format for Day of the month without leading zeros
          'W' => 'WW',
          'H' => 'HH24',
          'h' => 'HH12',
          'i' => 'MI',
          's' => 'SS',
          'A' => 'AM',
          );
        $format = strtr($format, $replace);
        return "TO_CHAR($field, '$format')";
      case 'sqlite':
        $replace = array(
          'Y' => '%Y', // 4 digit year number
          'y' => '%Y', // no format for 2 digit year number
          'M' => '%m', // no format for 3 letter month name
          'm' => '%m', // month number with leading zeros
          'n' => '%m', // no format for month number without leading zeros
          'F' => '%m', // no format for full month name
          'D' => '%d', // no format for 3 letter day name
          'd' => '%d', // day of month number with leading zeros
          'l' => '%d', // no format for full day name
          'j' => '%d', // no format for day of month number without leading zeros
          'W' => '%W', // ISO week number
          'H' => '%H', // 24 hour hour with leading zeros
          'h' => '%H', // no format for 12 hour hour with leading zeros
          'i' => '%M', // minutes with leading zeros
          's' => '%S', // seconds with leading zeros
          'A' => '', // no format for  AM/PM
        );
        $format = strtr($format, $replace);
        return "strftime('$format', $field, 'unixepoch')";
    }
  }

  /**
   * Creates cross-database SQL dates.
   *
   * @return string
   *   An appropriate SQL string for the db type and field type.
   */
  public function getSQLDateField() {
    $field = "$this->tableAlias.$this->realField";
    $db_type = Database::getConnection()->databaseType();
    $offset = $this->getTimezone();
    if (isset($offset) && !is_numeric($offset)) {
705 706
      $dtz = new \DateTimeZone($offset);
      $dt = new \DateTime('now', $dtz);
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
      $offset_seconds = $dtz->getOffset($dt);
    }

    switch ($db_type) {
      case 'mysql':
        $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
        if (!empty($offset)) {
          $field = "($field + INTERVAL $offset_seconds SECOND)";
        }
        return $field;
      case 'pgsql':
        $field = "TO_TIMESTAMP($field)";
        if (!empty($offset)) {
          $field = "($field + INTERVAL '$offset_seconds SECONDS')";
        }
        return $field;
      case 'sqlite':
        if (!empty($offset)) {
          $field = "($field + '$offset_seconds')";
        }
        return $field;
    }
  }

  /**
   * Figure out what timezone we're in; needed for some date manipulations.
   */
  public static function getTimezone() {
735
    $timezone = drupal_get_user_timezone();
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

    // set up the database timezone
    $db_type = Database::getConnection()->databaseType();
    if (in_array($db_type, array('mysql', 'pgsql'))) {
      $offset = '+00:00';
      static $already_set = FALSE;
      if (!$already_set) {
        if ($db_type == 'pgsql') {
          db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
        }
        elseif ($db_type == 'mysql') {
          db_query("SET @@session.time_zone = '$offset'");
        }

        $already_set = TRUE;
      }
    }

    return $timezone;
  }

  /**
   * 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) {
768
    $data = drupal_container()->get('views.views_data')->get($table);
769
    if (isset($data['table']['join'][$base_table])) {
770 771 772
      $join_info = $data['table']['join'][$base_table];
      if (!empty($join_info['join_id'])) {
        $id = $join_info['join_id'];
773 774 775 776 777
      }
      else {
        $id = 'standard';
      }

778 779 780 781
      $configuration = $join_info;
      // Fill in some easy defaults.
      if (empty($configuration['table'])) {
        $configuration['table'] = $table;
782 783
      }
      // If this is empty, it's a direct link.
784 785
      if (empty($configuration['left_table'])) {
        $configuration['left_table'] = $base_table;
786 787
      }

788 789 790 791
      if (isset($join_info['arguments'])) {
        foreach ($join_info['arguments'] as $key => $argument) {
          $configuration[$key] = $argument;
        }
792 793
      }

794 795 796
      $join = drupal_container()->get('plugin.manager.views.join')->createInstance($id, $configuration);

      return $join;
797 798 799
    }
  }

800 801 802 803 804 805 806 807 808 809 810 811 812
  /**
   * 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') {
813
      $views_data = drupal_container()->get('views.views_data')->get($this->view->relationship->table);
814 815
    }
    else {
816
      $views_data = drupal_container()->get('views.views_data')->get($this->view->storage->get('base_table'));
817 818 819 820 821 822
    }

    if (isset($views_data['table']['entity type'])) {
      return $views_data['table']['entity type'];
    }
    else {
823
      throw new \Exception(format_string('No entity type for field @field on view @view', array('@field' => $this->options['id'], '@view' => $this->view->storage->id())));
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 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
  /**
   * 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;
  }

dawehner's avatar
dawehner committed
941
}