diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php index 8b53b38..e4f14c0 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php @@ -35,6 +35,13 @@ protected $column; /** + * Current user object. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** * {@inheritdoc} */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) { @@ -114,10 +121,7 @@ public static function validateElement(array $element, FormStateInterface $form_ protected function getOptions(FieldableEntityInterface $entity) { if (!isset($this->options)) { // Limit the settable options for the current user account. - $options = $this->fieldDefinition - ->getFieldStorageDefinition() - ->getOptionsProvider($this->column, $entity) - ->getSettableOptions(\Drupal::currentUser()); + $options = $item->getSettableOptions($this->currentUser()); // Add an empty option if the widget needs one. if ($empty_label = $this->getEmptyLabel()) { @@ -201,4 +205,16 @@ protected function sanitizeLabel(&$label) { */ protected function getEmptyLabel() { } + /** + * Gets the current active user. + * + * @return \Drupal\Core\Session\AccountInterface + */ + protected function currentUser() { + if (!$this->currentUser) { + $this->currentUser = \Drupal::currentUser(); + } + return $this->currentUser; + } + } diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php new file mode 100644 index 0000000..1c7d554 --- /dev/null +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php @@ -0,0 +1,217 @@ + 'radios', + '#title' => t('Autocomplete matching'), + '#default_value' => $this->getSetting('match_operator'), + '#options' => array( + 'STARTS_WITH' => t('Starts with'), + 'CONTAINS' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of entities.'), + ); + $element['size'] = array( + '#type' => 'number', + '#title' => t('Size of textfield'), + '#default_value' => $this->getSetting('size'), + '#min' => 1, + '#required' => TRUE, + ); + $element['placeholder'] = array( + '#type' => 'textfield', + '#title' => t('Placeholder'), + '#default_value' => $this->getSetting('placeholder'), + '#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'), + ); + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = array(); + + $summary[] = t('Autocomplete matching: @match_operator', array('@match_operator' => $this->getSetting('match_operator'))); + $summary[] = t('Textfield size: !size', array('!size' => $this->getSetting('size'))); + $placeholder = $this->getSetting('placeholder'); + if (!empty($placeholder)) { + $summary[] = t('Placeholder: @placeholder', array('@placeholder' => $placeholder)); + } + else { + $summary[] = t('No placeholder'); + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) { + $user = $this->currentUser(); + + $entity = $items->getEntity(); + + // Prepare the autocomplete route parameters. + $autocomplete_route_parameters = array( + 'type' => $this->getSetting('autocomplete_type'), + 'field_name' => $this->fieldDefinition->getFieldName(), + 'entity_type' => $entity->entityType(), + 'bundle_name' => $entity->bundle(), + ); + + if ($entity_id = $entity->id()) { + $autocomplete_route_parameters['entity_id'] = $entity_id; + } + + $element += array( + '#type' => 'textfield', + '#maxlength' => 1024, + '#default_value' => implode(', ', $this->getLabels($items)), + '#autocomplete_route_name' => 'entity_reference.autocomplete', + '#autocomplete_route_parameters' => $autocomplete_route_parameters, + '#size' => $this->getSetting('size'), + '#placeholder' => $this->getSetting('placeholder'), + '#element_validate' => array(array($this, 'elementValidate')), + // @todo: Use wrapper to get the user if exists or needed. + '#autocreate_uid' => isset($entity->uid) ? $entity->uid : $user->id(), + ); + + return array('target_id' => $element); + } + + /** + * {@inheritdoc} + */ + public function errorElement(array $element, ConstraintViolationInterface $error, array $form, array &$form_state) { + return $element['target_id']; + } + + /** + * Validates an element. + */ + public function elementValidate($element, &$form_state, $form) { } + + /** + * Gets the entity labels. + */ + protected function getLabels(FieldItemListInterface $items) { + if ($items->isEmpty()) { + return array(); + } + + $entity_ids = array(); + $entity_labels = array(); + + // Build an array of entity IDs. + foreach ($items as $item) { + $entity_ids[] = $item->target_id; + } + + // Load those entities and loop through them to extract their labels. + $entities = entity_load_multiple($this->getFieldSetting('target_type'), $entity_ids); + + foreach ($entities as $entity_id => $entity_item) { + $label = $entity_item->label(); + $key = "$label ($entity_id)"; + // Labels containing commas or quotes must be wrapped in quotes. + if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { + $key = '"' . str_replace('"', '""', $key) . '"'; + } + $entity_labels[] = $key; + } + return $entity_labels; + } + + /** + * Creates a new entity from a label entered in the autocomplete input. + * + * @param string $label + * The entity label. + * @param int $uid + * The entity uid. + * + * @return \Drupal\Core\Entity\EntityInterface + */ + protected function createNewEntity($label, $uid) { + $entity_manager = \Drupal::entityManager(); + $target_type = $this->getFieldSetting('target_type'); + $target_bundles = $this->getSelectionHandlerSetting('target_bundles'); + + // Get the bundle. + if (!empty($target_bundles)) { + $bundle = reset($target_bundles); + } + else { + $bundles = entity_get_bundles($target_type); + $bundle = reset($bundles); + } + + $entity_info = $entity_manager->getDefinition($target_type); + $bundle_key = $entity_info['entity_keys']['bundle']; + $label_key = $entity_info['entity_keys']['label']; + + return $entity_manager->getStorageController($target_type)->create(array( + $label_key => $label, + $bundle_key => $bundle, + 'uid' => $uid, + )); + } + + /** + * Returns the value of a setting for the entity reference selection handler. + * + * @param string $setting_name + * The setting name. + * + * @return mixed + * The setting value. + */ + protected function getSelectionHandlerSetting($setting_name) { + $settings = $this->getFieldSetting('handler_settings'); + return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL; + } + + /** + * Gets the current active user. + * + * @return \Drupal\Core\Session\AccountInterface + */ + protected function currentUser() { + if (!$this->currentUser) { + $this->currentUser = \Drupal::currentUser(); + } + return $this->currentUser; + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php new file mode 100644 index 0000000..b2ffe00 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php @@ -0,0 +1,353 @@ +outputKey; + } + + /** + * Returns the resultsKey property. + * + * @return string + * The resultsKey property. + */ + public function getResultsKey() { + return $this->resultsKey; + } + + /** + * Return a string to display as the clickable title for the + * access control. + */ + public function summaryTitle() { + return t('Unknown'); + } + + /** + * Determine the expiration time of the cache type, or NULL if no expire. + * + * Plugins must override this to implement expiration. + * + * @param $type + * The cache type, either 'query', 'result' or 'output'. + */ + protected function cacheExpire($type) { } + + /** + * Determine expiration time in the cache table of the cache type + * or CACHE_PERMANENT if item shouldn't be removed automatically from cache. + * + * Plugins must override this to implement expiration in the cache table. + * + * @param $type + * The cache type, either 'query', 'result' or 'output'. + */ + protected function cacheSetExpire($type) { + return CacheBackendInterface::CACHE_PERMANENT; + } + + + /** + * Save data to the cache. + * + * A plugin should override this to provide specialized caching behavior. + */ + public function cacheSet($type) { + switch ($type) { + case 'query': + // Not supported currently, but this is certainly where we'd put it. + break; + case 'results': + $data = array( + 'result' => $this->view->result, + 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0, + 'current_page' => $this->view->getCurrentPage(), + ); + cache($this->table)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type)); + break; + case 'output': + $this->storage['output'] = $this->view->display_handler->output; + $this->gatherHeaders(); + cache($this->table)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type)); + break; + } + } + + + /** + * Retrieve data from the cache. + * + * A plugin should override this to provide specialized caching behavior. + */ + public function cacheGet($type) { + $cutoff = $this->cacheExpire($type); + switch ($type) { + case 'query': + // Not supported currently, but this is certainly where we'd put it. + return FALSE; + case 'results': + // Values to set: $view->result, $view->total_rows, $view->execute_time, + // $view->current_page. + if ($cache = cache($this->table)->get($this->generateResultsKey())) { + if (!$cutoff || $cache->created > $cutoff) { + $this->view->result = $cache->data['result']; + $this->view->total_rows = $cache->data['total_rows']; + $this->view->setCurrentPage($cache->data['current_page']); + $this->view->execute_time = 0; + return TRUE; + } + } + return FALSE; + case 'output': + if ($cache = cache($this->table)->get($this->generateOutputKey())) { + if (!$cutoff || $cache->created > $cutoff) { + $this->storage = $cache->data; + $this->view->display_handler->output = $cache->data['output']; + $this->restoreHeaders(); + return TRUE; + } + } + return FALSE; + } + } + + /** + * Clear out cached data for a view. + * + * We're just going to nuke anything related to the view, regardless of display, + * to be sure that we catch everything. Maybe that's a bad idea. + */ + public function cacheFlush() { + cache($this->table)->deleteTags(array($this->view->storage->id() => TRUE)); + } + + /** + * Post process any rendered data. + * + * This can be valuable to be able to cache a view and still have some level of + * dynamic output. In an ideal world, the actual output will include HTML + * comment based tokens, and then the post process can replace those tokens. + * + * Example usage. If it is known that the view is a node view and that the + * primary field will be a nid, you can do something like this: + * + * + * + * And then in the post render, create an array with the text that should + * go there: + * + * strtr($output, array('', 'output for FIELD of nid 1'); + * + * All of the cached result data will be available in $view->result, as well, + * so all ids used in the query should be discoverable. + */ + public function postRender(&$output) { } + + /** + * Start caching the html head. + * + * This takes a snapshot of the current system state so that we don't + * duplicate it. Later on, when gatherHeaders() is run, this information + * will be removed so that we don't hold onto it. + * + * @see drupal_add_html_head() + */ + public function cacheStart() { + $this->storage['head'] = drupal_add_html_head(); + } + + /** + * Gather the JS/CSS from the render array, the html head from the band data. + */ + protected function gatherHeaders() { + // Simple replacement for head + if (isset($this->storage['head'])) { + $this->storage['head'] = str_replace($this->storage['head'], '', drupal_add_html_head()); + } + else { + $this->storage['head'] = ''; + } + + $attached = drupal_render_collect_attached($this->storage['output']); + $this->storage['css'] = $attached['css']; + $this->storage['js'] = $attached['js']; + } + + /** + * Restore out of band data saved to cache. Copied from Panels. + */ + public function restoreHeaders() { + if (!empty($this->storage['head'])) { + drupal_add_html_head($this->storage['head']); + } + if (!empty($this->storage['css'])) { + foreach ($this->storage['css'] as $args) { + $this->view->element['#attached']['css'][] = $args; + } + } + if (!empty($this->storage['js'])) { + foreach ($this->storage['js'] as $key => $args) { + if ($key !== 'settings') { + $this->view->element['#attached']['js'][] = $args; + } + else { + foreach ($args as $setting) { + $this->view->element['#attached']['js']['setting'][] = $setting; + } + } + } + } + } + + /** + * Calculates and sets a cache ID used for the result cache. + * + * @return string + * The generated cache ID. + */ + public function generateResultsKey() { + $user = $this->currentUser(); + + if (!isset($this->resultsKey)) { + $build_info = $this->view->build_info; + + foreach (array('query', 'count_query') as $index) { + // If the default query back-end is used generate SQL query strings from + // the query objects. + if ($build_info[$index] instanceof Select) { + $query = clone $build_info[$index]; + $query->preExecute(); + $build_info[$index] = (string)$query; + } + } + $key_data = array( + 'build_info' => $build_info, + 'roles' => $user->getRoles(), + 'super-user' => $user->id() == 1, // special caching for super user. + 'langcode' => language(Language::TYPE_INTERFACE)->id, + 'base_url' => $GLOBALS['base_url'], + ); + $request = \Drupal::request(); + foreach (array('exposed_info', 'page', 'sort', 'order', 'items_per_page', 'offset') as $key) { + if ($request->query->has($key)) { + $key_data[$key] = $request->query->get($key); + } + } + + $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data)); + } + + return $this->resultsKey; + } + + /** + * Calculates and sets a cache ID used for the output cache. + * + * @return string + * The generated cache ID. + */ + public function generateOutputKey() { + $user = $this->currentUser(); + if (!isset($this->outputKey)) { + $key_data = array( + 'result' => $this->view->result, + 'roles' => $user->getRoles(), + 'super-user' => $user->id() == 1, // special caching for super user. + 'theme' => $GLOBALS['theme'], + 'langcode' => language(Language::TYPE_INTERFACE)->id, + 'base_url' => $GLOBALS['base_url'], + ); + + $this->outputKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':output:' . hash('sha256', serialize($key_data)); + } + + return $this->outputKey; + } + + /** + * Gets the current active user. + * + * @return \Drupal\Core\Session\AccountInterface + */ + protected function currentUser() { + if (!$this->currentUser) { + $this->currentUser = \Drupal::currentUser(); + } + return $this->currentUser; + } + +} + +/** + * @} + */ diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php new file mode 100644 index 0000000..9462273 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php @@ -0,0 +1,2901 @@ +view = $view; + $this->setOptionDefaults($this->options, $this->defineOptions()); + $this->display = &$display; + + // Load extenders as soon as possible. + $this->extender = array(); + $extenders = views_get_enabled_display_extenders(); + if (!empty($extenders)) { + $manager = Views::pluginManager('display_extender'); + foreach ($extenders as $extender) { + $plugin = $manager->createInstance($extender); + if ($plugin) { + $plugin->init($this->view, $this); + $this->extender[$extender] = $plugin; + } + } + } + + // Track changes that the user should know about. + $changed = FALSE; + + // Make some modifications: + if (!isset($options) && isset($display['display_options'])) { + $options = $display['display_options']; + } + + if ($this->isDefaultDisplay() && isset($options['defaults'])) { + unset($options['defaults']); + } + + // Cache for unpackOptions, but not if we are in the ui. + static $unpack_options = array(); + if (empty($view->editing)) { + $cid = 'unpackOptions:' . hash('sha256', serialize(array($this->options, $options))); + if (empty($unpack_options[$cid])) { + $cache = views_cache_get($cid, TRUE); + if (!empty($cache->data)) { + $this->options = $cache->data; + } + else { + $this->unpackOptions($this->options, $options); + views_cache_set($cid, $this->options, TRUE); + } + $unpack_options[$cid] = $this->options; + } + else { + $this->options = $unpack_options[$cid]; + } + } + else { + $this->unpackOptions($this->options, $options); + } + + // Convert the field_language and field_language_add_to_query settings. + $field_language = $this->getOption('field_language'); + $field_language_add_to_query = $this->getOption('field_language_add_to_query'); + if (isset($field_langcode)) { + $this->setOption('field_langcode', $field_language); + $this->setOption('field_langcode_add_to_query', $field_language_add_to_query); + $changed = TRUE; + } + + // Mark the view as changed so the user has a chance to save it. + if ($changed) { + $this->view->changed = TRUE; + } + } + + public function destroy() { + parent::destroy(); + + foreach ($this->handlers as $type => $handlers) { + foreach ($handlers as $id => $handler) { + if (is_object($handler)) { + $this->handlers[$type][$id]->destroy(); + } + } + } + + if (isset($this->default_display)) { + unset($this->default_display); + } + + foreach ($this->extender as $extender) { + $extender->destroy(); + } + } + + /** + * Determine if this display is the 'default' display which contains + * fallback settings + */ + public function isDefaultDisplay() { return FALSE; } + + /** + * Determine if this display uses exposed filters, so the view + * will know whether or not to build them. + */ + public function usesExposed() { + if (!isset($this->has_exposed)) { + foreach ($this->handlers as $type => $value) { + foreach ($this->view->$type as $id => $handler) { + if ($handler->canExpose() && $handler->isExposed()) { + // one is all we need; if we find it, return true. + $this->has_exposed = TRUE; + return TRUE; + } + } + } + $pager = $this->getPlugin('pager'); + if (isset($pager) && $pager->usesExposed()) { + $this->has_exposed = TRUE; + return TRUE; + } + $this->has_exposed = FALSE; + } + + return $this->has_exposed; + } + + /** + * Determine if this display should display the exposed + * filters widgets, so the view will know whether or not + * to render them. + * + * Regardless of what this function + * returns, exposed filters will not be used nor + * displayed unless usesExposed() returns TRUE. + */ + public function displaysExposed() { + return TRUE; + } + + /** + * Whether the display allows the use of AJAX or not. + * + * @return bool + */ + public function usesAJAX() { + return $this->usesAJAX; + } + + /** + * Whether the display is actually using AJAX or not. + * + * @return bool + */ + public function ajaxEnabled() { + if ($this->usesAJAX()) { + return $this->getOption('use_ajax'); + } + return FALSE; + } + + /** + * Whether the display is enabled. + * + * @return bool + * Returns TRUE if the display is marked as enabled, else FALSE. + */ + public function isEnabled() { + return (bool) $this->getOption('enabled'); + } + + /** + * Whether the display allows the use of a pager or not. + * + * @return bool + */ + + public function usesPager() { + return $this->usesPager; + } + + /** + * Whether the display is using a pager or not. + * + * @return bool + */ + public function isPagerEnabled() { + if ($this->usesPager()) { + $pager = $this->getPlugin('pager'); + if ($pager) { + return $pager->usePager(); + } + } + return FALSE; + } + + /** + * Whether the display allows the use of a 'more' link or not. + * + * @return bool + */ + public function usesMore() { + return $this->usesMore; + } + + /** + * Whether the display is using the 'more' link or not. + * + * @return bool + */ + public function isMoreEnabled() { + if ($this->usesMore()) { + return $this->getOption('use_more'); + } + return FALSE; + } + + /** + * Does the display have groupby enabled? + */ + public function useGroupBy() { + return $this->getOption('group_by'); + } + + /** + * Should the enabled display more link be shown when no more items? + */ + public function useMoreAlways() { + if ($this->usesMore()) { + return $this->getOption('use_more_always'); + } + return FALSE; + } + + /** + * Does the display have custom link text? + */ + public function useMoreText() { + if ($this->usesMore()) { + return $this->getOption('use_more_text'); + } + return FALSE; + } + + /** + * Determines whether this display can use attachments. + * + * @return bool + */ + public function acceptAttachments() { + // To be able to accept attachments this display have to be able to use + // attachments but at the same time, you cannot attach a display to itself. + if (!$this->usesAttachments() || ($this->definition['id'] == $this->view->current_display)) { + return FALSE; + } + + if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) { + foreach ($this->view->argument as $argument_id => $argument) { + if ($argument->needsStylePlugin() && empty($argument->argument_validated)) { + return FALSE; + } + } + } + + return TRUE; + } + + /** + * Returns whether the display can use attachments. + * + * @return bool + */ + public function usesAttachments() { + return $this->usesAttachments; + } + + /** + * Returns whether the display can use areas. + * + * @return bool + * TRUE if the display can use areas, or FALSE otherwise. + */ + public function usesAreas() { + return $this->usesAreas; + } + + /** + * Allow displays to attach to other views. + */ + public function attachTo(ViewExecutable $view, $display_id) { } + + /** + * Static member function to list which sections are defaultable + * and what items each section contains. + */ + public function defaultableSections($section = NULL) { + $sections = array( + 'access' => array('access'), + 'cache' => array('cache'), + 'title' => array('title'), + 'css_class' => array('css_class'), + 'use_ajax' => array('use_ajax'), + 'hide_attachment_summary' => array('hide_attachment_summary'), + 'show_admin_links' => array('show_admin_links'), + 'group_by' => array('group_by'), + 'query' => array('query'), + 'use_more' => array('use_more', 'use_more_always', 'use_more_text'), + 'use_more_always' => array('use_more', 'use_more_always', 'use_more_text'), + 'use_more_text' => array('use_more', 'use_more_always', 'use_more_text'), + 'link_display' => array('link_display', 'link_url'), + + // Force these to cascade properly. + 'style' => array('style', 'row'), + 'row' => array('style', 'row'), + + 'pager' => array('pager', 'pager_options'), + 'pager_options' => array('pager', 'pager_options'), + + 'exposed_form' => array('exposed_form', 'exposed_form_options'), + 'exposed_form_options' => array('exposed_form', 'exposed_form_options'), + + // These guys are special + 'header' => array('header'), + 'footer' => array('footer'), + 'empty' => array('empty'), + 'relationships' => array('relationships'), + 'fields' => array('fields'), + 'sorts' => array('sorts'), + 'arguments' => array('arguments'), + 'filters' => array('filters', 'filter_groups'), + 'filter_groups' => array('filters', 'filter_groups'), + ); + + // If the display cannot use a pager, then we cannot default it. + if (!$this->usesPager()) { + unset($sections['pager']); + unset($sections['items_per_page']); + } + + foreach ($this->extender as $extender) { + $extender->defaultableSections($sections, $section); + } + + if ($section) { + if (!empty($sections[$section])) { + return $sections[$section]; + } + } + else { + return $sections; + } + } + + protected function defineOptions() { + $options = array( + 'defaults' => array( + 'default' => array( + 'access' => TRUE, + 'cache' => TRUE, + 'query' => TRUE, + 'title' => TRUE, + 'css_class' => TRUE, + + 'display_description' => FALSE, + 'use_ajax' => TRUE, + 'hide_attachment_summary' => TRUE, + 'show_admin_links' => TRUE, + 'pager' => TRUE, + 'use_more' => TRUE, + 'use_more_always' => TRUE, + 'use_more_text' => TRUE, + 'exposed_form' => TRUE, + + 'link_display' => TRUE, + 'link_url' => '', + 'group_by' => TRUE, + + 'style' => TRUE, + 'row' => TRUE, + + 'header' => TRUE, + 'footer' => TRUE, + 'empty' => TRUE, + + 'relationships' => TRUE, + 'fields' => TRUE, + 'sorts' => TRUE, + 'arguments' => TRUE, + 'filters' => TRUE, + 'filter_groups' => TRUE, + ), + ), + + 'title' => array( + 'default' => '', + 'translatable' => TRUE, + ), + 'enabled' => array( + 'default' => TRUE, + 'translatable' => FALSE, + 'bool' => TRUE, + ), + 'display_comment' => array( + 'default' => '', + ), + 'css_class' => array( + 'default' => '', + 'translatable' => FALSE, + ), + 'display_description' => array( + 'default' => '', + 'translatable' => TRUE, + ), + 'use_ajax' => array( + 'default' => FALSE, + 'bool' => TRUE, + ), + 'hide_attachment_summary' => array( + 'default' => FALSE, + 'bool' => TRUE, + ), + 'show_admin_links' => array( + 'default' => TRUE, + 'bool' => TRUE, + ), + 'use_more' => array( + 'default' => FALSE, + 'bool' => TRUE, + ), + 'use_more_always' => array( + 'default' => FALSE, + 'bool' => TRUE, + ), + 'use_more_text' => array( + 'default' => 'more', + 'translatable' => TRUE, + ), + 'link_display' => array( + 'default' => '', + ), + 'link_url' => array( + 'default' => '', + ), + 'group_by' => array( + 'default' => FALSE, + 'bool' => TRUE, + ), + 'field_langcode' => array( + 'default' => '***CURRENT_LANGUAGE***', + ), + 'field_langcode_add_to_query' => array( + 'default' => TRUE, + 'bool' => TRUE, + ), + + // These types are all plugins that can have individual settings + // and therefore need special handling. + 'access' => array( + 'contains' => array( + 'type' => array('default' => 'none'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'cache' => array( + 'contains' => array( + 'type' => array('default' => 'none'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'query' => array( + 'contains' => array( + 'type' => array('default' => 'views_query'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'exposed_form' => array( + 'contains' => array( + 'type' => array('default' => 'basic'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'pager' => array( + 'contains' => array( + 'type' => array('default' => 'mini'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'style' => array( + 'contains' => array( + 'type' => array('default' => 'default'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + 'row' => array( + 'contains' => array( + 'type' => array('default' => 'fields'), + 'options' => array('default' => array()), + ), + 'merge_defaults' => array($this, 'mergePlugin'), + ), + + 'exposed_block' => array( + 'default' => FALSE, + ), + + 'header' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'footer' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'empty' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + + // We want these to export last. + // These are the 5 handler types. + 'relationships' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'fields' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'sorts' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'arguments' => array( + 'default' => array(), + 'merge_defaults' => array($this, 'mergeHandler'), + ), + 'filter_groups' => array( + 'contains' => array( + 'operator' => array('default' => 'AND'), + 'groups' => array('default' => array(1 => 'AND')), + ), + ), + 'filters' => array( + 'default' => array(), + ), + ); + + if (!$this->usesPager()) { + $options['defaults']['default']['use_pager'] = FALSE; + $options['defaults']['default']['items_per_page'] = FALSE; + $options['defaults']['default']['offset'] = FALSE; + $options['defaults']['default']['pager'] = FALSE; + $options['pager']['contains']['type']['default'] = 'some'; + } + + if ($this->isDefaultDisplay()) { + unset($options['defaults']); + } + + foreach ($this->extender as $extender) { + $extender->defineOptionsAlter($options); + } + + return $options; + } + + /** + * Check to see if the display has a 'path' field. + * + * This is a pure function and not just a setting on the definition + * because some displays (such as a panel pane) may have a path based + * upon configuration. + * + * By default, displays do not have a path. + */ + public function hasPath() { return FALSE; } + + /** + * Check to see if the display has some need to link to another display. + * + * For the most part, displays without a path will use a link display. However, + * sometimes displays that have a path might also need to link to another display. + * This is true for feeds. + */ + public function usesLinkDisplay() { return !$this->hasPath(); } + + /** + * Check to see if the display can put the exposed formin a block. + * + * By default, displays that do not have a path cannot disconnect + * the exposed form and put it in a block, because the form has no + * place to go and Views really wants the forms to go to a specific + * page. + */ + public function usesExposedFormInBlock() { return $this->hasPath(); } + + /** + * Find out all displays which are attached to this display. + * + * The method is just using the pure storage object to avoid loading of the + * sub displays which would kill lazy loading. + */ + public function getAttachedDisplays() { + $current_display_id = $this->display['id']; + $attached_displays = array(); + + // Go through all displays and search displays which link to this one. + foreach ($this->view->storage->get('display') as $display_id => $display) { + if (isset($display['display_options']['displays'])) { + $displays = $display['display_options']['displays']; + if (isset($displays[$current_display_id])) { + $attached_displays[] = $display_id; + } + } + } + + return $attached_displays; + } + + /** + * Check to see which display to use when creating links within + * a view using this display. + */ + public function getLinkDisplay() { + $display_id = $this->getOption('link_display'); + // If unknown, pick the first one. + if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) { + foreach ($this->view->displayHandlers as $display_id => $display) { + if (!empty($display) && $display->hasPath()) { + return $display_id; + } + } + } + else { + return $display_id; + } + // fall-through returns NULL + } + + /** + * Return the base path to use for this display. + * + * This can be overridden for displays that do strange things + * with the path. + */ + public function getPath() { + if ($this->hasPath()) { + return $this->getOption('path'); + } + + $display_id = $this->getLinkDisplay(); + if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) { + return $this->view->displayHandlers->get($display_id)->getPath(); + } + } + + public function getUrl() { + return $this->view->getUrl(); + } + + /** + * Check to see if the display needs a breadcrumb + * + * By default, displays do not need breadcrumbs + */ + public function usesBreadcrumb() { return FALSE; } + + /** + * Determine if a given option is set to use the default display or the + * current display + * + * @return + * TRUE for the default display + */ + public function isDefaulted($option) { + return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]); + } + + /** + * Intelligently get an option either from this display or from the + * default display, if directed to do so. + */ + public function getOption($option) { + if ($this->isDefaulted($option)) { + return $this->default_display->getOption($option); + } + + if (array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + } + + /** + * Determine if the display's style uses fields. + * + * @return bool + */ + public function usesFields() { + return $this->getPlugin('style')->usesFields(); + } + + /** + * Get the instance of a plugin, for example style or row. + * + * @param string $type + * The type of the plugin. + * + * @return \Drupal\views\Plugin\views\PluginBase + */ + public function getPlugin($type) { + // Look up the plugin name to use for this instance. + $options = $this->getOption($type); + + // Return now if no options have been loaded. + if (empty($options) || !isset($options['type'])) { + return; + } + + // Query plugins allow specifying a specific query class per base table. + if ($type == 'query') { + $views_data = Views::viewsData()->get($this->view->storage->get('base_table')); + $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query'; + } + else { + $name = $options['type']; + } + + // Plugin instances are stored on the display for re-use. + if (!isset($this->plugins[$type][$name])) { + $plugin = Views::pluginManager($type)->createInstance($name); + + // Initialize the plugin. + $plugin->init($this->view, $this, $options['options']); + + $this->plugins[$type][$name] = $plugin; + } + + return $this->plugins[$type][$name]; + } + + /** + * Get the handler object for a single handler. + */ + public function &getHandler($type, $id) { + if (!isset($this->handlers[$type])) { + $this->getHandlers($type); + } + + if (isset($this->handlers[$type][$id])) { + return $this->handlers[$type][$id]; + } + + // So we can return a reference. + $null = NULL; + return $null; + } + + /** + * Get a full array of handlers for $type. This caches them. + */ + public function getHandlers($type) { + if (!isset($this->handlers[$type])) { + $this->handlers[$type] = array(); + $types = ViewExecutable::viewsHandlerTypes(); + $plural = $types[$type]['plural']; + + foreach ($this->getOption($plural) as $id => $info) { + // If this is during form submission and there are temporary options + // which can only appear if the view is in the edit cache, use those + // options instead. This is used for AJAX multi-step stuff. + if (\Drupal::request()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) { + $info = $this->view->temporary_options[$type][$id]; + } + + if ($info['id'] != $id) { + $info['id'] = $id; + } + + // If aggregation is on, the group type might override the actual + // handler that is in use. This piece of code checks that and, + // if necessary, sets the override handler. + $override = NULL; + if ($this->useGroupBy() && !empty($info['group_type'])) { + if (empty($this->view->query)) { + $this->view->initQuery(); + } + $aggregate = $this->view->query->getAggregationInfo(); + if (!empty($aggregate[$info['group_type']]['handler'][$type])) { + $override = $aggregate[$info['group_type']]['handler'][$type]; + } + } + + if (!empty($types[$type]['type'])) { + $handler_type = $types[$type]['type']; + } + else { + $handler_type = $type; + } + + if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) { + // Special override for area types so they know where they come from. + if ($handler instanceof AreaPluginBase) { + $handler->areaType = $type; + } + + $handler->init($this->view, $this, $info); + $this->handlers[$type][$id] = &$handler; + } + + // Prevent reference problems. + unset($handler); + } + } + + return $this->handlers[$type]; + } + + /** + * Retrieves a list of fields for the current display. + * + * This also takes into account any associated relationships, if they exist. + * + * @param bool $groupable_only + * (optional) TRUE to only return an array of field labels from handlers + * that support the useStringGroupBy method, defaults to FALSE. + * + * @return array + * An array of applicable field options, keyed by ID. + */ + public function getFieldLabels($groupable_only = FALSE) { + $options = array(); + foreach ($this->getHandlers('relationship') as $relationship => $handler) { + $relationships[$relationship] = $handler->adminLabel(); + } + + foreach ($this->getHandlers('field') as $id => $handler) { + if ($groupable_only && !$handler->useStringGroupBy()) { + // Continue to next handler if it's not groupable. + continue; + } + if ($label = $handler->label()) { + $options[$id] = $label; + } + else { + $options[$id] = $handler->adminLabel(); + } + if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) { + $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id]; + } + } + return $options; + } + + /** + * Intelligently set an option either from this display or from the + * default display, if directed to do so. + */ + public function setOption($option, $value) { + if ($this->isDefaulted($option)) { + return $this->default_display->setOption($option, $value); + } + + // Set this in two places: On the handler where we'll notice it + // but also on the display object so it gets saved. This should + // only be a temporary fix. + $this->display['display_options'][$option] = $value; + return $this->options[$option] = $value; + } + + /** + * Set an option and force it to be an override. + */ + public function overrideOption($option, $value) { + $this->setOverride($option, FALSE); + $this->setOption($option, $value); + } + + /** + * Because forms may be split up into sections, this provides + * an easy URL to exactly the right section. Don't override this. + */ + public function optionLink($text, $section, $class = '', $title = '') { + if (!empty($class)) { + $text = '' . $text . ''; + } + + if (!trim($text)) { + $text = t('Broken field'); + } + + if (empty($title)) { + $title = $text; + } + + return l($text, 'admin/structure/views/nojs/display/' . $this->view->storage->id() . '/' . $this->display['id'] . '/' . $section, array('attributes' => array('class' => 'views-ajax-link ' . $class, 'title' => $title, 'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)), 'html' => TRUE)); + } + + /** + * Returns to tokens for arguments. + * + * This function is similar to views_handler_field::getRenderTokens() + * but without fields tokens. + */ + public function getArgumentsTokens() { + $tokens = array(); + if (!empty($this->view->build_info['substitutions'])) { + $tokens = $this->view->build_info['substitutions']; + } + $count = 0; + foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) { + $token = '%' . ++$count; + if (!isset($tokens[$token])) { + $tokens[$token] = ''; + } + + // Use strip tags as there should never be HTML in the path. + // However, we need to preserve special characters like " that + // were removed by check_plain(). + $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : ''; + } + + return $tokens; + } + + /** + * Provide the default summary for options in the views UI. + * + * This output is returned as an array. + */ + public function optionsSummary(&$categories, &$options) { + $categories = array( + 'title' => array( + 'title' => t('Title'), + 'column' => 'first', + ), + 'format' => array( + 'title' => t('Format'), + 'column' => 'first', + ), + 'filters' => array( + 'title' => t('Filters'), + 'column' => 'first', + ), + 'fields' => array( + 'title' => t('Fields'), + 'column' => 'first', + ), + 'pager' => array( + 'title' => t('Pager'), + 'column' => 'second', + ), + 'exposed' => array( + 'title' => t('Exposed form'), + 'column' => 'third', + 'build' => array( + '#weight' => 1, + ), + ), + 'access' => array( + 'title' => '', + 'column' => 'second', + 'build' => array( + '#weight' => -5, + ), + ), + 'other' => array( + 'title' => t('Other'), + 'column' => 'third', + 'build' => array( + '#weight' => 2, + ), + ), + ); + + if ($this->display['id'] != 'default') { + $options['display_id'] = array( + 'category' => 'other', + 'title' => t('Machine Name'), + 'value' => !empty($this->display['new_id']) ? check_plain($this->display['new_id']) : check_plain($this->display['id']), + 'desc' => t('Change the machine name of this display.'), + ); + } + + $display_comment = check_plain(drupal_substr($this->getOption('display_comment'), 0, 10)); + $options['display_comment'] = array( + 'category' => 'other', + 'title' => t('Administrative comment'), + 'value' => !empty($display_comment) ? $display_comment : t('None'), + 'desc' => t('Comment or document this display.'), + ); + + $title = strip_tags($this->getOption('title')); + if (!$title) { + $title = t('None'); + } + + $options['title'] = array( + 'category' => 'title', + 'title' => t('Title'), + 'value' => $title, + 'desc' => t('Change the title that this display will use.'), + ); + + $style_plugin_instance = $this->getPlugin('style'); + $style_summary = empty($style_plugin_instance->definition['title']) ? t('Missing style plugin') : $style_plugin_instance->summaryTitle(); + $style_title = empty($style_plugin_instance->definition['title']) ? t('Missing style plugin') : $style_plugin_instance->pluginTitle(); + + $style = ''; + + $options['style'] = array( + 'category' => 'format', + 'title' => t('Format'), + 'value' => $style_title, + 'setting' => $style_summary, + 'desc' => t('Change the way content is formatted.'), + ); + + // This adds a 'Settings' link to the style_options setting if the style has options. + if ($style_plugin_instance->usesOptions()) { + $options['style']['links']['style_options'] = t('Change settings for this format'); + } + + if ($style_plugin_instance->usesRowPlugin()) { + $row_plugin_instance = $this->getPlugin('row'); + $row_summary = empty($row_plugin_instance->definition['title']) ? t('Missing style plugin') : $row_plugin_instance->summaryTitle(); + $row_title = empty($row_plugin_instance->definition['title']) ? t('Missing style plugin') : $row_plugin_instance->pluginTitle(); + + $options['row'] = array( + 'category' => 'format', + 'title' => t('Show'), + 'value' => $row_title, + 'setting' => $row_summary, + 'desc' => t('Change the way each row in the view is styled.'), + ); + // This adds a 'Settings' link to the row_options setting if the row style has options. + if ($row_plugin_instance->usesOptions()) { + $options['row']['links']['row_options'] = t('Change settings for this style'); + } + } + if ($this->usesAJAX()) { + $options['use_ajax'] = array( + 'category' => 'other', + 'title' => t('Use AJAX'), + 'value' => $this->getOption('use_ajax') ? t('Yes') : t('No'), + 'desc' => t('Change whether or not this display will use AJAX.'), + ); + } + if ($this->usesAttachments()) { + $options['hide_attachment_summary'] = array( + 'category' => 'other', + 'title' => t('Hide attachments in summary'), + 'value' => $this->getOption('hide_attachment_summary') ? t('Yes') : t('No'), + 'desc' => t('Change whether or not to display attachments when displaying a contextual filter summary.'), + ); + } + if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) { + $options['show_admin_links'] = array( + 'category' => 'other', + 'title' => t('Contextual links'), + 'value' => $this->getOption('show_admin_links') ? t('Shown') : t('Hidden'), + 'desc' => t('Change whether or not to display contextual links for this view.'), + ); + } + + $pager_plugin = $this->getPlugin('pager'); + if (!$pager_plugin) { + // default to the no pager plugin. + $pager_plugin = Views::pluginManager('pager')->createInstance('none'); + } + + $pager_str = $pager_plugin->summaryTitle(); + + $options['pager'] = array( + 'category' => 'pager', + 'title' => t('Use pager'), + 'value' => $pager_plugin->pluginTitle(), + 'setting' => $pager_str, + 'desc' => t("Change this display's pager setting."), + ); + + // If pagers aren't allowed, change the text of the item: + if (!$this->usesPager()) { + $options['pager']['title'] = t('Items to display'); + } + + if ($pager_plugin->usesOptions()) { + $options['pager']['links']['pager_options'] = t('Change settings for this pager type.'); + } + + if ($this->usesMore()) { + $options['use_more'] = array( + 'category' => 'pager', + 'title' => t('More link'), + 'value' => $this->getOption('use_more') ? t('Yes') : t('No'), + 'desc' => t('Specify whether this display will provide a "more" link.'), + ); + } + + $this->view->initQuery(); + if ($this->view->query->getAggregationInfo()) { + $options['group_by'] = array( + 'category' => 'other', + 'title' => t('Use aggregation'), + 'value' => $this->getOption('group_by') ? t('Yes') : t('No'), + 'desc' => t('Allow grouping and aggregation (calculation) of fields.'), + ); + } + + $options['query'] = array( + 'category' => 'other', + 'title' => t('Query settings'), + 'value' => t('Settings'), + 'desc' => t('Allow to set some advanced settings for the query plugin'), + ); + + $languages = array( + '***CURRENT_LANGUAGE***' => t("Current user's language"), + '***DEFAULT_LANGUAGE***' => t("Default site language"), + Language::LANGCODE_NOT_SPECIFIED => t('Language neutral'), + ); + if (\Drupal::moduleHandler()->moduleExists('language')) { + $languages = array_merge($languages, language_list()); + } + $options['field_langcode'] = array( + 'category' => 'other', + 'title' => t('Field Language'), + 'value' => $languages[$this->getOption('field_langcode')], + 'desc' => t('All fields which support translations will be displayed in the selected language.'), + ); + + $access_plugin = $this->getPlugin('access'); + if (!$access_plugin) { + // default to the no access control plugin. + $access_plugin = Views::pluginManager('access')->createInstance('none'); + } + + $access_str = $access_plugin->summaryTitle(); + + $options['access'] = array( + 'category' => 'access', + 'title' => t('Access'), + 'value' => $access_plugin->pluginTitle(), + 'setting' => $access_str, + 'desc' => t('Specify access control type for this display.'), + ); + + if ($access_plugin->usesOptions()) { + $options['access']['links']['access_options'] = t('Change settings for this access type.'); + } + + $cache_plugin = $this->getPlugin('cache'); + if (!$cache_plugin) { + // default to the no cache control plugin. + $cache_plugin = Views::pluginManager('cache')->createInstance('none'); + } + + $cache_str = $cache_plugin->summaryTitle(); + + $options['cache'] = array( + 'category' => 'other', + 'title' => t('Caching'), + 'value' => $cache_plugin->pluginTitle(), + 'setting' => $cache_str, + 'desc' => t('Specify caching type for this display.'), + ); + + if ($cache_plugin->usesOptions()) { + $options['cache']['links']['cache_options'] = t('Change settings for this caching type.'); + } + + if ($access_plugin->usesOptions()) { + $options['access']['links']['access_options'] = t('Change settings for this access type.'); + } + + if ($this->usesLinkDisplay()) { + $display_id = $this->getLinkDisplay(); + $displays = $this->view->storage->get('display'); + $link_display = empty($displays[$display_id]) ? t('None') : check_plain($displays[$display_id]['display_title']); + $link_display = $this->getOption('link_display') == 'custom_url' ? t('Custom URL') : $link_display; + $options['link_display'] = array( + 'category' => 'pager', + 'title' => t('Link display'), + 'value' => $link_display, + 'desc' => t('Specify which display or custom url this display will link to.'), + ); + } + + if ($this->usesExposedFormInBlock()) { + $options['exposed_block'] = array( + 'category' => 'exposed', + 'title' => t('Exposed form in block'), + 'value' => $this->getOption('exposed_block') ? t('Yes') : t('No'), + 'desc' => t('Allow the exposed form to appear in a block instead of the view.'), + ); + } + + $exposed_form_plugin = $this->getPlugin('exposed_form'); + if (!$exposed_form_plugin) { + // default to the no cache control plugin. + $exposed_form_plugin = Views::pluginManager('exposed_form')->createInstance('basic'); + } + + $exposed_form_str = $exposed_form_plugin->summaryTitle(); + + $options['exposed_form'] = array( + 'category' => 'exposed', + 'title' => t('Exposed form style'), + 'value' => $exposed_form_plugin->pluginTitle(), + 'setting' => $exposed_form_str, + 'desc' => t('Select the kind of exposed filter to use.'), + ); + + if ($exposed_form_plugin->usesOptions()) { + $options['exposed_form']['links']['exposed_form_options'] = t('Exposed form settings for this exposed form style.'); + } + + $css_class = check_plain(trim($this->getOption('css_class'))); + if (!$css_class) { + $css_class = t('None'); + } + + $options['css_class'] = array( + 'category' => 'other', + 'title' => t('CSS class'), + 'value' => $css_class, + 'desc' => t('Change the CSS class name(s) that will be added to this display.'), + ); + + $options['analyze-theme'] = array( + 'category' => 'other', + 'title' => t('Output'), + 'value' => t('Templates'), + 'desc' => t('Get information on how to theme this display'), + ); + + foreach ($this->extender as $extender) { + $extender->optionsSummary($categories, $options); + } + } + + /** + * Provide the default form for setting options. + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($form, $form_state); + if ($this->defaultableSections($form_state['section'])) { + views_ui_standard_display_dropdown($form, $form_state, $form_state['section']); + } + $form['#title'] = check_plain($this->display['display_title']) . ': '; + + // Set the 'section' to hilite on the form. + // If it's the item we're looking at is pulling from the default display, + // reflect that. Don't use is_defaulted since we want it to show up even + // on the default display. + if (!empty($this->options['defaults'][$form_state['section']])) { + $form['#section'] = 'default-' . $form_state['section']; + } + else { + $form['#section'] = $this->display['id'] . '-' . $form_state['section']; + } + + switch ($form_state['section']) { + case 'display_id': + $form['#title'] .= t('The machine name of this display'); + $form['display_id'] = array( + '#type' => 'textfield', + '#title' => t('Machine name of the display'), + '#default_value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'], + '#required' => TRUE, + '#size' => 64, + ); + break; + case 'display_title': + $form['#title'] .= t('The name and the description of this display'); + $form['display_title'] = array( + '#title' => t('Administrative name'), + '#type' => 'textfield', + '#default_value' => $this->display['display_title'], + ); + $form['display_description'] = array( + '#title' => t('Administrative description'), + '#type' => 'textfield', + '#default_value' => $this->getOption('display_description'), + ); + break; + case 'display_comment': + $form['#title'] .= t('Administrative comment'); + $form['display_comment'] = array( + '#type' => 'textarea', + '#title' => t('Administrative comment'), + '#description' => t('This description will only be seen within the administrative interface and can be used to document this display.'), + '#default_value' => $this->getOption('display_comment'), + ); + break; + case 'title': + $form['#title'] .= t('The title of this view'); + $form['title'] = array( + '#title' => t('Title'), + '#type' => 'textfield', + '#description' => t('This title will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc.'), + '#default_value' => $this->getOption('title'), + ); + break; + case 'css_class': + $form['#title'] .= t('CSS class'); + $form['css_class'] = array( + '#type' => 'textfield', + '#title' => t('CSS class name(s)'), + '#description' => t('Multiples classes should be separated by spaces.'), + '#default_value' => $this->getOption('css_class'), + ); + break; + case 'use_ajax': + $form['#title'] .= t('Use AJAX when available to load this view'); + $form['use_ajax'] = array( + '#description' => t('When viewing a view, things like paging, table sorting, and exposed filters will not trigger a page refresh.'), + '#type' => 'checkbox', + '#title' => t('Use AJAX'), + '#default_value' => $this->getOption('use_ajax') ? 1 : 0, + ); + break; + case 'hide_attachment_summary': + $form['#title'] .= t('Hide attachments when displaying a contextual filter summary'); + $form['hide_attachment_summary'] = array( + '#type' => 'checkbox', + '#title' => t('Hide attachments in summary'), + '#default_value' => $this->getOption('hide_attachment_summary') ? 1 : 0, + ); + break; + case 'show_admin_links': + $form['#title'] .= t('Show contextual links on this view.'); + $form['show_admin_links'] = array( + '#type' => 'checkbox', + '#title' => t('Show contextual links'), + '#default_value' => $this->getOption('show_admin_links'), + ); + break; + case 'use_more': + $form['#title'] .= t('Add a more link to the bottom of the display.'); + $form['use_more'] = array( + '#type' => 'checkbox', + '#title' => t('Create more link'), + '#description' => t("This will add a more link to the bottom of this view, which will link to the page view. If you have more than one page view, the link will point to the display specified in 'Link display' section under advanced. You can override the url at the link display setting."), + '#default_value' => $this->getOption('use_more'), + ); + $form['use_more_always'] = array( + '#type' => 'checkbox', + '#title' => t('Always display the more link'), + '#description' => t('Check this to display the more link even if there are no more items to display.'), + '#default_value' => $this->getOption('use_more_always'), + '#states' => array( + 'visible' => array( + ':input[name="use_more"]' => array('checked' => TRUE), + ), + ), + ); + $form['use_more_text'] = array( + '#type' => 'textfield', + '#title' => t('More link text'), + '#description' => t('The text to display for the more link.'), + '#default_value' => $this->getOption('use_more_text'), + '#states' => array( + 'visible' => array( + ':input[name="use_more"]' => array('checked' => TRUE), + ), + ), + ); + break; + case 'group_by': + $form['#title'] .= t('Allow grouping and aggregation (calculation) of fields.'); + $form['group_by'] = array( + '#type' => 'checkbox', + '#title' => t('Aggregate'), + '#description' => t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'), + '#default_value' => $this->getOption('group_by'), + ); + break; + case 'access': + $form['#title'] .= t('Access restrictions'); + $form['access'] = array( + '#prefix' => '
The following tokens are available for this link.
'); + foreach (array_keys($options) as $type) { + if (!empty($options[$type])) { + $items = array(); + foreach ($options[$type] as $key => $value) { + $items[] = $key . ' == ' . $value; + } + $item_list = array( + '#theme' => 'item_list', + '#items' => $items, + '#list_type' => $type, + ); + $output .= drupal_render($item_list); + } + } + } + + $form['link_url'] = array( + '#type' => 'textfield', + '#title' => t('Custom URL'), + '#default_value' => $this->getOption('link_url'), + '#description' => t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.') . $output, + '#states' => array( + 'visible' => array( + ':input[name="link_display"]' => array('value' => 'custom_url'), + ), + ), + ); + break; + case 'analyze-theme': + $form['#title'] .= t('Theming information'); + if ($theme = \Drupal::request()->request->get('theme')) { + $this->theme = $theme; + } + elseif (empty($this->theme)) { + $this->theme = \Drupal::config('system.theme')->get('default'); + } + + if (isset($GLOBALS['theme']) && $GLOBALS['theme'] == $this->theme) { + $this->theme_registry = theme_get_registry(); + $theme_engine = $GLOBALS['theme_engine']; + } + else { + $themes = list_themes(); + $theme = $themes[$this->theme]; + + // Find all our ancestor themes and put them in an array. + $base_theme = array(); + $ancestor = $this->theme; + while ($ancestor && isset($themes[$ancestor]->base_theme)) { + $ancestor = $themes[$ancestor]->base_theme; + $base_theme[] = $themes[$ancestor]; + } + + // The base themes should be initialized in the right order. + $base_theme = array_reverse($base_theme); + + // This code is copied directly from _drupal_theme_initialize() + $theme_engine = NULL; + + // Initialize the theme. + if (isset($theme->engine)) { + // Include the engine. + include_once DRUPAL_ROOT . '/' . $theme->owner; + + $theme_engine = $theme->engine; + if (function_exists($theme_engine . '_init')) { + foreach ($base_theme as $base) { + call_user_func($theme_engine . '_init', $base); + } + call_user_func($theme_engine . '_init', $theme); + } + } + else { + // include non-engine theme files + foreach ($base_theme as $base) { + // Include the theme file or the engine. + if (!empty($base->owner)) { + include_once DRUPAL_ROOT . '/' . $base->owner; + } + } + // and our theme gets one too. + if (!empty($theme->owner)) { + include_once DRUPAL_ROOT . '/' . $theme->owner; + } + } + $this->theme_registry = _theme_load_registry($theme, $base_theme, $theme_engine); + } + + // If there's a theme engine involved, we also need to know its extension + // so we can give the proper filename. + $this->theme_extension = '.html.twig'; + if (isset($theme_engine)) { + $extension_function = $theme_engine . '_extension'; + if (function_exists($extension_function)) { + $this->theme_extension = $extension_function(); + } + } + + $funcs = array(); + // Get theme functions for the display. Note that some displays may + // not have themes. The 'feed' display, for example, completely + // delegates to the style. + if (!empty($this->definition['theme'])) { + $funcs[] = $this->optionLink(t('Display output'), 'analyze-theme-display') . ': ' . $this->formatThemes($this->themeFunctions()); + } + + $plugin = $this->getPlugin('style'); + if ($plugin) { + $funcs[] = $this->optionLink(t('Style output'), 'analyze-theme-style') . ': ' . $this->formatThemes($plugin->themeFunctions()); + + if ($plugin->usesRowPlugin()) { + $row_plugin = $this->getPlugin('row'); + if ($row_plugin) { + $funcs[] = $this->optionLink(t('Row style output'), 'analyze-theme-row') . ': ' . $this->formatThemes($row_plugin->themeFunctions()); + } + } + + if ($plugin->usesFields()) { + foreach ($this->getHandlers('field') as $id => $handler) { + $funcs[] = $this->optionLink(t('Field @field (ID: @id)', array('@field' => $handler->adminLabel(), '@id' => $id)), 'analyze-theme-field') . ': ' . $this->formatThemes($handler->themeFunctions()); + } + } + } + + $form['important'] = array( + '#markup' => '' . t('This section lists all possible templates for the display plugin and for the style plugins, ordered roughly from the least specific to the most specific. The active template for each plugin -- which is the most specific template found on the system -- is highlighted in bold.') . '
' . t('Back to !info.', array('!info' => $this->optionLink(t('theming information'), 'analyze-theme'))) . '
'; + + if (empty($this->definition['theme'])) { + $output .= t('This display has no theming information'); + } + else { + $output .= '' . t('This is the default theme template used for this display.') . '
'; + $output .= '' . check_plain(file_get_contents('./' . $this->definition['theme_path'] . '/' . strtr($this->definition['theme'], '_', '-') . '.tpl.php')) . ''; + } + + $form['analysis'] = array( + '#markup' => '
' . t('Back to !info.', array('!info' => $this->optionLink(t('theming information'), 'analyze-theme'))) . '
'; + + $plugin = $this->getPlugin('style'); + + if (empty($plugin->definition['theme'])) { + $output .= t('This display has no style theming information'); + } + else { + $output .= '' . t('This is the default theme template used for this style.') . '
'; + $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme_path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . ''; + } + + $form['analysis'] = array( + '#markup' => '
' . t('Back to !info.', array('!info' => $this->optionLink(t('theming information'), 'analyze-theme'))) . '
'; + + $plugin = $this->getPlugin('row'); + + if (empty($plugin->definition['theme'])) { + $output .= t('This display has no row style theming information'); + } + else { + $output .= '' . t('This is the default theme template used for this row style.') . '
'; + $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme_path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . ''; + } + + $form['analysis'] = array( + '#markup' => '
' . t('Back to !info.', array('!info' => $this->optionLink(t('theming information'), 'analyze-theme'))) . '
'; + + $output .= '' . t('This is the default theme template used for this row style.') . '
'; + + // Field templates aren't registered the normal way...and they're always + // this one, anyhow. + $output .= '' . check_plain(file_get_contents(drupal_get_path('module', 'views') . '/templates/views-view-field.tpl.php')) . ''; + + $form['analysis'] = array( + '#markup' => '