diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityExportForm.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityExportForm.php new file mode 100644 index 0000000..58e7cb4 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityExportForm.php @@ -0,0 +1,85 @@ +entityManager = $entity_manager; + $this->configStorage = $config_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('config.storage') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, array &$form_state) { + $entity_storage = $this->entityManager->getStorageController($this->entity->entityType()); + + $filename = $entity_storage->getConfigPrefix() . $this->entity->id() . '.yml'; + $form['export'] = array( + '#title' => $this->t('Export for %filename', array('%filename' => $filename)), + '#type' => 'textarea', + '#default_value' => $entity_storage->getExport($this->entity), + '#rows' => 100, + ); + + $form = parent::form($form, $form_state); + $form['#title'] = $this->t('Export of @label', array('@label' => $this->entity->label())); + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, array &$form_state) { + // Export does not provide actions. + return array(); + } + +} diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 60d0ce0..77099e4 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -32,7 +32,7 @@ * after the config_prefix in a config name forms the entity ID. Additional or * custom suffixes are not possible. */ -class ConfigStorageController extends EntityStorageControllerBase { +class ConfigStorageController extends EntityStorageControllerBase implements ConfigStorageControllerInterface { /** * Name of the entity's UUID property. @@ -204,26 +204,14 @@ public function getQuery($conjunction = 'AND') { } /** - * Returns the config prefix used by the configuration entity type. - * - * @return string - * The full configuration prefix, for example 'views.view.'. + * {@inheritdoc} */ public function getConfigPrefix() { return $this->entityInfo['config_prefix'] . '.'; } /** - * Extracts the configuration entity ID from the full configuration name. - * - * @param string $config_name - * The full configuration name to extract the ID from. E.g. - * 'views.view.archive'. - * @param string $config_prefix - * The config prefix of the configuration entity. E.g. 'views.view' - * - * @return string - * The ID of the configuration entity. + * {@inheritdoc} */ public static function getIDFromConfigName($config_name, $config_prefix) { return substr($config_name, strlen($config_prefix . '.')); @@ -460,17 +448,7 @@ public function getQueryServicename() { } /** - * Create configuration upon synchronizing configuration changes. - * - * This callback is invoked when configuration is synchronized between storages - * and allows a module to take over the synchronization of configuration data. - * - * @param string $name - * The name of the configuration object. - * @param \Drupal\Core\Config\Config $new_config - * A configuration object containing the new configuration data. - * @param \Drupal\Core\Config\Config $old_config - * A configuration object containing the old configuration data. + * {@inheritdoc} */ public function importCreate($name, Config $new_config, Config $old_config) { $entity = $this->create($new_config->get()); @@ -479,17 +457,7 @@ public function importCreate($name, Config $new_config, Config $old_config) { } /** - * Updates configuration upon synchronizing configuration changes. - * - * This callback is invoked when configuration is synchronized between storages - * and allows a module to take over the synchronization of configuration data. - * - * @param string $name - * The name of the configuration object. - * @param \Drupal\Core\Config\Config $new_config - * A configuration object containing the new configuration data. - * @param \Drupal\Core\Config\Config $old_config - * A configuration object containing the old configuration data. + * {@inheritdoc} */ public function importUpdate($name, Config $new_config, Config $old_config) { $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']); @@ -509,17 +477,7 @@ public function importUpdate($name, Config $new_config, Config $old_config) { } /** - * Delete configuration upon synchronizing configuration changes. - * - * This callback is invoked when configuration is synchronized between storages - * and allows a module to take over the synchronization of configuration data. - * - * @param string $name - * The name of the configuration object. - * @param \Drupal\Core\Config\Config $new_config - * A configuration object containing the new configuration data. - * @param \Drupal\Core\Config\Config $old_config - * A configuration object containing the old configuration data. + * {@inheritdoc} */ public function importDelete($name, Config $new_config, Config $old_config) { $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']); @@ -528,4 +486,13 @@ public function importDelete($name, Config $new_config, Config $old_config) { return TRUE; } + /** + * {@inheritdoc} + */ + public function getExport(ConfigEntityInterface $entity) { + // Read the config data and encode it for output. + $data = $this->configStorage->read($this->getConfigPrefix() . $entity->id()); + return $this->configStorage->encode($data); + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerInterface.php new file mode 100644 index 0000000..4277c45 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerInterface.php @@ -0,0 +1,101 @@ + 'Configuration entity Export UI', + 'description' => 'Tests the user interface for exporting configuration entities.', + 'group' => 'Configuration', + ); + } + + /** + * Tests the Export link in the listing and the generated export. + */ + public function testConfigTestExport() { + $this->drupalGet('admin/structure/config_test'); + $this->clickLink('Export'); + $this->assertUrl('admin/structure/config_test/manage/dotted.default/export'); + $element = $this->xpath('//textarea[@id="edit-export"]'); + $this->assertTrue(!empty($element), 'Textarea with config export found.'); + + // This assertion assumes the file storage uses YAML. + $export = Yaml::dump(array( + 'id' => 'dotted.default', + 'uuid' => '76696577-7364-7572-7061-6c636f726512', + 'label' => 'Default', + 'weight' => '0', + 'style' => '', + 'status' => '1', + 'langcode' => 'en', + 'protected_property' => 'Default', + )); + $this->assertEqual((string) $element[0], $export); + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php index b26e7f2..359dacd 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php @@ -59,6 +59,12 @@ function testList() { 'options' => $uri['options'], 'weight' => 10, ), + 'export' => array( + 'title' => t('Export'), + 'href' => $uri['path'] . '/export', + 'options' => $uri['options'], + 'weight' => 30, + ), 'disable' => array( 'title' => t('Disable'), 'href' => $uri['path'] . '/disable', @@ -138,6 +144,12 @@ function testList() { 'options' => $uri['options'], 'weight' => 10, ), + 'export' => array( + 'title' => t('Export'), + 'href' => $uri['path'] . '/export', + 'options' => $uri['options'], + 'weight' => 30, + ), 'delete' => array( 'title' => t('Delete'), 'href' => $uri['path'] . '/delete', diff --git a/core/modules/config/tests/config_test/config/config_test.dynamic.dotted.default.yml b/core/modules/config/tests/config_test/config/config_test.dynamic.dotted.default.yml index 93c3a77..beb3196 100644 --- a/core/modules/config/tests/config_test/config/config_test.dynamic.dotted.default.yml +++ b/core/modules/config/tests/config_test/config/config_test.dynamic.dotted.default.yml @@ -2,5 +2,6 @@ id: dotted.default label: Default weight: 0 protected_property: Default +uuid: 76696577-7364-7572-7061-6c636f726512 # Intentionally commented out to verify default status behavior. # status: 1 diff --git a/core/modules/config/tests/config_test/config_test.routing.yml b/core/modules/config/tests/config_test/config_test.routing.yml index 7a2689c..caa34e5 100644 --- a/core/modules/config/tests/config_test/config_test.routing.yml +++ b/core/modules/config/tests/config_test/config_test.routing.yml @@ -43,3 +43,10 @@ config_test.entity_delete: entity_type: 'config_test' requirements: _access: 'TRUE' + +config_test.entity_export: + path: '/admin/structure/config_test/manage/{config_test}/export' + defaults: + _entity_form: 'config_test.export' + requirements: + _access: 'TRUE' diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php index 564876a..cf64769 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestListController.php @@ -33,4 +33,21 @@ public function buildRow(EntityInterface $entity) { return $row + parent::buildRow($entity); } + /** + * {@inheritdoc} + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + if ($entity->access('update')) { + $uri = $entity->uri(); + $operations['export'] = array( + 'title' => t('Export'), + 'href' => $uri['path'] . '/export', + 'options' => $uri['options'], + 'weight' => 30, + ); + } + return $operations; + } + } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php index c355e25..14e5ae5 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php @@ -24,6 +24,7 @@ * "list" = "Drupal\config_test\ConfigTestListController", * "form" = { * "default" = "Drupal\config_test\ConfigTestFormController", + * "export" = "Drupal\Core\Config\Entity\ConfigEntityExportForm", * "delete" = "Drupal\config_test\Form\ConfigTestDeleteForm" * }, * "access" = "Drupal\config_test\ConfigTestAccessController" diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/Views/ContentTranslationViewsUITest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/Views/ContentTranslationViewsUITest.php index 5dbe880..4939059 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/Views/ContentTranslationViewsUITest.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/Views/ContentTranslationViewsUITest.php @@ -40,7 +40,7 @@ public static function getInfo() { * Tests the views UI. */ public function testViewsUI() { - $this->drupalGet('admin/structure/views/view/test_view/edit'); + $this->drupalGet('admin/structure/views/view/test_view'); $this->assertTitle(t('@label (@table) | @site-name', array('@label' => 'Test view', '@table' => 'Views test data', '@site-name' => $this->container->get('config.factory')->get('system.site')->get('name')))); } diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php b/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php index 5995f65..e1e560f 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/Controller/ViewsUIController.php @@ -244,14 +244,6 @@ public function autocompleteTag(Request $request) { * An array containing the Views edit and preview forms. */ public function edit(ViewUI $view, $display_id = NULL) { - $name = $view->label(); - $data = $this->viewsData->get($view->get('base_table')); - - if (isset($data['table']['base']['title'])) { - $name .= ' (' . $data['table']['base']['title'] . ')'; - } - drupal_set_title($name); - $build['edit'] = $this->entityManager->getForm($view, 'edit', array('display_id' => $display_id)); $build['preview'] = $this->entityManager->getForm($view, 'preview', array('display_id' => $display_id)); return $build; diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php index fb67096..0f82861 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php @@ -674,6 +674,10 @@ public function renderDisplayTop(ViewUI $view) { 'title' => $this->t('Clone view'), 'href' => "admin/structure/views/view/{$view->id()}/clone", ), + 'export' => array( + 'title' => $this->t('Export view'), + 'href' => "admin/structure/views/view/{$view->id()}/export", + ), 'reorder' => array( 'title' => $this->t('Reorder displays'), 'href' => "admin/structure/views/nojs/reorder-displays/{$view->id()}/$display_id", diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php index 33ac4de..328f7d2 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php @@ -137,14 +137,23 @@ public function buildHeader() { */ public function getOperations(EntityInterface $entity) { $operations = parent::getOperations($entity); - $uri = $entity->uri(); - $operations['clone'] = array( - 'title' => t('Clone'), - 'href' => $uri['path'] . '/clone', - 'options' => $uri['options'], - 'weight' => 15, - ); + if ($entity->access('update')) { + $uri = $entity->uri(); + $operations['clone'] = array( + 'title' => t('Clone'), + 'href' => $uri['path'] . '/clone', + 'options' => $uri['options'], + 'weight' => 15, + ); + + $operations['export'] = array( + 'title' => t('Export'), + 'href' => $uri['path'] . '/export', + 'options' => $uri['options'], + 'weight' => 30, + ); + } // Add AJAX functionality to enable/disable operations. foreach (array('enable', 'disable') as $op) { diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module index 3d09c2c..17ed5db 100644 --- a/core/modules/views_ui/views_ui.module +++ b/core/modules/views_ui/views_ui.module @@ -6,6 +6,7 @@ */ use Drupal\views\ViewExecutable; +use Drupal\views\Views; use Drupal\views\ViewStorageInterface; use Drupal\views_ui\ViewUI; use Drupal\views\Analyzer; @@ -47,6 +48,8 @@ function views_ui_menu() { // The primary Edit View page. Secondary tabs for each Display are added in // views_ui_menu_local_tasks_alter(). $items['admin/structure/views/view/%'] = array( + 'title callback' => 'views_ui_edit_page_title', + 'title arguments' => array(4), 'route_name' => 'views_ui.edit', ); $items['admin/structure/views/view/%/edit'] = array( @@ -67,6 +70,14 @@ function views_ui_menu() { 'route_name' => 'views_ui.break_lock', 'type' => MENU_VISIBLE_IN_BREADCRUMB, ); + $items['admin/structure/views/view/%/clone'] = array( + 'route_name' => 'views_ui.clone', + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + ); + $items['admin/structure/views/view/%/export'] = array( + 'route_name' => 'views_ui.export', + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + ); // NoJS/AJAX callbacks that can use the default Views AJAX form system. $items['admin/structure/views/nojs/%/%'] = array( @@ -92,6 +103,26 @@ function views_ui_menu() { } /** + * Returns the view title. + * + * Title callback for hook_menu(). + * + * @param \Drupal\views\ViewStorageInterface $view + * The view being edited. + * + * @return string + * The view title with base table appended. + */ +function views_ui_edit_page_title(ViewStorageInterface $view) { + $name = $view->label(); + $data = Views::viewsData()->get($view->get('base_table')); + if (isset($data['table']['base']['title'])) { + $name .= ' (' . $data['table']['base']['title'] . ')'; + } + return $name; +} + +/** * Implements hook_entity_info(). */ function views_ui_entity_info(&$entity_info) { @@ -102,6 +133,7 @@ function views_ui_entity_info(&$entity_info) { 'add' => 'Drupal\views_ui\ViewAddFormController', 'preview' => 'Drupal\views_ui\ViewPreviewFormController', 'clone' => 'Drupal\views_ui\ViewCloneFormController', + 'export' => 'Drupal\Core\Config\Entity\ConfigEntityExportForm', 'delete' => 'Drupal\views_ui\ViewDeleteFormController', 'break_lock' => 'Drupal\views_ui\Form\BreakLockForm', ), diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml index 3efc9ea..58740f4 100644 --- a/core/modules/views_ui/views_ui.routing.yml +++ b/core/modules/views_ui/views_ui.routing.yml @@ -111,6 +111,13 @@ views_ui.break_lock: requirements: _permission: 'administer views' +views_ui.export: + path: '/admin/structure/views/view/{view}/export' + defaults: + _entity_form: 'view.export' + requirements: + _entity_access: 'view.update' + views_ui.form_add_item: path: '/admin/structure/views/{js}/add-item/{view}/{display_id}/{type}' options: