Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,7 @@
name: 'Configuration Manager'
type: module
description: 'Allows administrators to manage configuration changes.'
package: Core
version: VERSION
core: 8.x
configure: config.sync

View file

@ -0,0 +1,32 @@
<?php
/**
* @file
* Install, update and uninstall functions for the config module.
*/
/**
* Implements hook_requirements().
*/
function config_requirements($phase) {
$requirements = [];
try {
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
}
catch (\Exception $e) {
// system_requirements() guarantees that the CONFIG_SYNC_DIRECTORY exists
// as the config.storage.staging service relies on it.
$directory = FALSE;
}
// Ensure the configuration sync directory is writable. This is only a warning
// because only configuration import from a tarball requires the folder to be
// web writable.
if ($phase !== 'install' && !is_writable($directory)) {
$requirements['config directory ' . CONFIG_SYNC_DIRECTORY] = [
'title' => t('Configuration directory: %type', ['%type' => CONFIG_SYNC_DIRECTORY]),
'description' => t('The directory %directory is not writable.', ['%directory' => $directory]),
'severity' => REQUIREMENT_WARNING,
];
}
return $requirements;
}

View file

@ -0,0 +1,5 @@
config.sync:
title: 'Configuration synchronization'
description: 'Import and export your configuration.'
route_name: config.sync
parent: system.admin_config_development

View file

@ -0,0 +1,34 @@
config.sync:
route_name: config.sync
base_route: config.sync
title: 'Synchronize'
config.import:
route_name: config.import_full
title: 'Import'
base_route: config.sync
config.export:
route_name: config.export_full
title: 'Export'
base_route: config.sync
config.export_full:
route_name: config.export_full
title: Full archive
parent_id: config.export
config.import_full:
route_name: config.import_full
title: Full archive
parent_id: config.import
config.export_single:
route_name: config.export_single
title: Single item
parent_id: config.export
config.import_single:
route_name: config.import_single
title: Single item
parent_id: config.import

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Allows site administrators to modify configuration.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function config_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.config':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Configuration Manager module provides a user interface for importing and exporting configuration changes between installations of your website in different environments. Configuration is stored in YAML format. For more information, see the <a href=":url">online documentation for the Configuration Manager module</a>.', array(':url' => 'https://www.drupal.org/documentation/administer/config')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Exporting the full configuration') . '</dt>';
$output .= '<dd>' . t('You can create and download an archive consisting of all your site\'s configuration exported as <em>*.yml</em> files on the <a href=":url">Export</a> page.', array(':url' => \Drupal::url('config.export_full'))) . '</dd>';
$output .= '<dt>' . t('Importing a full configuration') . '</dt>';
$output .= '<dd>' . t('You can upload a full site configuration from an archive file on the <a href=":url">Import</a> page. When importing data from a different environment, the site and import files must have matching configuration values for UUID in the <em>system.site</em> configuration item. That means that your other environments should initially be set up as clones of the target site. Migrations are not supported.', array(':url' => \Drupal::url('config.import_full'))) . '</dd>';
$output .= '<dt>' . t('Synchronizing configuration') . '</dt>';
$output .= '<dd>' . t('You can review differences between the active configuration and an imported configuration archive on the <a href=":synchronize">Synchronize</a> page to ensure that the changes are as expected, before finalizing the import. The Synchronize page also shows configuration items that would be added or removed.', array(':synchronize' => \Drupal::url('config.sync'))) . '</dd>';
$output .= '<dt>' . t('Exporting a single configuration item') . '</dt>';
$output .= '<dd>' . t('You can export a single configuration item by selecting a <em>Configuration type</em> and <em>Configuration name</em> on the <a href=":single-export">Single export</a> page. The configuration and its corresponding <em>*.yml file name</em> are then displayed on the page for you to copy.', array(':single-export' => \Drupal::url('config.export_single'))) . '</dd>';
$output .= '<dt>' . t('Importing a single configuration item') . '</dt>';
$output .= '<dd>' . t('You can import a single configuration item by pasting it in YAML format into the form on the <a href=":single-import">Single import</a> page.', array(':single-import' => \Drupal::url('config.import_single'))) . '</dd>';
$output .= '</dl>';
return $output;
case 'config.sync':
$output = '';
$output .= '<p>' . t('Compare the configuration uploaded to your sync directory with the active configuration before completing the import.') . '</p>';
return $output;
case 'config.export_full':
$output = '';
$output .= '<p>' . t('Export and download the full configuration of this site as a gzipped tar file.') . '</p>';
return $output;
case 'config.import_full':
$output = '';
$output .= '<p>' . t('Upload a full site configuration archive to the sync directory. It can then be compared and imported on the Synchronize page.') . '</p>';
return $output;
case 'config.export_single':
$output = '';
$output .= '<p>' . t('Choose a configuration item to display its YAML structure.') . '</p>';
return $output;
case 'config.import_single':
$output = '';
$output .= '<p>' . t('Import a single configuration item by pasting its YAML structure into the text field.') . '</p>';
return $output;
}
}
/**
* Implements hook_file_download().
*/
function config_file_download($uri) {
$scheme = file_uri_scheme($uri);
$target = file_uri_target($uri);
if ($scheme == 'temporary' && $target == 'config.tar.gz') {
if (\Drupal::currentUser()->hasPermission('export configuration')) {
$request = \Drupal::request();
$date = DateTime::createFromFormat('U', $request->server->get('REQUEST_TIME'));
$date_string = $date->format('Y-m-d-H-i');
$hostname = str_replace('.', '-', $request->getHttpHost());
$filename = 'config' . '-' . $hostname . '-' . $date_string . '.tar.gz';
$disposition = 'attachment; filename="' . $filename . '"';
return array(
'Content-disposition' => $disposition,
);
}
return -1;
}
}

View file

@ -0,0 +1,9 @@
synchronize configuration:
title: 'Synchronize configuration'
restrict access: true
export configuration:
title: 'Export configuration'
restrict access: true
import configuration:
title: 'Import configuration'
restrict access: true

View file

@ -0,0 +1,64 @@
config.sync:
path: '/admin/config/development/configuration'
defaults:
_form: '\Drupal\config\Form\ConfigSync'
_title: 'Synchronize'
requirements:
_permission: 'synchronize configuration'
config.diff:
path: '/admin/config/development/configuration/sync/diff/{source_name}/{target_name}'
defaults:
_controller: '\Drupal\config\Controller\ConfigController::diff'
target_name: NULL
requirements:
_permission: 'synchronize configuration'
config.diff_collection:
path: '/admin/config/development/configuration/sync/diff_collection/{collection}/{source_name}/{target_name}'
defaults:
_controller: '\Drupal\config\Controller\ConfigController::diff'
target_name: NULL
requirements:
_permission: 'synchronize configuration'
config.export_download:
path: '/admin/config/development/configuration/full/export-download'
defaults:
_controller: '\Drupal\config\Controller\ConfigController::downloadExport'
requirements:
_permission: 'export configuration'
config.export_full:
path: '/admin/config/development/configuration/full/export'
defaults:
_form: '\Drupal\config\Form\ConfigExportForm'
_title: 'Export'
requirements:
_permission: 'export configuration'
config.import_full:
path: '/admin/config/development/configuration/full/import'
defaults:
_form: '\Drupal\config\Form\ConfigImportForm'
_title: 'Import'
requirements:
_permission: 'import configuration'
config.import_single:
path: '/admin/config/development/configuration/single/import'
defaults:
_title: 'Single import'
_form: '\Drupal\config\Form\ConfigSingleImportForm'
requirements:
_permission: 'import configuration'
config.export_single:
path: '/admin/config/development/configuration/single/export/{config_type}/{config_name}'
defaults:
_title: 'Single export'
_form: '\Drupal\config\Form\ConfigSingleExportForm'
config_type: NULL
config_name: NULL
requirements:
_permission: 'export configuration'

View file

@ -0,0 +1,5 @@
services:
config.config_subscriber:
class: Drupal\config\ConfigSubscriber
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\config;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\ConfigImporterEvent;
use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
/**
* Config subscriber.
*/
class ConfigSubscriber extends ConfigImportValidateEventSubscriberBase {
/**
* Checks that the Configuration module is not being uninstalled.
*
* @param ConfigImporterEvent $event
* The config import event.
*/
public function onConfigImporterValidate(ConfigImporterEvent $event) {
$importer = $event->getConfigImporter();
$core_extension = $importer->getStorageComparer()->getSourceStorage()->read('core.extension');
if (!isset($core_extension['module']['config'])) {
$importer->logError($this->t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'));
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[ConfigEvents::IMPORT_VALIDATE][] = array('onConfigImporterValidate', 20);
return $events;
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Drupal\config\Controller;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Diff\DiffFormatter;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Url;
use Drupal\system\FileDownloadController;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns responses for config module routes.
*/
class ConfigController implements ContainerInjectionInterface {
/**
* The target storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $targetStorage;
/**
* The source storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $sourceStorage;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*/
protected $configManager;
/**
* The file download controller.
*
* @var \Drupal\system\FileDownloadController
*/
protected $fileDownloadController;
/**
* The diff formatter.
*
* @var \Drupal\Core\Diff\DiffFormatter
*/
protected $diffFormatter;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.storage'),
$container->get('config.storage.sync'),
$container->get('config.manager'),
new FileDownloadController(),
$container->get('diff.formatter')
);
}
/**
* Constructs a ConfigController object.
*
* @param \Drupal\Core\Config\StorageInterface $target_storage
* The target storage.
* @param \Drupal\Core\Config\StorageInterface $source_storage
* The source storage
* @param \Drupal\system\FileDownloadController $file_download_controller
* The file download controller.
*/
public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, ConfigManagerInterface $config_manager, FileDownloadController $file_download_controller, DiffFormatter $diff_formatter) {
$this->targetStorage = $target_storage;
$this->sourceStorage = $source_storage;
$this->configManager = $config_manager;
$this->fileDownloadController = $file_download_controller;
$this->diffFormatter = $diff_formatter;
}
/**
* Downloads a tarball of the site configuration.
*/
public function downloadExport() {
file_unmanaged_delete(file_directory_temp() . '/config.tar.gz');
$archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz');
// Get raw configuration data without overrides.
foreach ($this->configManager->getConfigFactory()->listAll() as $name) {
$archiver->addString("$name.yml", Yaml::encode($this->configManager->getConfigFactory()->get($name)->getRawData()));
}
// Get all override data from the remaining collections.
foreach ($this->targetStorage->getAllCollectionNames() as $collection) {
$collection_storage = $this->targetStorage->createCollection($collection);
foreach ($collection_storage->listAll() as $name) {
$archiver->addString(str_replace('.', '/', $collection) . "/$name.yml", Yaml::encode($collection_storage->read($name)));
}
}
$request = new Request(array('file' => 'config.tar.gz'));
return $this->fileDownloadController->download($request, 'temporary');
}
/**
* Shows diff of specified configuration file.
*
* @param string $source_name
* The name of the configuration file.
* @param string $target_name
* (optional) The name of the target configuration file if different from
* the $source_name.
* @param string $collection
* (optional) The configuration collection name. Defaults to the default
* collection.
*
* @return string
* Table showing a two-way diff between the active and staged configuration.
*/
public function diff($source_name, $target_name = NULL, $collection = NULL) {
if (!isset($collection)) {
$collection = StorageInterface::DEFAULT_COLLECTION;
}
$diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $source_name, $target_name, $collection);
$this->diffFormatter->show_header = FALSE;
$build = array();
$build['#title'] = t('View changes of @config_file', array('@config_file' => $source_name));
// Add the CSS for the inline diff.
$build['#attached']['library'][] = 'system/diff';
$build['diff'] = array(
'#type' => 'table',
'#attributes' => array(
'class' => array('diff'),
),
'#header' => array(
array('data' => t('Active'), 'colspan' => '2'),
array('data' => t('Staged'), 'colspan' => '2'),
),
'#rows' => $this->diffFormatter->format($diff),
);
$build['back'] = array(
'#type' => 'link',
'#attributes' => array(
'class' => array(
'dialog-cancel',
),
),
'#title' => "Back to 'Synchronize configuration' page.",
'#url' => Url::fromRoute('config.sync'),
);
return $build;
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\config\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the configuration export form.
*/
class ConfigExportForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_export_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Export'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRedirect('config.export_download');
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\config\Form;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the configuration import form.
*/
class ConfigImportForm extends FormBase {
/**
* The configuration storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $configStorage;
/**
* Constructs a new ConfigImportForm.
*
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The configuration storage.
*/
public function __construct(StorageInterface $config_storage) {
$this->configStorage = $config_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.storage.sync')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_import_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
$directory_is_writable = is_writable($directory);
if (!$directory_is_writable) {
drupal_set_message($this->t('The directory %directory is not writable.', ['%directory' => $directory]), 'error');
}
$form['import_tarball'] = array(
'#type' => 'file',
'#title' => $this->t('Configuration archive'),
'#description' => $this->t('Allowed types: @extensions.', array('@extensions' => 'tar.gz tgz tar.bz2')),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Upload'),
'#disabled' => !$directory_is_writable,
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$all_files = $this->getRequest()->files->get('files', []);
if (!empty($all_files['import_tarball'])) {
$file_upload = $all_files['import_tarball'];
if ($file_upload->isValid()) {
$form_state->setValue('import_tarball', $file_upload->getRealPath());
return;
}
}
$form_state->setErrorByName('import_tarball', $this->t('The file could not be uploaded.'));
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($path = $form_state->getValue('import_tarball')) {
$this->configStorage->deleteAll();
try {
$archiver = new ArchiveTar($path, 'gz');
$files = array();
foreach ($archiver->listContent() as $file) {
$files[] = $file['filename'];
}
$archiver->extractList($files, config_get_config_directory(CONFIG_SYNC_DIRECTORY));
drupal_set_message($this->t('Your configuration files were successfully uploaded and are ready for import.'));
$form_state->setRedirect('config.sync');
}
catch (\Exception $e) {
drupal_set_message($this->t('Could not extract the contents of the tar file. The error message is <em>@message</em>', array('@message' => $e->getMessage())), 'error');
}
drupal_unlink($path);
}
}
}

View file

@ -0,0 +1,204 @@
<?php
namespace Drupal\config\Form;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for exporting a single configuration file.
*/
class ConfigSingleExportForm extends FormBase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The config storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $configStorage;
/**
* Tracks the valid config entity type definitions.
*
* @var \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected $definitions = array();
/**
* Constructs a new ConfigSingleImportForm.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The config storage.
*/
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) {
$this->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 getFormId() {
return 'config_single_export_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $config_type = NULL, $config_name = NULL) {
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$this->definitions[$entity_type] = $definition;
}
}
$entity_types = array_map(function (EntityTypeInterface $definition) {
return $definition->getLabel();
}, $this->definitions);
// Sort the entity types by label, then add the simple config to the top.
uasort($entity_types, 'strnatcasecmp');
$config_types = array(
'system.simple' => $this->t('Simple configuration'),
) + $entity_types;
$form['config_type'] = array(
'#title' => $this->t('Configuration type'),
'#type' => 'select',
'#options' => $config_types,
'#default_value' => $config_type,
'#ajax' => array(
'callback' => '::updateConfigurationType',
'wrapper' => 'edit-config-type-wrapper',
),
);
$default_type = $form_state->getValue('config_type', $config_type);
$form['config_name'] = array(
'#title' => $this->t('Configuration name'),
'#type' => 'select',
'#options' => $this->findConfiguration($default_type),
'#default_value' => $config_name,
'#prefix' => '<div id="edit-config-type-wrapper">',
'#suffix' => '</div>',
'#ajax' => array(
'callback' => '::updateExport',
'wrapper' => 'edit-export-wrapper',
),
);
$form['export'] = array(
'#title' => $this->t('Here is your configuration:'),
'#type' => 'textarea',
'#rows' => 24,
'#prefix' => '<div id="edit-export-wrapper">',
'#suffix' => '</div>',
);
if ($config_type && $config_name) {
$fake_form_state = (new FormState())->setValues([
'config_type' => $config_type,
'config_name' => $config_name,
]);
$form['export'] = $this->updateExport($form, $fake_form_state);
}
return $form;
}
/**
* Handles switching the configuration type selector.
*/
public function updateConfigurationType($form, FormStateInterface $form_state) {
$form['config_name']['#options'] = $this->findConfiguration($form_state->getValue('config_type'));
return $form['config_name'];
}
/**
* Handles switching the export textarea.
*/
public function updateExport($form, FormStateInterface $form_state) {
// Determine the full config name for the selected config entity.
if ($form_state->getValue('config_type') !== 'system.simple') {
$definition = $this->entityManager->getDefinition($form_state->getValue('config_type'));
$name = $definition->getConfigPrefix() . '.' . $form_state->getValue('config_name');
}
// The config name is used directly for simple configuration.
else {
$name = $form_state->getValue('config_name');
}
// Read the raw data for this config name, encode it, and display it.
$form['export']['#value'] = Yaml::encode($this->configStorage->read($name));
$form['export']['#description'] = $this->t('Filename: %name', array('%name' => $name . '.yml'));
return $form['export'];
}
/**
* Handles switching the configuration type selector.
*/
protected function findConfiguration($config_type) {
$names = array(
'' => $this->t('- Select -'),
);
// For a given entity type, load all entities.
if ($config_type && $config_type !== 'system.simple') {
$entity_storage = $this->entityManager->getStorage($config_type);
foreach ($entity_storage->loadMultiple() as $entity) {
$entity_id = $entity->id();
if ($label = $entity->label()) {
$names[$entity_id] = new TranslatableMarkup('@label (@id)', ['@label' => $label, '@id' => $entity_id]);
}
else {
$names[$entity_id] = $entity_id;
}
}
}
// Handle simple configuration.
else {
// Gather the config entity prefixes.
$config_prefixes = array_map(function (EntityTypeInterface $definition) {
return $definition->getConfigPrefix() . '.';
}, $this->definitions);
// Find all config, and then filter our anything matching a config prefix.
$names = $this->configStorage->listAll();
$names = array_combine($names, $names);
foreach ($names as $config_name) {
foreach ($config_prefixes as $config_prefix) {
if (strpos($config_name, $config_prefix) === 0) {
unset($names[$config_name]);
}
}
}
}
return $names;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Nothing to submit.
}
}

View file

