diff --git a/composer.json b/composer.json
index 727e031..c55a9a3 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
"wikimedia/composer-merge-plugin": "~1.3"
},
"replace": {
- "drupal/core": "~8.2"
+ "drupal/core": "~8.3"
},
"minimum-stability": "dev",
"prefer-stable": true,
diff --git a/composer.lock b/composer.lock
index 8d2b325..d1a3af0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "7d101b08e5ae002d827cd42ae9a4e344",
- "content-hash": "60f7057617c6d995bf9946d0b12f0b5d",
+ "hash": "64b08387a4402f685cc35b1ad9197380",
+ "content-hash": "0e7de9d6c3256344615aad4b059a850a",
"packages": [
{
"name": "asm89/stack-cors",
diff --git a/core/core.services.yml b/core/core.services.yml
index 6310c21..4872750 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -298,7 +298,7 @@ services:
- { name: event_subscriber }
config.installer:
class: Drupal\Core\Config\ConfigInstaller
- arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher']
+ arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '%install_profile%']
lazy: true
config.storage:
class: Drupal\Core\Config\CachedStorage
@@ -323,7 +323,7 @@ services:
- { name: backend_overridable }
config.storage.schema:
class: Drupal\Core\Config\ExtensionInstallStorage
- arguments: ['@config.storage', 'config/schema']
+ arguments: ['@config.storage', '%install_profile%', 'config/schema']
config.typed:
class: Drupal\Core\Config\TypedConfigManager
arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index ec12097..897e54e 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -12,7 +12,6 @@
use Drupal\Core\Render\Markup;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Site\Settings;
use Drupal\Core\Utility\Error;
use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -716,31 +715,19 @@ function drupal_installation_attempted() {
* When this function is called during Drupal's initial installation process,
* the name of the profile that's about to be installed is stored in the global
* installation state. At all other times, the "install_profile" setting will be
- * available in settings.php.
+ * available in settings.php, or declared as a Distribution.
*
* @return string|null $profile
* The name of the installation profile or NULL if no installation profile is
* currently active. This is the case for example during the first steps of
* the installer or during unit tests.
+ *
+ * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0.
+ * Use the install_profile container parameter or \Drupal::installProfile()
+ * instead.
*/
function drupal_get_profile() {
- global $install_state;
-
- if (drupal_installation_attempted()) {
- // If the profile has been selected return it.
- if (isset($install_state['parameters']['profile'])) {
- $profile = $install_state['parameters']['profile'];
- }
- else {
- $profile = NULL;
- }
- }
- else {
- // Fall back to NULL, if there is no 'install_profile' setting.
- $profile = Settings::get('install_profile');
- }
-
- return $profile;
+ return \Drupal::installProfile();
}
/**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 1019270..6e9636f 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -13,6 +13,7 @@
use Drupal\Core\Installer\Exception\AlreadyInstalledException;
use Drupal\Core\Installer\Exception\InstallerException;
use Drupal\Core\Installer\Exception\NoProfilesException;
+use Drupal\Core\Installer\Exception\TooManyDistributionsException;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager;
@@ -1212,13 +1213,16 @@ function _install_select_profile(&$install_state) {
return $profile;
}
}
- // Check for a distribution profile.
- foreach ($install_state['profiles'] as $profile) {
- $profile_info = install_profile_info($profile->getName());
- if (!empty($profile_info['distribution'])) {
- return $profile->getName();
+ // Check for a distribution. If the site has more than one distribution force
+ // the user to choose which will ensure that settings.php has to be written.
+ try {
+ if ($distribution = \Drupal::service('kernel')->getDistribution()) {
+ return $distribution;
}
}
+ catch (TooManyDistributionsException $e) {
+ // The user must choose.
+ }
// Get all visible (not hidden) profiles.
$visible_profiles = array_filter($install_state['profiles'], function ($profile) {
@@ -2170,13 +2174,34 @@ function install_display_requirements($install_state, $requirements) {
}
/**
- * Installation task; ensures install profile is written to settings.php.
+ * Installation task; writes profile to settings.php (absent a distribution).
*
* @param array $install_state
* An array of information about the current installation state.
+ *
+ * @see _install_select_profile()
*/
function install_write_profile($install_state) {
- if (Settings::get('install_profile') !== $install_state['parameters']['profile']) {
+ $settings_value = Settings::get('install_profile');
+ // We need to write to settings.php if the value in settings.php does not
+ // equal the selected profile.
+ $need_to_write = $settings_value !== $install_state['parameters']['profile'];
+ // However, if we're dealing with a distribution and the profile is not
+ // writable do not write the value to settings.php if the current value is not
+ // set.
+ $distribution = FALSE;
+ try {
+ $distribution = \Drupal::service('kernel')->getDistribution();
+ }
+ catch (TooManyDistributionsException $e) {
+ // The user will have chosen.
+ }
+
+ if ($settings_value == '' && $distribution && !is_writable(\Drupal::service('site.path') . '/settings.php')) {
+ $need_to_write = FALSE;
+ }
+
+ if ($need_to_write) {
// Remember the profile which was used.
$settings['settings']['install_profile'] = (object) array(
'value' => $install_state['parameters']['profile'],
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 3a9c2bc..6a4070c 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -334,7 +334,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
}
// Write the new settings file.
- if (file_put_contents($settings_file, $buffer) === FALSE) {
+ if (@file_put_contents($settings_file, $buffer) === FALSE) {
throw new Exception(t('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
}
else {
@@ -621,6 +621,12 @@ function drupal_install_system($install_state) {
// Install base system configuration.
\Drupal::service('config.installer')->installDefaultConfig('core', 'core');
+ // Ensure to also store the installation profile in configuration for later
+ // use.
+ \Drupal::configFactory()->getEditable('core.extension')
+ ->set('profile', $install_state['parameters']['profile'])
+ ->save();
+
// Install System module and rebuild the newly available routes.
$kernel->getContainer()->get('module_installer')->install(array('system'), FALSE);
\Drupal::service('router.builder')->rebuild();
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 5d1588d..b7865b3 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -5,6 +5,7 @@
* Contains \Drupal.
*/
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\DependencyInjection\ContainerNotInitializedException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Url;
@@ -81,7 +82,7 @@ class Drupal {
/**
* The current system version.
*/
- const VERSION = '8.2.0-dev';
+ const VERSION = '8.3.0-dev';
/**
* Core API compatibility.
@@ -182,6 +183,22 @@ public static function root() {
}
/**
+ * Gets the active install profile.
+ *
+ * @return string|null
+ * The name of the any active install profile or distribution.
+ */
+ public static function installProfile() {
+ if (static::hasContainer()) {
+ return static::getContainer()->getParameter('install_profile');
+ }
+ else {
+ $config_storage = BootstrapConfigStorageFactory::getDatabaseStorage();
+ return $config_storage->read('core.extension')['profile'];
+ }
+ }
+
+ /**
* Indicates if there is a currently active request object.
*
* @return bool
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 97c3688..329592a 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -6,7 +6,6 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\Config\Entity\ConfigEntityDependency;
-use Drupal\Core\Site\Settings;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ConfigInstaller implements ConfigInstallerInterface {
@@ -61,6 +60,13 @@ class ConfigInstaller implements ConfigInstallerInterface {
protected $isSyncing = FALSE;
/**
+ * The name of the currently active installation profile.
+ *
+ * @var string
+ */
+ protected $installProfile;
+
+ /**
* Constructs the configuration installer.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
@@ -73,13 +79,16 @@ class ConfigInstaller implements ConfigInstallerInterface {
* The configuration manager.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
+ * @param string $install_profile
+ * The name of the currently active installation profile.
*/
- public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) {
+ public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) {
$this->configFactory = $config_factory;
$this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
$this->typedConfig = $typed_config;
$this->configManager = $config_manager;
$this->eventDispatcher = $event_dispatcher;
+ $this->installProfile = $install_profile;
}
/**
@@ -140,7 +149,7 @@ public function installDefaultConfig($type, $name) {
// Install any optional configuration entities whose dependencies can now
// be met. This searches all the installed modules config/optional
// directories.
- $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE);
+ $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE);
$this->installOptionalConfig($storage, [$type => $name]);
}
@@ -156,7 +165,7 @@ public function installOptionalConfig(StorageInterface $storage = NULL, $depende
$optional_profile_config = [];
if (!$storage) {
// Search the install profile's optional configuration too.
- $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
+ $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
// The extension install storage ensures that overrides are used.
$profile_storage = NULL;
}
@@ -331,7 +340,7 @@ protected function createConfiguration($collection, array $config_to_create) {
* {@inheritdoc}
*/
public function installCollectionDefaultConfig($collection) {
- $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted());
+ $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted());
// Only install configuration for enabled extensions.
$enabled_extensions = $this->getEnabledExtensions();
$config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
@@ -644,9 +653,7 @@ protected function drupalGetPath($type, $name) {
* of the installer or during unit tests.
*/
protected function drupalGetProfile() {
- // Settings is safe to use because settings.php is written before any module
- // is installed.
- return Settings::get('install_profile');
+ return $this->installProfile;
}
/**
diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
index 14e80dd..96e1e38 100644
--- a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
+++ b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
@@ -2,7 +2,6 @@
namespace Drupal\Core\Config;
-use Drupal\Core\Site\Settings;
use Drupal\Core\Extension\ExtensionDiscovery;
/**
@@ -28,11 +27,20 @@ class ExtensionInstallStorage extends InstallStorage {
protected $includeProfile = TRUE;
/**
+ * The name of the currently active installation profile.
+ *
+ * @var string
+ */
+ protected $installProfile;
+
+ /**
* Overrides \Drupal\Core\Config\InstallStorage::__construct().
*
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The active configuration store where the list of enabled modules and
* themes is stored.
+ * @param string $profile
+ * The current installation profile.
* @param string $directory
* The directory to scan in each extension to scan for files. Defaults to
* 'config/install'.
@@ -43,10 +51,12 @@ class ExtensionInstallStorage extends InstallStorage {
* (optional) Whether to include the install profile in extensions to
* search and to get overrides from.
*/
- public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE) {
+ public function __construct(StorageInterface $config_storage, $profile, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE) {
parent::__construct($directory, $collection);
+
$this->configStorage = $config_storage;
$this->includeProfile = $include_profile;
+ $this->installProfile = $profile;
}
/**
@@ -77,22 +87,20 @@ protected function getAllFolders() {
$this->folders = array();
$this->folders += $this->getCoreNames();
- $install_profile = Settings::get('install_profile');
- $profile = drupal_get_profile();
$extensions = $this->configStorage->read('core.extension');
// @todo Remove this scan as part of https://www.drupal.org/node/2186491
$listing = new ExtensionDiscovery(\Drupal::root());
if (!empty($extensions['module'])) {
$modules = $extensions['module'];
// Remove the install profile as this is handled later.
- unset($modules[$install_profile]);
+ unset($modules[$this->installProfile]);
$profile_list = $listing->scan('profile');
- if ($profile && isset($profile_list[$profile])) {
+ if ($this->installProfile && isset($profile_list[$this->installProfile])) {
// Prime the drupal_get_filename() static cache with the profile info
// file location so we can use drupal_get_path() on the active profile
// during the module scan.
// @todo Remove as part of https://www.drupal.org/node/2186491
- drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
+ drupal_get_filename('profile', $this->installProfile, $profile_list[$this->installProfile]->getPathname());
}
$module_list_scan = $listing->scan('module');
$module_list = array();
@@ -117,12 +125,12 @@ protected function getAllFolders() {
// The install profile can override module default configuration. We do
// this by replacing the config file path from the module/theme with the
// install profile version if there are any duplicates.
- if (isset($profile)) {
+ if ($this->installProfile) {
if (!isset($profile_list)) {
$profile_list = $listing->scan('profile');
}
- if (isset($profile_list[$profile])) {
- $profile_folders = $this->getComponentNames(array($profile_list[$profile]));
+ if (isset($profile_list[$this->installProfile])) {
+ $profile_folders = $this->getComponentNames(array($profile_list[$this->installProfile]));
$this->folders = $profile_folders + $this->folders;
}
}
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 3ccb2d7..f1fc596 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -14,8 +14,10 @@
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\YamlFileLoader;
use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\InfoParser;
use Drupal\Core\File\MimeType\MimeTypeGuesser;
use Drupal\Core\Http\TrustedHostsRequestFactory;
+use Drupal\Core\Installer\Exception\TooManyDistributionsException;
use Drupal\Core\Language\Language;
use Drupal\Core\Site\Settings;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -1153,6 +1155,7 @@ protected function compileContainer() {
$container = $this->getContainerBuilder();
$container->set('kernel', $this);
$container->setParameter('container.modules', $this->getModulesParameter());
+ $container->setParameter('install_profile', $this->getInstallProfile());
// Get a list of namespaces and put it onto the container.
$namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames());
@@ -1503,4 +1506,63 @@ protected function addServiceFiles(array $service_yamls) {
$this->serviceYamls['site'] = array_filter($service_yamls, 'file_exists');
}
+ /**
+ * Gets the active install profile.
+ *
+ * @return string|null
+ * The name of the any active install profile or distribution.
+ */
+ protected function getInstallProfile() {
+ $install_profile = Settings::get('install_profile');
+
+ if (empty($install_profile) && ($config = $this->getConfigStorage()->read('core.extension')) && isset($config['profile'])) {
+ $install_profile = $config['profile'];
+ }
+ elseif (empty($install_profile)) {
+ $install_profile = $this->getDistribution();
+ }
+ return $install_profile;
+ }
+
+ /**
+ * Get the name of any discovered profile that is a distribution.
+ *
+ * Scans the filesystem looking for all installation profiles. Returns the one
+ * that has a 'distribution' entry in its info.yml file. If multiple profiles
+ * are distributions, an exception will be thrown.
+ *
+ * This is used in two places:
+ * 1) During installation, if there is a single distribution, then
+ * the installer will not write the installation profile name
+ * to settings.php.
+ * 2) Whenever DrupalKernel::getInstallProfile() is called, if there
+ * is no installation profile name noted in settings.php, then
+ * it will call this function to determine the distribution
+ * to use.
+ *
+ * @return string|FALSE
+ * The machine name of any discovered distribution. FALSE if there are no
+ * distributions.
+ *
+ * @throws \Drupal\Core\Installer\Exception\TooManyDistributionsException
+ * Thrown when a site has more than one distribution installation profile.
+ */
+ protected function getDistribution() {
+ $listing = new ExtensionDiscovery($this->root);
+ $listing->setProfileDirectories([]);
+ $info_parser = new InfoParser();
+ $distributions = [];
+ foreach ($listing->scan('profile') as $profile) {
+ $info = $info_parser->parse($profile->getPathname());
+ if (!empty($info['distribution'])) {
+ $distributions[] = $profile->getName();
+ }
+ }
+ // There can be only one.
+ if (count($distributions) > 1) {
+ throw new TooManyDistributionsException('A site can only have one distribution, multiple installation profiles discovered: ' . implode(', ', $distributions));
+ }
+ return reset($distributions);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index 0d9283c..69b349a 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -516,4 +516,11 @@ protected function getInfoParser() {
return $this->infoParser;
}
+ /**
+ * Reset the discovered files.
+ */
+ public static function reset() {
+ static::$files = [];
+ }
+
}
diff --git a/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php b/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php
new file mode 100644
index 0000000..d9028db
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Exception/TooManyDistributionsException.php
@@ -0,0 +1,11 @@
+getStorage('block')->loadByProperties(['theme' => $theme]);
foreach ($blocks as $block_id => $block) {
// Disable blocks in invalid regions.
- $region = $block->getRegion();
- if ($region !== BlockInterface::BLOCK_REGION_NONE) {
- if (!empty($region) && !isset($regions[$region]) && $block->status()) {
- drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', ['%info' => $block_id, '%region' => $region]), 'warning');
- $block->disable();
- }
- // Set region to none if not enabled.
- if (!$block->status()) {
- $block->setRegion(BlockInterface::BLOCK_REGION_NONE);
- $block->save();
+ if (!isset($regions[$block->getRegion()])) {
+ if ($block->status()) {
+ drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', ['%info' => $block_id, '%region' => $block->getRegion()]), 'warning');
}
+ $block
+ ->setRegion(system_default_region($theme))
+ ->disable()
+ ->save();
}
}
}
diff --git a/core/modules/block/block.post_update.php b/core/modules/block/block.post_update.php
index f208f65..7bab413 100644
--- a/core/modules/block/block.post_update.php
+++ b/core/modules/block/block.post_update.php
@@ -75,5 +75,12 @@ function block_post_update_disable_blocks_with_missing_contexts() {
}
/**
+ * Disable blocks that are placed into the "disabled" region.
+ */
+function block_post_update_disabled_region_update() {
+ // An empty update will flush caches, forcing block_rebuild() to run.
+}
+
+/**
* @} End of "addtogroup updates-8.0.0-beta".
*/
diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml
index 396bf0d..e12cd8b 100644
--- a/core/modules/block/block.routing.yml
+++ b/core/modules/block/block.routing.yml
@@ -25,6 +25,22 @@ entity.block.edit_form:
requirements:
_entity_access: 'block.update'
+entity.block.enable:
+ path: '/admin/structure/block/manage/{block}/enable'
+ defaults:
+ _controller: '\Drupal\block\Controller\BlockController::performOperation'
+ op: enable
+ requirements:
+ _entity_access: 'block.enable'
+
+entity.block.disable:
+ path: '/admin/structure/block/manage/{block}/disable'
+ defaults:
+ _controller: '\Drupal\block\Controller\BlockController::performOperation'
+ op: disable
+ requirements:
+ _entity_access: 'block.disable'
+
block.admin_display:
path: '/admin/structure/block'
defaults:
diff --git a/core/modules/block/css/block.admin.css b/core/modules/block/css/block.admin.css
index ed12038..7fde3d7 100644
--- a/core/modules/block/css/block.admin.css
+++ b/core/modules/block/css/block.admin.css
@@ -40,3 +40,7 @@ a.block-demo-backlink:hover {
.block-form .form-item-settings-admin-label label:after {
content: ':';
}
+.block-disabled:not(:hover) {
+ background: #fcfcfa;
+ opacity: 0.675;
+}
diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php
index 1b1f427..9b1b027 100644
--- a/core/modules/block/src/BlockForm.php
+++ b/core/modules/block/src/BlockForm.php
@@ -188,7 +188,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#title' => $this->t('Region'),
'#description' => $this->t('Select the region where this block should be displayed.'),
'#default_value' => $region,
- '#empty_value' => BlockInterface::BLOCK_REGION_NONE,
+ '#required' => TRUE,
'#options' => system_region_list($theme, REGIONS_VISIBLE),
'#prefix' => '
',
'#suffix' => '
',
diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php
index bd46671..0056523 100644
--- a/core/modules/block/src/BlockInterface.php
+++ b/core/modules/block/src/BlockInterface.php
@@ -16,6 +16,8 @@
/**
* Denotes that a block is not enabled in any region and should not be shown.
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0.
*/
const BLOCK_REGION_NONE = -1;
diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php
index 3365415..7858d6b 100644
--- a/core/modules/block/src/BlockListBuilder.php
+++ b/core/modules/block/src/BlockListBuilder.php
@@ -156,6 +156,7 @@ protected function buildBlocksForm() {
'weight' => $entity->getWeight(),
'entity' => $entity,
'category' => $definition['category'],
+ 'status' => $entity->status(),
);
}
@@ -186,8 +187,7 @@ protected function buildBlocksForm() {
// Loop over each region and build blocks.
$regions = $this->systemRegionList($this->getThemeName(), REGIONS_VISIBLE);
- $block_regions_with_disabled = $regions + array(BlockInterface::BLOCK_REGION_NONE => $this->t('Disabled', array(), array('context' => 'Plural')));
- foreach ($block_regions_with_disabled as $region => $title) {
+ foreach ($regions as $region => $title) {
$form['#tabledrag'][] = array(
'action' => 'match',
'relationship' => 'sibling',
@@ -214,9 +214,9 @@ protected function buildBlocksForm() {
'#attributes' => array('class' => 'region-title__action'),
)
),
- '#prefix' => $region != BlockInterface::BLOCK_REGION_NONE ? $title : $block_regions_with_disabled[$region],
+ '#prefix' => $title,
'#type' => 'link',
- '#title' => $this->t('Place block in the %region region', ['%region' => $block_regions_with_disabled[$region]]),
+ '#title' => $this->t('Place block in the %region region', ['%region' => $title]),
'#url' => Url::fromRoute('block.admin_library', ['theme' => $this->getThemeName()], ['query' => ['region' => $region]]),
'#wrapper_attributes' => array(
'colspan' => 5,
@@ -255,12 +255,13 @@ protected function buildBlocksForm() {
'class' => array('draggable'),
),
);
+ $form[$entity_id]['#attributes']['class'][] = $info['status'] ? 'block-enabled' : 'block-disabled';
if ($placement && $placement == Html::getClass($entity_id)) {
$form[$entity_id]['#attributes']['class'][] = 'color-success';
$form[$entity_id]['#attributes']['class'][] = 'js-block-placed';
}
$form[$entity_id]['info'] = array(
- '#plain_text' => $info['label'],
+ '#plain_text' => $info['status'] ? $info['label'] : $this->t('@label (disabled)', ['@label' => $info['label']]),
'#wrapper_attributes' => array(
'class' => array('block'),
),
@@ -271,7 +272,7 @@ protected function buildBlocksForm() {
$form[$entity_id]['region-theme']['region'] = array(
'#type' => 'select',
'#default_value' => $region,
- '#empty_value' => BlockInterface::BLOCK_REGION_NONE,
+ '#required' => TRUE,
'#title' => $this->t('Region for @block block', array('@block' => $info['label'])),
'#title_display' => 'invisible',
'#options' => $regions,
@@ -361,12 +362,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$entity_values = $form_state->getValue(array('blocks', $entity_id));
$entity->setWeight($entity_values['weight']);
$entity->setRegion($entity_values['region']);
- if ($entity->getRegion() == BlockInterface::BLOCK_REGION_NONE) {
- $entity->disable();
- }
- else {
- $entity->enable();
- }
$entity->save();
}
drupal_set_message(t('The block settings have been updated.'));
diff --git a/core/modules/block/src/Controller/BlockController.php b/core/modules/block/src/Controller/BlockController.php
index d8f4226..58417d5 100644
--- a/core/modules/block/src/Controller/BlockController.php
+++ b/core/modules/block/src/Controller/BlockController.php
@@ -3,6 +3,7 @@
namespace Drupal\block\Controller;
use Drupal\Component\Utility\Html;
+use Drupal\block\BlockInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -40,6 +41,23 @@ public static function create(ContainerInterface $container) {
}
/**
+ * Calls a method on a block and reloads the listing page.
+ *
+ * @param \Drupal\block\BlockInterface $block
+ * The block being acted upon.
+ * @param string $op
+ * The operation to perform, e.g., 'enable' or 'disable'.
+ *
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ * A redirect back to the listing page.
+ */
+ public function performOperation(BlockInterface $block, $op) {
+ $block->$op()->save();
+ drupal_set_message($this->t('The block settings have been updated.'));
+ return $this->redirect('block.admin_display');
+ }
+
+ /**
* Returns a block theme demo page.
*
* @param string $theme
diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php
index 9cbd30b..63b5205 100644
--- a/core/modules/block/src/Entity/Block.php
+++ b/core/modules/block/src/Entity/Block.php
@@ -28,11 +28,14 @@
* },
* admin_permission = "administer blocks",
* entity_keys = {
- * "id" = "id"
+ * "id" = "id",
+ * "status" = "status"
* },
* links = {
* "delete-form" = "/admin/structure/block/manage/{block}/delete",
- * "edit-form" = "/admin/structure/block/manage/{block}"
+ * "edit-form" = "/admin/structure/block/manage/{block}",
+ * "enable" = "/admin/structure/block/manage/{block}/enable",
+ * "disable" = "/admin/structure/block/manage/{block}/disable",
* },
* config_export = {
* "id",
@@ -70,7 +73,7 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin
*
* @var string
*/
- protected $region = self::BLOCK_REGION_NONE;
+ protected $region;
/**
* The block weight.
@@ -209,13 +212,13 @@ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b)
if ($status !== 0) {
return $status;
}
- // Sort by weight, unless disabled.
- if ($a->getRegion() != static::BLOCK_REGION_NONE) {
- $weight = $a->getWeight() - $b->getWeight();
- if ($weight) {
- return $weight;
- }
+
+ // Sort by weight.
+ $weight = $a->getWeight() - $b->getWeight();
+ if ($weight) {
+ return $weight;
}
+
// Sort by label.
return strcmp($a->label(), $b->label());
}
@@ -327,4 +330,21 @@ public function createDuplicateBlock($new_id = NULL, $new_theme = NULL) {
return $duplicate;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function preSave(EntityStorageInterface $storage) {
+ parent::preSave($storage);
+
+ // Ensure the region is valid to mirror the behavior of block_rebuild().
+ // This is done primarily for backwards compatibility support of
+ // \Drupal\block\BlockInterface::BLOCK_REGION_NONE.
+ $regions = system_region_list($this->theme);
+ if (!isset($regions[$this->region]) && $this->status()) {
+ $this
+ ->setRegion(system_default_region($this->theme))
+ ->disable();
+ }
+ }
+
}
diff --git a/core/modules/block/src/Tests/BlockInvalidRegionTest.php b/core/modules/block/src/Tests/BlockInvalidRegionTest.php
index 438a0c1..99ef06d 100644
--- a/core/modules/block/src/Tests/BlockInvalidRegionTest.php
+++ b/core/modules/block/src/Tests/BlockInvalidRegionTest.php
@@ -37,8 +37,8 @@ protected function setUp() {
function testBlockInInvalidRegion() {
// Enable a test block and place it in an invalid region.
$block = $this->drupalPlaceBlock('test_html');
- $block->setRegion('invalid_region');
- $block->save();
+ \Drupal::configFactory()->getEditable('block.block.' . $block->id())->set('region', 'invalid_region')->save();
+ $block = Block::load($block->id());
$warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block->id(), '%region' => 'invalid_region'));
@@ -51,9 +51,8 @@ function testBlockInInvalidRegion() {
$this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.');
// Place disabled test block in the invalid region of the default theme.
+ \Drupal::configFactory()->getEditable('block.block.' . $block->id())->set('region', 'invalid_region')->save();
$block = Block::load($block->id());
- $block->setRegion('invalid_region');
- $block->save();
// Clear the cache to check if the warning message is not triggered.
$this->drupalPostForm('admin/config/development/performance', array(), 'Clear all caches');
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index 039aa64..41d376a 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -178,6 +178,10 @@ function testBlock() {
// Place page title block to test error messages.
$this->drupalPlaceBlock('page_title_block');
+ // Disable the block.
+ $this->drupalGet('admin/structure/block');
+ $this->clickLink('Disable');
+
// Select the 'Powered by Drupal' block to be configured and moved.
$block = array();
$block['id'] = 'system_powered_by_block';
@@ -199,13 +203,12 @@ function testBlock() {
$this->moveBlockToRegion($block, $region);
}
- // Set the block to the disabled region.
- $edit = array();
- $edit['blocks[' . $block['id'] . '][region]'] = -1;
- $this->drupalPostForm('admin/structure/block', $edit, t('Save blocks'));
+ // Disable the block.
+ $this->drupalGet('admin/structure/block');
+ $this->clickLink('Disable');
// Confirm that the block is now listed as disabled.
- $this->assertText(t('The block settings have been updated.'), 'Block successfully move to disabled region.');
+ $this->assertText(t('The block settings have been updated.'), 'Block successfully moved to disabled region.');
// Confirm that the block instance title and markup are not displayed.
$this->drupalGet('node');
@@ -218,7 +221,7 @@ function testBlock() {
// Test deleting the block from the edit form.
$this->drupalGet('admin/structure/block/manage/' . $block['id']);
$this->clickLink(t('Delete'));
- $this->assertRaw(t('Are you sure you want to delete the block %name?', array('%name' => $block['settings[label]'])));
+ $this->assertRaw(t('Are you sure you want to delete the block @name?', array('@name' => $block['settings[label]'])));
$this->drupalPostForm(NULL, array(), t('Delete'));
$this->assertRaw(t('The block %name has been deleted.', array('%name' => $block['settings[label]'])));
@@ -226,7 +229,7 @@ function testBlock() {
$block = $this->drupalPlaceBlock('system_powered_by_block');
$this->drupalGet('admin/structure/block/manage/' . $block->id(), array('query' => array('destination' => 'admin')));
$this->clickLink(t('Delete'));
- $this->assertRaw(t('Are you sure you want to delete the block %name?', array('%name' => $block->label())));
+ $this->assertRaw(t('Are you sure you want to delete the block @name?', array('@name' => $block->label())));
$this->drupalPostForm(NULL, array(), t('Delete'));
$this->assertRaw(t('The block %name has been deleted.', array('%name' => $block->label())));
$this->assertUrl('admin');
diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php
index ec720e9..35c3b5f 100644
--- a/core/modules/block/src/Tests/BlockUiTest.php
+++ b/core/modules/block/src/Tests/BlockUiTest.php
@@ -257,12 +257,15 @@ public function testMachineNameSuggestion() {
$url = 'admin/structure/block/add/test_block_instantiation/classy';
$this->drupalGet($url);
$this->assertFieldByName('id', 'displaymessage', 'Block form uses raw machine name suggestion when no instance already exists.');
- $this->drupalPostForm($url, array(), 'Save block');
+ $edit = ['region' => 'content'];
+ $this->drupalPostForm($url, $edit, 'Save block');
+ $this->assertText('The block configuration has been saved.');
// Now, check to make sure the form starts by autoincrementing correctly.
$this->drupalGet($url);
$this->assertFieldByName('id', 'displaymessage_2', 'Block form appends _2 to plugin-suggested machine name when an instance already exists.');
- $this->drupalPostForm($url, array(), 'Save block');
+ $this->drupalPostForm($url, $edit, 'Save block');
+ $this->assertText('The block configuration has been saved.');
// And verify that it continues working beyond just the first two.
$this->drupalGet($url);
@@ -292,7 +295,7 @@ public function testBlockPlacementIndicator() {
* Tests if validation errors are passed plugin form to the parent form.
*/
public function testBlockValidateErrors() {
- $this->drupalPostForm('admin/structure/block/add/test_settings_validation/classy', ['settings[digits]' => 'abc'], t('Save block'));
+ $this->drupalPostForm('admin/structure/block/add/test_settings_validation/classy', ['region' => 'content', 'settings[digits]' => 'abc'], t('Save block'));
$arguments = [':message' => 'Only digits are allowed'];
$pattern = '//div[contains(@class,"messages messages--error")]/div[contains(text()[2],:message)]';
diff --git a/core/modules/block/src/Tests/Update/BlockRemoveDisabledRegionUpdateTest.php b/core/modules/block/src/Tests/Update/BlockRemoveDisabledRegionUpdateTest.php
new file mode 100644
index 0000000..60ba24c
--- /dev/null
+++ b/core/modules/block/src/Tests/Update/BlockRemoveDisabledRegionUpdateTest.php
@@ -0,0 +1,57 @@
+databaseDumpFiles = [
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.update-test-block-disabled-2513534.php',
+ ];
+ }
+
+ /**
+ * Tests that block context mapping is updated properly.
+ */
+ public function testUpdateHookN() {
+ $this->runUpdates();
+
+ // Disable maintenance mode.
+ \Drupal::state()->set('system.maintenance_mode', FALSE);
+
+ // We finished updating so we can login the user now.
+ $this->drupalLogin($this->rootUser);
+
+ // Verify that a disabled block is in the default region.
+ $this->drupalGet('admin/structure/block');
+ $element = $this->xpath("//tr[contains(@data-drupal-selector, :block) and contains(@class, :status)]//select/option[@selected and @value=:region]",
+ [':block' => 'edit-blocks-pagetitle-1', ':status' => 'block-disabled', ':region' => 'header']);
+ $this->assertTrue(!empty($element));
+
+ // Verify that an enabled block is now disabled and in the default region.
+ $this->drupalGet('admin/structure/block');
+ $element = $this->xpath("//tr[contains(@data-drupal-selector, :block) and contains(@class, :status)]//select/option[@selected and @value=:region]",
+ [':block' => 'edit-blocks-pagetitle-2', ':status' => 'block-disabled', ':region' => 'header']);
+ $this->assertTrue(!empty($element));
+
+ }
+
+}
diff --git a/core/modules/block/src/Tests/Views/DisplayBlockTest.php b/core/modules/block/src/Tests/Views/DisplayBlockTest.php
index faf68c4..368af68 100644
--- a/core/modules/block/src/Tests/Views/DisplayBlockTest.php
+++ b/core/modules/block/src/Tests/Views/DisplayBlockTest.php
@@ -185,8 +185,10 @@ public function testViewsBlockForm() {
// Test that that machine name field is hidden from display and has been
// saved as expected from the default value.
$this->assertNoFieldById('edit-machine-name', 'views_block__test_view_block_1', 'The machine name is hidden on the views block form.');
+
// Save the block.
- $this->drupalPostForm(NULL, array(), t('Save block'));
+ $edit = ['region' => 'content'];
+ $this->drupalPostForm(NULL, $edit, t('Save block'));
$storage = $this->container->get('entity_type.manager')->getStorage('block');
$block = $storage->load('views_block__test_view_block_block_1');
// This will only return a result if our new block has been created with the
@@ -195,7 +197,7 @@ public function testViewsBlockForm() {
for ($i = 2; $i <= 3; $i++) {
// Place the same block again and make sure we have a new ID.
- $this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, array(), t('Save block'));
+ $this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
$block = $storage->load('views_block__test_view_block_block_1_' . $i);
// This will only return a result if our new block has been created with the
// expected machine name.
@@ -204,7 +206,7 @@ public function testViewsBlockForm() {
// Tests the override capability of items per page.
$this->drupalGet('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme);
- $edit = array();
+ $edit = ['region' => 'content'];
$edit['settings[override][items_per_page]'] = 10;
$this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
@@ -222,7 +224,7 @@ public function testViewsBlockForm() {
$this->assertEqual(5, $config['items_per_page'], "'Items per page' is properly saved.");
// Tests the override of the label capability.
- $edit = array();
+ $edit = ['region' => 'content'];
$edit['settings[views_label_checkbox]'] = 1;
$edit['settings[views_label]'] = 'Custom title';
$this->drupalPostForm('admin/structure/block/add/views_block:test_view_block-block_1/' . $default_theme, $edit, t('Save block'));
diff --git a/core/modules/block/tests/src/Kernel/BlockRebuildTest.php b/core/modules/block/tests/src/Kernel/BlockRebuildTest.php
new file mode 100644
index 0000000..33de12c
--- /dev/null
+++ b/core/modules/block/tests/src/Kernel/BlockRebuildTest.php
@@ -0,0 +1,103 @@
+container->get('theme_installer')->install(['stable', 'classy']);
+ $this->container->get('config.factory')->getEditable('system.theme')->set('default', 'classy')->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ // @todo Once block_rebuild() is refactored to auto-loadable code, remove
+ // this require statement.
+ require_once static::getDrupalRoot() . '/core/modules/block/block.module';
+ }
+
+ /**
+ * @covers ::block_rebuild
+ */
+ public function testRebuildNoBlocks() {
+ block_rebuild();
+ $messages = drupal_get_messages();
+ $this->assertEquals([], $messages);
+ }
+
+ /**
+ * @covers ::block_rebuild
+ */
+ public function testRebuildNoInvalidBlocks() {
+ $this->placeBlock('system_powered_by_block', ['region' => 'content']);
+
+ block_rebuild();
+ $messages = drupal_get_messages();
+ $this->assertEquals([], $messages);
+ }
+
+ /**
+ * @covers ::block_rebuild
+ */
+ public function testRebuildInvalidBlocks() {
+ $this->placeBlock('system_powered_by_block', ['region' => 'content']);
+ $block1 = $this->placeBlock('system_powered_by_block');
+ $block2 = $this->placeBlock('system_powered_by_block');
+ $block2->disable()->save();
+ // Use the config API directly to bypass Block::preSave().
+ \Drupal::configFactory()->getEditable('block.block.' . $block1->id())->set('region', 'INVALID')->save();
+ \Drupal::configFactory()->getEditable('block.block.' . $block2->id())->set('region', 'INVALID')->save();
+
+ // Reload block entities.
+ $block1 = Block::load($block1->id());
+ $block2 = Block::load($block2->id());
+
+ $this->assertSame('INVALID', $block1->getRegion());
+ $this->assertTrue($block1->status());
+ $this->assertSame('INVALID', $block2->getRegion());
+ $this->assertFalse($block2->status());
+
+ block_rebuild();
+
+ // Reload block entities.
+ $block1 = Block::load($block1->id());
+ $block2 = Block::load($block2->id());
+
+ $messages = drupal_get_messages();
+ $expected = ['warning' => [new TranslatableMarkup('The block %info was assigned to the invalid region %region and has been disabled.', ['%info' => $block1->id(), '%region' => 'INVALID'])]];
+ $this->assertEquals($expected, $messages);
+
+ $default_region = system_default_region('classy');
+ $this->assertSame($default_region, $block1->getRegion());
+ $this->assertFalse($block1->status());
+ $this->assertSame($default_region, $block2->getRegion());
+ $this->assertFalse($block2->status());
+ }
+
+}
diff --git a/core/modules/block/tests/src/Kernel/BlockStorageUnitTest.php b/core/modules/block/tests/src/Kernel/BlockStorageUnitTest.php
index 6dfc66c..5704476 100644
--- a/core/modules/block/tests/src/Kernel/BlockStorageUnitTest.php
+++ b/core/modules/block/tests/src/Kernel/BlockStorageUnitTest.php
@@ -34,6 +34,8 @@ protected function setUp() {
parent::setUp();
$this->controller = $this->container->get('entity_type.manager')->getStorage('block');
+
+ $this->container->get('theme_installer')->install(['stark']);
}
/**
@@ -66,6 +68,7 @@ protected function createTests() {
$entity = $this->controller->create(array(
'id' => 'test_block',
'theme' => 'stark',
+ 'region' => 'content',
'plugin' => 'test_html',
));
$entity->save();
@@ -84,7 +87,7 @@ protected function createTests() {
'dependencies' => array('module' => array('block_test'), 'theme' => array('stark')),
'id' => 'test_block',
'theme' => 'stark',
- 'region' => '-1',
+ 'region' => 'content',
'weight' => NULL,
'provider' => NULL,
'plugin' => 'test_html',
@@ -111,7 +114,7 @@ protected function loadTests() {
$this->assertTrue($entity instanceof Block, 'The loaded entity is a Block.');
// Verify several properties of the block.
- $this->assertEqual($entity->getRegion(), '-1');
+ $this->assertSame('content', $entity->getRegion());
$this->assertTrue($entity->status());
$this->assertEqual($entity->getTheme(), 'stark');
$this->assertTrue($entity->uuid());
diff --git a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
index 1ceadd1..bc59f71 100644
--- a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
+++ b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
@@ -28,6 +28,10 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
*/
protected function setUp() {
parent::setUp();
+
+ // Install the themes used for this test.
+ $this->container->get('theme_installer')->install(['bartik', 'seven', 'test_theme']);
+
$this->installConfig(['block_content']);
$this->installEntitySchema('block_content');
@@ -37,9 +41,6 @@ protected function setUp() {
$config->set('admin', 'seven');
$config->save();
- // Install one of D8's test themes.
- \Drupal::service('theme_handler')->install(['test_theme']);
-
$this->executeMigrations([
'd6_filter_format',
'block_content_type',
@@ -69,13 +70,14 @@ protected function setUp() {
* @param string $label_display
* The block label display setting.
*/
- public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display) {
+ public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display, $status = TRUE) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
$this->assertIdentical($visibility, $block->getVisibility());
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
+ $this->assertIdentical($status, $block->status());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
@@ -122,7 +124,9 @@ public function testBlockMigration() {
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = '/node/1';
- $this->assertEntity('system', $visibility, 'footer', 'bartik', -5, '', '0');
+ // @todo https://www.drupal.org/node/2753939 This block is the footer region
+ // but Bartik in D8 does not have this region.
+ $this->assertEntity('system', $visibility, 'header', 'bartik', -5, '', '0', FALSE);
// Check menu blocks
$visibility = [];
@@ -137,7 +141,9 @@ public function testBlockMigration() {
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '/node';
- $this->assertEntity('block_1', $visibility, 'sidebar_second', 'bluemarine', -4, 'Another Static Block', 'visible');
+ // @todo https://www.drupal.org/node/2753939 The bluemarine theme does not
+ // exist.
+ $this->assertEntity('block_1', $visibility, '', 'bluemarine', -4, 'Another Static Block', 'visible', FALSE);
$visibility = [];
$this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7, '', '0');
diff --git a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php
index 571c210..49be0ee 100644
--- a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php
+++ b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockTest.php
@@ -33,6 +33,10 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
*/
protected function setUp() {
parent::setUp();
+
+ // Install the themes used for this test.
+ $this->container->get('theme_installer')->install(['bartik', 'seven']);
+
$this->installConfig(static::$modules);
$this->installEntitySchema('block_content');
@@ -42,9 +46,6 @@ protected function setUp() {
$config->set('admin', 'seven');
$config->save();
- // Install one of D8's test themes.
- \Drupal::service('theme_handler')->install(['bartik']);
-
$this->executeMigrations([
'd7_filter_format',
'd7_user_role',
@@ -77,7 +78,7 @@ protected function setUp() {
* @param string $label_display
* The block label display setting.
*/
- public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight, $label, $label_display) {
+ public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight, $label, $label_display, $status = TRUE) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
/** @var \Drupal\block\BlockInterface $block */
@@ -95,6 +96,7 @@ public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $th
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
+ $this->assertIdentical($status, $block->status());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
@@ -108,7 +110,9 @@ public function testBlockMigration() {
$this->assertEntity('bartik_system_main', 'system_main_block', [], '', 'content', 'bartik', 0, '', '0');
$this->assertEntity('bartik_search_form', 'search_form_block', [], '', 'sidebar_first', 'bartik', -1, '', '0');
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0, '', '0');
- $this->assertEntity('bartik_system_powered_by', 'system_powered_by_block', [], '', 'footer', 'bartik', 10, '', '0');
+ // @todo https://www.drupal.org/node/2753939 This block is the footer region
+ // but Bartik in D8 does not have this region.
+ $this->assertEntity('bartik_system_powered_by', 'system_powered_by_block', [], '', 'header', 'bartik', 10, '', '0', FALSE);
$this->assertEntity('seven_system_main', 'system_main_block', [], '', 'content', 'seven', 0, '', '0');
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10, '', '0');
diff --git a/core/modules/block_content/src/Tests/BlockContentCreationTest.php b/core/modules/block_content/src/Tests/BlockContentCreationTest.php
index 0cca85e..e45892c 100644
--- a/core/modules/block_content/src/Tests/BlockContentCreationTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentCreationTest.php
@@ -107,7 +107,7 @@ public function testBlockContentCreationMultipleViewModes() {
)), 'Basic block created.');
// Save our block permanently
- $this->drupalPostForm(NULL, NULL, t('Save block'));
+ $this->drupalPostForm(NULL, ['region' => 'content'], t('Save block'));
// Set test_view_mode as a custom display to be available on the list.
$this->drupalGet('admin/structure/block/block-content');
@@ -134,6 +134,7 @@ public function testBlockContentCreationMultipleViewModes() {
$this->assertFieldByXPath('//select[@name="settings[view_mode]"]', NULL, 'View mode setting shown because multiple exist');
// Change the view mode.
+ $view_mode['region'] = 'content';
$view_mode['settings[view_mode]'] = 'test_view_mode';
$this->drupalPostForm(NULL, $view_mode, t('Save block'));
diff --git a/core/modules/block_content/src/Tests/BlockContentTypeTest.php b/core/modules/block_content/src/Tests/BlockContentTypeTest.php
index e73aa2a..8f6ffe1 100644
--- a/core/modules/block_content/src/Tests/BlockContentTypeTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentTypeTest.php
@@ -211,7 +211,7 @@ public function testsBlockContentAddTypes() {
if (!empty($blocks)) {
$block = reset($blocks);
$this->assertUrl(\Drupal::url('block.admin_add', array('plugin_id' => 'block_content:' . $block->uuid(), 'theme' => $theme), array('absolute' => TRUE)));
- $this->drupalPostForm(NULL, array(), t('Save block'));
+ $this->drupalPostForm(NULL, ['region' => 'content'], t('Save block'));
$this->assertUrl(\Drupal::url('block.admin_display_theme', array('theme' => $theme), array('absolute' => TRUE, 'query' => array('block-placement' => Html::getClass($edit['info[0][value]'])))));
}
else {
diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml
index e37cb48..b9f8fb8 100644
--- a/core/modules/locale/locale.services.yml
+++ b/core/modules/locale/locale.services.yml
@@ -1,7 +1,7 @@
services:
locale.default.config.storage:
class: Drupal\locale\LocaleDefaultConfigStorage
- arguments: ['@config.storage', '@language_manager']
+ arguments: ['@config.storage', '@language_manager', '%install_profile%']
public: false
locale.config_manager:
class: Drupal\locale\LocaleConfigManager
diff --git a/core/modules/locale/src/LocaleDefaultConfigStorage.php b/core/modules/locale/src/LocaleDefaultConfigStorage.php
index 29697a1..bcb242b 100644
--- a/core/modules/locale/src/LocaleDefaultConfigStorage.php
+++ b/core/modules/locale/src/LocaleDefaultConfigStorage.php
@@ -57,12 +57,12 @@ class LocaleDefaultConfigStorage {
* @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
*/
- public function __construct(StorageInterface $config_storage, ConfigurableLanguageManagerInterface $language_manager) {
+ public function __construct(StorageInterface $config_storage, ConfigurableLanguageManagerInterface $language_manager, $install_profile) {
$this->configStorage = $config_storage;
$this->languageManager = $language_manager;
- $this->requiredInstallStorage = new ExtensionInstallStorage($this->configStorage);
- $this->optionalInstallStorage = new ExtensionInstallStorage($this->configStorage, ExtensionInstallStorage::CONFIG_OPTIONAL_DIRECTORY);
+ $this->requiredInstallStorage = new ExtensionInstallStorage($this->configStorage, $install_profile);
+ $this->optionalInstallStorage = new ExtensionInstallStorage($this->configStorage, $install_profile, ExtensionInstallStorage::CONFIG_OPTIONAL_DIRECTORY);
}
/**
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 5cf42dd..dfb0272 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -36,6 +36,9 @@
*/
class EntityResource extends ResourceBase implements DependentPluginInterface {
+ use EntityResourceValidationTrait;
+ use EntityResourceAccessTrait;
+
/**
* The entity type targeted by this resource.
*
@@ -156,14 +159,7 @@ public function post(EntityInterface $entity = NULL) {
throw new BadRequestHttpException('Only new entities can be created');
}
- // Only check 'edit' permissions for fields that were actually
- // submitted by the user. Field access makes no difference between 'create'
- // and 'update', so the 'edit' operation is used here.
- foreach ($entity->_restSubmittedFields as $key => $field_name) {
- if (!$entity->get($field_name)->access('edit')) {
- throw new AccessDeniedHttpException("Access denied on creating field '$field_name'");
- }
- }
+ $this->checkEditFieldAccess($entity);
// Validate the received data before saving.
$this->validate($entity);
@@ -175,8 +171,7 @@ public function post(EntityInterface $entity = NULL) {
// body. These responses are not cacheable, so we add no cacheability
// metadata here.
$url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
- $response = new ModifiedResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
- return $response;
+ return new ModifiedResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
}
catch (EntityStorageException $e) {
throw new HttpException(500, 'Internal Server Error', $e);
@@ -277,39 +272,6 @@ public function delete(EntityInterface $entity) {
}
/**
- * Verifies that the whole entity does not violate any validation constraints.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity object.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\HttpException
- * If validation errors are found.
- */
- protected function validate(EntityInterface $entity) {
- // @todo Remove when https://www.drupal.org/node/2164373 is committed.
- if (!$entity instanceof FieldableEntityInterface) {
- return;
- }
- $violations = $entity->validate();
-
- // Remove violations of inaccessible fields as they cannot stem from our
- // changes.
- $violations->filterByFieldAccess();
-
- if (count($violations) > 0) {
- $message = "Unprocessable Entity: validation failed.\n";
- foreach ($violations as $violation) {
- $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
- }
- // Instead of returning a generic 400 response we use the more specific
- // 422 Unprocessable Entity code from RFC 4918. That way clients can
- // distinguish between general syntax errors in bad serializations (code
- // 400) and semantic errors in well-formed requests (code 422).
- throw new HttpException(422, $message);
- }
- }
-
- /**
* {@inheritdoc}
*/
public function permissions() {
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php b/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php
new file mode 100644
index 0000000..7bf8e82
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php
@@ -0,0 +1,35 @@
+_restSubmittedFields as $key => $field_name) {
+ if (!$entity->get($field_name)->access('edit')) {
+ throw new AccessDeniedHttpException("Access denied on creating field '$field_name'.");
+ }
+ }
+ }
+
+}
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php
new file mode 100644
index 0000000..a2ff40a
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php
@@ -0,0 +1,44 @@
+validate();
+
+ // Remove violations of inaccessible fields as they cannot stem from our
+ // changes.
+ $violations->filterByFieldAccess();
+
+ if ($violations->count() > 0) {
+ $message = "Unprocessable Entity: validation failed.\n";
+ foreach ($violations as $violation) {
+ $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
+ }
+ throw new UnprocessableEntityHttpException($message);
+ }
+ }
+
+}
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 584c003..1a52474 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -270,6 +270,15 @@ protected function entityValues($entity_type_id) {
'vid' => 'tags',
'name' => $this->randomMachineName(),
];
+ case 'block':
+ // Block placements depend on themes, ensure Bartik is installed.
+ $this->container->get('theme_installer')->install(['bartik']);
+ return [
+ 'id' => strtolower($this->randomMachineName(8)),
+ 'plugin' => 'system_powered_by_block',
+ 'theme' => 'bartik',
+ 'region' => 'header',
+ ];
default:
if ($this->isConfigEntity($entity_type_id)) {
return $this->configEntityValues($entity_type_id);
diff --git a/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php b/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php
new file mode 100644
index 0000000..20a6175
--- /dev/null
+++ b/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php
@@ -0,0 +1,73 @@
+getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait');
+
+ $method = new \ReflectionMethod($trait, 'validate');
+ $method->setAccessible(TRUE);
+
+ $entity = $this->prophesize(Node::class);
+
+ $violations = $this->prophesize(EntityConstraintViolationList::class);
+ $violations->filterByFieldAccess()->willReturn([]);
+ $violations->count()->willReturn(0);
+
+ $entity->validate()->willReturn($violations->reveal());
+
+ $method->invoke($trait, $entity->reveal());
+ }
+
+ /**
+ * @covers ::validate
+ */
+ public function testFailedValidate() {
+ $violation1 = $this->prophesize(ConstraintViolationInterface::class);
+ $violation1->getPropertyPath()->willReturn('property_path');
+ $violation1->getMessage()->willReturn('message');
+
+ $violation2 = $this->prophesize(ConstraintViolationInterface::class);
+ $violation2->getPropertyPath()->willReturn('property_path');
+ $violation2->getMessage()->willReturn('message');
+
+ $entity = $this->prophesize(User::class);
+
+ $violations = $this->getMockBuilder(EntityConstraintViolationList::class)
+ ->setConstructorArgs([$entity->reveal(), [$violation1->reveal(), $violation2->reveal()]])
+ ->setMethods(['filterByFieldAccess'])
+ ->getMock();
+
+ $violations->expects($this->once())
+ ->method('filterByFieldAccess')
+ ->will($this->returnValue([]));
+
+ $entity->validate()->willReturn($violations);
+
+ $trait = $this->getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait');
+
+ $method = new \ReflectionMethod($trait, 'validate');
+ $method->setAccessible(TRUE);
+
+ $this->setExpectedException(UnprocessableEntityHttpException::class);
+
+ $method->invoke($trait, $entity->reveal());
+ }
+
+}
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 15ecf1f..5876eb7 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -323,13 +323,13 @@ function testNoThemeByDefault() {
}
/**
- * Tests that drupal_get_profile() returns NULL.
+ * Tests that \Drupal::installProfile() returns FALSE.
*
* As the currently active installation profile is used when installing
* configuration, for example, this is essential to ensure test isolation.
*/
public function testDrupalGetProfile() {
- $this->assertNull(drupal_get_profile());
+ $this->assertFalse(\Drupal::installProfile());
}
/**
diff --git a/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php b/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php
new file mode 100644
index 0000000..32d2872
--- /dev/null
+++ b/core/modules/statistics/src/NodeStatisticsDatabaseStorage.php
@@ -0,0 +1,148 @@
+connection = $connection;
+ $this->state = $state;
+ $this->requestStack = $request_stack;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function recordView($id) {
+ return (bool) $this->connection
+ ->merge('node_counter')
+ ->key('nid', $id)
+ ->fields([
+ 'daycount' => 1,
+ 'totalcount' => 1,
+ 'timestamp' => $this->getRequestTime(),
+ ])
+ ->expression('daycount', 'daycount + 1')
+ ->expression('totalcount', 'totalcount + 1')
+ ->execute();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchViews($ids) {
+ $views = $this->connection
+ ->select('node_counter', 'nc')
+ ->fields('nc', ['totalcount', 'daycount', 'timestamp'])
+ ->condition('nid', $ids, 'IN')
+ ->execute()
+ ->fetchAll();
+ foreach ($views as $id => $view) {
+ $views[$id] = new StatisticsViewsResult($view->totalcount, $view->daycount, $view->timestamp);
+ }
+ return $views;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchView($id) {
+ $views = $this->fetchViews(array($id));
+ return reset($views);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($order = 'totalcount', $limit = 5) {
+ assert(in_array($order, ['totalcount', 'daycount', 'timestamp']), "Invalid order argument.");
+
+ return $this->connection
+ ->select('node_counter', 'nc')
+ ->fields('nc', ['nid'])
+ ->orderBy($order, 'DESC')
+ ->range(0, $limit)
+ ->execute()
+ ->fetchCol();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteViews($id) {
+ return (bool) $this->connection
+ ->delete('node_counter')
+ ->condition('nid', $id)
+ ->execute();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resetDayCount() {
+ $statistics_timestamp = $this->state->get('statistics.day_timestamp') ?: 0;
+ if (($this->getRequestTime() - $statistics_timestamp) >= 86400) {
+ $this->state->set('statistics.day_timestamp', $this->getRequestTime());
+ $this->connection->update('node_counter')
+ ->fields(['daycount' => 0])
+ ->execute();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function maxTotalCount() {
+ $query = $this->connection->select('node_counter', 'nc');
+ $query->addExpression('MAX(totalcount)');
+ $max_total_count = (int)$query->execute()->fetchField();
+ return $max_total_count;
+ }
+
+ /**
+ * Get current request time.
+ *
+ * @return int
+ * Unix timestamp for current server request time.
+ */
+ protected function getRequestTime() {
+ return $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
+ }
+
+}
diff --git a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
index 8f4384e..8bd84b2 100644
--- a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
+++ b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
@@ -4,8 +4,14 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\statistics\StatisticsStorageInterface;
/**
* Provides a 'Popular content' block.
@@ -15,7 +21,72 @@
* admin_label = @Translation("Popular content")
* )
*/
-class StatisticsPopularBlock extends BlockBase {
+class StatisticsPopularBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The entity repository service.
+ *
+ * @var \Drupal\Core\Entity\EntityRepositoryInterface
+ */
+ protected $entityRepository;
+
+ /**
+ * The storage for statistics.
+ *
+ * @var \Drupal\statistics\StatisticsStorageInterface
+ */
+ protected $statisticsStorage;
+
+ /**
+ * @var \Drupal\Core\Render\RendererInterface
+ */
+ protected $renderer;
+
+ /**
+ * Constructs an StatisticsPopularBlock object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+ * The entity repository service
+ * @param \Drupal\statistics\StatisticsStorageInterface $statistics_storage
+ * The storage for statistics.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, StatisticsStorageInterface $statistics_storage, RendererInterface $renderer) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $this->entityTypeManager = $entity_type_manager;
+ $this->entityRepository = $entity_repository;
+ $this->statisticsStorage = $statistics_storage;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ $container->get('entity.repository'),
+ $container->get('statistics.storage.node'),
+ $container->get('renderer')
+ );
+ }
/**
* {@inheritdoc}
@@ -82,28 +153,64 @@ public function build() {
$content = array();
if ($this->configuration['top_day_num'] > 0) {
- $result = statistics_title_list('daycount', $this->configuration['top_day_num']);
- if ($result) {
- $content['top_day'] = node_title_list($result, $this->t("Today's:"));
+ $nids = $this->statisticsStorage->fetchAll('daycount', $this->configuration['top_day_num']);
+ if ($nids) {
+ $content['top_day'] = $this->nodeTitleList($nids, $this->t("Today's:"));
$content['top_day']['#suffix'] = '
';
}
}
if ($this->configuration['top_all_num'] > 0) {
- $result = statistics_title_list('totalcount', $this->configuration['top_all_num']);
- if ($result) {
- $content['top_all'] = node_title_list($result, $this->t('All time:'));
+ $nids = $this->statisticsStorage->fetchAll('totalcount', $this->configuration['top_all_num']);
+ if ($nids) {
+ $content['top_all'] = $this->nodeTitleList($nids, $this->t('All time:'));
$content['top_all']['#suffix'] = '
';
}
}
if ($this->configuration['top_last_num'] > 0) {
- $result = statistics_title_list('timestamp', $this->configuration['top_last_num']);
- $content['top_last'] = node_title_list($result, $this->t('Last viewed:'));
+ $nids = $this->statisticsStorage->fetchAll('timestamp', $this->configuration['top_last_num']);
+ $content['top_last'] = $this->nodeTitleList($nids, $this->t('Last viewed:'));
$content['top_last']['#suffix'] = '
';
}
return $content;
}
+ /**
+ * Generates the ordered array of node links for build().
+ *
+ * @param int[] $nids
+ * An ordered array of node ids.
+ * @param string $title
+ * The title for the list.
+ *
+ * @return array
+ * A render array for the list.
+ */
+ protected function nodeTitleList(array $nids, $title) {
+ $nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids);
+
+ $items = [];
+ foreach ($nids as $nid) {
+ $node = $this->entityRepository->getTranslationFromContext($nodes[$nid]);
+ $item = [
+ '#type' => 'link',
+ '#title' => $node->getTitle(),
+ '#url' => $node->urlInfo('canonical'),
+ ];
+ $this->renderer->addCacheableDependency($item, $node);
+ $items[] = $item;
+ }
+
+ return [
+ '#theme' => 'item_list__node',
+ '#items' => $items,
+ '#title' => $title,
+ '#cache' => [
+ 'tags' => $this->entityTypeManager->getDefinition('node')->getListCacheTags(),
+ ],
+ ];
+ }
+
}
diff --git a/core/modules/statistics/src/StatisticsStorageInterface.php b/core/modules/statistics/src/StatisticsStorageInterface.php
new file mode 100644
index 0000000..ccb51e4
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsStorageInterface.php
@@ -0,0 +1,85 @@
+totalCount = $total_count;
+ $this->dayCount = $day_count;
+ $this->timestamp = $timestamp;
+ }
+
+ /**
+ * Total number of times the entity has been viewed.
+ *
+ * @return int
+ */
+ public function getTotalCount() {
+ return $this->totalCount;
+ }
+
+
+ /**
+ * Total number of times the entity has been viewed "today".
+ *
+ * @return int
+ */
+ public function getDayCount() {
+ return $this->dayCount;
+ }
+
+
+ /**
+ * Timestamp of when the entity was last viewed.
+ *
+ * @return int
+ */
+ public function getTimestamp() {
+ return $this->timestamp;
+ }
+
+}
diff --git a/core/modules/statistics/src/Tests/StatisticsReportsTest.php b/core/modules/statistics/src/Tests/StatisticsReportsTest.php
index 9c0d26c..0fe2e28 100644
--- a/core/modules/statistics/src/Tests/StatisticsReportsTest.php
+++ b/core/modules/statistics/src/Tests/StatisticsReportsTest.php
@@ -2,6 +2,9 @@
namespace Drupal\statistics\Tests;
+use Drupal\Core\Cache\Cache;
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
+
/**
* Tests display of statistics report blocks.
*
@@ -9,6 +12,8 @@
*/
class StatisticsReportsTest extends StatisticsTestBase {
+ use AssertPageCacheContextsAndTagsTrait;
+
/**
* Tests the "popular content" block.
*/
@@ -30,7 +35,7 @@ function testPopularContentBlock() {
$client->post($stats_path, array('headers' => $headers, 'body' => $post));
// Configure and save the block.
- $this->drupalPlaceBlock('statistics_popular_block', array(
+ $block = $this->drupalPlaceBlock('statistics_popular_block', array(
'label' => 'Popular content',
'top_day_num' => 3,
'top_all_num' => 3,
@@ -44,9 +49,16 @@ function testPopularContentBlock() {
$this->assertText('All time', 'Found the all time popular content.');
$this->assertText('Last viewed', 'Found the last viewed popular content.');
- // statistics.module doesn't use node entities, prevent the node language
- // from being added to the options.
- $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical', ['language' => NULL])), 'Found link to visited node.');
+ $tags = Cache::mergeTags($node->getCacheTags(), $block->getCacheTags());
+ $tags = Cache::mergeTags($tags, $this->blockingUser->getCacheTags());
+ $tags = Cache::mergeTags($tags, ['block_view', 'config:block_list', 'node_list', 'rendered', 'user_view']);
+ $this->assertCacheTags($tags);
+ $contexts = Cache::mergeContexts($node->getCacheContexts(), $block->getCacheContexts());
+ $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format']);
+ $this->assertCacheContexts($contexts);
+
+ // Check if the node link is displayed.
+ $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical')), 'Found link to visited node.');
}
}
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 5079e43..5419645 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -52,9 +52,9 @@ function statistics_node_links_alter(array &$links, NodeInterface $entity, array
if ($context['view_mode'] != 'rss') {
$links['#cache']['contexts'][] = 'user.permissions';
if (\Drupal::currentUser()->hasPermission('view post access counter')) {
- $statistics = statistics_get($entity->id());
+ $statistics = \Drupal::service('statistics.storage.node')->fetchView($entity->id());
if ($statistics) {
- $statistics_links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics['totalcount'], '1 view', '@count views');
+ $statistics_links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics->getTotalCount(), '1 view', '@count views');
$links['statistics'] = array(
'#theme' => 'links__node__statistics',
'#links' => $statistics_links,
@@ -70,18 +70,10 @@ function statistics_node_links_alter(array &$links, NodeInterface $entity, array
* Implements hook_cron().
*/
function statistics_cron() {
- $statistics_timestamp = \Drupal::state()->get('statistics.day_timestamp') ?: 0;
-
- if ((REQUEST_TIME - $statistics_timestamp) >= 86400) {
- // Reset day counts.
- db_update('node_counter')
- ->fields(array('daycount' => 0))
- ->execute();
- \Drupal::state()->set('statistics.day_timestamp', REQUEST_TIME);
- }
-
- // Calculate the maximum of node views, for node search ranking.
- \Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, db_query('SELECT MAX(totalcount) FROM {node_counter}')->fetchField()));
+ $storage = \Drupal::service('statistics.storage.node');
+ $storage->resetDayCount();
+ $max_total_count = $storage->maxTotalCount();
+ \Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, $max_total_count));
}
/**
@@ -123,26 +115,21 @@ function statistics_title_list($dbfield, $dbrows) {
return FALSE;
}
-
/**
* Retrieves a node's "view statistics".
*
- * @param int $nid
- * The node ID.
- *
- * @return array
- * An associative array containing:
- * - totalcount: Integer for the total number of times the node has been
- * viewed.
- * - daycount: Integer for the total number of times the node has been viewed
- * "today". For the daycount to be reset, cron must be enabled.
- * - timestamp: Integer for the timestamp of when the node was last viewed.
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ * Use \Drupal::service('statistics.storage.node')->fetchView($id).
*/
-function statistics_get($nid) {
-
- if ($nid > 0) {
- // Retrieve an array with both totalcount and daycount.
- return db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = :nid', array(':nid' => $nid), array('target' => 'replica'))->fetchAssoc();
+function statistics_get($id) {
+ if ($id > 0) {
+ /** @var \Drupal\statistics\StatisticsViewsResult $statistics */
+ $statistics = \Drupal::service('statistics.storage.node')->fetchView($id);
+ return [
+ 'totalcount' => $statistics->getTotalCount(),
+ 'daycount' => $statistics->getDayCount(),
+ 'timestamp' => $statistics->getTimestamp(),
+ ];
}
}
@@ -151,9 +138,8 @@ function statistics_get($nid) {
*/
function statistics_node_predelete(EntityInterface $node) {
// Clean up statistics table when node is deleted.
- db_delete('node_counter')
- ->condition('nid', $node->id())
- ->execute();
+ $id = $node->id();
+ return \Drupal::service('statistics.storage.node')->deleteViews($id);
}
/**
diff --git a/core/modules/statistics/statistics.php b/core/modules/statistics/statistics.php
index a79af5f..a43509e 100644
--- a/core/modules/statistics/statistics.php
+++ b/core/modules/statistics/statistics.php
@@ -14,8 +14,9 @@
$kernel = DrupalKernel::createFromRequest(Request::createFromGlobals(), $autoloader, 'prod');
$kernel->boot();
+$container = $kernel->getContainer();
-$views = $kernel->getContainer()
+$views = $container
->get('config.factory')
->get('statistics.settings')
->get('count_content_views');
@@ -23,15 +24,7 @@
if ($views) {
$nid = filter_input(INPUT_POST, 'nid', FILTER_VALIDATE_INT);
if ($nid) {
- \Drupal::database()->merge('node_counter')
- ->key('nid', $nid)
- ->fields(array(
- 'daycount' => 1,
- 'totalcount' => 1,
- 'timestamp' => REQUEST_TIME,
- ))
- ->expression('daycount', 'daycount + 1')
- ->expression('totalcount', 'totalcount + 1')
- ->execute();
+ $container->get('request_stack')->push(Request::createFromGlobals());
+ $container->get('statistics.storage.node')->recordView($nid);
}
}
diff --git a/core/modules/statistics/statistics.services.yml b/core/modules/statistics/statistics.services.yml
new file mode 100644
index 0000000..cf15573
--- /dev/null
+++ b/core/modules/statistics/statistics.services.yml
@@ -0,0 +1,6 @@
+services:
+ statistics.storage.node:
+ class: Drupal\statistics\NodeStatisticsDatabaseStorage
+ arguments: ['@database', '@state', '@request_stack']
+ tags:
+ - { name: backend_overridable }
diff --git a/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php b/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php
new file mode 100644
index 0000000..bf3205c
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php
@@ -0,0 +1,162 @@
+info = [
+ 'type' => 'profile',
+ 'core' => \Drupal::CORE_COMPATIBILITY,
+ 'name' => 'Distribution profile',
+ 'distribution' => [
+ 'name' => 'My Distribution',
+ 'install' => [
+ 'theme' => 'bartik',
+ ],
+ ],
+ ];
+ // File API functions are not available yet.
+ $path = $this->siteDirectory . '/profiles/mydistro';
+ mkdir($path, 0777, TRUE);
+ file_put_contents("$path/mydistro.info.yml", Yaml::encode($this->info));
+
+ // Pre-configure hash salt.
+ // Any string is valid, so simply use the class name of this test.
+ $this->settings['settings']['hash_salt'] = (object) [
+ 'value' => __CLASS__,
+ 'required' => TRUE,
+ ];
+
+ // Pre-configure database credentials.
+ $connection_info = Database::getConnectionInfo();
+ unset($connection_info['default']['pdo']);
+ unset($connection_info['default']['init_commands']);
+
+ $this->settings['databases']['default'] = (object) [
+ 'value' => $connection_info,
+ 'required' => TRUE,
+ ];
+
+ // Use the kernel to find the site path because the site.path service should
+ // not be available at this point in the install process.
+ $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
+ // Pre-configure config directories.
+ $this->settings['config_directories'] = [
+ CONFIG_SYNC_DIRECTORY => (object) [
+ 'value' => $site_path . '/files/config_staging',
+ 'required' => TRUE,
+ ],
+ ];
+ mkdir($this->settings['config_directories'][CONFIG_SYNC_DIRECTORY]->value, 0777, TRUE);
+ parent::setUp();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpLanguage() {
+ // Make settings file not writable.
+ $filename = $this->siteDirectory . '/settings.php';
+ // Make the settings file read-only.
+ // Not using File API; a potential error must trigger a PHP warning.
+ chmod($filename, 0444);
+
+ // Verify that the distribution name appears.
+ $this->assertRaw($this->info['distribution']['name']);
+ // Verify that the requested theme is used.
+ $this->assertRaw($this->info['distribution']['install']['theme']);
+ // Verify that the "Choose profile" step does not appear.
+ $this->assertNoText('profile');
+
+ parent::setUpLanguage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpProfile() {
+ // This step is skipped, because there is a distribution profile.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpSettings() {
+ // This step should not appear, since settings.php is fully configured
+ // already.
+ }
+
+ /**
+ * Confirms that the installation succeeded.
+ */
+ public function testInstalled() {
+ $this->assertUrl('user/1');
+ $this->assertResponse(200);
+ // Confirm that we are logged-in after installation.
+ $this->assertText($this->rootUser->getUsername());
+
+ // Confirm that Drupal recognizes this distribution as the current profile.
+ $this->assertEqual(\Drupal::installProfile(), 'mydistro');
+ $this->assertNull(Settings::get('install_profile'), 'The install profile has not been written to settings.php.');
+
+ $this->rebuildContainer();
+ $this->pass('Container can be rebuilt even though distribution is not written to settings.php.');
+
+ // Create another installation profile which is a distrubtion.
+ $info = [
+ 'type' => 'profile',
+ 'core' => \Drupal::CORE_COMPATIBILITY,
+ 'name' => 'Distribution profile 2',
+ 'distribution' => [
+ 'name' => 'My Distribution 2',
+ 'install' => [
+ 'theme' => 'bartik',
+ ],
+ ],
+ ];
+ // File API functions are not available yet.
+ $path = $this->siteDirectory . '/profiles/mydistro2';
+ mkdir($path, 0777, TRUE);
+ file_put_contents("$path/mydistro2.info.yml", Yaml::encode($info));
+
+ // Test that a site will multiple distributions will get an exception when
+ // rebuilding the container. In order to do this we need to reset the
+ // discovered files in ExtensionDiscovery.
+ try {
+ ExtensionDiscovery::reset();
+ $this->rebuildContainer();
+ $this->fail('TooManyDistributionsException exception thrown.');
+ }
+ catch (TooManyDistributionsException $e) {
+ $this->pass('TooManyDistributionsException exception thrown.');
+ $this->assertEqual('A site can only have one distribution, multiple installation profiles discovered: mydistro, mydistro2', $e->getMessage());
+ }
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Installer/DistributionProfileTest.php b/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
index 0bb47ac..01e25e6 100644
--- a/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
+++ b/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
@@ -3,6 +3,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Core\Serialization\Yaml;
+use Drupal\Core\Site\Settings;
use Drupal\simpletest\InstallerTestBase;
/**
@@ -68,6 +69,10 @@ public function testInstalled() {
$this->assertResponse(200);
// Confirm that we are logged-in after installation.
$this->assertText($this->rootUser->getUsername());
+
+ // Confirm that Drupal recognizes this distribution as the current profile.
+ $this->assertEqual(\Drupal::installProfile(), 'mydistro');
+ $this->assertEqual(Settings::get('install_profile'), 'mydistro', 'The install profile has been written to settings.php.');
}
}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php
new file mode 100644
index 0000000..69a81c6
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php
@@ -0,0 +1,131 @@
+settings['settings']['hash_salt'] = (object) [
+ 'value' => __CLASS__,
+ 'required' => TRUE,
+ ];
+
+ // Pre-configure database credentials.
+ $connection_info = Database::getConnectionInfo();
+ unset($connection_info['default']['pdo']);
+ unset($connection_info['default']['init_commands']);
+
+ $this->settings['databases']['default'] = (object) [
+ 'value' => $connection_info,
+ 'required' => TRUE,
+ ];
+
+ // During interactive install we'll change this to a different profile and
+ // this test will ensure that the new value is written to settings.php.
+ $this->settings['settings']['install_profile'] = (object) [
+ 'value' => 'minimal',
+ 'required' => TRUE,
+ ];
+
+ // Pre-configure config directories.
+ $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
+ $this->settings['config_directories'] = [
+ CONFIG_SYNC_DIRECTORY => (object) [
+ 'value' => $site_path . '/files/config_staging',
+ 'required' => TRUE,
+ ],
+ ];
+ mkdir($this->settings['config_directories'][CONFIG_SYNC_DIRECTORY]->value, 0777, TRUE);
+
+ // @todo Remove HTML once https://www.drupal.org/node/2514044 is fixed.
+ $this->exceptionMessage = (string) new FormattableMarkup('Failed to modify %path. Verify the file permissions.', ['%path' => $this->siteDirectory . '/settings.php']);
+ parent::setUp();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function visitInstaller() {
+ // Make settings file not writable. This will break the installer.
+ $filename = $this->siteDirectory . '/settings.php';
+ // Make the settings file read-only.
+ // Not using File API; a potential error must trigger a PHP warning.
+ chmod($filename, 0444);
+
+ $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=testing');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpLanguage() {
+ // This step is skipped, because there is a lagcode as a query param.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpProfile() {
+ // This step is skipped, because there is a profile as a query param.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpSettings() {
+ // This step should not appear, since settings.php is fully configured
+ // already.
+ }
+
+ protected function setUpSite() {
+ // This step should not appear, since settings.php could not be written.
+ }
+
+ /**
+ * Verifies that installation did not succeed.
+ */
+ public function testBrokenInstaller() {
+ $this->assertRaw(Html::escape($this->exceptionMessage));
+ // The exceptions are expected. Do not interpret them as a test failure.
+ // Not using File API; a potential error must trigger a PHP warning.
+ unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function error($message = '', $group = 'Other', array $caller = NULL) {
+ if ($group == 'Exception' && $message == $this->exceptionMessage) {
+ // Ignore the expected exception.
+ return FALSE;
+ }
+ return parent::error($message, $group, $caller);
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php
similarity index 64%
copy from core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
copy to core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php
index 84f861a..a179039 100644
--- a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php
@@ -13,7 +13,7 @@
*
* @group Installer
*/
-class InstallerExistingSettingsNoProfileTest extends InstallerTestBase {
+class InstallerExistingSettingsMismatchProfileTest extends InstallerTestBase {
/**
* {@inheritdoc}
@@ -39,6 +39,13 @@ protected function setUp() {
'required' => TRUE,
);
+ // During interactive install we'll change this to a different profile and
+ // this test will ensure that the new value is written to settings.php.
+ $this->settings['settings']['install_profile'] = (object) [
+ 'value' => 'minimal',
+ 'required' => TRUE,
+ ];
+
// Pre-configure config directories.
$this->settings['config_directories'] = array(
CONFIG_SYNC_DIRECTORY => (object) array(
@@ -54,6 +61,28 @@ protected function setUp() {
/**
* {@inheritdoc}
*/
+ protected function visitInstaller() {
+ // Provide profile and language in query string to skip these pages.
+ $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=testing');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpLanguage() {
+ // This step is skipped, because there is a lagcode as a query param.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpProfile() {
+ // This step is skipped, because there is a profile as a query param.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
protected function setUpSettings() {
// This step should not appear, since settings.php is fully configured
// already.
@@ -65,7 +94,8 @@ protected function setUpSettings() {
public function testInstaller() {
$this->assertUrl('user/1');
$this->assertResponse(200);
- $this->assertEqual('testing', Settings::get('install_profile'));
+ $this->assertEqual('testing', \Drupal::installProfile());
+ $this->assertEqual('testing', Settings::get('install_profile'), 'Profile was correctly changed to testing in Settings.php');
}
}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
index 84f861a..9517308 100644
--- a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
@@ -3,7 +3,6 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Core\DrupalKernel;
-use Drupal\Core\Site\Settings;
use Drupal\simpletest\InstallerTestBase;
use Drupal\Core\Database\Database;
use Symfony\Component\HttpFoundation\Request;
@@ -65,7 +64,7 @@ protected function setUpSettings() {
public function testInstaller() {
$this->assertUrl('user/1');
$this->assertResponse(200);
- $this->assertEqual('testing', Settings::get('install_profile'));
+ $this->assertEqual('testing', \Drupal::installProfile());
}
}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
index 6ecd9fd..eaca8fa 100644
--- a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
@@ -2,6 +2,7 @@
namespace Drupal\system\Tests\Installer;
+use Drupal\Core\Site\Settings;
use Drupal\simpletest\InstallerTestBase;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
@@ -74,7 +75,8 @@ protected function setUpSettings() {
public function testInstaller() {
$this->assertUrl('user/1');
$this->assertResponse(200);
- $this->assertEqual('testing', drupal_get_profile(), 'Profile was changed from minimal to testing during interactive install.');
+ $this->assertEqual('testing', \Drupal::installProfile(), 'Profile was changed from minimal to testing during interactive install.');
+ $this->assertEqual('testing', Settings::get('install_profile'), 'Profile was correctly changed to testing in Settings.php');
}
}
diff --git a/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php
new file mode 100644
index 0000000..4ac889e
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php
@@ -0,0 +1,109 @@
+ 'profile',
+ 'core' => \Drupal::CORE_COMPATIBILITY,
+ 'name' => $name . ' profile',
+ 'distribution' => [
+ 'name' => $name,
+ 'install' => [
+ 'theme' => 'bartik',
+ ],
+ ],
+ ];
+ // File API functions are not available yet.
+ $path = $this->siteDirectory . '/profiles/' . $name;
+ mkdir($path, 0777, TRUE);
+ file_put_contents("$path/$name.info.yml", Yaml::encode($info));
+ }
+ // Install the first distribution.
+ $this->profile = 'distribution_one';
+
+ parent::setUp();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpLanguage() {
+ $this->assertNoRaw('distribution_one');
+ $this->assertNoRaw('distribution_two');
+ // Verify that the "Choose profile" step appears.
+ $this->assertText('Choose profile');
+
+ parent::setUpLanguage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUpProfile() {
+ $this->assertText('distribution_one');
+ $this->assertText('distribution_two');
+ parent::setUpProfile();
+ }
+
+ /**
+ * Confirms that the installation succeeded.
+ */
+ public function testInstalled() {
+ $this->assertUrl('user/1');
+ $this->assertResponse(200);
+ // Confirm that we are logged-in after installation.
+ $this->assertText($this->rootUser->getUsername());
+
+ // Confirm that Drupal recognizes this distribution as the current profile.
+ $this->assertEqual(\Drupal::installProfile(), 'distribution_one');
+ $this->assertEqual(Settings::get('install_profile'), 'distribution_one', 'The install profile has been written to settings.php.');
+
+ // Test that a site will multiple distributions will get an exception when
+ // calling \Drupal\Core\DrupalKernel::getDistribution().
+ try {
+ $kernel = $this->container->get('kernel');
+ // getDistribution() is protected in the DrupalKernel class.
+ // Call setAccessible(TRUE) so that we can call it to test its behavior.
+ $getDistributionMethod = new \ReflectionMethod($kernel, 'getDistribution');
+ $getDistributionMethod->setAccessible(TRUE);
+ $getDistributionMethod->invokeArgs($kernel, array());
+ $this->fail('TooManyDistributionsException exception thrown.');
+ }
+ catch (TooManyDistributionsException $e) {
+ $this->pass('TooManyDistributionsException exception thrown.');
+ }
+ // To mirror the test in DistributionProfileExistingSettingsTest we reset
+ // the discovered files in ExtensionDiscovery.
+ // @see \Drupal\system\Tests\Installer\DistributionProfileExistingSettingsTest::testInstalled()
+ ExtensionDiscovery::reset();
+ $this->rebuildContainer();
+ $this->pass('Container can be rebuilt as distribution is written to settings.php.');
+ }
+
+}
diff --git a/core/modules/system/src/Tests/System/AccessDeniedTest.php b/core/modules/system/src/Tests/System/AccessDeniedTest.php
index 81f9fad..e7f17d7 100644
--- a/core/modules/system/src/Tests/System/AccessDeniedTest.php
+++ b/core/modules/system/src/Tests/System/AccessDeniedTest.php
@@ -65,7 +65,7 @@ function testAccessDenied() {
$this->drupalPostForm('admin/config/system/site-information', $edit, t('Save configuration'));
// Enable the user login block.
- $this->drupalPlaceBlock('user_login_block', array('id' => 'login'));
+ $block = $this->drupalPlaceBlock('user_login_block', array('id' => 'login'));
// Log out and check that the user login block is shown on custom 403 pages.
$this->drupalLogout();
@@ -90,10 +90,7 @@ function testAccessDenied() {
// Log back in, set the custom 403 page to /user/login and remove the block
$this->drupalLogin($this->adminUser);
$this->config('system.site')->set('page.403', '/user/login')->save();
- $edit = [
- 'region' => -1,
- ];
- $this->drupalPostForm('admin/structure/block/manage/login', $edit, t('Save block'));
+ $block->disable()->save();
// Check that we can log in from the 403 page.
$this->drupalLogout();
diff --git a/core/modules/system/tests/fixtures/update/block.block.secondtestfor2513534.yml b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2513534.yml
new file mode 100644
index 0000000..2999cab
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2513534.yml
@@ -0,0 +1,18 @@
+uuid: 3c4e92c3-5fb1-408d-993c-6066559230be
+langcode: en
+status: true
+dependencies:
+ theme:
+ - bartik
+id: pagetitle_2
+theme: bartik
+region: '-1'
+weight: null
+provider: null
+plugin: page_title_block
+settings:
+ id: page_title_block
+ label: 'Page title'
+ provider: core
+ label_display: '0'
+visibility: { }
diff --git a/core/modules/system/tests/fixtures/update/block.block.testfor2513534.yml b/core/modules/system/tests/fixtures/update/block.block.testfor2513534.yml
new file mode 100644
index 0000000..b8a55fa
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/block.block.testfor2513534.yml
@@ -0,0 +1,18 @@
+uuid: 87097da9-29d1-441f-8b00-b93852c760d6
+langcode: en
+status: false
+dependencies:
+ theme:
+ - bartik
+id: pagetitle_1
+theme: bartik
+region: '-1'
+weight: -8
+provider: null
+plugin: page_title_block
+settings:
+ id: page_title_block
+ label: 'Page title'
+ provider: core
+ label_display: '0'
+visibility: { }
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-block-disabled-2513534.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-block-disabled-2513534.php
new file mode 100644
index 0000000..fc8e1c5
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.update-test-block-disabled-2513534.php
@@ -0,0 +1,50 @@
+insert('config')
+ ->fields(array(
+ 'collection',
+ 'name',
+ 'data',
+ ))
+ ->values(array(
+ 'collection' => '',
+ 'name' => 'block.block.' . $block_config['id'],
+ 'data' => serialize($block_config),
+ ))
+ ->execute();
+}
+
+// Update the config entity query "index".
+$existing_blocks = $connection->select('key_value')
+ ->fields('key_value', ['value'])
+ ->condition('collection', 'config.entity.key_store.block')
+ ->condition('name', 'theme:bartik')
+ ->execute()
+ ->fetchField();
+$existing_blocks = unserialize($existing_blocks);
+
+$connection->update('key_value')
+ ->fields([
+ 'value' => serialize(array_merge($existing_blocks, ['block.block.testfor2513534', 'block.block.secondtestfor2513534']))
+ ])
+ ->condition('collection', 'config.entity.key_store.block')
+ ->condition('name', 'theme:bartik')
+ ->execute();
diff --git a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
index 3066ef0..76fcac2 100644
--- a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
+++ b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
@@ -47,14 +47,17 @@ function testVocabularyInterface() {
// Edit the vocabulary.
$this->drupalGet('admin/structure/taxonomy');
- $this->assertText($edit['name'], 'Vocabulary found in the vocabulary overview listing.');
+ $this->assertText($edit['name'], 'Vocabulary name found in the vocabulary overview listing.');
+ $this->assertText($edit['description'], 'Vocabulary description found in the vocabulary overview listing.');
$this->assertLinkByHref(Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $edit['vid']])->toString());
$this->clickLink(t('Edit vocabulary'));
$edit = array();
$edit['name'] = $this->randomMachineName();
+ $edit['description'] = $this->randomMachineName();
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/structure/taxonomy');
- $this->assertText($edit['name'], 'Vocabulary found in the vocabulary overview listing.');
+ $this->assertText($edit['name'], 'Vocabulary name found in the vocabulary overview listing.');
+ $this->assertText($edit['description'], 'Vocabulary description found in the vocabulary overview listing.');
// Try to submit a vocabulary with a duplicate machine name.
$edit['vid'] = $vid;
diff --git a/core/modules/taxonomy/src/VocabularyListBuilder.php b/core/modules/taxonomy/src/VocabularyListBuilder.php
index b5597bb..9c24311 100644
--- a/core/modules/taxonomy/src/VocabularyListBuilder.php
+++ b/core/modules/taxonomy/src/VocabularyListBuilder.php
@@ -56,6 +56,7 @@ public function getDefaultOperations(EntityInterface $entity) {
*/
public function buildHeader() {
$header['label'] = t('Vocabulary name');
+ $header['description'] = t('Description');
return $header + parent::buildHeader();
}
@@ -64,6 +65,7 @@ public function buildHeader() {
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
+ $row['description']['data'] = ['#markup' => $entity->getDescription()];
return $row + parent::buildRow($entity);
}
diff --git a/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php b/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php
new file mode 100644
index 0000000..6a243c3
--- /dev/null
+++ b/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php
@@ -0,0 +1,190 @@
+userSettings = $user_settings;
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->getParameter('serializer.formats'),
+ $container->get('logger.factory')->get('rest'),
+ $container->get('config.factory')->get('user.settings'),
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * Responds to user registration POST request.
+ *
+ * @param \Drupal\user\UserInterface $account
+ * The user account entity.
+ *
+ * @return \Drupal\rest\ModifiedResourceResponse
+ * The HTTP response object.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+ * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ */
+ public function post(UserInterface $account = NULL) {
+ $this->ensureAccountCanRegister($account);
+
+ // Only activate new users if visitors are allowed to register and no email
+ // verification required.
+ if ($this->userSettings->get('register') == USER_REGISTER_VISITORS && !$this->userSettings->get('verify_mail')) {
+ $account->activate();
+ }
+ else {
+ $account->block();
+ }
+
+ $this->checkEditFieldAccess($account);
+
+ // Make sure that the user entity is valid (email and name are valid).
+ $this->validate($account);
+
+ // Create the account.
+ $account->save();
+
+ $this->sendEmailNotifications($account);
+
+ return new ModifiedResourceResponse($account, 200);
+ }
+
+ /**
+ * Ensure the account can be registered in this request.
+ *
+ * @param \Drupal\user\UserInterface $account
+ * The user account to register.
+ */
+ protected function ensureAccountCanRegister(UserInterface $account = NULL) {
+ if ($account === NULL) {
+ throw new BadRequestHttpException('No user account data for registration received.');
+ }
+
+ // POSTed user accounts must not have an ID set, because we always want to
+ // create new entities here.
+ if (!$account->isNew()) {
+ throw new BadRequestHttpException('An ID has been set and only new user accounts can be registered.');
+ }
+
+ // Only allow anonymous users to register, authenticated users with the
+ // necessary permissions can POST a new user to the "user" REST resource.
+ // @see \Drupal\rest\Plugin\rest\resource\EntityResource
+ if (!$this->currentUser->isAnonymous()) {
+ throw new AccessDeniedHttpException('Only anonymous users can register a user.');
+ }
+
+ // Verify that the current user can register a user account.
+ if ($this->userSettings->get('register') == USER_REGISTER_ADMINISTRATORS_ONLY) {
+ throw new AccessDeniedHttpException('You cannot register a new user account.');
+ }
+
+ if (!$this->userSettings->get('verify_mail')) {
+ if (empty($account->getPassword())) {
+ // If no e-mail verification then the user must provide a password.
+ throw new UnprocessableEntityHttpException('No password provided.');
+ }
+ }
+ else {
+ if (!empty($account->getPassword())) {
+ // If e-mail verification required then a password cannot provided.
+ // The password will be set when the user logs in.
+ throw new UnprocessableEntityHttpException('A Password cannot be specified. It will be generated on login.');
+ }
+ }
+ }
+
+ /**
+ * Sends email notifications if necessary for user that was registered.
+ *
+ * @param \Drupal\user\UserInterface $account
+ * The user account.
+ */
+ protected function sendEmailNotifications(UserInterface $account) {
+ $approval_settings = $this->userSettings->get('register');
+ // No e-mail verification is required. Activating the user.
+ if ($approval_settings == USER_REGISTER_VISITORS) {
+ if ($this->userSettings->get('verify_mail')) {
+ // No administrator approval required.
+ _user_mail_notify('register_no_approval_required', $account);
+ }
+ }
+ // Administrator approval required.
+ elseif ($approval_settings == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
+ _user_mail_notify('register_pending_approval', $account);
+ }
+ }
+
+}
diff --git a/core/modules/user/src/Tests/RestRegisterUserTest.php b/core/modules/user/src/Tests/RestRegisterUserTest.php
new file mode 100644
index 0000000..c10d028
--- /dev/null
+++ b/core/modules/user/src/Tests/RestRegisterUserTest.php
@@ -0,0 +1,187 @@
+enableService('user_registration', 'POST', 'hal_json');
+
+ Role::load(RoleInterface::ANONYMOUS_ID)
+ ->grantPermission('restful post user_registration')
+ ->save();
+
+ Role::load(RoleInterface::AUTHENTICATED_ID)
+ ->grantPermission('restful post user_registration')
+ ->save();
+ }
+
+ /**
+ * Tests that only anonymous users can register users.
+ */
+ public function testRegisterUser() {
+ // Verify that an authenticated user cannot register a new user, despite
+ // being granted permission to do so because only anonymous users can
+ // register themselves, authenticated users with the necessary permissions
+ // can POST a new user to the "user" REST resource.
+ $user = $this->createUser();
+ $this->drupalLogin($user);
+ $this->registerRequest('palmer.eldritch');
+ $this->assertResponse('403', 'Only anonymous users can register users.');
+ $this->drupalLogout();
+
+ $user_settings = $this->config('user.settings');
+
+ // Test out different setting User Registration and Email Verification.
+ // Allow visitors to register with no email verification.
+ $user_settings->set('register', USER_REGISTER_VISITORS);
+ $user_settings->set('verify_mail', 0);
+ $user_settings->save();
+ $user = $this->registerUser('Palmer.Eldritch');
+ $this->assertFalse($user->isBlocked());
+ $this->assertFalse(empty($user->getPassword()));
+ $email_count = count($this->drupalGetMails());
+ $this->assertEqual(0, $email_count);
+
+ // Attempt to register without sending a password.
+ $this->registerRequest('Rick.Deckard', FALSE);
+ $this->assertResponse('422', 'No password provided');
+
+ // Allow visitors to register with email verification.
+ $user_settings->set('register', USER_REGISTER_VISITORS);
+ $user_settings->set('verify_mail', 1);
+ $user_settings->save();
+ $user = $this->registerUser('Jason.Taverner', FALSE);
+ $this->assertTrue(empty($user->getPassword()));
+ $this->assertTrue($user->isBlocked());
+ $this->assertMailString('body', 'You may now log in by clicking this link', 1);
+
+ // Attempt to register with a password when e-mail verification is on.
+ $this->registerRequest('Estraven', TRUE);
+ $this->assertResponse('422', 'A Password cannot be specified. It will be generated on login.');
+
+ // Allow visitors to register with Admin approval and e-mail verification.
+ $user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
+ $user_settings->set('verify_mail', 1);
+ $user_settings->save();
+ $user = $this->registerUser('Bob.Arctor', FALSE);
+ $this->assertTrue(empty($user->getPassword()));
+ $this->assertTrue($user->isBlocked());
+ $this->assertMailString('body', 'Your application for an account is', 2);
+ $this->assertMailString('body', 'Bob.Arctor has applied for an account', 2);
+
+ // Attempt to register with a password when e-mail verification is on.
+ $this->registerRequest('Ursula', TRUE);
+ $this->assertResponse('422', 'A Password cannot be specified. It will be generated on login.');
+
+ // Allow visitors to register with Admin approval and no email verification.
+ $user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
+ $user_settings->set('verify_mail', 0);
+ $user_settings->save();
+ $user = $this->registerUser('Argaven');
+ $this->assertFalse(empty($user->getPassword()));
+ $this->assertTrue($user->isBlocked());
+ $this->assertMailString('body', 'Your application for an account is', 2);
+ $this->assertMailString('body', 'Argaven has applied for an account', 2);
+
+ // Attempt to register without sending a password.
+ $this->registerRequest('Tibe', FALSE);
+ $this->assertResponse('422', 'No password provided');
+ }
+
+ /**
+ * Creates serialize user values.
+ *
+ * @param string $name
+ * The name of the user. Use only valid values for emails.
+ *
+ * @param bool $include_password
+ * Whether to include a password in the user values.
+ *
+ * @return string Serialized user values.
+ * Serialized user values.
+ */
+ protected function createSerializedUser($name, $include_password = TRUE) {
+ global $base_url;
+ // New user info to be serialized.
+ $data = [
+ "_links" =>
+ [
+ "type" => ["href" => $base_url . "/rest/type/user/user"],
+ ],
+ "langcode" => [
+ [
+ "value" => "en",
+ ],
+ ],
+ "name" => [
+ [
+ "value" => $name,
+ ],
+ ],
+ "mail" => [
+ [
+ "value" => "$name@example.com",
+ ],
+ ],
+ ];
+ if ($include_password) {
+ $data['pass']['value'] = 'SuperSecretPassword';
+ }
+
+ // Create a HAL+JSON version for the user entity we want to create.
+ $serialized = $this->container->get('serializer')
+ ->serialize($data, 'hal_json');
+ return $serialized;
+ }
+
+ /**
+ * Registers a user via REST resource.
+ *
+ * @param $name
+ * User name.
+ *
+ * @param bool $include_password
+ *
+ * @return bool|\Drupal\user\Entity\User
+ */
+ protected function registerUser($name, $include_password = TRUE) {
+ // Verify that an anonymous user can register.
+ $this->registerRequest($name, $include_password);
+ $this->assertResponse('200', 'HTTP response code is correct.');
+ $user = user_load_by_name($name);
+ $this->assertFalse(empty($user), 'User was create as expected');
+ return $user;
+ }
+
+ /**
+ * Make a REST user registration request.
+ *
+ * @param $name
+ * @param $include_password
+ */
+ protected function registerRequest($name, $include_password = TRUE) {
+ $serialized = $this->createSerializedUser($name, $include_password);
+ $this->httpRequest('/user/register', 'POST', $serialized, 'application/hal+json');
+ }
+
+}
diff --git a/core/modules/user/tests/src/Unit/UserRegistrationResourceTest.php b/core/modules/user/tests/src/Unit/UserRegistrationResourceTest.php
new file mode 100644
index 0000000..142685c
--- /dev/null
+++ b/core/modules/user/tests/src/Unit/UserRegistrationResourceTest.php
@@ -0,0 +1,151 @@
+logger = $this->prophesize(LoggerInterface::class)->reveal();
+
+ $this->userSettings = $this->prophesize(ImmutableConfig::class);
+
+ $this->currentUser = $this->prophesize(AccountInterface::class);
+
+ $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings->reveal(), $this->currentUser->reveal());
+ $this->reflection = new \ReflectionClass($this->testClass);
+ }
+
+ /**
+ * Tests that an exception is thrown when no data provided for the account.
+ */
+ public function testEmptyPost() {
+ $this->setExpectedException(BadRequestHttpException::class);
+ $this->testClass->post(NULL);
+ }
+
+ /**
+ * Tests that only new user accounts can be registered.
+ */
+ public function testExistedEntityPost() {
+ $entity = $this->prophesize(User::class);
+ $entity->isNew()->willReturn(FALSE);
+ $this->setExpectedException(BadRequestHttpException::class);
+
+ $this->testClass->post($entity->reveal());
+ }
+
+ /**
+ * Tests that admin permissions are required to register a user account.
+ */
+ public function testRegistrationAdminOnlyPost() {
+
+ $this->userSettings->get('register')->willReturn(USER_REGISTER_ADMINISTRATORS_ONLY);
+
+ $this->currentUser->isAnonymous()->willReturn(TRUE);
+
+ $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings->reveal(), $this->currentUser->reveal());
+
+ $entity = $this->prophesize(User::class);
+ $entity->isNew()->willReturn(TRUE);
+
+ $this->setExpectedException(AccessDeniedHttpException::class);
+
+ $this->testClass->post($entity->reveal());
+ }
+
+ /**
+ * Tests that only anonymous users can register users.
+ */
+ public function testRegistrationAnonymousOnlyPost() {
+ $this->currentUser->isAnonymous()->willReturn(FALSE);
+
+ $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings->reveal(), $this->currentUser->reveal());
+
+ $entity = $this->prophesize(User::class);
+ $entity->isNew()->willReturn(TRUE);
+
+ $this->setExpectedException(AccessDeniedHttpException::class);
+
+ $this->testClass->post($entity->reveal());
+ }
+
+}
diff --git a/core/modules/views/tests/src/Kernel/Handler/AreaEntityTest.php b/core/modules/views/tests/src/Kernel/Handler/AreaEntityTest.php
index af3d658..ebf9e05 100644
--- a/core/modules/views/tests/src/Kernel/Handler/AreaEntityTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/AreaEntityTest.php
@@ -2,9 +2,9 @@
namespace Drupal\Tests\views\Kernel\Handler;
-use Drupal\block\Entity\Block;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormState;
+use Drupal\simpletest\BlockCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Entity\View;
use Drupal\views\Views;
@@ -17,6 +17,8 @@
*/
class AreaEntityTest extends ViewsKernelTestBase {
+ use BlockCreationTrait;
+
/**
* Modules to enable.
*
@@ -42,14 +44,15 @@ protected function setUp($import_test_views = TRUE) {
* {@inheritdoc}
*/
protected function setUpFixtures() {
+ // Install the themes used for this test.
+ $this->container->get('theme_installer')->install(['bartik']);
+ $this->container->get('config.factory')->getEditable('system.theme')->set('default', 'bartik')->save();
+
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
$this->installConfig(['entity_test']);
- Block::create([
- 'id' => 'test_block',
- 'plugin' => 'system_main_block',
- ])->save();
+ $this->placeBlock('system_main_block', ['id' => 'test_block']);
parent::setUpFixtures();
}
diff --git a/core/modules/views/tests/src/Kernel/Handler/AreaOrderTest.php b/core/modules/views/tests/src/Kernel/Handler/AreaOrderTest.php
index 457c635..9ade514 100644
--- a/core/modules/views/tests/src/Kernel/Handler/AreaOrderTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/AreaOrderTest.php
@@ -2,7 +2,7 @@
namespace Drupal\Tests\views\Kernel\Handler;
-use Drupal\block\Entity\Block;
+use Drupal\simpletest\BlockCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Views;
@@ -14,6 +14,8 @@
*/
class AreaOrderTest extends ViewsKernelTestBase {
+ use BlockCreationTrait;
+
/**
* Modules to enable.
*
@@ -32,23 +34,21 @@ class AreaOrderTest extends ViewsKernelTestBase {
* {@inheritdoc}
*/
protected function setUpFixtures() {
- Block::create(
- [
- 'id' => 'bartik_branding',
- 'theme' => 'bartik',
- 'plugin' => 'system_branding_block',
- 'weight' => 1,
- ]
- )->save();
+ // Install the themes used for this test.
+ $this->container->get('theme_installer')->install(['bartik']);
+
+ $this->placeBlock('system_branding_block', [
+ 'id' => 'bartik_branding',
+ 'theme' => 'bartik',
+ 'plugin' => 'system_branding_block',
+ 'weight' => 1,
+ ]);
- Block::create(
- [
- 'id' => 'bartik_powered',
- 'theme' => 'bartik',
- 'plugin' => 'system_powered_by_block',
- 'weight' => 2,
- ]
- )->save();
+ $this->placeBlock('system_powered_by_block', [
+ 'id' => 'bartik_powered',
+ 'theme' => 'bartik',
+ 'weight' => 2,
+ ]);
parent::setUpFixtures();
}
diff --git a/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php b/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
index 6cdc4b3..e2108b3 100644
--- a/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
@@ -2,6 +2,7 @@
namespace Drupal\KernelTests\Core\Bootstrap;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
/**
@@ -17,14 +18,18 @@ class GetFilenameTest extends KernelTestBase {
public static $modules = ['system'];
/**
+ * {@inheritdoc}
+ */
+ public function register(ContainerBuilder $container) {
+ parent::register($container);
+ // Use the testing install profile.
+ $container->setParameter('install_profile', 'testing');
+ }
+
+ /**
* Tests that drupal_get_filename() works when the file is not in database.
*/
function testDrupalGetFilename() {
- // drupal_get_profile() is using obtaining the profile from state if the
- // install_state global is not set.
- global $install_state;
- $install_state['parameters']['profile'] = 'testing';
-
// Rebuild system.module.files state data.
// @todo Remove as part of https://www.drupal.org/node/2186491
drupal_static_reset('system_rebuild_module_data');
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 0da98e8..94a1e04 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -144,6 +144,11 @@
* @code
* 'prefix' => 'main_',
* @endcode
+ *
+ * Per-table prefixes are deprecated as of Drupal 8.2, and will be removed in
+ * Drupal 9.0. After that, only a single prefix for all tables will be
+ * supported.
+ *
* To provide prefixes for specific tables, set 'prefix' as an array.
* The array's keys are the table names and the values are the prefixes.
* The 'default' element is mandatory and holds the prefix for any tables