diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 76ba2ae..5d08331 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -385,6 +385,31 @@ protected function addDependency($type, $name) { } /** + * Adds multiple dependencies. + * + * @param array $dependencies. + * An array of dependencies keyed by the type of dependency. One example: + * @code + * array( + * 'module' => array( + * 'node', + * 'field', + * 'image' + * ), + * ); + * @endcode + * + * @see ::addDependency + */ + protected function addDependencies(array $dependencies) { + foreach ($dependencies as $dependency_type => $list) { + foreach ($list as $name) { + $this->addDependency($dependency_type, $name); + } + } + } + + /** * {@inheritdoc} */ public function getConfigDependencyName() { diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php index 030bd50..f242dc0 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php +++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php @@ -51,7 +51,12 @@ class Field extends FieldPluginBase { * * @var \Drupal\Core\Field\FieldDefinitionInterface */ - public $field_info; + public $fieldDefinition; + + /** + * The field config. + */ + protected $fieldConfig; /** * Does the field supports multiple field values. @@ -141,18 +146,43 @@ public static function create(ContainerInterface $container, array $configuratio } /** + * Gets the field definition. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface + * The field definition used by this handler. + */ + protected function getFieldDefinition() { + if (!$this->fieldDefinition) { + $field_config = $this->getFieldConfig(); + $this->fieldDefinition = FieldDefinition::createFromFieldStorageDefinition($field_config); + } + return $this->fieldDefinition; + } + + /** + * Gets the field configuration. + * + * @return \Drupal\field\FieldConfigInterface + */ + protected function getFieldConfig() { + if (!$this->fieldConfig) { + $this->fieldConfig = FieldHelper::fieldInfo()->getField($this->definition['entity_type'], $this->definition['field_name']); + } + return $this->fieldConfig; + } + + /** * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::init(). */ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { parent::init($view, $display, $options); - $field_storage_definition = FieldHelper::fieldInfo()->getField($this->definition['entity_type'], $this->definition['field_name']); - $this->field_info = FieldDefinition::createFromFieldStorageDefinition($field_storage_definition); $this->multiple = FALSE; $this->limit_values = FALSE; - $cardinality = $this->field_info->getCardinality(); - if ($this->field_info->isMultiple()) { + $field_info = $this->getFieldDefinition(); + $cardinality = $field_info->getCardinality(); + if ($field_info->isMultiple()) { $this->multiple = TRUE; // If "Display all values in the same row" is FALSE, then we always limit @@ -180,7 +210,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o public function access(AccountInterface $account) { $base_table = $this->get_base_table(); $access_controller = $this->entityManager->getAccessController($this->definition['entity_tables'][$base_table]); - return $access_controller->fieldAccess('view', $this->field_info, $account); + return $access_controller->fieldAccess('view', $this->getFieldDefinition(), $account); } /** @@ -227,6 +257,7 @@ public function query($use_groupby = FALSE) { unset($fields[$entity_type_key]); } + $field_definition = $this->getFieldDefinition(); if ($use_groupby) { // Add the fields that we're actually grouping on. $options = array(); @@ -240,8 +271,8 @@ public function query($use_groupby = FALSE) { // Go through the list and determine the actual column name from field api. foreach ($options as $column) { $name = $column; - if (isset($this->field_info['storage_details']['sql'][$rkey][$this->table][$column])) { - $name = $this->field_info['storage_details']['sql'][$rkey][$this->table][$column]; + if (isset($field_definition['storage_details']['sql'][$rkey][$this->table][$column])) { + $name = $field_definition['storage_details']['sql'][$rkey][$this->table][$column]; } $fields[$column] = $name; @@ -256,7 +287,7 @@ public function query($use_groupby = FALSE) { $this->addAdditionalFields($fields); // Filter by langcode, if field translation is enabled. - $field = $this->field_info; + $field = $field_definition; if ($field->isTranslatable() && !empty($this->view->display_handler->options['field_langcode_add_to_query'])) { $column = $this->tableAlias . '.langcode'; // By the same reason as field_language the field might be Language::LANGCODE_NOT_SPECIFIED in reality so allow it as well. @@ -401,7 +432,7 @@ protected function defineOptions() { public function buildOptionsForm(&$form, &$form_state) { parent::buildOptionsForm($form, $form_state); - $field = $this->field_info; + $field = $this->getFieldDefinition(); $formatters = $this->formatterPluginManager->getOptions($field->getType()); $column_names = array_keys($field->getColumns()); @@ -480,7 +511,7 @@ public function buildOptionsForm(&$form, &$form_state) { * Provide options for multiple value fields. */ function multiple_options_form(&$form, &$form_state) { - $field = $this->field_info; + $field = $this->getFieldDefinition(); $form['multiple_field_settings'] = array( '#type' => 'details', @@ -607,7 +638,7 @@ public function buildGroupByForm(&$form, &$form_state) { // With "field API" fields, the column target of the grouping function // and any additional grouping columns must be specified. - $field_columns = array_keys($this->field_info->getColumns()); + $field_columns = array_keys($this->getFieldDefinition()->getColumns()); $group_columns = array( 'entity_id' => t('Entity ID'), ) + array_map('ucfirst', array_combine($field_columns, $field_columns)); @@ -832,14 +863,14 @@ function render_item($count, $item) { } protected function documentSelfTokens(&$tokens) { - $field = $this->field_info; + $field = $this->getFieldDefinition(); foreach ($field->getColumns() as $id => $column) { $tokens['[' . $this->options['id'] . '-' . $id . ']'] = t('Raw @column', array('@column' => $id)); } } protected function addSelfTokens(&$tokens, $item) { - $field = $this->field_info; + $field = $this->getFieldDefinition(); foreach ($field->getColumns() as $id => $column) { // Use \Drupal\Component\Utility\Xss::filterAdmin() because it's user data // and we can't be sure it is safe. We know nothing about the data, @@ -866,7 +897,7 @@ protected function addSelfTokens(&$tokens, $item) { * according to the settings. */ function field_langcode(EntityInterface $entity) { - if ($this->field_info->isTranslatable()) { + if ($this->getFieldDefinition()->isTranslatable()) { $default_langcode = language_default()->id; $langcode = str_replace( array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'), @@ -888,4 +919,13 @@ function field_langcode(EntityInterface $entity) { } } + /** + * {@inheritdoc} + */ + public function getDependencies() { + // Add the module providing the configured field as a dependency. + return array('entity' => array($this->getFieldConfig()->getConfigDependencyName())); + } + + } diff --git a/core/modules/views/lib/Drupal/views/Entity/View.php b/core/modules/views/lib/Drupal/views/Entity/View.php index 304d1f8..c3e19aa 100644 --- a/core/modules/views/lib/Drupal/views/Entity/View.php +++ b/core/modules/views/lib/Drupal/views/Entity/View.php @@ -12,7 +12,6 @@ use Drupal\views\Views; use Drupal\views_ui\ViewUI; use Drupal\views\ViewStorageInterface; -use Drupal\views\ViewExecutable; /** * Defines a View configuration entity class. @@ -273,28 +272,43 @@ public function calculateDependencies() { // Ensure that the view is dependant on the module that implements the view. $this->addDependency('module', $this->module); - // Ensure that the view is dependant on the module that provides the schema + // Ensure that the view is dependent on the module that provides the schema // for the base table. - $schema = drupal_get_schema($this->base_table); + $schema = $this->drupalGetSchema($this->base_table); if ($this->module != $schema['module']) { $this->addDependency('module', $schema['module']); } $handler_types = array(); - foreach (ViewExecutable::getHandlerTypes() as $type) { + foreach (Views::getHandlerTypes() as $type) { $handler_types[] = $type['plural']; } + foreach ($this->get('display') as $display) { + // Collect all dependencies of all handlers. foreach ($handler_types as $handler_type) { if (!empty($display['display_options'][$handler_type])) { foreach ($display['display_options'][$handler_type] as $handler) { + // Add the provider as dependency. if (isset($handler['provider']) && empty($handler['optional'])) { $this->addDependency('module', $handler['provider']); } + // Add the additional dependencies from the handler configuration. + if (!empty($handler['dependencies'])) { + $this->addDependencies($handler['dependencies']); + } } } } + + // Collect all dependencies of plugins. + foreach (Views::getPluginTypes('plugin') as $plugin_type) { + if (!empty($display['display_options'][$plugin_type]['options']['dependencies'])) { + $this->addDependencies($display['display_options'][$plugin_type]['options']['dependencies']); + } + } } + return $this->dependencies; } @@ -386,4 +400,11 @@ public function mergeDefaultDisplaysOptions() { $this->set('display', $displays); } + /** + * Wraps drupal_get_schema(). + */ + protected function drupalGetSchema($table = NULL, $rebuild = FALSE) { + return drupal_get_schema($table, $rebuild); + } + } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php index e0bf952..cbff847 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php @@ -168,6 +168,7 @@ protected function defineOptions() { $options['relationship'] = array('default' => 'none'); $options['group_type'] = array('default' => 'group'); $options['admin_label'] = array('default' => '', 'translatable' => TRUE); + $options['dependencies'] = array('default' => array()); return $options; } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php index ba15dbb..f25e2d0 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/PluginBase.php @@ -424,4 +424,16 @@ public static function preRenderFlattenData($form) { return $form; } + /** + * Returns an array of module dependencies for this plugin. + * + * Dependencies are a list of module names, which might depend on the + * configuration. + * + * @return array + */ + public function getDependencies() { + return array(); + } + } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php index 50e8c85..4ed08e4 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php @@ -767,6 +767,7 @@ protected function defaultDisplayOptions() { foreach ($display_options as &$options) { $options['options'] = array(); $options['provider'] = 'views'; + $options['dependencies'] = array(); } // Add a least one field so the view validates and the user has a preview. diff --git a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php index 089f428..31b9f7b 100644 --- a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php +++ b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php @@ -29,4 +29,5 @@ public function &getDisplay($display_id); * Add defaults to the display options. */ public function mergeDefaultDisplaysOptions(); + } diff --git a/core/modules/views/tests/Drupal/views/Tests/Entity/ViewTest.php b/core/modules/views/tests/Drupal/views/Tests/Entity/ViewTest.php new file mode 100644 index 0000000..0aae557 --- /dev/null +++ b/core/modules/views/tests/Drupal/views/Tests/Entity/ViewTest.php @@ -0,0 +1,97 @@ + 'View entity test', + 'description' => 'Tests the \Drupal\views\Entity\View class.', + 'group' => 'Views', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + + // Setup the entity manager. + $entity_definition = new EntityType(array('id' => 'view')); + $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_manager->expects($this->any()) + ->method('getDefinition') + ->will($this->returnValue($entity_definition)); + $container_builder = new ContainerBuilder(); + $container_builder->set('entity.manager', $entity_manager); + + // Setup the string translation. + $string_translation = $this->getStringTranslationStub(); + $container_builder->set('string_translation', $string_translation); + \Drupal::setContainer($container_builder); + } + + /** + * Tests calculating dependencies. + * + * @covers ::calculateDependencies + * @dataProvider calculateDependenciesProvider + */ + public function testCalculateDependencies($values, $deps) { + $view = new TestView($values, 'view'); + $this->assertEquals(array('module' => $deps), $view->calculateDependencies()); + } + + public function calculateDependenciesProvider(){ + $handler['display']['default']['display_options']['fields']['example']['dependencies'] = array(); + $handler['display']['default']['display_options']['fields']['example2']['dependencies']['module'] = array('views', 'field'); + $handler['display']['default']['display_options']['fields']['example3']['dependencies']['module'] = array('views', 'image'); + + $plugin['display']['default']['display_options']['access']['options']['dependencies'] = array(); + $plugin['display']['default']['display_options']['row']['options']['dependencies']['module'] = array('views', 'field'); + $plugin['display']['default']['display_options']['style']['options']['dependencies']['module'] = array('views', 'image'); + + return array( + array(array(), array('node', 'views')), + array($handler, array('field', 'image', 'node', 'views')), + array($plugin, array('field', 'image', 'node', 'views')), + ); + } +} + +class TestView extends View { + + /** + * {@inheritdoc} + */ + protected function drupalGetSchema($table = NULL, $rebuild = FALSE) { + $result = array(); + if ($table == 'node') { + $result['module'] = 'node'; + } + return $result; + } + +} + +} diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ConfigHandler.php b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ConfigHandler.php index 4f3bc7b..8533e69 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ConfigHandler.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ConfigHandler.php @@ -238,6 +238,14 @@ public function submitForm(array &$form, array &$form_state) { // extra stuff on the form is not sent through. $handler->unpackOptions($handler->options, $options, NULL, FALSE); + // Add any dependencies as the handler is saved. Put it here so + // it does not need to be declared in defineOptions(). + if ($dependencies = $handler->getDependencies()) { + $handler->options['dependencies'] = $dependencies; + } + // Add the module providing the handler as a dependency as well. + $handler->options['dependencies']['module'][] = $handler->definition['provider']; + // Store the item back on the view $executable->setHandler($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);