@ -0,0 +1,424 @@
<?php
namespace Drupal\config\Form;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\config\StorageReplaceDataWrapper;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Provides a form for importing a single configuration file.
*/
class ConfigSingleImportForm extends ConfirmFormBase {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The config storage.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $configStorage;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface;
*/
protected $configManager;
/**
* The database lock object.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* If the config exists, this is that object. Otherwise, FALSE.
*
* @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool
*/
protected $configExists = FALSE;
/**
* The submitted data needing to be confirmed.
*
* @var array
*/
protected $data = array();
/**
* Constructs a new ConfigSingleImportForm.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The config storage.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher used to notify subscribers of config import events.
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* The configuration manager.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend to ensure multiple imports do not occur at the same time.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
$this->entityManager = $entity_manager;
$this->configStorage = $config_storage;
$this->renderer = $renderer;
// Services necessary for \Drupal\Core\Config\ConfigImporter.
$this->eventDispatcher = $event_dispatcher;
$this->configManager = $config_manager;
$this->lock = $lock;
$this->typedConfigManager = $typed_config;
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('config.storage'),
$container->get('renderer'),
$container->get('event_dispatcher'),
$container->get('config.manager'),
$container->get('lock.persistent'),
$container->get('config.typed'),
$container->get('module_handler'),
$container->get('module_installer'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_single_import_form';
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('config.import_single');
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
if ($this->data['config_type'] === 'system.simple') {
$name = $this->data['config_name'];
$type = $this->t('simple configuration');
}
else {
$definition = $this->entityManager->getDefinition($this->data['config_type']);
$name = $this->data['import'][$definition->getKey('id')];
$type = $definition->getLowercaseLabel();
}
$args = array(
'%name' => $name,
'@type' => strtolower($type),
);
if ($this->configExists) {
$question = $this->t('Are you sure you want to update the %name @type?', $args);
}
else {
$question = $this->t('Are you sure you want to create a new %name @type?', $args);
}
return $question;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// When this is the confirmation step fall through to the confirmation form.
if ($this->data) {
return parent::buildForm($form, $form_state);
}
$entity_types = array();
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$entity_types[$entity_type] = $definition->getLabel();
}
}
// Sort the entity types by label, then add the simple config to the top.
uasort($entity_types, 'strnatcasecmp');
$config_types = array(
'system.simple' => $this->t('Simple configuration'),
) + $entity_types;
$form['config_type'] = array(
'#title' => $this->t('Configuration type'),
'#type' => 'select',
'#options' => $config_types,
'#required' => TRUE,
);
$form['config_name'] = array(
'#title' => $this->t('Configuration name'),
'#description' => $this->t('Enter the name of the configuration file without the <em>.yml</em> extension. (e.g. <em>system.site</em>)'),
'#type' => 'textfield',
'#states' => array(
'required' => array(
':input[name="config_type"]' => array('value' => 'system.simple'),
),
'visible' => array(
':input[name="config_type"]' => array('value' => 'system.simple'),
),
),
);
$form['import'] = array(
'#title' => $this->t('Paste your configuration here'),
'#type' => 'textarea',
'#rows' => 24,
'#required' => TRUE,
);
$form['advanced'] = array(
'#type' => 'details',
'#title' => $this->t('Advanced'),
);
$form['advanced']['custom_entity_id'] = array(
'#title' => $this->t('Custom Entity ID'),
'#type' => 'textfield',
'#description' => $this->t('Specify a custom entity ID. This will override the entity ID in the configuration above.'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Import'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// The confirmation step needs no additional validation.
if ($this->data) {
return;
}
try {
// Decode the submitted import.
$data = Yaml::decode($form_state->getValue('import'));
}
catch (InvalidDataTypeException $e) {
$form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
}
// Validate for config entities.
if ($form_state->getValue('config_type') !== 'system.simple') {
$definition = $this->entityManager->getDefinition($form_state->getValue('config_type'));
$id_key = $definition->getKey('id');
// If a custom entity ID is specified, override the value in the
// configuration data being imported.
if (!$form_state->isValueEmpty('custom_entity_id')) {
$data[$id_key] = $form_state->getValue('custom_entity_id');
}
$entity_storage = $this->entityManager->getStorage($form_state->getValue('config_type'));
// If an entity ID was not specified, set an error.
if (!isset($data[$id_key])) {
$form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition->getLabel())));
return;
}
$config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
// If there is an existing entity, ensure matching ID and UUID.
if ($entity = $entity_storage->load($data[$id_key])) {
$this->configExists = $entity;
if (!isset($data['uuid'])) {
$form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the import did not specify a UUID.'));
return;
}
if ($data['uuid'] !== $entity->uuid()) {
$form_state->setErrorByName('import', $this->t('An entity with this machine name already exists but the UUID does not match.'));
return;
}
}
// If there is no entity with a matching ID, check for a UUID match.
elseif (isset($data['uuid']) && $entity_storage->loadByProperties(array('uuid' => $data['uuid']))) {
$form_state->setErrorByName('import', $this->t('An entity with this UUID already exists but the machine name does not match.'));
}
}
else {
$config_name = $form_state->getValue('config_name');
$config = $this->config($config_name);
$this->configExists = !$config->isNew() ? $config : FALSE;
}
// Use ConfigImporter validation.
if (!$form_state->getErrors()) {
$source_storage = new StorageReplaceDataWrapper($this->configStorage);
$source_storage->replaceData($config_name, $data);
$storage_comparer = new StorageComparer(
$source_storage,
$this->configStorage,
$this->configManager
);
if (!$storage_comparer->createChangelist()->hasChanges()) {
$form_state->setErrorByName('import', $this->t('There are no changes to import.'));
}
else {
$config_importer = new ConfigImporter(
$storage_comparer,
$this->eventDispatcher,
$this->configManager,
$this->lock,
$this->typedConfigManager,
$this->moduleHandler,
$this->moduleInstaller,
$this->themeHandler,
$this->getStringTranslation()
);
try {
$config_importer->validate();
$form_state->set('config_importer', $config_importer);
}
catch (ConfigImporterException $e) {
// There are validation errors.
$item_list = [
'#theme' => 'item_list',
'#items' => $config_importer->getErrors(),
'#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
];
$form_state->setErrorByName('import', $this->renderer->render($item_list));
}
}
}
// Store the decoded version of the submitted import.
$form_state->setValueForElement($form['import'], $data);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// If this form has not yet been confirmed, store the values and rebuild.
if (!$this->data) {
$form_state->setRebuild();
$this->data = $form_state->getValues();
return;
}
/** @var \Drupal\Core\Config\ConfigImporter $config_importer */
$config_importer = $form_state->get('config_importer');
if ($config_importer->alreadyImporting()) {
drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
}
else {
try {
$sync_steps = $config_importer->initialize();
$batch = [
'operations' => [],
'finished' => [ConfigSync::class, 'finishBatch'],
'title' => $this->t('Importing configuration'),
'init_message' => $this->t('Starting configuration import.'),
'progress_message' => $this->t('Completed @current step of @total.'),
'error_message' => $this->t('Configuration import has encountered an error.'),
];
foreach ($sync_steps as $sync_step) {
$batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
}
batch_set($batch);
}
catch (ConfigImporterException $e) {
// There are validation errors.
drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
foreach ($config_importer->getErrors() as $message) {
drupal_set_message($message, 'error');
}
}
}
}
}

View file

@ -0,0 +1,414 @@
<?php
namespace Drupal\config\Form;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Construct the storage changes in a configuration synchronization form.
*/
class ConfigSync extends FormBase {
/**
* The database lock object.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The sync configuration object.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $syncStorage;
/**
* The active configuration object.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $activeStorage;
/**
* The snapshot configuration object.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $snapshotStorage;
/**
* Event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface;
*/
protected $configManager;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs the object.
*
* @param \Drupal\Core\Config\StorageInterface $sync_storage
* The source storage.
* @param \Drupal\Core\Config\StorageInterface $active_storage
* The target storage.
* @param \Drupal\Core\Config\StorageInterface $snapshot_storage
* The snapshot storage.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock object.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* Event dispatcher.
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* Configuration manager.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer) {
$this->syncStorage = $sync_storage;
$this->activeStorage = $active_storage;
$this->snapshotStorage = $snapshot_storage;
$this->lock = $lock;
$this->eventDispatcher = $event_dispatcher;
$this->configManager = $config_manager;
$this->typedConfigManager = $typed_config;
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.storage.sync'),
$container->get('config.storage'),
$container->get('config.storage.snapshot'),
$container->get('lock.persistent'),
$container->get('event_dispatcher'),
$container->get('config.manager'),
$container->get('config.typed'),
$container->get('module_handler'),
$container->get('module_installer'),
$container->get('theme_handler'),
$container->get('renderer')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'config_admin_import_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Import all'),
);
$source_list = $this->syncStorage->listAll();
$storage_comparer = new StorageComparer($this->syncStorage, $this->activeStorage, $this->configManager);
if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) {
$form['no_changes'] = array(
'#type' => 'table',
'#header' => array($this->t('Name'), $this->t('Operations')),
'#rows' => array(),
'#empty' => $this->t('There are no configuration changes to import.'),
);
$form['actions']['#access'] = FALSE;
return $form;
}
elseif (!$storage_comparer->validateSiteUuid()) {
drupal_set_message($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'), 'error');
$form['actions']['#access'] = FALSE;
return $form;
}
// A list of changes will be displayed, so check if the user should be
// warned of potential losses to configuration.
if ($this->snapshotStorage->exists('core.extension')) {
$snapshot_comparer = new StorageComparer($this->activeStorage, $this->snapshotStorage, $this->configManager);
if (!$form_state->getUserInput() && $snapshot_comparer->createChangelist()->hasChanges()) {
$change_list = array();
foreach ($snapshot_comparer->getAllCollectionNames() as $collection) {
foreach ($snapshot_comparer->getChangelist(NULL, $collection) as $config_names) {
if (empty($config_names)) {
continue;
}
foreach ($config_names as $config_name) {
$change_list[] = $config_name;
}
}
}
sort($change_list);
$message = [
[
'#markup' => $this->t('The following items in your active configuration have changes since the last import that may be lost on the next import.')
],
[
'#theme' => 'item_list',
'#items' => $change_list,
]
];
drupal_set_message($this->renderer->renderPlain($message), 'warning');
}
}
// Store the comparer for use in the submit.
$form_state->set('storage_comparer', $storage_comparer);
// Add the AJAX library to the form for dialog support.
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
foreach ($storage_comparer->getAllCollectionNames() as $collection) {
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
$form[$collection]['collection_heading'] = array(
'#type' => 'html_tag',
'#tag' => 'h2',
'#value' => $this->t('@collection configuration collection', array('@collection' => $collection)),
);
}
foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
if (empty($config_names)) {
continue;
}
// @todo A table caption would be more appropriate, but does not have the
// visual importance of a heading.
$form[$collection][$config_change_type]['heading'] = array(
'#type' => 'html_tag',
'#tag' => 'h3',
);
switch ($config_change_type) {
case 'create':
$form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count new', '@count new');
break;
case 'update':
$form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count changed', '@count changed');
break;
case 'delete':
$form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count removed', '@count removed');
break;
case 'rename':
$form[$collection][$config_change_type]['heading']['#value'] = $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
break;
}
$form[$collection][$config_change_type]['list'] = array(
'#type' => 'table',
'#header' => array($this->t('Name'), $this->t('Operations')),
);
foreach ($config_names as $config_name) {
if ($config_change_type == 'rename') {
$names = $storage_comparer->extractRenameNames($config_name);
$route_options = array('source_name' => $names['old_name'], 'target_name' => $names['new_name']);
$config_name = $this->t('@source_name to @target_name', array('@source_name' => $names['old_name'], '@target_name' => $names['new_name']));
}
else {
$route_options = array('source_name' => $config_name);
}
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
$route_name = 'config.diff_collection';
$route_options['collection'] = $collection;
}
else {
$route_name = 'config.diff';
}
$links['view_diff'] = array(
'title' => $this->t('View differences'),
'url' => Url::fromRoute($route_name, $route_options),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode(array(
'width' => 700
)),
),
);
$form[$collection][$config_change_type]['list']['#rows'][] = array(
'name' => $config_name,
'operations' => array(
'data' => array(
'#type' => 'operations',
'#links' => $links,
),
),
);
}
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config_importer = new ConfigImporter(
$form_state->get('storage_comparer'),
$this->eventDispatcher,
$this->configManager,
$this->lock,
$this->typedConfigManager,
$this->moduleHandler,
$this->moduleInstaller,
$this->themeHandler,
$this->getStringTranslation()
);
if ($config_importer->alreadyImporting()) {
drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
}
else {
try {
$sync_steps = $config_importer->initialize();
$batch = array(
'operations' => array(),
'finished' => array(get_class($this), 'finishBatch'),
'title' => t('Synchronizing configuration'),
'init_message' => t('Starting configuration synchronization.'),
'progress_message' => t('Completed step @current of @total.'),
'error_message' => t('Configuration synchronization has encountered an error.'),
'file' => __DIR__ . '/../../config.admin.inc',
);
foreach ($sync_steps as $sync_step) {
$batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $sync_step));
}
batch_set($batch);
}
catch (ConfigImporterException $e) {
// There are validation errors.
drupal_set_message($this->t('The configuration cannot be imported because it failed validation for the following reasons:'), 'error');
foreach ($config_importer->getErrors() as $message) {
drupal_set_message($message, 'error');
}
}
}
}
/**
* Processes the config import batch and persists the importer.
*
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The batch config importer object to persist.
* @param string $sync_step
* The synchronization step to do.
* @param array $context
* The batch context.
*/
public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
if (!isset($context['sandbox']['config_importer'])) {
$context['sandbox']['config_importer'] = $config_importer;
}
$config_importer = $context['sandbox']['config_importer'];
$config_importer->doSyncStep($sync_step, $context);
if ($errors = $config_importer->getErrors()) {
if (!isset($context['results']['errors'])) {
$context['results']['errors'] = array();
}
$context['results']['errors'] += $errors;
}
}
/**
* Finish batch.
*
* This function is a static function to avoid serializing the ConfigSync
* object unnecessarily.
*/
public static function finishBatch($success, $results, $operations) {
if ($success) {
if (!empty($results['errors'])) {
foreach ($results['errors'] as $error) {
drupal_set_message($error, 'error');
\Drupal::logger('config_sync')->error($error);
}
drupal_set_message(\Drupal::translation()->translate('The configuration was imported with errors.'), 'warning');
}
else {
drupal_set_message(\Drupal::translation()->translate('The configuration was imported successfully.'));
}
}
else {
// An error occurred.
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$message = \Drupal::translation()->translate('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
drupal_set_message($message, 'error');
}
}
}

View file

@ -0,0 +1,203 @@
<?php
namespace Drupal\config;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Wraps a configuration storage to allow replacing specific configuration data.
*/
class StorageReplaceDataWrapper implements StorageInterface {
use DependencySerializationTrait;
/**
* The configuration storage to be wrapped.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* The configuration replacement data, keyed by configuration object name.
*
* @var array
*/
protected $replacementData = [];
/**
* The storage collection.
*
* @var string
*/
protected $collection;
/**
* Constructs a new StorageReplaceDataWrapper.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* A configuration storage to be used to read and write configuration.
* @param string $collection
* (optional) The collection to store configuration in. Defaults to the
* default collection.
*/
public function __construct(StorageInterface $storage, $collection = StorageInterface::DEFAULT_COLLECTION) {
$this->storage = $storage;
$this->collection = $collection;
$this->replacementData[$collection] = [];
}
/**
* {@inheritdoc}
*/
public function exists($name) {
return isset($this->replacementData[$this->collection][$name]) || $this->storage->exists($name);
}
/**
* {@inheritdoc}
*/
public function read($name) {
if (isset($this->replacementData[$this->collection][$name])) {
return $this->replacementData[$this->collection][$name];
}
return $this->storage->read($name);
}
/**
* {@inheritdoc}
*/
public function readMultiple(array $names) {
$data = $this->storage->readMultiple(($names));
foreach ($names as $name) {
if (isset($this->replacementData[$this->collection][$name])) {
$data[$name] = $this->replacementData[$this->collection][$name];
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function write($name, array $data) {
if (isset($this->replacementData[$this->collection][$name])) {
unset($this->replacementData[$this->collection][$name]);
}
return $this->storage->write($name, $data);
}
/**
* {@inheritdoc}
*/
public function delete($name) {
if (isset($this->replacementData[$this->collection][$name])) {
unset($this->replacementData[$this->collection][$name]);
}
return $this->storage->delete($name);
}
/**
* {@inheritdoc}
*/
public function rename($name, $new_name) {
if (isset($this->replacementData[$this->collection][$name])) {
$this->replacementData[$this->collection][$new_name] = $this->replacementData[$this->collection][$name];
unset($this->replacementData[$this->collection][$name]);
}
return $this->storage->rename($name, $new_name);
}
/**
* {@inheritdoc}
*/
public function encode($data) {
return $this->storage->encode($data);
}
/**
* {@inheritdoc}
*/
public function decode($raw) {
return $this->storage->decode($raw);
}
/**
* {@inheritdoc}
*/
public function listAll($prefix = '') {
$names = $this->storage->listAll($prefix);
$additional_names = [];
if ($prefix === '') {
$additional_names = array_keys($this->replacementData[$this->collection]);
}
else {
foreach (array_keys($this->replacementData[$this->collection]) as $name) {
if (strpos($name, $prefix) === 0) {
$additional_names[] = $name;
}
}
}
if (!empty($additional_names)) {
$names = array_unique(array_merge($names, $additional_names));
}
return $names;
}
/**
* {@inheritdoc}
*/
public function deleteAll($prefix = '') {
if ($prefix === '') {
$this->replacementData[$this->collection] = [];
}
else {
foreach (array_keys($this->replacementData[$this->collection]) as $name) {
if (strpos($name, $prefix) === 0) {
unset($this->replacementData[$this->collection][$name]);
}
}
}
return $this->storage->deleteAll($prefix);
}
/**
* {@inheritdoc}
*/
public function createCollection($collection) {
return new static(
$this->storage->createCollection($collection),
$collection
);
}
/**
* {@inheritdoc}
*/
public function getAllCollectionNames() {
return $this->storage->getAllCollectionNames();
}
/**
* {@inheritdoc}
*/
public function getCollectionName() {
return $this->collection;
}
/**
* Replaces the configuration object data with the supplied data.
*
* @param $name
* The configuration object name whose data to replace.
* @param array $data
* The configuration data.
*
* @return $this
*/
public function replaceData($name, array $data) {
$this->replacementData[$this->collection][$name] = $data;
return $this;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides test assertions for testing config entity synchronization.
*
* Can be used by test classes that extend \Drupal\simpletest\WebTestBase or
* \Drupal\KernelTests\KernelTestBase.
*/
trait AssertConfigEntityImportTrait {
/**
* Asserts that a config entity can be imported without changing it.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The config entity to test importing.
*/
public function assertConfigEntityImport(ConfigEntityInterface $entity) {
// Save original config information.
$entity_uuid = $entity->uuid();
$entity_type_id = $entity->getEntityTypeId();
$original_data = $entity->toArray();
// Copy everything to sync.
$this->copyConfig(\Drupal::service('config.storage'), \Drupal::service('config.storage.sync'));
// Delete the configuration from active. Don't worry about side effects of
// deleting config like fields cleaning up field storages. The coming import
// should recreate everything as necessary.
$entity->delete();
$this->configImporter()->reset()->import();
$imported_entity = \Drupal::entityManager()->loadEntityByUuid($entity_type_id, $entity_uuid);
$this->assertIdentical($original_data, $imported_entity->toArray());
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests if configuration overrides correctly affect cacheability metadata.
*
* @group config
*/
class CacheabilityMetadataConfigOverrideIntegrationTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'block_test',
'config_override_integration_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// @todo If our block does not contain any content then the cache context
// is not bubbling up and the test fails. Remove this line once the cache
// contexts are properly set. See https://www.drupal.org/node/2529980.
\Drupal::state()->set('block_test.content', 'Needs to have some content');
$this->drupalLogin($this->drupalCreateUser());
}
/**
* Tests if config overrides correctly set cacheability metadata.
*/
public function testConfigOverride() {
// Check the default (disabled) state of the cache context. The block label
// should not be overridden.
$this->drupalGet('<front>');
$this->assertNoText('Overridden block label');
// Both the cache context and tag should be present.
$this->assertCacheContext('config_override_integration_test');
$this->assertCacheTag('config_override_integration_test_tag');
// Flip the state of the cache context. The block label should now be
// overridden.
\Drupal::state()->set('config_override_integration_test.enabled', TRUE);
$this->drupalGet('<front>');
$this->assertText('Overridden block label');
// Both the cache context and tag should still be present.
$this->assertCacheContext('config_override_integration_test');
$this->assertCacheTag('config_override_integration_test_tag');
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\simpletest\WebTestBase;
/**
* Tests configuration entities.
*
* @group config
*/
class ConfigDependencyWebTest extends WebTestBase {
/**
* The maximum length for the entity storage used in this test.
*/
const MAX_ID_LENGTH = ConfigEntityStorage::MAX_ID_LENGTH;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* Tests ConfigDependencyDeleteFormTrait.
*
* @see \Drupal\Core\Config\Entity\ConfigDependencyDeleteFormTrait
*/
function testConfigDependencyDeleteFormTrait() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
$storage = $this->container->get('entity.manager')->getStorage('config_test');
// Entity1 will be deleted by the test.
$entity1 = $storage->create(
array(
'id' => 'entity1',
'label' => 'Entity One',
)
);
$entity1->save();
// Entity2 has a dependency on Entity1 but it can be fixed because
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
// dependency before config entities are deleted.
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
$this->drupalGet($entity2->urlInfo('delete-form'));
$this->assertNoText(t('Configuration updates'), 'No configuration updates found.');
$this->assertNoText(t('Configuration deletions'), 'No configuration deletes found.');
$this->drupalGet($entity1->urlInfo('delete-form'));
$this->assertNoText(t('Configuration updates'), 'No configuration updates found.');
$this->assertText(t('Configuration deletions'), 'Configuration deletions found.');
$this->assertText($entity2->id(), 'Entity2 id found');
$this->drupalPostForm($entity1->urlInfo('delete-form'), array(), 'Delete');
$storage->resetCache();
$this->assertFalse($storage->loadMultiple([$entity1->id(), $entity2->id()]), 'Test entities deleted');
// Set a more complicated test where dependencies will be fixed.
// Entity1 will be deleted by the test.
$entity1 = $storage->create(
array(
'id' => 'entity1',
)
);
$entity1->save();
\Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName()));
// Entity2 has a dependency on Entity1 but it can be fixed because
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
// dependency before config entities are deleted.
$entity2 = $storage->create(
array(
'id' => 'entity2',
'label' => 'Entity Two',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
// Entity3 will be unchanged because it is dependent on Entity2 which can
// be fixed.
$entity3 = $storage->create(
array(
'id' => 'entity3',
'dependencies' => array(
'enforced' => array(
'config' => array($entity2->getConfigDependencyName()),
),
),
)
);
$entity3->save();
$this->drupalGet($entity1->urlInfo('delete-form'));
$this->assertText(t('Configuration updates'), 'Configuration updates found.');
$this->assertNoText(t('Configuration deletions'), 'No configuration deletions found.');
$this->assertNoText($entity2->id(), 'Entity2 id not found');
$this->assertText($entity2->label(), 'Entity2 label not found');
$this->assertNoText($entity3->id(), 'Entity3 id not found');
$this->drupalPostForm($entity1->urlInfo('delete-form'), array(), 'Delete');
$storage->resetCache();
$this->assertFalse($storage->load('entity1'), 'Test entity 1 deleted');
$entity2 = $storage->load('entity2');
$this->assertTrue($entity2, 'Entity 2 not deleted');
$this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], array(), 'Entity 2 dependencies updated to remove dependency on Entity1.');
$entity3 = $storage->load('entity3');
$this->assertTrue($entity3, 'Entity 3 not deleted');
$this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests that config overrides do not bleed through in entity forms and lists.
*
* @group config
*/
class ConfigEntityFormOverrideTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('config_test');
/**
* Tests that overrides do not affect forms or listing screens.
*/
public function testFormsWithOverrides() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
$original_label = 'Default';
$overridden_label = 'Overridden label';
$edited_label = 'Edited label';
$config_test_storage = $this->container->get('entity.manager')->getStorage('config_test');
// Set up an override.
$settings['config']['config_test.dynamic.dotted.default']['label'] = (object) array(
'value' => $overridden_label,
'required' => TRUE,
);
$this->writeSettings($settings);
// Test that the overridden label is loaded with the entity.
$this->assertEqual($config_test_storage->load('dotted.default')->label(), $overridden_label);
// Test that the original label on the listing page is intact.
$this->drupalGet('admin/structure/config_test');
$this->assertText($original_label);
$this->assertNoText($overridden_label);
// Test that the original label on the editing page is intact.
$this->drupalGet('admin/structure/config_test/manage/dotted.default');
$elements = $this->xpath('//input[@name="label"]');
$this->assertIdentical((string) $elements[0]['value'], $original_label);
$this->assertNoText($overridden_label);
// Change to a new label and test that the listing now has the edited label.
$edit = array(
'label' => $edited_label,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/structure/config_test');
$this->assertNoText($overridden_label);
$this->assertText($edited_label);
// Test that the editing page now has the edited label.
$this->drupalGet('admin/structure/config_test/manage/dotted.default');
$elements = $this->xpath('//input[@name="label"]');
$this->assertIdentical((string) $elements[0]['value'], $edited_label);
// Test that the overridden label is still loaded with the entity.
$this->assertEqual($config_test_storage->load('dotted.default')->label(), $overridden_label);
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the listing of configuration entities in a multilingual scenario.
*
* @group config
*/
class ConfigEntityListMultilingualTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'language', 'block');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Delete the override config_test entity. It is not required by this test.
\Drupal::entityManager()->getStorage('config_test')->load('override')->delete();
ConfigurableLanguage::createFromLangcode('hu')->save();
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Tests the listing UI with different language scenarios.
*/
function testListUI() {
// Log in as an administrative user to access the full menu trail.
$this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer site configuration')));
// Get the list page.
$this->drupalGet('admin/structure/config_test');
$this->assertLinkByHref('admin/structure/config_test/manage/dotted.default');
// Add a new entity using the action link.
$this->clickLink('Add test configuration');
$edit = array(
'label' => 'Antilop',
'id' => 'antilop',
'langcode' => 'hu',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Ensure that operations for editing the Hungarian entity appear in English.
$this->assertLinkByHref('admin/structure/config_test/manage/antilop');
// Get the list page in Hungarian and assert Hungarian admin links
// regardless of language of config entities.
$this->drupalGet('hu/admin/structure/config_test');
$this->assertLinkByHref('hu/admin/structure/config_test/manage/dotted.default');
$this->assertLinkByHref('hu/admin/structure/config_test/manage/antilop');
}
}

View file

@ -0,0 +1,278 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\config_test\Entity\ConfigTest;
use Drupal\Core\Entity\EntityStorageInterface;
/**
* Tests the listing of configuration entities.
*
* @group config
*/
class ConfigEntityListTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['block', 'config_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Delete the override config_test entity since it is not required by this
// test.
\Drupal::entityManager()->getStorage('config_test')->load('override')->delete();
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Tests entity list builder methods.
*/
function testList() {
$controller = \Drupal::entityManager()->getListBuilder('config_test');
// Test getStorage() method.
$this->assertTrue($controller->getStorage() instanceof EntityStorageInterface, 'EntityStorage instance in storage.');
// Get a list of ConfigTest entities and confirm that it contains the
// ConfigTest entity provided by the config_test module.
// @see config_test.dynamic.dotted.default.yml
$list = $controller->load();
$this->assertEqual(count($list), 1, '1 ConfigTest entity found.');
$entity = $list['dotted.default'];
$this->assertTrue(!empty($entity), '"Default" ConfigTest entity ID found.');
$this->assertTrue($entity instanceof ConfigTest, '"Default" ConfigTest entity is an instance of ConfigTest.');
// Test getOperations() method.
$expected_operations = array(
'edit' => array (
'title' => t('Edit'),
'weight' => 10,
'url' => $entity->urlInfo(),
),
'disable' => array(
'title' => t('Disable'),
'weight' => 40,
'url' => $entity->urlInfo('disable'),
),
'delete' => array (
'title' => t('Delete'),
'weight' => 100,
'url' => $entity->urlInfo('delete-form'),
),
);
$actual_operations = $controller->getOperations($entity);
// Sort the operations to normalize link order.
uasort($actual_operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
$this->assertEqual($expected_operations, $actual_operations, 'The operations are identical.');
// Test buildHeader() method.
$expected_items = array(
'label' => 'Label',
'id' => 'Machine name',
'operations' => 'Operations',
);
$actual_items = $controller->buildHeader();
$this->assertEqual($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
// Test buildRow() method.
$build_operations = $controller->buildOperations($entity);
$expected_items = array(
'label' => 'Default',
'id' => 'dotted.default',
'operations' => array(
'data' => $build_operations,
),
);
$actual_items = $controller->buildRow($entity);
$this->assertEqual($expected_items, $actual_items, 'Return value from buildRow matches expected.');
// Test sorting.
$storage = $controller->getStorage();
$entity = $storage->create(array(
'id' => 'alpha',
'label' => 'Alpha',
'weight' => 1,
));
$entity->save();
$entity = $storage->create(array(
'id' => 'omega',
'label' => 'Omega',
'weight' => 1,
));
$entity->save();
$entity = $storage->create(array(
'id' => 'beta',
'label' => 'Beta',
'weight' => 0,
));
$entity->save();
$list = $controller->load();
$this->assertIdentical(array_keys($list), array('beta', 'dotted.default', 'alpha', 'omega'));
// Test that config entities that do not support status, do not have
// enable/disable operations.
$controller = $this->container->get('entity.manager')
->getListBuilder('config_test_no_status');
$list = $controller->load();
$entity = $list['default'];
// Test getOperations() method.
$expected_operations = array(
'edit' => array(
'title' => t('Edit'),
'weight' => 10,
'url' => $entity->urlInfo(),
),
'delete' => array(
'title' => t('Delete'),
'weight' => 100,
'url' => $entity->urlInfo('delete-form'),
),
);
$actual_operations = $controller->getOperations($entity);
// Sort the operations to normalize link order.
uasort($actual_operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
$this->assertEqual($expected_operations, $actual_operations, 'The operations are identical.');
}
/**
* Tests the listing UI.
*/
function testListUI() {
// Log in as an administrative user to access the full menu trail.
$this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer site configuration')));
// Get the list callback page.
$this->drupalGet('admin/structure/config_test');
// Test for the page title.
$this->assertTitle('Test configuration | Drupal');
// Test for the table.
$element = $this->xpath('//div[@class="layout-content"]//table');
$this->assertTrue($element, 'Configuration entity list table found.');
// Test the table header.
$elements = $this->xpath('//div[@class="layout-content"]//table/thead/tr/th');
$this->assertEqual(count($elements), 3, 'Correct number of table header cells found.');
// Test the contents of each th cell.
$expected_items = array('Label', 'Machine name', 'Operations');
foreach ($elements as $key => $element) {
$this->assertIdentical((string) $element[0], $expected_items[$key]);
}
// Check the number of table row cells.
$elements = $this->xpath('//div[@class="layout-content"]//table/tbody/tr[@class="odd"]/td');
$this->assertEqual(count($elements), 3, 'Correct number of table row cells found.');
// Check the contents of each row cell. The first cell contains the label,
// the second contains the machine name, and the third contains the
// operations list.
$this->assertIdentical((string) $elements[0], 'Default');
$this->assertIdentical((string) $elements[1], 'dotted.default');
$this->assertTrue($elements[2]->children()->xpath('//ul'), 'Operations list found.');
// Add a new entity using the operations link.
$this->assertLink('Add test configuration');
$this->clickLink('Add test configuration');
$this->assertResponse(200);
$edit = array(
'label' => 'Antelope',
'id' => 'antelope',
'weight' => 1,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
// Ensure that the entity's sort method was called.
$this->assertTrue(\Drupal::state()->get('config_entity_sort'), 'ConfigTest::sort() was called.');
// Confirm that the user is returned to the listing, and verify that the
// text of the label and machine name appears in the list (versus elsewhere
// on the page).
$this->assertFieldByXpath('//td', 'Antelope', "Label found for added 'Antelope' entity.");
$this->assertFieldByXpath('//td', 'antelope', "Machine name found for added 'Antelope' entity.");
// Edit the entity using the operations link.
$this->assertLinkByHref('admin/structure/config_test/manage/antelope');
$this->clickLink('Edit', 1);
$this->assertResponse(200);
$this->assertTitle('Edit Antelope | Drupal');
$edit = array('label' => 'Albatross', 'id' => 'albatross');
$this->drupalPostForm(NULL, $edit, t('Save'));
// Confirm that the user is returned to the listing, and verify that the
// text of the label and machine name appears in the list (versus elsewhere
// on the page).
$this->assertFieldByXpath('//td', 'Albatross', "Label found for updated 'Albatross' entity.");
$this->assertFieldByXpath('//td', 'albatross', "Machine name found for updated 'Albatross' entity.");
// Delete the added entity using the operations link.
$this->assertLinkByHref('admin/structure/config_test/manage/albatross/delete');
$this->clickLink('Delete', 1);
$this->assertResponse(200);
$this->assertTitle('Are you sure you want to delete the test configuration Albatross? | Drupal');
$this->drupalPostForm(NULL, array(), t('Delete'));
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertNoFieldByXpath('//td', 'Albatross', "No label found for deleted 'Albatross' entity.");
$this->assertNoFieldByXpath('//td', 'albatross', "No machine name found for deleted 'Albatross' entity.");
// Delete the original entity using the operations link.
$this->clickLink('Delete');
$this->assertResponse(200);
$this->assertTitle('Are you sure you want to delete the test configuration Default? | Drupal');
$this->drupalPostForm(NULL, array(), t('Delete'));
// Verify that the text of the label and machine name does not appear in
// the list (though it may appear elsewhere on the page).
$this->assertNoFieldByXpath('//td', 'Default', "No label found for deleted 'Default' entity.");
$this->assertNoFieldByXpath('//td', 'dotted.default', "No machine name found for deleted 'Default' entity.");
// Confirm that the empty text is displayed.
$this->assertText('There is no Test configuration yet.');
}
/**
* Test paging.
*/
public function testPager() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
$storage = \Drupal::entityManager()->getListBuilder('config_test')->getStorage();
// Create 51 test entities.
for ($i = 1; $i < 52; $i++) {
$storage->create(array(
'id' => str_pad($i, 2, '0', STR_PAD_LEFT),
'label' => 'Test config entity ' . $i,
'weight' => $i,
'protected_property' => $i,
))->save();
}
// Load the listing page.
$this->drupalGet('admin/structure/config_test');
// Item 51 should not be present.
$this->assertRaw('Test config entity 50', 'Config entity 50 is shown.');
$this->assertNoRaw('Test config entity 51', 'Config entity 51 is on the next page.');
// Browse to the next page.
$this->clickLink(t('Page 2'));
$this->assertNoRaw('Test config entity 50', 'Test config entity 50 is on the previous page.');
$this->assertRaw('dotted.default', 'Default config entity appears on page 2.');
$this->assertRaw('Test config entity 51', 'Test config entity 51 is on page 2.');
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests configuration entity status UI functionality.
*
* @group config
*/
class ConfigEntityStatusUITest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* Tests status operations.
*/
function testCRUD() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
$id = strtolower($this->randomMachineName());
$edit = array(
'id' => $id,
'label' => $this->randomMachineName(),
);
$this->drupalPostForm('admin/structure/config_test/add', $edit, 'Save');
$entity = entity_load('config_test', $id);
// Disable an entity.
$disable_url = $entity->urlInfo('disable');
$this->assertLinkByHref($disable_url->toString());
$this->drupalGet($disable_url);
$this->assertResponse(200);
$this->assertNoLinkByHref($disable_url->toString());
// Enable an entity.
$enable_url = $entity->urlInfo('enable');
$this->assertLinkByHref($enable_url->toString());
$this->drupalGet($enable_url);
$this->assertResponse(200);
$this->assertNoLinkByHref($enable_url->toString());
}
}

View file

@ -0,0 +1,360 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests configuration entities.
*
* @group config
*/
class ConfigEntityTest extends WebTestBase {
/**
* The maximum length for the entity storage used in this test.
*/
const MAX_ID_LENGTH = ConfigEntityStorage::MAX_ID_LENGTH;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* Tests CRUD operations.
*/
function testCRUD() {
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
// Verify default properties on a newly created empty entity.
$empty = entity_create('config_test');
$this->assertTrue($empty->uuid());
$this->assertIdentical($empty->label, NULL);
$this->assertIdentical($empty->style, NULL);
$this->assertIdentical($empty->language()->getId(), $default_langcode);
// Verify ConfigEntity properties/methods on the newly created empty entity.
$this->assertIdentical($empty->isNew(), TRUE);
$this->assertIdentical($empty->getOriginalId(), NULL);
$this->assertIdentical($empty->bundle(), 'config_test');
$this->assertIdentical($empty->id(), NULL);
$this->assertTrue($empty->uuid());
$this->assertIdentical($empty->label(), NULL);
$this->assertIdentical($empty->get('id'), NULL);
$this->assertTrue($empty->get('uuid'));
$this->assertIdentical($empty->get('label'), NULL);
$this->assertIdentical($empty->get('style'), NULL);
$this->assertIdentical($empty->language()->getId(), $default_langcode);
// Verify Entity properties/methods on the newly created empty entity.
$this->assertIdentical($empty->getEntityTypeId(), 'config_test');
// The URI can only be checked after saving.
try {
$empty->urlInfo();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify that an empty entity cannot be saved.
try {
$empty->save();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify that an entity with an empty ID string is considered empty, too.
$empty_id = entity_create('config_test', array(
'id' => '',
));
$this->assertIdentical($empty_id->isNew(), TRUE);
try {
$empty_id->save();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify properties on a newly created entity.
$config_test = entity_create('config_test', $expected = array(
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'style' => $this->randomMachineName(),
));
$this->assertTrue($config_test->uuid());
$this->assertNotEqual($config_test->uuid(), $empty->uuid());
$this->assertIdentical($config_test->label, $expected['label']);
$this->assertIdentical($config_test->style, $expected['style']);
$this->assertIdentical($config_test->language()->getId(), $default_langcode);
// Verify methods on the newly created entity.
$this->assertIdentical($config_test->isNew(), TRUE);
$this->assertIdentical($config_test->getOriginalId(), $expected['id']);
$this->assertIdentical($config_test->id(), $expected['id']);
$this->assertTrue($config_test->uuid());
$expected['uuid'] = $config_test->uuid();
$this->assertIdentical($config_test->label(), $expected['label']);
// Verify that the entity can be saved.
try {
$status = $config_test->save();
$this->pass('EntityMalformedException was not thrown.');
}
catch (EntityMalformedException $e) {
$this->fail('EntityMalformedException was not thrown.');
}
// The entity path can only be checked after saving.
$this->assertIdentical($config_test->url(), Url::fromRoute('entity.config_test.edit_form', ['config_test' => $expected['id']])->toString());
// Verify that the correct status is returned and properties did not change.
$this->assertIdentical($status, SAVED_NEW);
$this->assertIdentical($config_test->id(), $expected['id']);
$this->assertIdentical($config_test->uuid(), $expected['uuid']);
$this->assertIdentical($config_test->label(), $expected['label']);
$this->assertIdentical($config_test->isNew(), FALSE);
$this->assertIdentical($config_test->getOriginalId(), $expected['id']);
// Save again, and verify correct status and properties again.
$status = $config_test->save();
$this->assertIdentical($status, SAVED_UPDATED);
$this->assertIdentical($config_test->id(), $expected['id']);
$this->assertIdentical($config_test->uuid(), $expected['uuid']);
$this->assertIdentical($config_test->label(), $expected['label']);
$this->assertIdentical($config_test->isNew(), FALSE);
$this->assertIdentical($config_test->getOriginalId(), $expected['id']);
// Verify that a configuration entity can be saved with an ID of the
// maximum allowed length, but not longer.
// Test with a short ID.
$id_length_config_test = entity_create('config_test', array(
'id' => $this->randomMachineName(8),
));
try {
$id_length_config_test->save();
$this->pass(SafeMarkup::format("config_test entity with ID length @length was saved.", array(
'@length' => strlen($id_length_config_test->id()))
));
}
catch (ConfigEntityIdLengthException $e) {
$this->fail($e->getMessage());
}
// Test with an ID of the maximum allowed length.
$id_length_config_test = entity_create('config_test', array(
'id' => $this->randomMachineName(static::MAX_ID_LENGTH),
));
try {
$id_length_config_test->save();
$this->pass(SafeMarkup::format("config_test entity with ID length @length was saved.", array(
'@length' => strlen($id_length_config_test->id()),
)));
}
catch (ConfigEntityIdLengthException $e) {
$this->fail($e->getMessage());
}
// Test with an ID exceeding the maximum allowed length.
$id_length_config_test = entity_create('config_test', array(
'id' => $this->randomMachineName(static::MAX_ID_LENGTH + 1),
));
try {
$status = $id_length_config_test->save();
$this->fail(SafeMarkup::format("config_test entity with ID length @length exceeding the maximum allowed length of @max saved successfully", array(
'@length' => strlen($id_length_config_test->id()),
'@max' => static::MAX_ID_LENGTH,
)));
}
catch (ConfigEntityIdLengthException $e) {
$this->pass(SafeMarkup::format("config_test entity with ID length @length exceeding the maximum allowed length of @max failed to save", array(
'@length' => strlen($id_length_config_test->id()),
'@max' => static::MAX_ID_LENGTH,
)));
}
// Ensure that creating an entity with the same id as an existing one is not
// possible.
$same_id = entity_create('config_test', array(
'id' => $config_test->id(),
));
$this->assertIdentical($same_id->isNew(), TRUE);
try {
$same_id->save();
$this->fail('Not possible to overwrite an entity entity.');
}
catch (EntityStorageException $e) {
$this->pass('Not possible to overwrite an entity entity.');
}
// Verify that renaming the ID returns correct status and properties.
$ids = array($expected['id'], 'second_' . $this->randomMachineName(4), 'third_' . $this->randomMachineName(4));
for ($i = 1; $i < 3; $i++) {
$old_id = $ids[$i - 1];
$new_id = $ids[$i];
// Before renaming, everything should point to the current ID.
$this->assertIdentical($config_test->id(), $old_id);
$this->assertIdentical($config_test->getOriginalId(), $old_id);
// Rename.
$config_test->set('id', $new_id);
$this->assertIdentical($config_test->id(), $new_id);
$status = $config_test->save();
$this->assertIdentical($status, SAVED_UPDATED);
$this->assertIdentical($config_test->isNew(), FALSE);
// Verify that originalID points to new ID directly after renaming.
$this->assertIdentical($config_test->id(), $new_id);
$this->assertIdentical($config_test->getOriginalId(), $new_id);
}
// Test config entity prepopulation.
\Drupal::state()->set('config_test.prepopulate', TRUE);
$config_test = entity_create('config_test', array('foo' => 'bar'));
$this->assertEqual($config_test->get('foo'), 'baz', 'Initial value correctly populated');
}
/**
* Tests CRUD operations through the UI.
*/
function testCRUDUI() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
$id = strtolower($this->randomMachineName());
$label1 = $this->randomMachineName();
$label2 = $this->randomMachineName();
$label3 = $this->randomMachineName();
$message_insert = format_string('%label configuration has been created.', array('%label' => $label1));
$message_update = format_string('%label configuration has been updated.', array('%label' => $label2));
$message_delete = format_string('The test configuration %label has been deleted.', array('%label' => $label2));
// Create a configuration entity.
$edit = array(
'id' => $id,
'label' => $label1,
);
$this->drupalPostForm('admin/structure/config_test/add', $edit, 'Save');
$this->assertUrl('admin/structure/config_test');
$this->assertResponse(200);
$this->assertRaw($message_insert);
$this->assertNoRaw($message_update);
$this->assertLinkByHref("admin/structure/config_test/manage/$id");
// Update the configuration entity.
$edit = array(
'label' => $label2,
);
$this->drupalPostForm("admin/structure/config_test/manage/$id", $edit, 'Save');
$this->assertUrl('admin/structure/config_test');
$this->assertResponse(200);
$this->assertNoRaw($message_insert);
$this->assertRaw($message_update);
$this->assertLinkByHref("admin/structure/config_test/manage/$id");
$this->assertLinkByHref("admin/structure/config_test/manage/$id/delete");
// Delete the configuration entity.
$this->drupalGet("admin/structure/config_test/manage/$id");
$this->clickLink(t('Delete'));
$this->assertUrl("admin/structure/config_test/manage/$id/delete");
$this->drupalPostForm(NULL, array(), 'Delete');
$this->assertUrl('admin/structure/config_test');
$this->assertResponse(200);
$this->assertNoRaw($message_update);
$this->assertRaw($message_delete);
$this->assertNoText($label1);
$this->assertNoLinkByHref("admin/structure/config_test/manage/$id");
// Re-create a configuration entity.
$edit = array(
'id' => $id,
'label' => $label1,
);
$this->drupalPostForm('admin/structure/config_test/add', $edit, 'Save');
$this->assertUrl('admin/structure/config_test');
$this->assertResponse(200);
$this->assertText($label1);
$this->assertLinkByHref("admin/structure/config_test/manage/$id");
// Rename the configuration entity's ID/machine name.
$edit = array(
'id' => strtolower($this->randomMachineName()),
'label' => $label3,
);
$this->drupalPostForm("admin/structure/config_test/manage/$id", $edit, 'Save');
$this->assertUrl('admin/structure/config_test');
$this->assertResponse(200);
$this->assertNoText($label1);
$this->assertNoText($label2);
$this->assertText($label3);
$this->assertNoLinkByHref("admin/structure/config_test/manage/$id");
$id = $edit['id'];
$this->assertLinkByHref("admin/structure/config_test/manage/$id");
// Create a configuration entity with '0' machine name.
$edit = array(
'id' => '0',
'label' => '0',
);
$this->drupalPostForm('admin/structure/config_test/add', $edit, 'Save');
$this->assertResponse(200);
$message_insert = format_string('%label configuration has been created.', array('%label' => $edit['label']));
$this->assertRaw($message_insert);
$this->assertLinkByHref('admin/structure/config_test/manage/0');
$this->assertLinkByHref('admin/structure/config_test/manage/0/delete');
$this->drupalPostForm('admin/structure/config_test/manage/0/delete', array(), 'Delete');
$this->assertFalse(entity_load('config_test', '0'), 'Test entity deleted');
// Create a configuration entity with a property that uses AJAX to show
// extra form elements.
$this->drupalGet('admin/structure/config_test/add');
// Test that the dependent element is not shown initially.
$this->assertFieldByName('size');
$this->assertNoFieldByName('size_value');
$id = strtolower($this->randomMachineName());
$edit = [
'id' => $id,
'label' => $this->randomString(),
'size' => 'custom',
];
$this->drupalPostAjaxForm(NULL, $edit, 'size');
// Check that the dependent element is shown after selecting a 'size' value.
$this->assertFieldByName('size');
$this->assertFieldByName('size_value');
// Test the same scenario but it in a non-JS case by using a 'js-hidden'
// submit button.
$this->drupalGet('admin/structure/config_test/add');
$this->assertFieldByName('size');
$this->assertNoFieldByName('size_value');
$this->drupalPostForm(NULL, $edit, 'Change size');
$this->assertFieldByName('size');
$this->assertFieldByName('size_value');
// Submit the form with the regular 'Save' button and check that the entity
// values are correct.
$edit += ['size_value' => 'medium'];
$this->drupalPostForm(NULL, $edit, 'Save');
$entity = entity_load('config_test', $id);
$this->assertEqual($entity->get('size'), 'custom');
$this->assertEqual($entity->get('size_value'), 'medium');
}
}

View file

@ -0,0 +1,324 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests the user interface for importing/exporting configuration.
*
* Each testX method does a complete rebuild of a Drupal site, so values being
* tested need to be stored in protected properties in order to survive until
* the next rebuild.
*
* @group config
*/
class ConfigExportImportUITest extends WebTestBase {
/**
* The contents of the config export tarball, held between test methods.
*
* @var string
*/
protected $tarball;
/**
* Holds the original 'site slogan' before testing.
*
* @var string
*/
protected $originalSlogan;
/**
* Holds a randomly generated new 'site slogan' for testing.
*
* @var string
*/
protected $newSlogan;
/**
* Holds a content type.
*
* @var \Drupal\node\NodeInterface
*/
protected $contentType;
/**
* Holds the randomly-generated name of a field.
*
* @var string
*/
protected $fieldName;
/**
* Holds the field storage entity for $fieldName.
*
* @var \Drupal\field\FieldStorageConfigInterface
*/
protected $fieldStorage;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config', 'node', 'field');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// The initial import must be done with uid 1 because if separately named
// roles are created then the role is lost after import. If the roles
// created have the same name then the sync will fail because they will
// have different UUIDs.
$this->drupalLogin($this->rootUser);
}
/**
* Tests a simple site export import case.
*/
public function testExportImport() {
// After installation there is no snapshot and nothing to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('Warning message'));
$this->assertText(t('There are no configuration changes to import.'));
$this->originalSlogan = $this->config('system.site')->get('slogan');
$this->newSlogan = $this->randomString(16);
$this->assertNotEqual($this->newSlogan, $this->originalSlogan);
$this->config('system.site')
->set('slogan', $this->newSlogan)
->save();
$this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
// Create a content type.
$this->contentType = $this->drupalCreateContentType();
// Create a field.
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$this->fieldStorage = FieldStorageConfig::create(array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'text',
));
$this->fieldStorage->save();
FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => $this->contentType->id(),
])->save();
// Update the displays so that configuration does not change unexpectedly on
// import.
entity_get_form_display('node', $this->contentType->id(), 'default')
->setComponent($this->fieldName, array(
'type' => 'text_textfield',
))
->save();
entity_get_display('node', $this->contentType->id(), 'full')
->setComponent($this->fieldName)
->save();
entity_get_display('node', $this->contentType->id(), 'default')
->setComponent($this->fieldName)
->save();
entity_get_display('node', $this->contentType->id(), 'teaser')
->removeComponent($this->fieldName)
->save();
$this->drupalGet('node/add/' . $this->contentType->id());
$this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
// Export the configuration.
$this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
$this->tarball = $this->getRawContent();
$this->config('system.site')
->set('slogan', $this->originalSlogan)
->save();
$this->assertEqual($this->config('system.site')->get('slogan'), $this->originalSlogan);
// Delete the custom field.
$fields = FieldConfig::loadMultiple();
foreach ($fields as $field) {
if ($field->getName() == $this->fieldName) {
$field->delete();
}
}
$field_storages = FieldStorageConfig::loadMultiple();
foreach ($field_storages as $field_storage) {
if ($field_storage->getName() == $this->fieldName) {
$field_storage->delete();
}
}
$this->drupalGet('node/add/' . $this->contentType->id());
$this->assertNoFieldByName("{$this->fieldName}[0][value]", '', 'Widget is not displayed');
// Import the configuration.
$filename = 'temporary://' . $this->randomMachineName();
file_put_contents($filename, $this->tarball);
$this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
// There is no snapshot yet because an import has never run.
$this->assertNoText(t('Warning message'));
$this->assertNoText(t('There are no configuration changes to import.'));
$this->assertText($this->contentType->label());
$this->drupalPostForm(NULL, array(), 'Import all');
// After importing the snapshot has been updated an there are no warnings.
$this->assertNoText(t('Warning message'));
$this->assertText(t('There are no configuration changes to import.'));
$this->assertEqual($this->config('system.site')->get('slogan'), $this->newSlogan);
$this->drupalGet('node/add');
$this->assertFieldByName("{$this->fieldName}[0][value]", '', 'Widget is displayed');
$this->config('system.site')
->set('slogan', $this->originalSlogan)
->save();
$this->drupalGet('admin/config/development/configuration');
$this->assertText(t('Warning message'));
$this->assertText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
// Ensure the item is displayed as part of a list (to avoid false matches
// on the rest of the page) and that the list markup is not escaped.
$this->assertRaw('<li>system.site</li>');
// Remove everything from sync. The warning about differences between the
// active and snapshot should no longer exist.
\Drupal::service('config.storage.sync')->deleteAll();
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('Warning message'));
$this->assertNoText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
$this->assertText(t('There are no configuration changes to import.'));
// Write a file to sync. The warning about differences between the active
// and snapshot should now exist.
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = $this->container->get('config.storage.sync');
$data = $this->config('system.site')->get();
$data['slogan'] = 'in the face';
$this->copyConfig($this->container->get('config.storage'), $sync);
$sync->write('system.site', $data);
$this->drupalGet('admin/config/development/configuration');
$this->assertText(t('Warning message'));
$this->assertText('The following items in your active configuration have changes since the last import that may be lost on the next import.');
// Ensure the item is displayed as part of a list (to avoid false matches
// on the rest of the page) and that the list markup is not escaped.
$this->assertRaw('<li>system.site</li>');
}
/**
* Tests an export and import of collections.
*/
public function testExportImportCollections() {
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
$active_storage = \Drupal::service('config.storage');
$test1_storage = $active_storage->createCollection('collection.test1');
$test1_storage->write('config_test.create', array('foo' => 'bar'));
$test1_storage->write('config_test.update', array('foo' => 'bar'));
$test2_storage = $active_storage->createCollection('collection.test2');
$test2_storage->write('config_test.another_create', array('foo' => 'bar'));
$test2_storage->write('config_test.another_update', array('foo' => 'bar'));
// Export the configuration.
$this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
$this->tarball = $this->getRawContent();
$filename = file_directory_temp() . '/' . $this->randomMachineName();
file_put_contents($filename, $this->tarball);
// Set up the active storage collections to test import.
$test1_storage->delete('config_test.create');
$test1_storage->write('config_test.update', array('foo' => 'baz'));
$test1_storage->write('config_test.delete', array('foo' => 'bar'));
$test2_storage->delete('config_test.another_create');
$test2_storage->write('config_test.another_update', array('foo' => 'baz'));
$test2_storage->write('config_test.another_delete', array('foo' => 'bar'));
// Create a snapshot.
$snapshot_storage = \Drupal::service('config.storage.snapshot');
\Drupal::service('config.manager')->createSnapshot($active_storage, $snapshot_storage);
// Ensure that the snapshot has the expected collection data before import.
$test1_snapshot = $snapshot_storage->createCollection('collection.test1');
$data = $test1_snapshot->read('config_test.delete');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.delete in collection.test1 exists in the snapshot storage.');
$data = $test1_snapshot->read('config_test.update');
$this->assertEqual($data, array('foo' => 'baz'), 'The config_test.update in collection.test1 exists in the snapshot storage.');
$this->assertFalse($test1_snapshot->read('config_test.create'), 'The config_test.create in collection.test1 does not exist in the snapshot storage.');
$test2_snapshot = $snapshot_storage->createCollection('collection.test2');
$data = $test2_snapshot->read('config_test.another_delete');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_delete in collection.test2 exists in the snapshot storage.');
$data = $test2_snapshot->read('config_test.another_update');
$this->assertEqual($data, array('foo' => 'baz'), 'The config_test.another_update in collection.test2 exists in the snapshot storage.');
$this->assertFalse($test2_snapshot->read('config_test.another_create'), 'The config_test.another_create in collection.test2 does not exist in the snapshot storage.');
// Create the tar that contains the expected content for the collections.
$tar = new ArchiveTar($filename, 'gz');
$content_list = $tar->listContent();
// Convert the list of files into something easy to search.
$files = array();
foreach ($content_list as $file) {
$files[] = $file['filename'];
}
$this->assertTrue(in_array('collection/test1/config_test.create.yml', $files), 'Config export contains collection/test1/config_test.create.yml.');
$this->assertTrue(in_array('collection/test2/config_test.another_create.yml', $files), 'Config export contains collection/test2/config_test.another_create.yml.');
$this->assertTrue(in_array('collection/test1/config_test.update.yml', $files), 'Config export contains collection/test1/config_test.update.yml.');
$this->assertTrue(in_array('collection/test2/config_test.another_update.yml', $files), 'Config export contains collection/test2/config_test.another_update.yml.');
$this->assertFalse(in_array('collection/test1/config_test.delete.yml', $files), 'Config export does not contain collection/test1/config_test.delete.yml.');
$this->assertFalse(in_array('collection/test2/config_test.another_delete.yml', $files), 'Config export does not contain collection/test2/config_test.another_delete.yml.');
$this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
// Verify that there are configuration differences to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('There are no configuration changes to import.'));
$this->assertText(t('@collection configuration collection', array('@collection' => 'collection.test1')));
$this->assertText(t('@collection configuration collection', array('@collection' => 'collection.test2')));
$this->assertText('config_test.create');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.create');
$this->assertText('config_test.update');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.update');
$this->assertText('config_test.delete');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.delete');
$this->assertText('config_test.another_create');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_create');
$this->assertText('config_test.another_update');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_update');
$this->assertText('config_test.another_delete');
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_delete');
$this->drupalPostForm(NULL, array(), 'Import all');
$this->assertText(t('There are no configuration changes to import.'));
// Test data in collections.
$data = $test1_storage->read('config_test.create');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created.');
$data = $test1_storage->read('config_test.update');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated.');
$this->assertFalse($test1_storage->read('config_test.delete'), 'The config_test.delete in collection.test1 has been deleted.');
$data = $test2_storage->read('config_test.another_create');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created.');
$data = $test2_storage->read('config_test.another_update');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated.');
$this->assertFalse($test2_storage->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 has been deleted.');
// Ensure that the snapshot has been updated with the collection data.
$snapshot_storage = \Drupal::service('config.storage.snapshot');
$test1_snapshot = $snapshot_storage->createCollection('collection.test1');
$data = $test1_snapshot->read('config_test.create');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created in the snapshot storage.');
$data = $test1_snapshot->read('config_test.update');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated in the snapshot storage.');
$this->assertFalse($test1_snapshot->read('config_test.delete'), 'The config_test.delete in collection.test1 does not exist in the snapshot storage.');
$test2_snapshot = $snapshot_storage->createCollection('collection.test2');
$data = $test2_snapshot->read('config_test.another_create');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created in the snapshot storage.');
$data = $test2_snapshot->read('config_test.another_update');
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated in the snapshot storage.');
$this->assertFalse($test2_snapshot->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 does not exist in the snapshot storage.');
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Archiver\Tar;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\WebTestBase;
/**
* Tests the user interface for exporting configuration.
*
* @group config
*/
class ConfigExportUITest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config', 'config_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set up an override.
$settings['config']['system.maintenance']['message'] = (object) array(
'value' => 'Foo',
'required' => TRUE,
);
$this->writeSettings($settings);
$this->drupalLogin($this->drupalCreateUser(array('export configuration')));
}
/**
* Tests export of configuration.
*/
function testExport() {
// Verify the export page with export submit button is available.
$this->drupalGet('admin/config/development/configuration/full/export');
$this->assertFieldById('edit-submit', t('Export'));
// Submit the export form and verify response.
$this->drupalPostForm('admin/config/development/configuration/full/export', array(), t('Export'));
$this->assertResponse(200, 'User can access the download callback.');
// Test if header contains file name with hostname and timestamp.
$request = \Drupal::request();
$hostname = str_replace('.', '-', $request->getHttpHost());
$header_content_disposition = $this->drupalGetHeader('content-disposition');
$header_match = (boolean) preg_match('/attachment; filename="config-' . preg_quote($hostname) . '-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}\.tar\.gz"/', $header_content_disposition);
$this->assertTrue($header_match, "Header with filename matches the expected format.");
// Get the archived binary file provided to user for download.
$archive_data = $this->getRawContent();
// Temporarily save the archive file.
$uri = file_unmanaged_save_data($archive_data, 'temporary://config.tar.gz');
// Extract the archive and verify it's not empty.
$file_path = file_directory_temp() . '/' . file_uri_target($uri);
$archiver = new Tar($file_path);
$archive_contents = $archiver->listContents();
$this->assert(!empty($archive_contents), 'Downloaded archive file is not empty.');
// Prepare the list of config files from active storage, see
// \Drupal\config\Controller\ConfigController::downloadExport().
$storage_active = $this->container->get('config.storage');
$config_files = array();
foreach ($storage_active->listAll() as $config_name) {
$config_files[] = $config_name . '.yml';
}
// Assert that the downloaded archive file contents are the same as the test
// site active store.
$this->assertIdentical($archive_contents, $config_files);
// Ensure the test configuration override is in effect but was not exported.
$this->assertIdentical(\Drupal::config('system.maintenance')->get('message'), 'Foo');
$archiver->extract(file_directory_temp(), array('system.maintenance.yml'));
$file_contents = file_get_contents(file_directory_temp() . '/' . 'system.maintenance.yml');
$exported = Yaml::decode($file_contents);
$this->assertNotIdentical($exported['message'], 'Foo');
// Check the single export form doesn't have "form-required" elements.
$this->drupalGet('admin/config/development/configuration/single/export');
$this->assertNoRaw('js-form-required form-required', 'No form required fields are found.');
// Ensure the temporary file is not available to users without the
// permission.
$this->drupalLogout();
$this->drupalGet('system/temporary', ['query' => ['file' => 'config.tar.gz']]);
$this->assertResponse(403);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests config overrides do not appear on forms that extend ConfigFormBase.
*
* @group config
* @see \Drupal\Core\Form\ConfigFormBase
*/
class ConfigFormOverrideTest extends WebTestBase {
/**
* Tests that overrides do not affect forms.
*/
public function testFormsWithOverrides() {
$this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer site configuration')));
$overridden_name = 'Site name global conf override';
// Set up an override.
$settings['config']['system.site']['name'] = (object) array(
'value' => $overridden_name,
'required' => TRUE,
);
$this->writeSettings($settings);
// Test that everything on the form is the same, but that the override
// worked for the actual site name.
$this->drupalGet('admin/config/system/site-information');
$this->assertTitle('Basic site settings | ' . $overridden_name);
$elements = $this->xpath('//input[@name="site_name"]');
$this->assertIdentical((string) $elements[0]['value'], 'Drupal');
// Submit the form and ensure the site name is not changed.
$edit = array(
'site_name' => 'Custom site name',
);
$this->drupalPostForm('admin/config/system/site-information', $edit, t('Save configuration'));
$this->assertTitle('Basic site settings | ' . $overridden_name);
$elements = $this->xpath('//input[@name="site_name"]');
$this->assertIdentical((string) $elements[0]['value'], $edit['site_name']);
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\StorageComparer;
use Drupal\filter\Entity\FilterFormat;
use Drupal\system\Tests\Module\ModuleTestBase;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\taxonomy\Entity\Term;
/**
* Tests the largest configuration import possible with all available modules.
*
* @group config
*/
class ConfigImportAllTest extends ModuleTestBase {
use SchemaCheckTestTrait;
/**
* A user with the 'synchronize configuration' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* The profile to install as a basis for testing.
*
* Using the standard profile as this has a lot of additional configuration.
*
* @var string
*/
protected $profile = 'standard';
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(array('synchronize configuration'));
$this->drupalLogin($this->webUser);
}
/**
* Tests that a fixed set of modules can be installed and uninstalled.
*/
public function testInstallUninstall() {
// Get a list of modules to enable.
$all_modules = system_rebuild_module_data();
$all_modules = array_filter($all_modules, function ($module) {
// Filter contrib, hidden, already enabled modules and modules in the
// Testing package.
if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
return FALSE;
}
return TRUE;
});
// Install every module possible.
\Drupal::service('module_installer')->install(array_keys($all_modules));
$this->assertModules(array_keys($all_modules), TRUE);
foreach ($all_modules as $module => $info) {
$this->assertModuleConfig($module);
$this->assertModuleTablesExist($module);
}
// Export active config to sync.
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
system_list_reset();
$this->resetAll();
// Delete every field on the site so all modules can be uninstalled. For
// example, if a comment field exists then module becomes required and can
// not be uninstalled.
$field_storages = \Drupal::entityManager()->getStorage('field_storage_config')->loadMultiple();
\Drupal::entityManager()->getStorage('field_storage_config')->delete($field_storages);
// Purge the data.
field_purge_batch(1000);
// Delete all terms.
$terms = Term::loadMultiple();
entity_delete_multiple('taxonomy_term', array_keys($terms));
// Delete all filter formats.
$filters = FilterFormat::loadMultiple();
entity_delete_multiple('filter_format', array_keys($filters));
// Delete any shortcuts so the shortcut module can be uninstalled.
$shortcuts = Shortcut::loadMultiple();
entity_delete_multiple('shortcut', array_keys($shortcuts));
system_list_reset();
$all_modules = system_rebuild_module_data();
// Ensure that only core required modules and the install profile can not be uninstalled.
$validation_reasons = \Drupal::service('module_installer')->validateUninstall(array_keys($all_modules));
$this->assertEqual(['standard', 'system', 'user'], array_keys($validation_reasons));
$modules_to_uninstall = array_filter($all_modules, function ($module) use ($validation_reasons) {
// Filter required and not enabled modules.
if (!empty($module->info['required']) || $module->status == FALSE) {
return FALSE;
}
return TRUE;
});
// Can not uninstall config and use admin/config/development/configuration!
unset($modules_to_uninstall['config']);
$this->assertTrue(isset($modules_to_uninstall['comment']), 'The comment module will be disabled');
$this->assertTrue(isset($modules_to_uninstall['file']), 'The File module will be disabled');
$this->assertTrue(isset($modules_to_uninstall['editor']), 'The Editor module will be disabled');
// Uninstall all modules that can be uninstalled.
\Drupal::service('module_installer')->uninstall(array_keys($modules_to_uninstall));
$this->assertModules(array_keys($modules_to_uninstall), FALSE);
foreach ($modules_to_uninstall as $module => $info) {
$this->assertNoModuleConfig($module);
$this->assertModuleTablesDoNotExist($module);
}
// Import the configuration thereby re-installing all the modules.
$this->drupalPostForm('admin/config/development/configuration', array(), t('Import all'));
// Modules have been installed that have services.
$this->rebuildContainer();
// Check that there are no errors.
$this->assertIdentical($this->configImporter()->getErrors(), array());
// Check that all modules that were uninstalled are now reinstalled.
$this->assertModules(array_keys($modules_to_uninstall), TRUE);
foreach ($modules_to_uninstall as $module => $info) {
$this->assertModuleConfig($module);
$this->assertModuleTablesExist($module);
}
// Ensure that we have no configuration changes to import.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$this->assertIdentical($storage_comparer->createChangelist()->getChangelist(), $storage_comparer->getEmptyChangelist());
// Now we have all configuration imported, test all of them for schema
// conformance. Ensures all imported default configuration is valid when
// all modules are enabled.
$names = $this->container->get('config.storage')->listAll();
/** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */
$typed_config = $this->container->get('config.typed');
foreach ($names as $name) {
$config = $this->config($name);
$this->assertConfigSchema($typed_config, $name, $config->get());
}
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the importing/exporting configuration based on the install profile.
*
* @group config
*/
class ConfigImportInstallProfileTest extends WebTestBase {
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'testing_config_import';
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('config');
/**
* A user with the 'synchronize configuration' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(array('synchronize configuration'));
$this->drupalLogin($this->webUser);
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
}
/**
* Tests config importer cannot uninstall install profiles.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
*/
public function testInstallProfileValidation() {
$sync = $this->container->get('config.storage.sync');
$this->copyConfig($this->container->get('config.storage'), $sync);
$core = $sync->read('core.extension');
// Ensure install profiles can not be uninstalled.
unset($core['module']['testing_config_import']);
$sync->write('core.extension', $core);
$this->drupalPostForm('admin/config/development/configuration', array(), t('Import all'));
$this->assertText('The configuration cannot be imported because it failed validation for the following reasons:');
$this->assertText('Unable to uninstall the Testing config import profile since it is the install profile.');
// Uninstall dependencies of testing_config_import.
$core['module']['testing_config_import'] = 0;
unset($core['module']['syslog']);
unset($core['theme']['stark']);
$core['theme']['stable'] = 0;
$core['theme']['classy'] = 0;
$sync->write('core.extension', $core);
$sync->deleteAll('syslog.');
$theme = $sync->read('system.theme');
$theme['default'] = 'classy';
$sync->write('system.theme', $theme);
$this->drupalPostForm('admin/config/development/configuration', array(), t('Import all'));
$this->assertText('The configuration was imported successfully.');
$this->rebuildContainer();
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('syslog'), 'The syslog module has been uninstalled.');
$this->assertFalse(\Drupal::service('theme_handler')->themeExists('stark'), 'The stark theme has been uninstalled.');
$this->assertTrue(\Drupal::service('theme_handler')->themeExists('classy'), 'The classy theme has been installed.');
}
}

View file

@ -0,0 +1,519 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\InstallStorage;
use Drupal\simpletest\WebTestBase;
/**
* Tests the user interface for importing/exporting configuration.
*
* @group config
*/
class ConfigImportUITest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('config', 'config_test', 'config_import_test', 'text', 'options');
/**
* A user with the 'synchronize configuration' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(array('synchronize configuration'));
$this->drupalLogin($this->webUser);
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
}
/**
* Tests importing configuration.
*/
function testImport() {
$name = 'system.site';
$dynamic_name = 'config_test.dynamic.new';
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = $this->container->get('config.storage.sync');
$this->drupalGet('admin/config/development/configuration');
$this->assertText('There are no configuration changes to import.');
$this->assertNoFieldById('edit-submit', t('Import all'));
// Create updated configuration object.
$new_site_name = 'Config import test ' . $this->randomString();
$this->prepareSiteNameUpdate($new_site_name);
$this->assertIdentical($sync->exists($name), TRUE, $name . ' found.');
// Create new config entity.
$original_dynamic_data = array(
'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
'langcode' => \Drupal::languageManager()->getDefaultLanguage()->getId(),
'status' => TRUE,
'dependencies' => array(),
'id' => 'new',
'label' => 'New',
'weight' => 0,
'style' => '',
'size' => '',
'size_value' => '',
'protected_property' => '',
);
$sync->write($dynamic_name, $original_dynamic_data);
$this->assertIdentical($sync->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
// Enable the Action and Ban modules during import. The Ban
// module is used because it creates a table during the install. The Action
// module is used because it creates a single simple configuration file
// during the install.
$core_extension = $this->config('core.extension')->get();
$core_extension['module']['action'] = 0;
$core_extension['module']['ban'] = 0;
$core_extension['module'] = module_config_sort($core_extension['module']);
// Bartik is a subtheme of classy so classy must be enabled.
$core_extension['theme']['classy'] = 0;
$core_extension['theme']['bartik'] = 0;
$sync->write('core.extension', $core_extension);
// Use the install storage so that we can read configuration from modules
// and themes that are not installed.
$install_storage = new InstallStorage();
// Set the Bartik theme as default.
$system_theme = $this->config('system.theme')->get();
$system_theme['default'] = 'bartik';
$sync->write('system.theme', $system_theme);
// Read the action config from module default config folder.
$action_settings = $install_storage->read('action.settings');
$action_settings['recursion_limit'] = 50;
$sync->write('action.settings', $action_settings);
// Uninstall the Options and Text modules to ensure that dependencies are
// handled correctly. Options depends on Text so Text should be installed
// first. Since they were enabled during the test setup the core.extension
// file in sync will already contain them.
\Drupal::service('module_installer')->uninstall(array('text', 'options'));
// Set the state system to record installations and uninstallations.
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
// Verify that both appear as ready to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertRaw('<td>' . $name);
$this->assertRaw('<td>' . $dynamic_name);
$this->assertRaw('<td>core.extension');
$this->assertRaw('<td>system.theme');
$this->assertRaw('<td>action.settings');
$this->assertFieldById('edit-submit', t('Import all'));
// Import and verify that both do not appear anymore.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertNoRaw('<td>' . $name);
$this->assertNoRaw('<td>' . $dynamic_name);
$this->assertNoRaw('<td>core.extension');
$this->assertNoRaw('<td>system.theme');
$this->assertNoRaw('<td>action.settings');
$this->assertNoFieldById('edit-submit', t('Import all'));
// Verify that there are no further changes to import.
$this->assertText(t('There are no configuration changes to import.'));
// Verify site name has changed.
$this->assertIdentical($new_site_name, $this->config('system.site')->get('name'));
// Verify that new config entity exists.
$this->assertIdentical($original_dynamic_data, $this->config($dynamic_name)->get());
// Verify the cache got cleared.
$this->assertTrue(isset($GLOBALS['hook_cache_flush']));
$this->rebuildContainer();
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module installed during import.');
$this->assertTrue(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip exists.');
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('action'), 'Action module installed during import.');
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('options'), 'Options module installed during import.');
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('text'), 'Text module installed during import.');
$theme_info = \Drupal::service('theme_handler')->listInfo();
$this->assertTrue($theme_info['bartik']->status, 'Bartik theme installed during import.');
// Ensure installations and uninstallation occur as expected.
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
$expected = array('action', 'ban', 'text', 'options');
$this->assertIdentical($expected, $installed, 'Action, Ban, Text and Options modules installed in the correct order.');
$this->assertTrue(empty($uninstalled), 'No modules uninstalled during import');
// Verify that the action.settings configuration object was only written
// once during the import process and only with the value set in the staged
// configuration. This verifies that the module's default configuration is
// used during configuration import and, additionally, that after installing
// a module, that configuration is not synced twice.
$recursion_limit_values = \Drupal::state()->get('ConfigImportUITest.action.settings.recursion_limit', array());
$this->assertIdentical($recursion_limit_values, array(50));
$core_extension = $this->config('core.extension')->get();
unset($core_extension['module']['action']);
unset($core_extension['module']['ban']);
unset($core_extension['module']['options']);
unset($core_extension['module']['text']);
unset($core_extension['theme']['bartik']);
$sync->write('core.extension', $core_extension);
$sync->delete('action.settings');
$sync->delete('text.settings');
$system_theme = $this->config('system.theme')->get();
$system_theme['default'] = 'stark';
$system_theme['admin'] = 'stark';
$sync->write('system.theme', $system_theme);
// Set the state system to record installations and uninstallations.
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_installed', array());
\Drupal::state()->set('ConfigImportUITest.core.extension.modules_uninstalled', array());
// Verify that both appear as ready to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertRaw('<td>core.extension');
$this->assertRaw('<td>system.theme');
$this->assertRaw('<td>action.settings');
// Import and verify that both do not appear anymore.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertNoRaw('<td>core.extension');
$this->assertNoRaw('<td>system.theme');
$this->assertNoRaw('<td>action.settings');
$this->rebuildContainer();
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module uninstalled during import.');
$this->assertFalse(\Drupal::database()->schema()->tableExists('ban_ip'), 'The database table ban_ip does not exist.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('action'), 'Action module uninstalled during import.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('options'), 'Options module uninstalled during import.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('text'), 'Text module uninstalled during import.');
// Ensure installations and uninstallation occur as expected.
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
$expected = array('options', 'text', 'ban', 'action');
$this->assertIdentical($expected, $uninstalled, 'Options, Text, Ban and Action modules uninstalled in the correct order.');
$this->assertTrue(empty($installed), 'No modules installed during import');
$theme_info = \Drupal::service('theme_handler')->listInfo();
$this->assertFalse(isset($theme_info['bartik']), 'Bartik theme uninstalled during import.');
// Verify that the action.settings configuration object was only deleted
// once during the import process.
$delete_called = \Drupal::state()->get('ConfigImportUITest.action.settings.delete', 0);
$this->assertIdentical($delete_called, 1, "The action.settings configuration was deleted once during configuration import.");
}
/**
* Tests concurrent importing of configuration.
*/
function testImportLock() {
// Create updated configuration object.
$new_site_name = 'Config import test ' . $this->randomString();
$this->prepareSiteNameUpdate($new_site_name);
// Verify that there are configuration differences to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('There are no configuration changes to import.'));
// Acquire a fake-lock on the import mechanism.
$config_importer = $this->configImporter();
$this->container->get('lock.persistent')->acquire($config_importer::LOCK_NAME);
// Attempt to import configuration and verify that an error message appears.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertText(t('Another request may be synchronizing configuration already.'));
// Release the lock, just to keep testing sane.
$this->container->get('lock.persistent')->release($config_importer::LOCK_NAME);
// Verify site name has not changed.
$this->assertNotEqual($new_site_name, $this->config('system.site')->get('name'));
}
/**
* Tests verification of site UUID before importing configuration.
*/
function testImportSiteUuidValidation() {
$sync = \Drupal::service('config.storage.sync');
// Create updated configuration object.
$config_data = $this->config('system.site')->get();
// Generate a new site UUID.
$config_data['uuid'] = \Drupal::service('uuid')->generate();
$sync->write('system.site', $config_data);
// Verify that there are configuration differences to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertText(t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'));
$this->assertNoFieldById('edit-submit', t('Import all'));
}
/**
* Tests the screen that shows differences between active and sync.
*/
function testImportDiff() {
$sync = $this->container->get('config.storage.sync');
$config_name = 'config_test.system';
$change_key = 'foo';
$remove_key = '404';
$add_key = 'biff';
$add_data = '<em>bangpow</em>';
$change_data = '<p><em>foobar</em></p>';
$original_data = array(
'foo' => '<p>foobar</p>',
'baz' => '<strong>no change</strong>',
'404' => '<em>herp</em>',
);
// Update active storage to have html in config data.
$this->config($config_name)->setData($original_data)->save();
// Change a configuration value in sync.
$sync_data = $original_data;
$sync_data[$change_key] = $change_data;
$sync_data[$add_key] = $add_data;
unset($sync_data[$remove_key]);
$sync->write($config_name, $sync_data);
// Load the diff UI and verify that the diff reflects the change.
$this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name);
$this->assertTitle(format_string('View changes of @config_name | Drupal', array('@config_name' => $config_name)));
// The following assertions do not use $this::assertEscaped() because
// \Drupal\Component\Diff\DiffFormatter adds markup that signifies what has
// changed.
// Changed values are escaped.
$this->assertText(Html::escape("foo: '<p><em>foobar</em></p>'"));
$this->assertText(Html::escape("foo: '<p>foobar</p>'"));
// The no change values are escaped.
$this->assertText(Html::escape("baz: '<strong>no change</strong>'"));
// Added value is escaped.
$this->assertText(Html::escape("biff: '<em>bangpow</em>'"));
// Deleted value is escaped.
$this->assertText(Html::escape("404: '<em>herp</em>'"));
// Verify diff colors are displayed.
$result = $this->xpath('//table[contains(@class, :class)]', array(':class' => 'diff'));
$this->assertEqual(count($result), 1, "Diff UI is displaying colors.");
// Reset data back to original, and remove a key
$sync_data = $original_data;
unset($sync_data[$remove_key]);
$sync->write($config_name, $sync_data);
// Load the diff UI and verify that the diff reflects a removed key.
$this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name);
// The no change values are escaped.
$this->assertText(Html::escape("foo: '<p>foobar</p>'"));
$this->assertText(Html::escape("baz: '<strong>no change</strong>'"));
// Removed key is escaped.
$this->assertText(Html::escape("404: '<em>herp</em>'"));
// Reset data back to original and add a key
$sync_data = $original_data;
$sync_data[$add_key] = $add_data;
$sync->write($config_name, $sync_data);
// Load the diff UI and verify that the diff reflects an added key.
$this->drupalGet('admin/config/development/configuration/sync/diff/' . $config_name);
// The no change values are escaped.
$this->assertText(Html::escape("baz: '<strong>no change</strong>'"));
$this->assertText(Html::escape("404: '<em>herp</em>'"));
// Added key is escaped.
$this->assertText(Html::escape("biff: '<em>bangpow</em>'"));
}
/**
* Tests that multiple validation errors are listed on the page.
*/
public function testImportValidation() {
// Set state value so that
// \Drupal\config_import_test\EventSubscriber::onConfigImportValidate() logs
// validation errors.
\Drupal::state()->set('config_import_test.config_import_validate_fail', TRUE);
// Ensure there is something to import.
$new_site_name = 'Config import test ' . $this->randomString();
$this->prepareSiteNameUpdate($new_site_name);
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('There are no configuration changes to import.'));
$this->drupalPostForm(NULL, array(), t('Import all'));
// Verify that the validation messages appear.
$this->assertText('The configuration cannot be imported because it failed validation for the following reasons:');
$this->assertText('Config import validate error 1.');
$this->assertText('Config import validate error 2.');
// Verify site name has not changed.
$this->assertNotEqual($new_site_name, $this->config('system.site')->get('name'));
}
public function testConfigUninstallConfigException() {
$sync = $this->container->get('config.storage.sync');
$core_extension = $this->config('core.extension')->get();
unset($core_extension['module']['config']);
$sync->write('core.extension', $core_extension);
$this->drupalGet('admin/config/development/configuration');
$this->assertText('core.extension');
// Import and verify that both do not appear anymore.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertText('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.');
}
function prepareSiteNameUpdate($new_site_name) {
$sync = $this->container->get('config.storage.sync');
// Create updated configuration object.
$config_data = $this->config('system.site')->get();
$config_data['name'] = $new_site_name;
$sync->write('system.site', $config_data);
}
/**
* Tests an import that results in an error.
*/
function testImportErrorLog() {
$name_primary = 'config_test.dynamic.primary';
$name_secondary = 'config_test.dynamic.secondary';
$sync = $this->container->get('config.storage.sync');
$uuid = $this->container->get('uuid');
$values_primary = array(
'uuid' => $uuid->generate(),
'langcode' => 'en',
'status' => TRUE,
'dependencies' => array(),
'id' => 'primary',
'label' => 'Primary',
'weight' => 0,
'style' => NULL,
'size' => NULL,
'size_value' => NULL,
'protected_property' => NULL,
);
$sync->write($name_primary, $values_primary);
$values_secondary = array(
'uuid' => $uuid->generate(),
'langcode' => 'en',
'status' => TRUE,
// Add a dependency on primary, to ensure that is synced first.
'dependencies' => array(
'config' => array($name_primary),
),
'id' => 'secondary',
'label' => 'Secondary Sync',
'weight' => 0,
'style' => NULL,
'size' => NULL,
'size_value' => NULL,
'protected_property' => NULL,
);
$sync->write($name_secondary, $values_secondary);
// Verify that there are configuration differences to import.
$this->drupalGet('admin/config/development/configuration');
$this->assertNoText(t('There are no configuration changes to import.'));
// Attempt to import configuration and verify that an error message appears.
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertText(SafeMarkup::format('Deleted and replaced configuration entity "@name"', array('@name' => $name_secondary)));
$this->assertText(t('The configuration was imported with errors.'));
$this->assertNoText(t('The configuration was imported successfully.'));
$this->assertText(t('There are no configuration changes to import.'));
}
/**
* Tests the config importer cannot delete bundles with existing entities.
*
* @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
*/
public function testEntityBundleDelete() {
\Drupal::service('module_installer')->install(array('node'));
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
$node_type = $this->drupalCreateContentType();
$node = $this->drupalCreateNode(array('type' => $node_type->id()));
$this->drupalGet('admin/config/development/configuration');
// The node type, body field and entity displays will be scheduled for
// removal.
$this->assertText(format_string('node.type.@type', array('@type' => $node_type->id())));
$this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
// Attempt to import configuration and verify that an error message appears
// and the node type, body field and entity displays are still scheduled for
// removal.
$this->drupalPostForm(NULL, array(), t('Import all'));
$validation_message = t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $node->getEntityType()->getLabel(), '%bundle_label' => $node->getEntityType()->getBundleLabel(), '%bundle' => $node_type->label()));
$this->assertRaw($validation_message);
$this->assertText(format_string('node.type.@type', array('@type' => $node_type->id())));
$this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
$this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
// Delete the node and try to import again.
$node->delete();
$this->drupalPostForm(NULL, array(), t('Import all'));
$this->assertNoRaw($validation_message);
$this->assertText(t('There are no configuration changes to import.'));
$this->assertNoText(format_string('node.type.@type', array('@type' => $node_type->id())));
$this->assertNoText(format_string('field.field.node.@type.body', array('@type' => $node_type->id())));
$this->assertNoText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id())));
$this->assertNoText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id())));
$this->assertNoText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id())));
}
/**
* Tests config importer cannot uninstall extensions which are depended on.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
*/
public function testExtensionValidation() {
\Drupal::service('module_installer')->install(['node']);
\Drupal::service('theme_handler')->install(['bartik']);
$this->rebuildContainer();
$sync = $this->container->get('config.storage.sync');
$this->copyConfig($this->container->get('config.storage'), $sync);
$core = $sync->read('core.extension');
// Node depends on text.
unset($core['module']['text']);
$module_data = system_rebuild_module_data();
$this->assertTrue(isset($module_data['node']->requires['text']), 'The Node module depends on the Text module.');
// Bartik depends on classy.
unset($core['theme']['classy']);
$theme_data = \Drupal::service('theme_handler')->rebuildThemeData();
$this->assertTrue(isset($theme_data['bartik']->requires['classy']), 'The Bartik theme depends on the Classy theme.');
// This module does not exist.
$core['module']['does_not_exist'] = 0;
// This theme does not exist.
$core['theme']['does_not_exist'] = 0;
$sync->write('core.extension', $core);
$this->drupalPostForm('admin/config/development/configuration', array(), t('Import all'));
$this->assertText('The configuration cannot be imported because it failed validation for the following reasons:');
$this->assertText('Unable to uninstall the Text module since the Node module is installed.');
$this->assertText('Unable to uninstall the Classy theme since the Bartik theme is installed.');
$this->assertText('Unable to install the does_not_exist module since it does not exist.');
$this->assertText('Unable to install the does_not_exist theme since it does not exist.');
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests importing configuration from an uploaded file.
*
* @group config
*/
class ConfigImportUploadTest extends WebTestBase {
/**
* A user with the 'import configuration' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('config');
protected function setUp() {
parent::setUp();
$this->webUser = $this->drupalCreateUser(array('import configuration'));
$this->drupalLogin($this->webUser);
}
/**
* Tests importing configuration.
*/
function testImport() {
// Verify access to the config upload form.
$this->drupalGet('admin/config/development/configuration/full/import');
$this->assertResponse(200);
// Attempt to upload a non-tar file.
$text_file = current($this->drupalGetTestFiles('text'));
$edit = array('files[import_tarball]' => drupal_realpath($text_file->uri));
$this->drupalPostForm('admin/config/development/configuration/full/import', $edit, t('Upload'));
$this->assertText(t('Could not extract the contents of the tar file'));
// Make the sync directory read-only.
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
\Drupal::service('file_system')->chmod($directory, 0555);
$this->drupalGet('admin/config/development/configuration/full/import');
$this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory]));
// Ensure submit button for \Drupal\config\Form\ConfigImportForm is
// disabled.
$submit_is_disabled = $this->cssSelect('form.config-import-form input[type="submit"]:disabled');
$this->assertTrue(count($submit_is_disabled) === 1, 'The submit button is disabled.');
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\InstallStorage;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Config\FileStorage;
use Drupal\system\Entity\Action;
use Drupal\tour\Entity\Tour;
/**
* Tests installation and removal of configuration objects in install, disable
* and uninstall functionality.
*
* @group config
*/
class ConfigInstallProfileOverrideTest extends WebTestBase {
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'testing_config_overrides';
/**
* Tests install profile config changes.
*/
function testInstallProfileConfigOverwrite() {
$config_name = 'system.cron';
// The expected configuration from the system module.
$expected_original_data = array(
'threshold' => array(
'requirements_warning' => 172800,
'requirements_error' => 1209600,
),
);
// The expected active configuration altered by the install profile.
$expected_profile_data = array(
'threshold' => array(
'requirements_warning' => 259200,
'requirements_error' => 1209600,
),
);
$expected_profile_data['_core']['default_config_hash'] = Crypt::hashBase64(serialize($expected_profile_data));
// Verify that the original data matches. We have to read the module config
// file directly, because the install profile default system.cron.yml
// configuration file was used to create the active configuration.
$config_dir = drupal_get_path('module', 'system') . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
$this->assertTrue(is_dir($config_dir));
$source_storage = new FileStorage($config_dir);
$data = $source_storage->read($config_name);
$this->assertIdentical($data, $expected_original_data);
// Verify that active configuration matches the expected data, which was
// created from the testing install profile's system.cron.yml file.
$config = $this->config($config_name);
$this->assertIdentical($config->get(), $expected_profile_data);
// Ensure that the configuration entity has the expected dependencies and
// overrides.
$action = Action::load('user_block_user_action');
$this->assertEqual($action->label(), 'Overridden block the selected user(s)');
$action = Action::load('user_cancel_user_action');
$this->assertEqual($action->label(), 'Cancel the selected user account(s)', 'Default configuration that is not overridden is not affected.');
// Ensure that optional configuration can be overridden.
$tour = Tour::load('language');
$this->assertEqual(count($tour->getTips()), 1, 'Optional configuration can be overridden. The language tour only has one tip');
$tour = Tour::load('language-add');
$this->assertEqual(count($tour->getTips()), 3, 'Optional configuration that is not overridden is not affected.');
// Ensure that optional configuration from a profile is created if
// dependencies are met.
$this->assertEqual(Tour::load('testing_config_overrides')->label(), 'Config override test');
// Ensure that optional configuration from a profile is not created if
// dependencies are not met. Cannot use the entity system since the entity
// type does not exist.
$optional_dir = drupal_get_path('module', 'testing_config_overrides') . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$optional_storage = new FileStorage($optional_dir);
foreach (['config_test.dynamic.dotted.default', 'config_test.dynamic.override', 'config_test.dynamic.override_unmet'] as $id) {
$this->assertTrue(\Drupal::config($id)->isNew(), "The config_test entity $id contained in the profile's optional directory does not exist.");
// Make that we don't get false positives from the assertion above.
$this->assertTrue($optional_storage->exists($id), "The config_test entity $id does exist in the profile's optional directory.");
}
// Install the config_test module and ensure that the override from the
// install profile is used. Optional configuration can override
// configuration in a modules config/install directory.
$this->container->get('module_installer')->install(['config_test']);
$this->rebuildContainer();
$config_test_storage = \Drupal::entityManager()->getStorage('config_test');
$this->assertEqual($config_test_storage->load('dotted.default')->label(), 'Default install profile override', 'The config_test entity is overridden by the profile optional configuration.');
// Test that override of optional configuration does work.
$this->assertEqual($config_test_storage->load('override')->label(), 'Override', 'The optional config_test entity is overridden by the profile optional configuration.');
// Test that override of optional configuration which introduces an unmet
// dependency does not get created.
$this->assertNull($config_test_storage->load('override_unmet'), 'The optional config_test entity with unmet dependencies is not created.');
$this->assertNull($config_test_storage->load('completely_new'), 'The completely new optional config_test entity with unmet dependencies is not created.');
// Installing dblog creates the optional configuration.
$this->container->get('module_installer')->install(['dblog']);
$this->rebuildContainer();
$this->assertEqual($config_test_storage->load('override_unmet')->label(), 'Override', 'The optional config_test entity is overridden by the profile optional configuration and is installed when its dependencies are met.');
$this->assertEqual($config_test_storage->load('completely_new')->label(), 'Completely new optional configuration', 'The optional config_test entity is provided by the profile optional configuration and is installed when its dependencies are met.');
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
* Tests install profile config overrides can not add unmet dependencies.
*
* @group Config
*/
class ConfigInstallProfileUnmetDependenciesTest extends InstallerTestBase {
/**
* The installation profile to install.
*
* @var string
*/
protected $profile = 'testing_config_overrides';
/**
* Set to TRUE if the expected exception is thrown.
*
* @var bool
*/
protected $expectedException = FALSE;
protected function setUp() {
// Copy the testing_config_overrides install profile so we can change the
// configuration to include a dependency that can not be met. File API
// functions are not available yet.
$dest = $this->siteDirectory . '/profiles/testing_config_overrides';
mkdir($dest, 0777, TRUE);
$source = DRUPAL_ROOT . '/core/profiles/testing_config_overrides';
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $item) {
if ($item->isDir()) {
mkdir($dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
else {
copy($item, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
}
}
// Add a dependency that can not be met because User is installed before
// Action.
$config_file = $dest . DIRECTORY_SEPARATOR . InstallStorage::CONFIG_INSTALL_DIRECTORY . DIRECTORY_SEPARATOR . 'system.action.user_block_user_action.yml';
$action = Yaml::decode(file_get_contents($config_file));
$action['dependencies']['module'][] = 'action';
file_put_contents($config_file, Yaml::encode($action));
parent::setUp();
}
/**
* {@inheritdoc}
*
* Override the error method so we can test for the expected exception.
*/
protected function error($message = '', $group = 'Other', array $caller = NULL) {
if ($group == 'User notice') {
// Since 'User notice' is set by trigger_error() which is used for debug
// set the message to a status of 'debug'.
return $this->assert('debug', $message, 'Debug', $caller);
}
if ($group == 'Drupal\Core\Config\UnmetDependenciesException') {
$this->expectedException = TRUE;
return FALSE;
}
return $this->assert('exception', $message, $group, $caller);
}
/**
* {@inheritdoc}
*/
protected function setUpSite() {
// This step is not reached due to the exception.
}
/**
* Confirms that the installation succeeded.
*/
public function testInstalled() {
if ($this->expectedException) {
$this->pass('Expected Drupal\Core\Config\UnmetDependenciesException exception thrown');
}
else {
$this->fail('Expected Drupal\Core\Config\UnmetDependenciesException exception thrown');
}
$this->assertErrorLogged('Configuration objects (system.action.user_block_user_action) provided by user have unmet dependencies in');
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\StorageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests installation and removal of configuration objects in install, disable
* and uninstall functionality.
*
* @group config
*/
class ConfigInstallWebTest extends WebTestBase {
/**
* The admin user used in this test.
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes', 'administer site configuration'));
// Ensure the global variable being asserted by this test does not exist;
// a previous test executed in this request/process might have set it.
unset($GLOBALS['hook_config_test']);
}
/**
* Tests module re-installation.
*/
function testIntegrationModuleReinstallation() {
$default_config = 'config_integration_test.settings';
$default_configuration_entity = 'config_test.dynamic.config_integration_test';
// Install the config_test module we're integrating with.
\Drupal::service('module_installer')->install(array('config_test'));
// Verify the configuration does not exist prior to installation.
$config_static = $this->config($default_config);
$this->assertIdentical($config_static->isNew(), TRUE);
$config_entity = $this->config($default_configuration_entity);
$this->assertIdentical($config_entity->isNew(), TRUE);
// Install the integration module.
\Drupal::service('module_installer')->install(array('config_integration_test'));
// Verify that default module config exists.
\Drupal::configFactory()->reset($default_config);
\Drupal::configFactory()->reset($default_configuration_entity);
$config_static = $this->config($default_config);
$this->assertIdentical($config_static->isNew(), FALSE);
$this->assertIdentical($config_static->get('foo'), 'default setting');
$config_entity = $this->config($default_configuration_entity);
$this->assertIdentical($config_entity->isNew(), FALSE);
$this->assertIdentical($config_entity->get('label'), 'Default integration config label');
// Customize both configuration objects.
$config_static->set('foo', 'customized setting')->save();
$config_entity->set('label', 'Customized integration config label')->save();
// @todo FIXME: Setting config keys WITHOUT SAVING retains the changed config
// object in memory. Every new call to $this->config() MUST revert in-memory changes
// that haven't been saved!
// In other words: This test passes even without this reset, but it shouldn't.
$this->container->get('config.factory')->reset();
// Disable and uninstall the integration module.
$this->container->get('module_installer')->uninstall(array('config_integration_test'));
// Verify the integration module's config was uninstalled.
$config_static = $this->config($default_config);
$this->assertIdentical($config_static->isNew(), TRUE);
// Verify the integration config still exists.
$config_entity = $this->config($default_configuration_entity);
$this->assertIdentical($config_entity->isNew(), FALSE);
$this->assertIdentical($config_entity->get('label'), 'Customized integration config label');
// Reinstall the integration module.
try {
\Drupal::service('module_installer')->install(array('config_integration_test'));
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_integration_test');
$this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.config_integration_test']]);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.config_integration_test) provided by config_integration_test already exist in active configuration');
}
// Delete the configuration entity so that the install will work.
$config_entity->delete();
\Drupal::service('module_installer')->install(array('config_integration_test'));
// Verify the integration module's config was re-installed.
\Drupal::configFactory()->reset($default_config);
\Drupal::configFactory()->reset($default_configuration_entity);
$config_static = $this->config($default_config);
$this->assertIdentical($config_static->isNew(), FALSE);
$this->assertIdentical($config_static->get('foo'), 'default setting');
// Verify the integration config is using the default.
$config_entity = \Drupal::config($default_configuration_entity);
$this->assertIdentical($config_entity->isNew(), FALSE);
$this->assertIdentical($config_entity->get('label'), 'Default integration config label');
}
/**
* Tests pre-existing configuration detection.
*/
public function testPreExistingConfigInstall() {
$this->drupalLogin($this->adminUser);
// Try to install config_install_fail_test and config_test. Doing this
// will install the config_test module first because it is a dependency of
// config_install_fail_test.
// @see \Drupal\system\Form\ModulesListForm::submitForm()
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE, 'modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
// Uninstall the config_test module to test the confirm form.
$this->drupalPostForm('admin/modules/uninstall', array('uninstall[config_test]' => TRUE), t('Uninstall'));
$this->drupalPostForm(NULL, array(), t('Uninstall'));
// Try to install config_install_fail_test without selecting config_test.
// The user is shown a confirm form because the config_test module is a
// dependency.
// @see \Drupal\system\Form\ModulesListConfirmForm::submitForm()
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm(NULL, array(), t('Continue'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
// Test that collection configuration clashes during a module install are
// reported correctly.
\Drupal::service('module_installer')->install(['language']);
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('fr')->save();
\Drupal::languageManager()
->getLanguageConfigOverride('fr', 'config_test.dynamic.dotted.default')
->set('label', 'Je suis Charlie')
->save();
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default</em> already exist in active configuration.');
// Test installing a theme through the UI that has existing configuration.
// This relies on the fact the config_test has been installed and created
// the config_test.dynamic.dotted.default configuration and the translation
// override created still exists.
$this->drupalGet('admin/appearance');
$url = $this->xpath("//a[contains(@href,'config_clash_test_theme') and contains(@href,'/install?')]/@href")[0];
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertRaw('Unable to install config_clash_test_theme, <em class="placeholder">config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default</em> already exist in active configuration.');
// Test installing a theme through the API that has existing configuration.
try {
\Drupal::service('theme_handler')->install(['config_clash_test_theme']);
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_clash_test_theme');
$this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.dotted.default'], 'language.fr' => ['config_test.dynamic.dotted.default']]);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default) provided by config_clash_test_theme already exist in active configuration');
}
}
/**
* Tests unmet dependencies detection.
*/
public function testUnmetDependenciesInstall() {
$this->drupalLogin($this->adminUser);
// We need to install separately since config_install_dependency_test does
// not depend on config_test and order is important.
$this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Install'));
$this->assertRaw('Unable to install Config install dependency test, <em class="placeholder">config_other_module_config_test.weird_simple_config, config_test.dynamic.other_module_test_with_dependency</em> have unmet dependencies.');
$this->drupalPostForm('admin/modules', array('modules[Testing][config_other_module_config_test][enable]' => TRUE), t('Install'));
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_dependency_test][enable]' => TRUE), t('Install'));
$this->rebuildContainer();
$this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.');
}
/**
* Tests config_requirements().
*/
public function testConfigModuleRequirements() {
$this->drupalLogin($this->adminUser);
$this->drupalPostForm('admin/modules', array('modules[Core][config][enable]' => TRUE), t('Install'));
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
file_unmanaged_delete_recursive($directory);
$this->drupalGet('/admin/reports/status');
$this->assertRaw(t('The directory %directory does not exist.', array('%directory' => $directory)));
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
\Drupal::service('file_system')->chmod($directory, 0555);
$this->drupalGet('/admin/reports/status');
$this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory]));
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase;
/**
* Tests language overrides applied through the website.
*
* @group config
*/
class ConfigLanguageOverrideWebTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = [
'block',
'language',
'system'
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
}
/**
* Tests translating the site name.
*/
function testSiteNameTranslation() {
$adminUser = $this->drupalCreateUser(array('administer site configuration', 'administer languages'));
$this->drupalLogin($adminUser);
// Add a custom language.
$langcode = 'xx';
$name = $this->randomMachineName(16);
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'label' => $name,
'direction' => LanguageInterface::DIRECTION_LTR,
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
\Drupal::languageManager()
->getLanguageConfigOverride($langcode, 'system.site')
->set('name', 'XX site name')
->save();
// Place branding block with site name into header region.
$this->drupalPlaceBlock('system_branding_block', ['region' => 'header']);
$this->drupalLogout();
// The home page in English should not have the override.
$this->drupalGet('');
$this->assertNoText('XX site name');
// During path resolution the system.site configuration object is used to
// determine the front page. This occurs before language negotiation causing
// the configuration factory to cache an object without the correct
// overrides. We are testing that the configuration factory is
// re-initialised after language negotiation. Ensure that it applies when
// we access the XX front page.
// @see \Drupal\Core\PathProcessor::processInbound()
$this->drupalGet('xx');
$this->assertText('XX site name');
// Set the xx language to be the default language and delete the English
// language so the site is no longer multilingual and confirm configuration
// overrides still work.
$language_manager = \Drupal::languageManager()->reset();
$this->assertTrue($language_manager->isMultilingual(), 'The test site is multilingual.');
$this->config('system.site')->set('default_langcode', 'xx')->save();
ConfigurableLanguage::load('en')->delete();
$this->assertFalse($language_manager->isMultilingual(), 'The test site is monolingual.');
$this->drupalGet('xx');
$this->assertText('XX site name');
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests default configuration provided by a module that does not own it.
*
* @group config
*/
class ConfigOtherModuleTest extends WebTestBase {
/**
* Tests enabling the provider of the default configuration first.
*/
public function testInstallOtherModuleFirst() {
$this->installModule('config_other_module_config_test');
// Check that the config entity doesn't exist before the config_test module
// is enabled. We cannot use the entity system because the config_test
// entity type does not exist.
$config = $this->config('config_test.dynamic.other_module_test');
$this->assertTrue($config->isNew(), 'Default configuration for other modules is not installed if that module is not enabled.');
// Install the module that provides the entity type. This installs the
// default configuration.
$this->installModule('config_test');
$this->assertTrue(entity_load('config_test', 'other_module_test', TRUE), 'Default configuration has been installed.');
// Uninstall the module that provides the entity type. This will remove the
// default configuration.
$this->uninstallModule('config_test');
$config = $this->config('config_test.dynamic.other_module_test');
$this->assertTrue($config->isNew(), 'Default configuration for other modules is removed when the config entity provider is disabled.');
// Install the module that provides the entity type again. This installs the
// default configuration.
$this->installModule('config_test');
$other_module_config_entity = entity_load('config_test', 'other_module_test', TRUE);
$this->assertTrue($other_module_config_entity, "Default configuration has been recreated.");
// Update the default configuration to test that the changes are preserved
// if the module that provides the default configuration is uninstalled.
$other_module_config_entity->set('style', "The piano ain't got no wrong notes.");
$other_module_config_entity->save();
// Uninstall the module that provides the default configuration.
$this->uninstallModule('config_other_module_config_test');
$this->assertTrue(entity_load('config_test', 'other_module_test', TRUE), 'Default configuration for other modules is not removed when the module that provides it is uninstalled.');
// Default configuration provided by config_test should still exist.
$this->assertTrue(entity_load('config_test', 'dotted.default', TRUE), 'The configuration is not deleted.');
// Re-enable module to test that pre-existing optional configuration does
// not throw an error.
$this->installModule('config_other_module_config_test');
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('config_other_module_config_test'), 'The config_other_module_config_test module is installed.');
// Ensure that optional configuration with unmet dependencies is only
// installed once all the dependencies are met.
$this->assertNull(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_unmet whose dependencies are not met is not created.');
$this->assertNull(entity_load('config_test', 'other_module_test_optional_entity_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_optional_entity_unmet whose dependencies are not met is not created.');
$this->installModule('config_install_dependency_test');
$this->assertTrue(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_unmet whose dependencies are met is now created.');
// Although the following configuration entity's are now met it is not
// installed because it does not have a direct dependency on the
// config_install_dependency_test module.
$this->assertNull(entity_load('config_test', 'other_module_test_optional_entity_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_optional_entity_unmet whose dependencies are met is not created.');
}
/**
* Tests enabling the provider of the config entity type first.
*/
public function testInstallConfigEntityModuleFirst() {
$this->installModule('config_test');
$this->assertFalse(entity_load('config_test', 'other_module_test', TRUE), 'Default configuration provided by config_other_module_config_test does not exist.');
$this->installModule('config_other_module_config_test');
$this->assertTrue(entity_load('config_test', 'other_module_test', TRUE), 'Default configuration provided by config_other_module_config_test has been installed.');
}
/**
* Tests uninstalling Node module removes views which are dependent on it.
*/
public function testUninstall() {
$this->installModule('views');
$storage = $this->container->get('entity_type.manager')->getStorage('view');
$storage->resetCache(array('frontpage'));
$this->assertTrue($storage->load('frontpage') === NULL, 'After installing Views, frontpage view which is dependant on the Node and Views modules does not exist.');
$this->installModule('node');
$storage->resetCache(array('frontpage'));
$this->assertTrue($storage->load('frontpage') !== NULL, 'After installing Node, frontpage view which is dependant on the Node and Views modules exists.');
$this->uninstallModule('node');
$storage = $this->container->get('entity_type.manager')->getStorage('view');
$storage->resetCache(array('frontpage'));
$this->assertTrue($storage->load('frontpage') === NULL, 'After uninstalling Node, frontpage view which is dependant on the Node and Views modules does not exist.');
}
/**
* Installs a module.
*
* @param string $module
* The module name.
*/
protected function installModule($module) {
$this->container->get('module_installer')->install(array($module));
$this->container = \Drupal::getContainer();
}
/**
* Uninstalls a module.
*
* @param string $module
* The module name.
*/
protected function uninstallModule($module) {
$this->container->get('module_installer')->uninstall(array($module));
$this->container = \Drupal::getContainer();
}
}

View file

@ -0,0 +1,227 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\WebTestBase;
/**
* Tests the user interface for importing/exporting a single configuration.
*
* @group config
*/
class ConfigSingleImportExportTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'config',
'config_test',
// Adding language module makes it possible to involve non-default
// (language.xx) collections in import/export operations.
'language',
];
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests importing a single configuration file.
*/
public function testImport() {
$storage = \Drupal::entityManager()->getStorage('config_test');
$uuid = \Drupal::service('uuid');
$this->drupalLogin($this->drupalCreateUser(array('import configuration')));
// Attempt an import with invalid YAML.
$edit = [
'config_type' => 'action',
'import' => '{{{',
];
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
// Assert the static portion of the error since different parsers could give different text in their error.
$this->assertText('The import failed with the following message: ');
$import = <<<EOD
label: First
weight: 0
style: ''
status: '1'
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
// Attempt an import with a missing ID.
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => 'id', '@entity_type' => 'Test configuration')));
// Perform an import with no specified UUID and a unique ID.
$this->assertNull($storage->load('first'));
$edit['import'] = "id: first\n" . $edit['import'];
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'first', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('first');
$this->assertIdentical($entity->label(), 'First');
$this->assertIdentical($entity->id(), 'first');
$this->assertTrue($entity->status());
$this->assertRaw(t('The configuration was imported successfully.'));
// Attempt an import with an existing ID but missing UUID.
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('An entity with this machine name already exists but the import did not specify a UUID.'));
// Attempt an import with a mismatched UUID and existing ID.
$edit['import'] .= "\nuuid: " . $uuid->generate();
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('An entity with this machine name already exists but the UUID does not match.'));
// Attempt an import with a custom ID.
$edit['custom_entity_id'] = 'custom_id';
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'custom_id', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$this->assertRaw(t('The configuration was imported successfully.'));
// Perform an import with a unique ID and UUID.
$import = <<<EOD
id: second
label: Second
weight: 0
style: ''
status: '0'
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
$second_uuid = $uuid->generate();
$edit['import'] .= "\nuuid: " . $second_uuid;
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The configuration was imported successfully.'));
$this->assertIdentical($entity->label(), 'Second');
$this->assertIdentical($entity->id(), 'second');
$this->assertFalse($entity->status());
$this->assertIdentical($entity->uuid(), $second_uuid);
// Perform an update.
$import = <<<EOD
id: second
uuid: $second_uuid
label: 'Second updated'
weight: 0
style: ''
status: '0'
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The configuration was imported successfully.'));
$this->assertIdentical($entity->label(), 'Second updated');
// Try to perform an update which adds missing dependencies.
$import = <<<EOD
id: second
uuid: $second_uuid
label: 'Second updated'
weight: 0
style: ''
status: '0'
dependencies:
module:
- does_not_exist
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Configuration %name depends on the %owner module that will not be installed after import.', ['%name' => 'config_test.dynamic.second', '%owner' => 'does_not_exist']));
}
/**
* Tests importing a simple configuration file.
*/
public function testImportSimpleConfiguration() {
$this->drupalLogin($this->drupalCreateUser(array('import configuration')));
$config = $this->config('system.site')->set('name', 'Test simple import');
// Place branding block with site name into header region.
$this->drupalPlaceBlock('system_branding_block', ['region' => 'header']);
$edit = array(
'config_type' => 'system.simple',
'config_name' => $config->getName(),
'import' => Yaml::encode($config->get()),
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => $config->getName(), '@type' => 'simple configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$this->drupalGet('');
$this->assertText('Test simple import');
// Ensure that ConfigImporter validation is running when importing simple
// configuration.
$config_data = $this->config('core.extension')->get();
// Simulate uninstalling the Config module.
unset($config_data['module']['config']);
$edit = array(
'config_type' => 'system.simple',
'config_name' => 'core.extension',
'import' => Yaml::encode($config_data),
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'));
}
/**
* Tests exporting a single configuration file.
*/
public function testExport() {
$this->drupalLogin($this->drupalCreateUser(array('export configuration')));
$this->drupalGet('admin/config/development/configuration/single/export/system.simple');
$this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Simple configuration'), 'The simple configuration option is selected when specified in the URL.');
// Spot check several known simple configuration files.
$element = $this->xpath('//select[@name="config_name"]');
$options = $this->getAllOptions($element[0]);
$expected_options = array('system.site', 'user.settings');
foreach ($options as &$option) {
$option = (string) $option;
}
$this->assertIdentical($expected_options, array_intersect($expected_options, $options), 'The expected configuration files are listed.');
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.image');
$this->assertFieldByXPath('//textarea[@name="export"]', "toolkit: gd\n_core:\n default_config_hash: durWHaKeBaq4d9Wpi4RqwADj1OufDepcnJuhVLmKN24\n", 'The expected system configuration is displayed.');
$this->drupalGet('admin/config/development/configuration/single/export/date_format');
$this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Date format'), 'The date format entity type is selected when specified in the URL.');
$this->drupalGet('admin/config/development/configuration/single/export/date_format/fallback');
$this->assertFieldByXPath('//select[@name="config_name"]//option[@selected="selected"]', t('Fallback date format (fallback)'), 'The fallback date format config entity is selected when specified in the URL.');
$fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback');
$yaml_text = (string) $this->xpath('//textarea[@name="export"]')[0];
$this->assertEqual(Yaml::decode($yaml_text), $fallback_date->toArray(), 'The fallback date format config entity export code is displayed.');
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests language-negotiation overrides are not on language-negotiation form.
*
* @group config
* @see \Drupal\Core\Form\ConfigFormBase
*/
class LanguageNegotiationFormOverrideTest extends WebTestBase {
public static $modules = array('language', 'locale', 'locale_test');
/**
* Tests that overrides do not affect language-negotiation form values.
*/
public function testFormWithOverride() {
$this->drupalLogin($this->rootUser);
$overridden_value_en = 'whatever';
$overridden_value_es = 'loquesea';
// Set up an override.
$settings['config']['language.negotiation']['url']['prefixes'] = (object) array(
'value' => array('en' => $overridden_value_en, 'es' => $overridden_value_es),
'required' => TRUE,
);
$this->writeSettings($settings);
// Add predefined language.
$edit = array(
'predefined_langcode' => 'es',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Overridden string for language-negotiation should not exist in the form.
$this->drupalGet('admin/config/regional/language/detection/url');
// The language-negotiation form should be found.
$this->assertText('Path prefix configuration', 'Language-negotiation form found for English.');
// The English override should not be found.
$this->assertNoFieldByName('prefix[en]', $overridden_value_en, 'Language-negotiation config override not found in English.');
// Now check the Spanish version of the page for the same thing.
$this->drupalGet($overridden_value_es . '/admin/config/regional/language/detection/url');
// The language-negotiation form should be found.
$this->assertText('Path prefix configuration', 'Language-negotiation form found for Spanish using the overridden prefix.');
// The Spanish override should not be found.
$this->assertNoFieldByName('prefix[es]', $overridden_value_es, 'Language-negotiation config override not found in Spanish.');
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Config\Schema\SchemaCheckTrait;
use Drupal\Component\Utility\SafeMarkup;
/**
* Provides a class for checking configuration schema.
*/
trait SchemaCheckTestTrait {
use SchemaCheckTrait;
/**
* Asserts the TypedConfigManager has a valid schema for the configuration.
*
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The TypedConfigManager.
* @param string $config_name
* The configuration name.
* @param array $config_data
* The configuration data.
*/
public function assertConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data) {
$errors = $this->checkConfigSchema($typed_config, $config_name, $config_data);
if ($errors === FALSE) {
// @todo Since the use of this trait is under TestBase, it works.
// Can be fixed as part of https://www.drupal.org/node/2260053.
$this->fail(SafeMarkup::format('No schema for @config_name', array('@config_name' => $config_name)));
return;
}
elseif ($errors === TRUE) {
// @todo Since the use of this trait is under TestBase, it works.
// Can be fixed as part of https://www.drupal.org/node/2260053.
$this->pass(SafeMarkup::format('Schema found for @config_name and values comply with schema.', array('@config_name' => $config_name)));
}
else {
foreach ($errors as $key => $error) {
// @todo Since the use of this trait is under TestBase, it works.
// Can be fixed as part of https://www.drupal.org/node/2260053.
$this->fail(SafeMarkup::format('Schema key @key failed with: @error', array('@key' => $key, '@error' => $error)));
}
}
}
/**
* Asserts configuration, specified by name, has a valid schema.
*
* @param string $config_name
* The configuration name.
*/
public function assertConfigSchemaByName($config_name) {
$config = $this->config($config_name);
$this->assertConfigSchema(\Drupal::service('config.typed'), $config->getName(), $config->get());
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\config\Tests;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\simpletest\WebTestBase;
/**
* Tests the functionality of ConfigSchemaChecker in WebTestBase tests.
*
* @group config
*/
class SchemaConfigListenerWebTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('config_test');
/**
* Tests \Drupal\Core\Config\Testing\ConfigSchemaChecker.
*/
public function testConfigSchemaChecker() {
$this->drupalLogin($this->drupalCreateUser(['administer site configuration']));
// Test a non-existing schema.
$msg = 'Expected SchemaIncompleteException thrown';
try {
$this->config('config_schema_test.schemaless')->set('foo', 'bar')->save();
$this->fail($msg);
}
catch (SchemaIncompleteException $e) {
$this->pass($msg);
$this->assertEqual('No schema for config_schema_test.schemaless', $e->getMessage());
}
// Test a valid schema.
$msg = 'Unexpected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')->set('int', 10);
try {
$config->save();
$this->pass($msg);
}
catch (SchemaIncompleteException $e) {
$this->fail($msg);
}
// Test an invalid schema.
$msg = 'Expected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')
->set('foo', 'bar')
->set('array', 1);
try {
$config->save();
$this->fail($msg);
}
catch (SchemaIncompleteException $e) {
$this->pass($msg);
$this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence, config_test.types:foo missing schema', $e->getMessage());
}
// Test that the config event listener is working in the child site.
$this->drupalGet('config_test/schema_listener');
$this->assertText('No schema for config_schema_test.schemaless');
}
}

View file

@ -0,0 +1,7 @@
# Clashes with default configuration provided by the config_test module.
id: dotted.default
label: 'Config install fail'
weight: 0
protected_property: Default
# Intentionally commented out to verify default status behavior.
# status: 1

View file

@ -0,0 +1,2 @@
# Clashes with default configuration provided by the config_test module.
label: 'Je suis'

View file

@ -0,0 +1,10 @@
name: 'Test theme for configuration clash detection'
type: theme
description: 'Test theme for configuration clash detection'
version: VERSION
base theme: classy
core: 8.x
regions:
content: Content
left: Left
right: Right

View file

@ -0,0 +1,9 @@
# This should contain a copy of the configuration from the
# config_collection_install_test module.
name: 'Config collection clash test module'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- config_collection_install_test

View file

@ -0,0 +1,7 @@
config_collection_install_test.test:
type: config_object
label: 'Collection test'
mapping:
collection:
type: string
label: 'Collection'

View file

@ -0,0 +1,5 @@
name: 'Configuration events test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
services:
config_events_test.event_subscriber:
class: Drupal\config_collection_install_test\EventSubscriber
arguments: ['@state']
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\config_collection_install_test;
use Drupal\Core\Config\ConfigCollectionInfo;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\State\StateInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EventSubscriber implements EventSubscriberInterface {
/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs the Event Subscriber object.
*
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* Reacts to the ConfigEvents::COLLECTION_INFO event.
*
* @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info
* The configuration collection info event.
*/
public function addCollections(ConfigCollectionInfo $collection_info) {
$collections = $this->state->get('config_collection_install_test.collection_names', array());
foreach ($collections as $collection) {
$collection_info->addCollection($collection);
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[ConfigEvents::COLLECTION_INFO][] = array('addCollections');
return $events;
}
}

View file

@ -0,0 +1,5 @@
name: 'Configuration entity static cache test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Provides configuration entity static cache test helpers.
*/
use Drupal\Component\Utility\Random;
/**
* Implements hook_ENTITY_TYPE_load() for 'static_cache_test_config_test'.
*/
function config_entity_static_cache_test_config_test_load($entities) {
static $random;
if (!$random) {
$random = new Random();
}
foreach ($entities as $entity) {
// Add a random stamp for every load(), so that during tests, we can tell
// if an entity was retrieved from cache (unchanged stamp) or reloaded.
$entity->_loadStamp = $random->string(8, TRUE);
}
}
/**
* Implements hook_entity_type_alter().
*/
function config_entity_static_cache_test_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['config_test']->set('static_cache', TRUE);
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\config_entity_static_cache_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Tests module overrides for configuration.
*/
class ConfigOverrider implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
return array(
'config_test.dynamic.test_1' => array(
'label' => 'Overridden label',
)
);
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'config_entity_static_cache_test';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View file

@ -0,0 +1,7 @@
config_events_test.test:
type: config_object
label: 'Configuration events test'
mapping:
key:
type: string
label: 'Value'

View file

@ -0,0 +1,5 @@
name: 'Configuration events test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
services:
config_events_test.event_subscriber:
class: Drupal\config_events_test\EventSubscriber
arguments: ['@state']
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\config_events_test;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\State\StateInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EventSubscriber implements EventSubscriberInterface {
/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs the Event Subscriber object.
*
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* Reacts to config event.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
* The configuration event.
* @param string $name
* The event name.
*/
public function configEventRecorder(ConfigCrudEvent $event, $name) {
$config = $event->getConfig();
$this->state->set('config_events_test.event', array(
'event_name' => $name,
'current_config_data' => $config->get(),
'original_config_data' => $config->getOriginal(),
'raw_config_data' => $config->getRawData()
));
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = array('configEventRecorder');
$events[ConfigEvents::DELETE][] = array('configEventRecorder');
$events[ConfigEvents::RENAME][] = array('configEventRecorder');
return $events;
}
}

View file

@ -0,0 +1,5 @@
name: 'Configuration import test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Provides configuration import test helpers.
*/
/**
* Implements hook_config_import_steps_alter().
*/
function config_import_test_config_import_steps_alter(&$sync_steps) {
$sync_steps[] = '_config_import_test_config_import_steps_alter';
}
/**
* Implements configuration synchronization step added by an alter for testing.
*
* @param array $context
* The batch context.
*/
function _config_import_test_config_import_steps_alter(&$context) {
$GLOBALS['hook_config_test']['config_import_steps_alter'] = TRUE;
$context['finished'] = 1;
return;
}

View file

@ -0,0 +1,6 @@
services:
config_import_test.event_subscriber:
class: Drupal\config_import_test\EventSubscriber
tags:
- { name: event_subscriber }
arguments: ['@state']

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\config_import_test;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\ConfigImporterEvent;
use Drupal\Core\Config\Importer\MissingContentEvent;
use Drupal\Core\State\StateInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Config import subscriber for config import events.
*/
class EventSubscriber implements EventSubscriberInterface {
/**
* The key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs the event subscriber.
*
* @param \Drupal\Core\State\StateInterface $state
* The key value store.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* Validates the configuration to be imported.
*
* @param \Drupal\Core\Config\ConfigImporterEvent $event
* The Event to process.
*
* @throws \Drupal\Core\Config\ConfigNameException
*/
public function onConfigImporterValidate(ConfigImporterEvent $event) {
if ($this->state->get('config_import_test.config_import_validate_fail', FALSE)) {
// Log more than one error to test multiple validation errors.
$event->getConfigImporter()->logError('Config import validate error 1.');
$event->getConfigImporter()->logError('Config import validate error 2.');
}
}
/**
* Handles the missing content event.
*
* @param \Drupal\Core\Config\Importer\MissingContentEvent $event
* The missing content event.
*/
public function onConfigImporterMissingContentOne(MissingContentEvent $event) {
if ($this->state->get('config_import_test.config_import_missing_content', FALSE) && $this->state->get('config_import_test.config_import_missing_content_one', FALSE) === FALSE) {
$missing = $event->getMissingContent();
$uuid = key($missing);
$this->state->set('config_import_test.config_import_missing_content_one', key($missing));
$event->resolveMissingContent($uuid);
// Stopping propagation ensures that onConfigImporterMissingContentTwo
// will be fired on the next batch step.
$event->stopPropagation();
}
}
/**
* Handles the missing content event.
*
* @param \Drupal\Core\Config\Importer\MissingContentEvent $event
* The missing content event.
*/
public function onConfigImporterMissingContentTwo(MissingContentEvent $event) {
if ($this->state->get('config_import_test.config_import_missing_content', FALSE) && $this->state->get('config_import_test.config_import_missing_content_two', FALSE) === FALSE) {
$missing = $event->getMissingContent();
$uuid = key($missing);
$this->state->set('config_import_test.config_import_missing_content_two', key($missing));
$event->resolveMissingContent($uuid);
}
}
/**
* Reacts to a config save and records information in state for testing.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
*/
public function onConfigSave(ConfigCrudEvent $event) {
$config = $event->getConfig();
if ($config->getName() == 'action.settings') {
$values = $this->state->get('ConfigImportUITest.action.settings.recursion_limit', array());
$values[] = $config->get('recursion_limit');
$this->state->set('ConfigImportUITest.action.settings.recursion_limit', $values);
}
if ($config->getName() == 'core.extension') {
$installed = $this->state->get('ConfigImportUITest.core.extension.modules_installed', array());
$uninstalled = $this->state->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
$original = $config->getOriginal('module');
$data = $config->get('module');
$install = array_diff_key($data, $original);
if (!empty($install)) {
$installed[] = key($install);
}
$uninstall = array_diff_key($original, $data);
if (!empty($uninstall)) {
$uninstalled[] = key($uninstall);
}
$this->state->set('ConfigImportUITest.core.extension.modules_installed', $installed);
$this->state->set('ConfigImportUITest.core.extension.modules_uninstalled', $uninstalled);
}
}
/**
* Reacts to a config delete and records information in state for testing.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
*/
public function onConfigDelete(ConfigCrudEvent $event) {
$config = $event->getConfig();
if ($config->getName() == 'action.settings') {
$value = $this->state->get('ConfigImportUITest.action.settings.delete', 0);
$this->state->set('ConfigImportUITest.action.settings.delete', $value + 1);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = array('onConfigSave', 40);
$events[ConfigEvents::DELETE][] = array('onConfigDelete', 40);
$events[ConfigEvents::IMPORT_VALIDATE] = array('onConfigImporterValidate');
$events[ConfigEvents::IMPORT_MISSING_CONTENT] = array(array('onConfigImporterMissingContentOne'), array('onConfigImporterMissingContentTwo', -100));
return $events;
}
}

View file

@ -0,0 +1,11 @@
id: other_module_test_with_dependency
label: 'Other module test with dependency'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
module:
- config_other_module_config_test

View file

@ -0,0 +1,5 @@
name: 'Config install dependency test'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Provides hook implementations for testing purposes.
*/
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_ENTITY_TYPE_create.
*/
function config_install_dependency_test_config_test_create(EntityInterface $entity) {
// Add an enforced dependency on this module so that we can test if this is
// possible during module installation.
$entity->setEnforcedDependencies(['module' => ['config_install_dependency_test']]);
}

View file

@ -0,0 +1,7 @@
# Clashes with default configuration provided by the config_test module.
id: dotted.default
label: 'Config install fail'
weight: 0
protected_property: Default
# Intentionally commented out to verify default status behavior.
# status: 1

View file

@ -0,0 +1,2 @@
# Clashes with default configuration provided by the config_test module.
label: 'Je suis'

View file

@ -0,0 +1,7 @@
name: 'Configuration install fail test'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- config_test

View file

@ -0,0 +1,2 @@
id: config_integration_test
label: 'Default integration config label'

View file

@ -0,0 +1,9 @@
# Schema for the configuration files of the Configuration Integration Test module.
config_integration_test.settings:
type: config_object
label: 'Configuration integration test settings'
mapping:
foo:
type: string
label: 'Foo'

View file

@ -0,0 +1,7 @@
name: 'ConfigTest integration'
type: module
package: 'Testing'
version: VERSION
core: 8.x
dependencies:
- config_test

View file

@ -0,0 +1,7 @@
id: other_module_test
label: 'Other module test'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default

View file

@ -0,0 +1,11 @@
id: other_module_test_optional_entity_unmet
label: 'Other module test to test optional config installation'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
config:
- config_test.dynamic.other_module_test_unmet

View file

@ -0,0 +1,11 @@
id: other_module_test_unmet
label: 'Other module test to test optional config installation'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
module:
- config_install_dependency_test

View file

@ -0,0 +1,5 @@
config_other_module_config_test.weird_simple_config:
type: config_object
mapping:
foo:
type: string

View file

@ -0,0 +1,5 @@
name: 'Config other module config'
type: module
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,24 @@
id: config_override_test
theme: classy
weight: 0
status: true
langcode: en
region: content
plugin: test_cache
settings:
label: 'Test HTML block'
provider: block_test
label_display: visible
status: true
info: ''
view_mode: default
dependencies:
module:
- block_test
theme:
- classy
visibility:
request_path:
id: request_path
pages: ''
negate: false

View file

@ -0,0 +1,9 @@
name: 'Configuration override integration test'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- block
- block_test

View file

@ -0,0 +1,9 @@
services:
cache_context.config_override_integration_test:
class: Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext
tags:
- { name: cache.context }
config_override_integration_test.config_override:
class: Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride
tags:
- { name: config.factory.override }

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\config_override_integration_test\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
/**
* A cache context service intended for the config override integration test.
*
* Cache context ID: 'config_override_integration_test'.
*/
class ConfigOverrideIntegrationTestCacheContext implements CacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Config override integration test');
}
/**
* {@inheritdoc}
*/
public function getContext() {
// Default to the 'disabled' state.
$state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE) ? 'yes' : 'no';
return 'config_override_integration_test.' . $state;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
// Since this depends on State this can change at any time and is not
// cacheable.
$metadata = new CacheableMetadata();
$metadata->setCacheMaxAge(0);
return $metadata;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\config_override_integration_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Test implementation of a config override that provides cacheability metadata.
*/
class CacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = [];
// Override the test block depending on the state set in the test.
$state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE);
if (in_array('block.block.config_override_test', $names) && $state !== FALSE) {
$overrides = $overrides + [
'block.block.config_override_test' => [
'settings' => ['label' => 'Overridden block label'],
],
];
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'config_override_integration_test';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
if ($name === 'block.block.config_override_test') {
$metadata
->setCacheContexts(['config_override_integration_test'])
->setCacheTags(['config_override_integration_test_tag']);
}
return $metadata;
}
}

View file

@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
module:
- block_content
theme:
- classy
id: call_to_action
theme: classy
region: content
weight: null
provider: null
plugin: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
settings:
id: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
label: 'Shop for cheap now!'
provider: block_content
label_display: visible
status: true
info: ''
view_mode: default
visibility:
request_path:
id: request_path
pages: ''
negate: false

View file

@ -0,0 +1,4 @@
threshold:
requirements_warning: 172800
requirements_error: 1209600

View file

@ -0,0 +1,9 @@
name: 'Configuration override test'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- block
- block_content

View file

@ -0,0 +1,17 @@
services:
cache_context.pirate_day:
class: Drupal\config_override_test\Cache\PirateDayCacheContext
tags:
- { name: cache.context }
config_override_test.overrider:
class: Drupal\config_override_test\ConfigOverrider
tags:
- { name: config.factory.override}
config_override_test.low_priority_overrider:
class: Drupal\config_override_test\ConfigOverriderLowPriority
tags:
- { name: config.factory.override, priority: -100 }
config_override_test.pirate_day_cacheability_metadata_override:
class: Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride
tags:
- { name: config.factory.override }

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\config_override_test\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
/**
* Defines the PirateDayCacheContext service that allows to cache the booty.
*
* Cache context ID: 'pirate_day'.
*/
class PirateDayCacheContext implements CacheContextInterface {
/**
* The length of Pirate Day. It lasts 24 hours.
*
* This is a simplified test implementation. In a real life Pirate Day module
* this data wouldn't be defined in a constant, but calculated in a static
* method. If it were Pirate Day it should return the number of seconds until
* midnight, and on all other days it should return the number of seconds
* until the start of the next Pirate Day.
*/
const PIRATE_DAY_MAX_AGE = 86400;
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Pirate day');
}
/**
* {@inheritdoc}
*/
public function getContext() {
$is_pirate_day = static::isPirateDay() ? 'yarr' : 'nay';
return "pirate_day." . $is_pirate_day;
}
/**
* Returns whether or not it is Pirate Day.
*
* To ease testing this is determined with a global variable rather than using
* the traditional compass and sextant.
*
* @return bool
* Returns TRUE if it is Pirate Day today.
*/
public static function isPirateDay() {
return !empty($GLOBALS['it_is_pirate_day']);
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\config_override_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
/**
* Tests module overrides for configuration.
*/
class ConfigOverrider implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = array();
if (!empty($GLOBALS['config_test_run_module_overrides'])) {
if (in_array('system.site', $names)) {
$overrides = $overrides + array('system.site' => array('name' => 'ZOMG overridden site name'));
}
if (in_array('config_override_test.new', $names)) {
$overrides = $overrides + array('config_override_test.new' => array('module' => 'override'));
}
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigOverrider';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\config_override_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Tests module overrides for configuration.
*/
class ConfigOverriderLowPriority implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = array();
if (!empty($GLOBALS['config_test_run_module_overrides'])) {
if (in_array('system.site', $names)) {
$overrides = array('system.site' =>
array(
'name' => 'Should not apply because of higher priority listener',
// This override should apply because it is not overridden by the
// higher priority listener.
'slogan' => 'Yay for overrides!',
)
);
}
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigOverriderLowPriority';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\config_override_test;
use Drupal\config_override_test\Cache\PirateDayCacheContext;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Test implementation of a config override that provides cacheability metadata.
*/
class PirateDayCacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = [];
// Override the theme and the 'call_to_action' block on Pirate Day.
if (PirateDayCacheContext::isPirateDay()) {
if (in_array('system.theme', $names)) {
$overrides = $overrides + ['system.theme' => ['default' => 'pirate']];
}
if (in_array('block.block.call_to_action', $names)) {
$overrides = $overrides + [
'block.block.call_to_action' => [
'settings' => ['label' => 'Draw yer cutlasses!'],
],
];
}
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'PirateDayConfigOverrider';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
$metadata
->setCacheContexts(['pirate_day'])
->setCacheTags(['pirate-day-tag'])
->setCacheMaxAge(PirateDayCacheContext::PIRATE_DAY_MAX_AGE);
return $metadata;
}
/**
* Returns whether or not our overrides are potentially applicable.
*
* @param string $name
* The name of the config object that is being constructed.
*
* @return bool
* TRUE if the merchant ship will be boarded. FALSE if we drink rum instead.
*/
protected function isCacheabilityMetadataApplicable($name) {
return in_array($name, ['system.theme', 'block.block.call_to_action']);
}
}

View file

@ -0,0 +1,9 @@
label: 'Label string'
irrelevant: 123
indescribable:
abc: 789
def:
- 456
- 'abc'
xyz: 13.4
weight: 27

View file

@ -0,0 +1,4 @@
testitem: "Whatever structure there is in this file"
testlist:
- "the main file has no schema, so individual items"
- "will not have any schema information."

View file

@ -0,0 +1,30 @@
tests:
-
plugin_id: boolean
value: TRUE
-
plugin_id: boolean:derivative
value: TRUE
-
plugin_id: string
value: 'Foo'
-
plugin_id: string:derivative
value: 'Foo'
test_with_parents:
-
plugin_id: boolean
settings:
value: TRUE
-
plugin_id: boolean:derivative
settings:
value: TRUE
-
plugin_id: string
settings:
value: 'Foo'
-
plugin_id: string:derivative
settings:
value: 'Foo'

View file

@ -0,0 +1,2 @@
testid: 'Test id'
testdescription: 'Test description'

Some files were not shown because too many files have changed in this diff Show more