Update to Drupal 8.1.9. For more information, see https://www.drupal.org/project/drupal/releases/8.1.9

This commit is contained in:
Pantheon Automation 2016-09-07 13:26:21 -07:00 committed by Greg Anderson
parent f9f23cdf38
commit 09b113657a
125 changed files with 2307 additions and 385 deletions

View file

@ -90,6 +90,19 @@ INSTALLATION
mv drupal-x.y.z/* drupal-x.y.z/.htaccess drupal-x.y.z/.csslintrc drupal-x.y.z/.editorconfig drupal-x.y.z/.eslintignore drupal-x.y.z/.eslintrc drupal-x.y.z/.gitattributes /path/to/your/installation
You can also download the latest version of Drupal using Git on the command
line and set up a repository by following the instructions at
https://www.drupal.org/project/drupal/git-instructions for "Setting up
repository for the first time".
Once you have downloaded Drupal successfully, you may install Composer
globally using the instructions at
https://getcomposer.org/doc/00-intro.md#globally
With Composer installed, run the following command from the Drupal web root:
composer install
2. Create the Drupal database.
Because Drupal stores all site information in a database, the Drupal

View file

@ -23,9 +23,10 @@ Drupal 8
(Release Manager)
- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott
(Framework Manager)
Provisional membership:
- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser
(Framework Manager - Frontend)
Provisional membership: None at this time.
Drupal 7
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
@ -472,11 +473,13 @@ Views module
- Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett
- Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip
- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude
Views UI module
- Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner
- Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett
- Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip
- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude
Theme maintainers
@ -523,6 +526,9 @@ Multi-lingual
Web services
- Larry Garfield 'Crell' https://www.drupal.org/u/crell
Workflow Initiative
- Dick Olsson 'dixon_' https://www.drupal.org/u/dixon_
Provisional membership: None at this time.

View file

@ -1894,21 +1894,32 @@
function hook_cron() {
// Short-running operation example, not using a queue:
// Delete all expired records since the last cron run.
$expires = \Drupal::state()->get('mymodule.cron_last_run', REQUEST_TIME);
db_delete('mymodule_table')
$expires = \Drupal::state()->get('mymodule.last_check', 0);
\Drupal::database()->delete('mymodule_table')
->condition('expires', $expires, '>=')
->execute();
\Drupal::state()->set('mymodule.cron_last_run', REQUEST_TIME);
\Drupal::state()->set('mymodule.last_check', REQUEST_TIME);
// Long-running operation example, leveraging a queue:
// Fetch feeds from other sites.
$result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time AND refresh <> :never', array(
':time' => REQUEST_TIME,
':never' => AGGREGATOR_CLEAR_NEVER,
));
// Queue news feeds for updates once their refresh interval has elapsed.
$queue = \Drupal::queue('aggregator_feeds');
foreach ($result as $feed) {
$queue->createItem($feed);
$ids = \Drupal::entityManager()->getStorage('aggregator_feed')->getFeedIdsToRefresh();
foreach (Feed::loadMultiple($ids) as $feed) {
if ($queue->createItem($feed)) {
// Add timestamp to avoid queueing item more than once.
$feed->setQueuedTime(REQUEST_TIME);
$feed->save();
}
}
$ids = \Drupal::entityQuery('aggregator_feed')
->condition('queued', REQUEST_TIME - (3600 * 6), '<')
->execute();
if ($ids) {
$feeds = Feed::loadMultiple($ids);
foreach ($feeds as $feed) {
$feed->setQueuedTime(0);
$feed->save();
}
}
}
@ -2036,7 +2047,7 @@ function hook_mail_alter(&$message) {
* An array of parameters supplied by the caller of
* MailManagerInterface->mail().
*
* @see \Drupal\Core\Mail\MailManagerInterface->mail()
* @see \Drupal\Core\Mail\MailManagerInterface::mail()
*/
function hook_mail($key, &$message, $params) {
$account = $params['account'];

View file

@ -1220,7 +1220,9 @@ function file_directory_os_temp() {
foreach ($directories as $directory) {
if (is_dir($directory) && is_writable($directory)) {
return $directory;
// Both sys_get_temp_dir() and ini_get('upload_tmp_dir') can return paths
// with a trailing directory separator.
return rtrim($directory, DIRECTORY_SEPARATOR);
}
}
return FALSE;

View file

@ -369,7 +369,9 @@ function install_begin_request($class_loader, &$install_state) {
$install_state['config_verified'] = FALSE;
}
$install_state['database_verified'] = install_verify_database_settings($site_path);
$install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
// A valid settings.php has database settings and a hash_salt value. Other
// settings like config_directories will be checked by system_requirements().
$install_state['settings_verified'] = $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE);
// Install factory tables only after checking the database.
if ($install_state['database_verified'] && $install_state['database_ready']) {

View file

@ -596,7 +596,7 @@ function update_already_performed($module, $number) {
* An array of return values obtained by merging the results of the
* hook_update_dependencies() implementations in all installed modules.
*
* @see \Drupal::moduleHandler()->invokeAll()
* @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll()
* @see hook_update_dependencies()
*/
function update_retrieve_dependencies() {

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.1.8';
const VERSION = '8.1.9';
/**
* Core API compatibility.

View file

@ -23,7 +23,7 @@ class Schema extends DatabaseSchema {
* This is collected by DatabaseConnection_pgsql->queryTableInformation(),
* by introspecting the database.
*
* @see DatabaseConnection_pgsql->queryTableInformation()
* @see \Drupal\Core\Database\Driver\pgsql\Schema::queryTableInformation()
* @var array
*/
protected $tableInformation = array();

View file

@ -51,10 +51,23 @@ class FormattedDateDiff implements RenderableInterface, CacheableDependencyInter
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->maxAge;
}
/**
* The maximum age for which this object may be cached.
*
* @return int
* The maximum time in seconds that this object may be cached.
*
* @deprecated in Drupal 8.1.9 and will be removed before Drupal 9.0.0. Use
* \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge() instead.
*/
public function getMaxAge() {
return $this->maxAge;
return $this->getCacheMaxAge();
}
/**

View file

@ -297,7 +297,7 @@ class EntityAutocomplete extends Textfield {
$multiples[] = $name . ' (' . $id . ')';
}
$params['@id'] = $id;
$form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples))));
$form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)) + $params));
}
else {
// Take the one and only matching entity.

View file

@ -17,13 +17,26 @@ class EntityAccessCheck implements AccessInterface {
* Checks access to the entity operation on the given route.
*
* The value of the '_entity_access' key must be in the pattern
* 'entity_type.operation.' The entity type must match the {entity_type}
* parameter in the route pattern. This will check a node for 'update' access:
* 'entity_slug_name.operation.' For example, this will check a node for
* 'update' access:
* @code
* pattern: '/foo/{node}/bar'
* requirements:
* _entity_access: 'node.update'
* @endcode
* And this will check a dynamic entity type:
* @code
* example.route:
* path: foo/{entity_type}/{example}
* requirements:
* _entity_access: example.update
* options:
* parameters:
* example:
* type: entity:{entity_type}
* @endcode
* @see \Drupal\Core\ParamConverter\EntityConverter
*
* Available operations are 'view', 'update', 'create', and 'delete'.
*
* @param \Symfony\Component\Routing\Route $route

View file

@ -31,7 +31,6 @@ interface QueryInterface extends AlterableInterface {
* ->condition('greetings', 'merhaba', '=', 'tr')
* ->condition('greetings.value', 'siema', '=', 'pl')
* ->execute();
* $entity_ids = $query->execute();
* @endcode
*
* @param $field

View file

@ -1543,6 +1543,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$item_query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC))
->fields('t')
->condition('entity_id', $row['entity_id'])
->condition('deleted', 1)
->orderBy('delta');
foreach ($item_query->execute() as $item_row) {
@ -1581,10 +1582,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
$this->database->delete($table_name)
->condition('revision_id', $revision_id)
->condition('deleted', 1)
->execute();
if ($this->entityType->isRevisionable()) {
$this->database->delete($revision_name)
->condition('revision_id', $revision_id)
->condition('deleted', 1)
->execute();
}
}
@ -1684,6 +1687,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
* Whether the field has been already deleted.
*/
protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) {
// Configurable fields are marked for deletion.
if ($storage_definition instanceOf FieldStorageConfigInterface) {
return $storage_definition->isDeleted();
}
// For non configurable fields check whether they are still in the last
// installed schema repository.
return !array_key_exists($storage_definition->getName(), $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityTypeId));
}

View file

@ -1252,21 +1252,6 @@ function hook_ENTITY_TYPE_revision_delete(Drupal\Core\Entity\EntityInterface $en
}
}
/**
* Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface.
*
* @param \Drupal\Core\Entity\Query\QueryInterface $query
* Note the $query->altered attribute which is TRUE in case the query has
* already been altered once. This happens with cloned queries.
* If there is a pager, then such a cloned query will be executed to count
* all elements. This query can be detected by checking for
* ($query->pager && $query->count), allowing the driver to return 0 from
* the count query and disable the pager.
*/
function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query) {
// @todo: code example.
}
/**
* Act on entities being assembled before rendering.
*
@ -1860,7 +1845,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
*
* @param string $operation
* The operation to be performed. See
* \Drupal\Core\Access\AccessibleInterface::access() for possible values.
* \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess()
* for possible values.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param \Drupal\Core\Session\AccountInterface $account
@ -1871,6 +1857,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess()
*/
function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) {
if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') {

View file

@ -157,12 +157,6 @@ function hook_archiver_info_alter(&$info) {
* will always be passed the full path to the root of the site that should
* be used to restrict where file transfer operations can occur (the $jail)
* and an array of settings values returned by the settings form.
* - 'file': Required. The include file containing the FileTransfer class.
* This should be a separate .inc file, not just the .module file, so that
* the minimum possible code is loaded when authorize.php is running.
* - 'file path': Optional. The directory (relative to the Drupal root)
* where the include file lives. If not defined, defaults to the base
* directory of the module implementing the hook.
* - 'weight': Optional. Integer weight used for sorting connection types on
* the authorize.php form.
*

View file

@ -41,7 +41,7 @@ abstract class ConfigFormBase extends FormBase {
'#button_type' => 'primary',
);
// By default, render the form using theme_system_config_form().
// By default, render the form using system-config-form.html.twig.
$form['#theme'] = 'system_config_form';
return $form;

View file

@ -172,7 +172,7 @@ class ProjectInfo {
* @return
* Array of .info.yml file data we need for the update manager.
*
* @see \Drupal\Core\Utility\ProjectInfo->processInfoList()
* @see \Drupal\Core\Utility\ProjectInfo::processInfoList()
*/
function filterProjectInfo($info, $additional_whitelist = array()) {
$whitelist = array(

View file

@ -62,7 +62,7 @@
*/
prepareDialogButtons: function ($dialog) {
var buttons = [];
var $buttons = $dialog.find('.form-actions input[type=submit]');
var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button');
$buttons.each(function () {
// Hidden form buttons need special attention. For browser consistency,
// the button needs to be "visible" in order to have the enter key fire
@ -74,15 +74,23 @@
width: 0,
height: 0,
padding: 0,
border: 0
border: 0,
overflow: 'hidden'
});
buttons.push({
text: $originalButton.html() || $originalButton.attr('value'),
class: $originalButton.attr('class'),
click: function (e) {
// If the original button is an anchor tag, triggering the "click"
// event will not simulate a click. Use the click method instead.
if ($originalButton.is('a')) {
$originalButton[0].click();
}
else {
$originalButton.trigger('mousedown').trigger('mouseup').trigger('click');
e.preventDefault();
}
}
});
});
return buttons;

View file

@ -108,10 +108,10 @@ class ActionListBuilder extends ConfigEntityListBuilder {
* {@inheritdoc}
*/
public function render() {
$build['action_header']['#markup'] = '<h3>' . t('Available actions:') . '</h3>';
$build['action_header']['#markup'] = '<h3>' . $this->t('Available actions:') . '</h3>';
$build['action_table'] = parent::render();
if (!$this->hasConfigurableActions) {
unset($build['action_table']['#header']['operations']);
unset($build['action_table']['table']['#header']['operations']);
}
$build['action_admin_manage_form'] = \Drupal::formBuilder()->getForm('Drupal\action\Form\ActionAdminManageForm');
return $build;

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\action\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Test behaviors when visiting the action listing page.
*
* @group action
*/
class ActionListTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('action');
/**
* Tests the behavior when there are no actions to list in the admin page.
*/
public function testEmptyActionList() {
// Create a user with permission to view the actions administration pages.
$this->drupalLogin($this->drupalCreateUser(['administer actions']));
// Ensure the empty text appears on the action list page.
/** @var $storage \Drupal\Core\Entity\EntityStorageInterface */
$storage = $this->container->get('entity.manager')->getStorage('action');
$actions = $storage->loadMultiple();
$storage->delete($actions);
$this->drupalGet('/admin/config/system/actions');
$this->assertRaw('There is no Action yet.');
}
}

View file

@ -104,9 +104,15 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
static::invalidateBlockPluginCache();
}
// Invalidate the block cache to update custom block-based derivatives.
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
static::invalidateBlockPluginCache();
}
/**
@ -237,4 +243,12 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
return $this;
}
/**
* Invalidates the block plugin cache after changes and deletions.
*/
protected static function invalidateBlockPluginCache() {
// Invalidate the block cache to update custom block-based derivatives.
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
}

View file

@ -44,6 +44,8 @@ class BlockContent extends DeriverBase implements ContainerDeriverInterface {
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$block_contents = $this->blockContentStorage->loadMultiple();
// Reset the discovered definitions.
$this->derivatives = [];
/** @var $block_content \Drupal\block_content\Entity\BlockContent */
foreach ($block_contents as $block_content) {
$this->derivatives[$block_content->uuid()] = $base_plugin_definition;

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\block_content\Kernel;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Plugin\PluginBase;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that deleting a block clears the cached definitions.
*
* @group block_content
*/
class BlockContentDeletionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['block', 'block_content', 'system', 'user'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installSchema('system', ['sequence']);
$this->installEntitySchema('user');
$this->installEntitySchema('block_content');
}
/**
* Tests deleting a block_content updates the discovered block plugin.
*/
public function testDeletingBlockContentShouldClearPluginCache() {
// Create a block content type.
$block_content_type = BlockContentType::create([
'id' => 'spiffy',
'label' => 'Mucho spiffy',
'description' => "Provides a block type that increases your site's spiffiness by upto 11%",
]);
$block_content_type->save();
// And a block content entity.
$block_content = BlockContent::create([
'info' => 'Spiffy prototype',
'type' => 'spiffy',
]);
$block_content->save();
// Make sure the block content provides a derivative block plugin in the
// block repository.
/** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */
$block_manager = $this->container->get('plugin.manager.block');
$plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid();
$this->assertTrue($block_manager->hasDefinition($plugin_id));
// Now delete the block content entity.
$block_content->delete();
// The plugin should no longer exist.
$this->assertFalse($block_manager->hasDefinition($plugin_id));
}
}

View file

@ -20,7 +20,7 @@ class CommentVariablePerCommentType extends CommentVariable {
$return = array();
foreach ($node_types as $node_type => $data) {
// Only 2 comment types depending on subject field visibility.
if (empty($data['comment_subject_field'])) {
if (!empty($data['comment_subject_field'])) {
// Default label and description should be set in migration.
$return['comment'] = array(
'comment_type' => 'comment',

View file

@ -40,7 +40,7 @@ class MigrateCommentTest extends MigrateDrupal7TestBase {
),
));
$this->executeMigrations([
'd7_node:test_content_type',
'd7_node',
'd7_comment_type',
'd7_comment',
]);

View file

@ -24,10 +24,10 @@ class CommentVariablePerCommentTypeTest extends MigrateSqlSourceTestCase {
// Each result will also include a label and description, but those are
// static values set by the source plugin and don't need to be asserted.
array(
'comment_type' => 'comment_no_subject',
'comment_type' => 'comment',
),
array(
'comment_type' => 'comment',
'comment_type' => 'comment_no_subject',
),
);

View file

@ -44,6 +44,7 @@ class StorageReplaceDataWrapper implements StorageInterface {
public function __construct(StorageInterface $storage, $collection = StorageInterface::DEFAULT_COLLECTION) {
$this->storage = $storage;
$this->collection = $collection;
$this->replacementData[$collection] = [];
}
/**
@ -104,7 +105,7 @@ class StorageReplaceDataWrapper implements StorageInterface {
$this->replacementData[$this->collection][$new_name] = $this->replacementData[$this->collection][$name];
unset($this->replacementData[$this->collection][$name]);
}
return $this->rename($name, $new_name);
return $this->storage->rename($name, $new_name);
}
/**
@ -164,8 +165,10 @@ class StorageReplaceDataWrapper implements StorageInterface {
* {@inheritdoc}
*/
public function createCollection($collection) {
$this->collection = $collection;
return $this->storage->createCollection($collection);
return new static(
$this->storage->createCollection($collection),
$collection
);
}
/**

View file

@ -20,7 +20,10 @@ class ConfigSingleImportExportTest extends WebTestBase {
public static $modules = [
'block',
'config',
'config_test'
'config_test',
// Adding language module makes it possible to involve non-default
// (language.xx) collections in import/export operations.
'language',
];
protected function setUp() {

View file

@ -3,12 +3,12 @@
namespace Drupal\field\Plugin\migrate\process;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -21,7 +21,7 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
/**
* The cckfield plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -41,12 +41,12 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_plugin_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_plugin_manager
* The cckfield plugin manager.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration being run.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $cck_plugin_manager, MigrationInterface $migration = NULL) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateCckFieldPluginManagerInterface $cck_plugin_manager, MigrationInterface $migration = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->cckPluginManager = $cck_plugin_manager;
$this->migration = $migration;
@ -72,7 +72,8 @@ class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
$field_type = is_array($value) ? $value[0] : $value;
try {
return $this->cckPluginManager->createInstance($field_type, [], $this->migration)->getFieldType($row);
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this->migration);
return $this->cckPluginManager->createInstance($plugin_id, [], $this->migration)->getFieldType($row);
}
catch (PluginNotFoundException $e) {
return parent::transform($value, $migrate_executable, $row, $destination_property);

View file

@ -208,6 +208,7 @@ class EntityReferenceAdminTest extends WebTestBase {
'id' => 'node_test_view',
'label' => 'Node Test View',
'show[wizard_key]' => 'node',
'show[sort]' => 'none',
'page[create]' => 1,
'page[title]' => 'Test Node View',
'page[path]' => 'test/node/view',
@ -221,6 +222,14 @@ class EntityReferenceAdminTest extends WebTestBase {
'style_options[search_fields][title]' => 'title',
);
$this->drupalPostForm(NULL, $edit, t('Apply'));
// Set sort to NID ascending.
$edit = [
'name[node_field_data.nid]' => 1,
];
$this->drupalPostForm('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort', $edit, t('Add and configure sort criteria'));
$this->drupalPostForm(NULL, NULL, t('Apply'));
$this->drupalPostForm('admin/structure/views/view/node_test_view/edit/entity_reference_1', array(), t('Save'));
$this->clickLink(t('Settings'));
@ -301,6 +310,7 @@ class EntityReferenceAdminTest extends WebTestBase {
$this->assertText(t('Multiple entities match this reference;'));
$this->assertText(t("@node1", ['@node1' => $node1->getTitle() . ' (' . $node1->id() . ')']));
$this->assertText(t("@node2", ['@node2' => $node2->getTitle() . ' (' . $node2->id() . ')']));
$this->assertText(t('Specify the one you want by appending the id in parentheses, like "@example".', ['@example' => $node2->getTitle() . ' (' . $node2->id() . ')']));
$edit = array(
'title[0][value]' => 'Test',

View file

@ -210,6 +210,98 @@ class BulkDeleteTest extends FieldKernelTestBase {
$this->assertFalse(array_diff($found, array_keys($this->entities)));
}
/**
* Tests that recreating a field with the name as a deleted field works.
*/
public function testPurgeWithDeletedAndActiveField() {
$bundle = reset($this->bundles);
// Create another field storage.
$field_name = 'bf_3';
$deleted_field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => $this->entityTypeId,
'type' => 'test_field',
'cardinality' => 1
));
$deleted_field_storage->save();
// Create the field.
FieldConfig::create([
'field_storage' => $deleted_field_storage,
'bundle' => $bundle,
])->save();
for ($i = 0; $i < 20; $i++) {
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId)
->create(array('type' => $bundle));
$entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
$entity->save();
}
// Delete the field.
$deleted_field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
$deleted_field->delete();
$deleted_field_uuid = $deleted_field->uuid();
// Reload the field storage.
$field_storages = entity_load_multiple_by_properties('field_storage_config', array('uuid' => $deleted_field_storage->uuid(), 'include_deleted' => TRUE));
$deleted_field_storage = reset($field_storages);
// Create the field again.
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => $this->entityTypeId,
'type' => 'test_field',
'cardinality' => 1
));
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $bundle,
])->save();
// The field still exists, deleted, with the same field name.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $deleted_field_uuid, 'include_deleted' => TRUE));
$this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->isDeleted(), 'The field exists and is deleted');
$this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->getName() == $field_name);
for ($i = 0; $i < 10; $i++) {
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId)
->create(array('type' => $bundle));
$entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
$entity->save();
}
// Check that the two field storages have different tables.
$storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $storage->getTableMapping();
$deleted_table_name = $table_mapping->getDedicatedDataTableName($deleted_field_storage, TRUE);
$active_table_name = $table_mapping->getDedicatedDataTableName($field_storage);
field_purge_batch(50);
// Ensure the new field still has its table and the deleted one has been
// removed.
$this->assertTrue(\Drupal::database()->schema()->tableExists($active_table_name));
$this->assertFalse(\Drupal::database()->schema()->tableExists($deleted_table_name));
// The field has been removed from the system.
$fields = entity_load_multiple_by_properties('field_config', array('field_storage_uuid' => $deleted_field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 0, 'The field is gone');
// Verify there are still 10 entries in the main table.
$count = \Drupal::database()
->select('entity_test__' . $field_name, 'f')
->fields('f', array('entity_id'))
->condition('bundle', $bundle)
->countQuery()
->execute()
->fetchField();
$this->assertEqual($count, 10);
}
/**
* Verify that field data items and fields are purged when a field storage is
* deleted.

View file

@ -2,6 +2,7 @@
namespace Drupal\Tests\field\Kernel\EntityReference\Views;
use Drupal\entity_test\Entity\EntityTestMulChanged;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestMul;
@ -27,6 +28,7 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
*/
public static $testViews = array(
'test_entity_reference_entity_test_view',
'test_entity_reference_entity_test_view_long',
'test_entity_reference_reverse_entity_test_view',
'test_entity_reference_entity_test_mul_view',
'test_entity_reference_reverse_entity_test_mul_view',
@ -55,6 +57,7 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
$this->installEntitySchema('entity_test_mul');
$this->installEntitySchema('entity_test_mul_changed');
// Create reference from entity_test to entity_test_mul.
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_data', 'field_test_data', 'entity_test_mul');
@ -62,6 +65,12 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
// Create reference from entity_test_mul to entity_test.
$this->createEntityReferenceField('entity_test_mul', 'entity_test_mul', 'field_data_test', 'field_data_test', 'entity_test');
// Create another field for testing with a long name. So it's storage name
// will become hashed. Use entity_test_mul_changed, so the resulting field
// tables created will be greater than 48 chars long.
// @see \Drupal\Core\Entity\Sql\DefaultTableMapping::generateFieldTableName()
$this->createEntityReferenceField('entity_test_mul_changed', 'entity_test_mul_changed', 'field_test_data_with_a_long_name', 'field_test_data_with_a_long_name', 'entity_test');
ViewTestData::createTestViews(get_class($this), array('entity_reference_test_views'));
}
@ -124,7 +133,6 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['field_test_data']->id(), 1);
$this->assertEqual($row->_relationship_entities['field_test_data']->bundle(), 'entity_test_mul');
}
// Check the backwards reference view.
@ -225,4 +233,47 @@ class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
}
}
/**
* Tests views data generated for relationship.
*
* @see entity_reference_field_views_data()
*/
public function testDataTableRelationshipWithLongFieldName() {
// Create some test entities which link each other.
$referenced_entity = EntityTest::create();
$referenced_entity->save();
$entity = EntityTestMulChanged::create();
$entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id();
$entity->save();
$this->entities[] = $entity;
$entity = EntityTestMulChanged::create();
$entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id();
$entity->save();
$this->entities[] = $entity;
Views::viewsData()->clear();
// Check an actual test view.
$view = Views::getView('test_entity_reference_entity_test_view_long');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
// Check that the actual ID of the entity is the expected one.
$this->assertEqual($row->id, $this->entities[$index]->id());
// Also check that we have the correct result entity.
$this->assertEqual($row->_entity->id(), $this->entities[$index]->id());
// Test the forward relationship.
//$this->assertEqual($row->entity_test_entity_test_mul__field_data_test_id, 1);
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['field_test_data_with_a_long_name']->id(), 1);
$this->assertEqual($row->_relationship_entities['field_test_data_with_a_long_name']->bundle(), 'entity_test');
}
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\field\Kernel\Migrate\d7;
use Drupal\field\Entity\FieldConfig;
use Drupal\migrate\MigrateExecutable;
/**
* Migrates and rolls back Drupal 7 fields.
*
* @group field
*/
class RollbackFieldInstanceTest extends MigrateFieldInstanceTest {
/**
* Tests migrating D7 fields to field_storage_config entities, then rolling back.
*/
public function testFieldInstances() {
// Test that the field instances have migrated (prior to rollback).
parent::testFieldInstances();
$this->executeRollback('d7_field_instance');
$this->executeRollback('d7_field');
// Check that field instances have been rolled back.
$field_instance_ids = [
'comment.comment_node_page.comment_body',
'node.page.body',
'comment.comment_node_article.comment_body',
'node.article.body',
'node.article.field_tags',
'node.article.field_image',
'comment.comment_node_blog.comment_body',
'node.blog.body',
'comment.comment_node_book.comment_body',
'node.book.body',
'node.forum.taxonomy_forums',
'comment.comment_node_forum.comment_body',
'node.forum.body',
'comment.comment_node_test_content_type.comment_body',
'node.test_content_type.field_boolean',
'node.test_content_type.field_email',
'node.test_content_type.field_phone',
'node.test_content_type.field_date',
'node.test_content_type.field_date_with_end_time',
'node.test_content_type.field_file',
'node.test_content_type.field_float',
'node.test_content_type.field_images',
'node.test_content_type.field_integer',
'node.test_content_type.field_link',
'node.test_content_type.field_text_list',
'node.test_content_type.field_integer_list',
'node.test_content_type.field_long_text',
'node.test_content_type.field_term_reference',
'node.test_content_type.field_text',
'comment.comment_node_test_content_type.field_integer',
'user.user.field_file',
];
foreach ($field_instance_ids as $field_instance_id) {
$this->assertNull(FieldConfig::load($field_instance_id));
}
}
/**
* Executes a single rollback.
*
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to rollback, or its ID.
*/
protected function executeRollback($migration) {
if (is_string($migration)) {
$this->migration = $this->getMigration($migration);
}
else {
$this->migration = $migration;
}
(new MigrateExecutable($this->migration, $this))->rollback();
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\Tests\field\Kernel\Migrate\d7;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\migrate\MigrateExecutable;
/**
* Migrates and rolls back Drupal 7 fields.
*
* @group field
*/
class RollbackFieldTest extends MigrateFieldTest {
/**
* Tests migrating D7 fields to field_storage_config entities, then rolling back.
*/
public function testFields() {
// Test that the fields have migrated (prior to rollback).
parent::testFields();
$this->executeRollback('d7_field');
// Check that fields have been rolled back.
$rolled_back_field_ids = [
'comment.field_integer',
'node.taxonomy_forums',
'node.field_integer',
'node.field_tags',
'node.field_term_reference',
'node.field_text_list',
'node.field_text',
'node.field_phone',
'node.field_file',
'node.field_images',
'node.field_image',
'node.field_long_text',
'node.field_date_with_end_time',
'node.field_integer_list',
'node.field_date',
'node.field_link',
'node.field_float',
'node.field_boolean',
'node.field_email',
'user.field_file',
];
foreach ($rolled_back_field_ids as $field_id) {
$this->assertNull(FieldStorageConfig::load($field_id));
}
// Check that fields that should persist have not been rolled back.
$non_rolled_back_field_ids = [
'node.body',
'comment.comment_body',
];
foreach ($non_rolled_back_field_ids as $field_id) {
$this->assertNotNull(FieldStorageConfig::load($field_id));
}
}
/**
* Executes a single rollback.
*
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to rollback, or its ID.
*/
protected function executeRollback($migration) {
if (is_string($migration)) {
$this->migration = $this->getMigration($migration);
}
else {
$this->migration = $migration;
}
(new MigrateExecutable($this->migration, $this))->rollback();
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Drupal\Tests\field\Kernel\Migrate\d7;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\migrate\MigrateExecutable;
/**
* Migrates and rolls back Drupal 7 view modes.
*
* @group field
*/
class RollbackViewModesTest extends MigrateViewModesTest {
/**
* Tests migrating D7 view modes, then rolling back.
*/
public function testMigration() {
// Test that the view modes have migrated (prior to rollback).
parent::testMigration();
$this->executeRollback('d7_view_modes');
// Check that view modes have been rolled back.
$view_mode_ids = [
'comment.full',
'node.teaser',
'node.full',
'user.full',
];
foreach ($view_mode_ids as $view_mode_id) {
$this->assertNull(EntityViewMode::load($view_mode_id));
}
}
/**
* Executes a single rollback.
*
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to rollback, or its ID.
*/
protected function executeRollback($migration) {
if (is_string($migration)) {
$this->migration = $this->getMigration($migration);
}
else {
$this->migration = $migration;
}
(new MigrateExecutable($this->migration, $this))->rollback();
}
}

View file

@ -81,9 +81,11 @@ class FileFieldItemList extends EntityReferenceFieldItemList {
parent::delete();
$entity = $this->getEntity();
// Delete all file usages within this entity.
// If a translation is deleted only decrement the file usage by one. If the
// default translation is deleted remove all file usages within this entity.
$count = $entity->isDefaultTranslation() ? 0 : 1;
foreach ($this->referencedEntities() as $file) {
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id(), 0);
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id(), $count);
}
}

View file

@ -2,6 +2,13 @@
namespace Drupal\Tests\file\Kernel;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests file usage functions.
*
@ -203,4 +210,57 @@ class UsageTest extends FileManagedUnitTestBase {
$this->assertTrue(file_exists($perm_new->getFileUri()), 'New permanent file was correctly ignored.');
}
/**
* Tests file usage with translated entities.
*/
public function testFileUsageWithEntityTranslation() {
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
$file_usage = $this->container->get('file.usage');
$this->enableModules(['node', 'language']);
$this->installEntitySchema('node');
$this->installSchema('node', ['node_access']);
// Activate English and Romanian languages.
ConfigurableLanguage::create(['id' => 'en'])->save();
ConfigurableLanguage::create(['id' => 'ro'])->save();
NodeType::create(['type' => 'page'])->save();
ContentLanguageSettings::loadByEntityTypeBundle('node', 'page')
->setLanguageAlterable(FALSE)
->setDefaultLangcode('en')
->save();
// Create a file field attached to 'page' node-type.
FieldStorageConfig::create([
'type' => 'file',
'entity_type' => 'node',
'field_name' => 'file',
])->save();
FieldConfig::create([
'entity_type' => 'node',
'bundle' => 'page',
'field_name' => 'file',
'label' => 'File',
])->save();
// Create a node, attach a file and add a Romanian translation.
$node = Node::create(['type' => 'page', 'title' => 'Page']);
$node
->set('file', $file = $this->createFile())
->addTranslation('ro', $node->getTranslation('en')->toArray())
->save();
// Check that the file is used twice.
$usage = $file_usage->listUsage($file);
$this->assertEquals(2, $usage['file']['node'][$node->id()]);
// Remove the Romanian translation.
$node->removeTranslation('ro');
$node->save();
// Check that one usage has been removed and is used only once now.
$usage = $file_usage->listUsage($file);
$this->assertEquals(1, $usage['file']['node'][$node->id()]);
}
}

View file

@ -16,11 +16,16 @@ process:
key: '@id'
process:
id:
plugin: static_map
default_value: filter_null
# If the filter ID cannot be mapped, it will be passed through
# unchanged because the bypass flag is set. The filter_id plugin
# will flatten the input value and default it to filter_null (the
# fallback filter plugin ID) if the flattened input value is not
# a valid plugin ID.
plugin: filter_id
source:
- module
- delta
bypass: true
map:
filter:
- filter_html

View file

@ -15,11 +15,16 @@ process:
key: '@id'
process:
id:
plugin: static_map
# If the filter ID cannot be mapped, it will pass through unmodified
# because the bypass flag is set. When the user actually tries to
# view text through an invalid filter plugin, the filter system will
# fall back to filter_null and display a helpful error message.
plugin: filter_id
bypass: true
source: name
map:
php_code: filter_null
# No need to map anything -- filter plugin IDs haven't changed since
# Drupal 7.
map: { }
settings:
plugin: filter_settings
source: settings

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\filter\Plugin\migrate\process;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "filter_id"
* )
*/
class FilterID extends StaticMap implements ContainerFactoryPluginInterface {
/**
* The filter plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface|\Drupal\Component\Plugin\FallbackPluginManagerInterface
*/
protected $filterManager;
/**
* FilterID constructor.
*
* @param array $configuration
* Plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Component\Plugin\PluginManagerInterface $filter_manager
* The filter plugin manager.
* @param TranslationInterface $translator
* (optional) The string translation service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $filter_manager, TranslationInterface $translator = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->filterManager = $filter_manager;
$this->stringTranslation = $translator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.filter'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$plugin_id = parent::transform($value, $migrate_executable, $row, $destination_property);
// If the static map is bypassed on failure, the returned plugin ID will be
// an array if $value was. Plugin IDs cannot be arrays, so flatten it before
// passing it into the filter manager.
if (is_array($plugin_id)) {
$plugin_id = implode(':', $plugin_id);
}
if ($this->filterManager->hasDefinition($plugin_id)) {
return $plugin_id;
}
else {
$fallback = $this->filterManager->getFallbackPluginId($plugin_id);
$message = $this->t('Filter @plugin_id could not be mapped to an existing filter plugin; defaulting to @fallback.', [
'@plugin_id' => $plugin_id,
'@fallback' => $fallback,
]);
$migrate_executable->saveMessage((string) $message, MigrationInterface::MESSAGE_WARNING);
return $fallback;
}
}
}

View file

@ -3,6 +3,7 @@
namespace Drupal\Tests\filter\Kernel\Migrate\d6;
use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterFormatInterface;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
@ -39,14 +40,18 @@ class MigrateFilterFormatTest extends MigrateDrupal6TestBase {
$this->assertFalse(isset($filters['filter_html_image_secure']));
// Check variables migrated into filter.
$this->assertIdentical('<a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']);
$this->assertIdentical(TRUE, $filters['filter_html']['settings']['filter_html_help']);
$this->assertIdentical(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
$this->assertIdentical(72, $filters['filter_url']['settings']['filter_url_length']);
$this->assertSame('<a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']);
$this->assertSame(TRUE, $filters['filter_html']['settings']['filter_html_help']);
$this->assertSame(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
$this->assertSame(72, $filters['filter_url']['settings']['filter_url_length']);
// Check that the PHP code filter is converted to filter_null.
$filters = FilterFormat::load('php_code')->get('filters');
$this->assertTrue(isset($filters['filter_null']));
// Assert that the php_code format was migrated with filter_null in the
// php_code filter's place.
$filter_format = FilterFormat::load('php_code');
$this->assertInstanceOf(FilterFormatInterface::class, $filter_format);
$filters = $filter_format->get('filters');
$this->assertArrayHasKey('filter_null', $filters);
$this->assertArrayNotHasKey('php_code', $filters);
}
}

View file

@ -42,13 +42,13 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
protected function assertEntity($id, $label, array $enabled_filters, $weight) {
/** @var \Drupal\filter\FilterFormatInterface $entity */
$entity = FilterFormat::load($id);
$this->assertTrue($entity instanceof FilterFormatInterface);
$this->assertIdentical($label, $entity->label());
$this->assertInstanceOf(FilterFormatInterface::class, $entity);
$this->assertSame($label, $entity->label());
// get('filters') will return enabled filters only, not all of them.
$this->assertIdentical(array_keys($enabled_filters), array_keys($entity->get('filters')));
$this->assertIdentical($weight, $entity->get('weight'));
$this->assertSame(array_keys($enabled_filters), array_keys($entity->get('filters')));
$this->assertSame($weight, $entity->get('weight'));
foreach ($entity->get('filters') as $filter_id => $filter) {
$this->assertIdentical($filter['weight'], $enabled_filters[$filter_id]);
$this->assertSame($filter['weight'], $enabled_filters[$filter_id]);
}
}
@ -68,15 +68,17 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
// Ensure that filter-specific settings were migrated.
/** @var \Drupal\filter\FilterFormatInterface $format */
$format = FilterFormat::load('filtered_html');
$this->assertInstanceOf(FilterFormatInterface::class, $format);
$config = $format->filters('filter_html')->getConfiguration();
$this->assertIdentical('<div> <span> <ul type> <li> <ol start type> <a href hreflang> <img src alt height width>', $config['settings']['allowed_html']);
$this->assertSame('<div> <span> <ul type> <li> <ol start type> <a href hreflang> <img src alt height width>', $config['settings']['allowed_html']);
$config = $format->filters('filter_url')->getConfiguration();
$this->assertIdentical(128, $config['settings']['filter_url_length']);
$this->assertSame(128, $config['settings']['filter_url_length']);
// The php_code format gets migrated, but the php_code filter is changed to
// filter_null.
$filters = FilterFormat::load('php_code')->get('filters');
$this->assertTrue(isset($filters['filter_null']));
$format = FilterFormat::load('php_code');
$this->assertInstanceOf(FilterFormatInterface::class, $format);
$this->assertArrayHasKey('filter_null', $format->get('filters'));
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Drupal\Tests\filter\Kernel\Plugin\migrate\process;
use Drupal\filter\Plugin\migrate\process\FilterID;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Unit tests of the filter_id plugin.
*
* @coversDefaultClass \Drupal\filter\Plugin\migrate\process\FilterID
* @group filter
*/
class FilterIdTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['filter'];
/**
* The mocked MigrateExecutable.
*
* @var MigrateExecutableInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $executable;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executable = $this->getMock(MigrateExecutableInterface::class);
}
/**
* Tests the filter_id plugin.
*
* @param mixed $value
* The input value to the plugin.
* @param string $expected_value
* The output value expected from the plugin.
* @param string $invalid_id
* (optional) The invalid plugin ID which is expected to be logged by the
* MigrateExecutable object.
*
* @dataProvider testProvider
*
* @covers ::transform
*/
public function test($value, $expected_value, $invalid_id = NULL) {
$configuration = [
'bypass' => TRUE,
'map' => [
'foo' => 'filter_html',
'baz' => 'php_code',
],
];
$plugin = FilterID::create($this->container, $configuration, 'filter_id', []);
if (isset($invalid_id)) {
$this->executable
->expects($this->exactly(1))
->method('saveMessage')
->with(
'Filter ' . $invalid_id . ' could not be mapped to an existing filter plugin; defaulting to filter_null.',
MigrationInterface::MESSAGE_WARNING
);
}
$row = new Row([], []);
$output_value = $plugin->transform($value, $this->executable, $row, 'foo');
$this->assertSame($expected_value, $output_value);
}
/**
* The test data provider.
*
* @return array
*/
public function testProvider() {
return [
// The filter ID is mapped, and the plugin exists.
[
'foo',
'filter_html',
],
// The filter ID isn't mapped, but it's unchanged from the source (i.e.,
// it bypasses the static map) and the plugin exists.
[
'filter_html',
'filter_html',
],
// The filter ID is mapped, but the plugin does not exist.
[
'baz',
'filter_null',
'php_code',
],
// The filter ID isn't mapped, but it's unchanged from the source (i.e.,
// it bypasses the static map) but the plugin does not exist.
[
'php_code',
'filter_null',
'php_code',
],
[
['filter', 1],
'filter_null',
'filter:1',
],
];
}
}

View file

@ -113,16 +113,11 @@ class ImageStyleDownloadController extends FileDownloadController {
// If using the private scheme, let other modules provide headers and
// control access to the file.
if ($scheme == 'private') {
if (file_exists($derivative_uri)) {
return parent::download($request, $scheme);
}
else {
$headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri));
if (in_array(-1, $headers) || empty($headers)) {
throw new AccessDeniedHttpException();
}
}
}
// Don't try to generate file if source is missing.
if (!file_exists($image_uri)) {

View file

@ -4,10 +4,7 @@ namespace Drupal\image;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Url;
/**
* Defines a class to build a listing of image style entities.
@ -16,40 +13,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class ImageStyleListBuilder extends ConfigEntityListBuilder {
/**
* The URL generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* Constructs a new ImageStyleListBuilder object.
*
* @param EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
* The image style entity storage class.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The URL generator.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $image_style_storage, UrlGeneratorInterface $url_generator) {
parent::__construct($entity_type, $image_style_storage);
$this->urlGenerator = $url_generator;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('url_generator'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
@ -87,7 +50,7 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', [
':url' => $this->urlGenerator->generateFromRoute('image.style_add'),
':url' => Url::fromRoute('image.style_add')->toString(),
]);
return $build;
}

View file

@ -155,6 +155,11 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$image = $this->container->get('image.factory')->get($generated_uri);
$this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.');
$this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), 'Expected Content-Length was reported.');
// Check that we did not download the original file.
$original_image = $this->container->get('image.factory')->get($original_uri);
$this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
if ($scheme == 'private') {
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertNotEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
@ -165,6 +170,12 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->drupalGet($generate_url);
$this->assertResponse(200, 'Image was generated at the URL.');
// Check that the second request also returned the generated image.
$this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize());
// Check that we did not download the original file.
$this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
// Make sure that access is denied for existing style files if we do not
// have access.
\Drupal::state()->delete('image.test_file_download');

View file

@ -169,7 +169,7 @@ class LinkItem extends FieldItemBase implements LinkItemInterface {
* {@inheritdoc}
*/
public function getUrl() {
return Url::fromUri($this->uri, $this->options);
return Url::fromUri($this->uri, (array) $this->options);
}
/**

View file

@ -5,6 +5,7 @@ namespace Drupal\Tests\link\Kernel;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
@ -154,6 +155,11 @@ class LinkItemTest extends FieldKernelTestBase {
$this->assertNull($entity->field_test->title);
$this->assertIdentical($entity->field_test->options, []);
// Check that setting options to NULL does not trigger an error when
// calling getUrl();
$entity->field_test->options = NULL;
$this->assertInstanceOf(Url::class, $entity->field_test[0]->getUrl());
// Check that setting LinkItem value NULL doesn't generate any error or
// warning.
$entity->field_test[0] = NULL;

View file

@ -81,7 +81,7 @@ final class MigrateEvents {
*
* This event allows modules to perform an action whenever a specific item
* is about to be saved by the destination plugin. The event listener method
* receives a \Drupal\migrate\Event\MigratePreSaveEvent instance.
* receives a \Drupal\migrate\Event\MigratePreRowSaveEvent instance.
*
* @Event
*

View file

@ -329,6 +329,12 @@ class MigrateExecutable implements MigrateExecutableInterface {
// We're now done with this row, so remove it from the map.
$id_map->deleteDestination($destination_key);
}
else {
// If there is no destination key the import probably failed and we can
// remove the row without further action.
$source_key = $id_map->currentSource();
$id_map->delete($source_key);
}
// Check for memory exhaustion.
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {

View file

@ -243,6 +243,14 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
*/
public function currentDestination();
/**
* Looks up the source identifier(s) currently being iterated.
*
* @return array
* The source identifier values of the record, or NULL on failure.
*/
public function currentSource();
/**
* Removes any persistent storage used by this map.
*

View file

@ -21,7 +21,7 @@ use Drupal\Core\Plugin\DefaultPluginManager;
*
* @ingroup migration
*/
class MigratePluginManager extends DefaultPluginManager {
class MigratePluginManager extends DefaultPluginManager implements MigratePluginManagerInterface {
/**
* Constructs a MigratePluginManager object.
@ -49,8 +49,6 @@ class MigratePluginManager extends DefaultPluginManager {
/**
* {@inheritdoc}
*
* A specific createInstance method is necessary to pass the migration on.
*/
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
$plugin_definition = $this->getDefinition($plugin_id);

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\PluginManagerInterface;
interface MigratePluginManagerInterface extends PluginManagerInterface {
/**
* Creates a pre-configured instance of a migration plugin.
*
* A specific createInstance method is necessary to pass the migration on.
*
* @param string $plugin_id
* The ID of the plugin being instantiated.
* @param array $configuration
* An array of configuration relevant to the plugin instance.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration context in which the plugin will run.
*
* @return object
* A fully configured plugin instance.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the instance cannot be created, such as if the ID is invalid.
*/
public function createInstance($plugin_id, array $configuration = [], MigrationInterface $migration = NULL);
}

View file

@ -268,16 +268,16 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
* The plugin definition.
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* The migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $source_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
* The source migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $process_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
* The process migration plugin manager.
* @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
* The destination migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $idmap_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
* The ID map migration plugin manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $source_plugin_manager, MigratePluginManager $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManager $idmap_plugin_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migrationPluginManager = $migration_plugin_manager;
$this->sourcePluginManager = $source_plugin_manager;

View file

@ -21,4 +21,12 @@ class EntityFieldInstance extends EntityConfigBase {
return $ids;
}
/**
* {@inheritdoc}
*/
public function rollback(array $destination_identifier) {
$destination_identifier = implode('.', $destination_identifier);
parent::rollback(array($destination_identifier));
}
}

View file

@ -20,4 +20,12 @@ class EntityFieldStorageConfig extends EntityConfigBase {
return $ids;
}
/**
* {@inheritdoc}
*/
public function rollback(array $destination_identifier) {
$destination_identifier = implode('.', $destination_identifier);
parent::rollback(array($destination_identifier));
}
}

View file

@ -20,4 +20,12 @@ class EntityViewMode extends EntityConfigBase {
return $ids;
}
/**
* {@inheritdoc}
*/
public function rollback(array $destination_identifier) {
$destination_identifier = implode('.', $destination_identifier);
parent::rollback(array($destination_identifier));
}
}

View file

@ -845,8 +845,26 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
if ($this->valid()) {
$result = array();
foreach ($this->destinationIdFields() as $destination_field_name => $idmap_field_name) {
if (!is_null($this->currentRow[$idmap_field_name])) {
$result[$destination_field_name] = $this->currentRow[$idmap_field_name];
}
}
return $result;
}
else {
return NULL;
}
}
/**
* @inheritdoc
*/
public function currentSource() {
if ($this->valid()) {
$result = array();
foreach ($this->sourceIdFields() as $field_name => $source_id) {
$result[$field_name] = $this->currentKey[$source_id];
}
return $result;
}
else {

View file

@ -15,6 +15,8 @@ use Drupal\migrate\Row;
* arguments can be passed to the callback as this would make the migration YAML
* file too complex.
*
* @link https://www.drupal.org/node/2181783 Online handbook documentation for callback process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "callback"
* )

View file

@ -10,6 +10,8 @@ use Drupal\migrate\Row;
/**
* Concatenates the strings in the current value.
*
* @link https://www.drupal.org/node/2345927 Online handbook documentation for concat process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "concat",
* handle_multiples = TRUE

View file

@ -15,6 +15,8 @@ use Drupal\Component\Utility\Unicode;
* creating filter format names, the current value is checked against the
* existing filter format names and if it exists, a numeric postfix is added
* and incremented until a unique value is created.
*
* @link https://www.drupal.org/node/2345929 Online handbook documentation for dedupebase process plugin @endlink
*/
abstract class DedupeBase extends ProcessPluginBase {

View file

@ -10,6 +10,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Ensures value is not duplicated against an entity field.
*
* @link https://www.drupal.org/node/2135325 Online handbook documentation for dedupe_entity process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "dedupe_entity"
* )

View file

@ -9,6 +9,8 @@ use Drupal\migrate\Row;
/**
* This plugin sets missing values on the destination.
*
* @link https://www.drupal.org/node/2135313 Online handbook documentation for default_value process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "default_value"
* )

View file

@ -10,6 +10,8 @@ use Drupal\migrate\Row;
/**
* This plugin explodes a delimited string into an array of values.
*
* @link https://www.drupal.org/node/2674504 Online handbook documentation for explode process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "explode"
* )

View file

@ -11,7 +11,7 @@ use Drupal\migrate\Row;
/**
* This plugin extracts a value from an array.
*
* @see https://www.drupal.org/node/2152731
* @link https://www.drupal.org/node/2152731 Online handbook documentation for extract process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "extract"

View file

@ -12,7 +12,7 @@ use Drupal\migrate\Row;
* once a single value gets transformed into multiple values. This plugin will
* flatten them back down to single values again.
*
* @see https://www.drupal.org/node/2154215
* @link https://www.drupal.org/node/2154215 Online handbook documentation for flatten process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "flatten",

View file

@ -9,6 +9,8 @@ use Drupal\migrate\Row;
/**
* This plugin copies from the source to the destination.
*
* @link https://www.drupal.org/node/2135307 Online handbook documentation for get process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "get"
* )

View file

@ -9,7 +9,7 @@ use Drupal\migrate\Row;
/**
* This plugin iterates and processes an array.
*
* @see https://www.drupal.org/node/2135345
* @link https://www.drupal.org/node/2135345 Online handbook documentation for iterator process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "iterator",

View file

@ -17,6 +17,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* and replaced by an underscore and multiple underscores are collapsed into
* one.
*
* @link https://www.drupal.org/node/2135323 Online handbook documentation for machine_name process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "machine_name"
* )

View file

@ -4,7 +4,7 @@ namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\ProcessPluginBase;
@ -16,6 +16,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Calculates the value of a property based on a previous migration.
*
* @link https://www.drupal.org/node/2149801 Online handbook documentation for migration process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "migration"
* )
@ -39,7 +41,7 @@ class Migration extends ProcessPluginBase implements ContainerFactoryPluginInter
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $process_plugin_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $process_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migrationPluginManager = $migration_plugin_manager;
$this->migration = $migration;

View file

@ -11,7 +11,10 @@ use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
*
* @link https://www.drupal.org/node/2750777 Online handbook documentation for route process plugin @endlink
*
* * @MigrateProcessPlugin(
* id = "route"
* )
*/

View file

@ -11,6 +11,8 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip processing or the whole row.
*
* @link https://www.drupal.org/node/2228793 Online handbook documentation for skip_on_empty process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "skip_on_empty"
* )

View file

@ -10,6 +10,8 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip the current row.
*
* @link https://www.drupal.org/node/2345935 Online handbook documentation for skip_row_if_not_set process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "skip_row_if_not_set",
* handle_multiples = TRUE

View file

@ -12,7 +12,7 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* This plugin changes the current value based on a static lookup map.
*
* @see https://www.drupal.org/node/2143521
* @link https://www.drupal.org/node/2143521 Online handbook documentation for static_map process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "static_map"

View file

@ -11,6 +11,8 @@ use Drupal\Component\Utility\Unicode;
/**
* This plugin returns a substring of the current value.
*
* @link https://www.drupal.org/node/2771965 Online handbook documentation for substr process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "substr"
* )

View file

@ -128,6 +128,9 @@ class MigrateRollbackTest extends MigrateTestBase {
$this->assertNotNull($map_row['destid1']);
}
// Add a failed row to test if this can be rolled back without errors.
$this->mockFailure($term_migration, ['id' => '4', 'vocab' => '2', 'name' => 'FAIL']);
// Rollback and verify the entities are gone.
$term_executable->rollback();
foreach ($term_data_rows as $row) {

View file

@ -640,6 +640,35 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$this->assertSame(0, count($source_id));
}
/**
* Tests currentDestination() and currentSource().
*/
public function testCurrentDestinationAndSource() {
// Simple map with one source and one destination ID.
$id_map = $this->setupRows(['nid'], ['nid'], [
[1, 101],
[2, 102],
[3, 103],
// Mock a failed row by setting the destination ID to NULL.
[4, NULL],
]);
// The rows are ordered by destination ID so the failed row should be first.
$id_map->rewind();
$this->assertEquals([], $id_map->currentDestination());
$this->assertEquals(['nid' => 4], $id_map->currentSource());
$id_map->next();
$this->assertEquals(['nid' => 101], $id_map->currentDestination());
$this->assertEquals(['nid' => 1], $id_map->currentSource());
$id_map->next();
$this->assertEquals(['nid' => 102], $id_map->currentDestination());
$this->assertEquals(['nid' => 2], $id_map->currentSource());
$id_map->next();
$this->assertEquals(['nid' => 103], $id_map->currentDestination());
$this->assertEquals(['nid' => 3], $id_map->currentSource());
$id_map->next();
}
/**
* Tests the imported count method.
*

View file

@ -15,7 +15,7 @@ use Drupal\migrate\Plugin\MigrationInterface;
*
* @ingroup migration
*/
class MigrateCckFieldPluginManager extends MigratePluginManager {
class MigrateCckFieldPluginManager extends MigratePluginManager implements MigrateCckFieldPluginManagerInterface {
/**
* The default version of core to use for cck field plugins.
@ -29,7 +29,7 @@ class MigrateCckFieldPluginManager extends MigratePluginManager {
/**
* {@inheritdoc}
*/
public function createInstance($field_type, array $configuration = array(), MigrationInterface $migration = NULL) {
public function getPluginIdFromFieldType($field_type, array $configuration = [], MigrationInterface $migration = NULL) {
$core = static::DEFAULT_CORE_VERSION;
if (!empty($configuration['core'])) {
$core = $configuration['core'];
@ -45,7 +45,7 @@ class MigrateCckFieldPluginManager extends MigratePluginManager {
foreach ($this->getDefinitions() as $plugin_id => $definition) {
if (in_array($core, $definition['core'])) {
if (array_key_exists($field_type, $definition['type_map']) || $field_type === $plugin_id) {
return parent::createInstance($plugin_id, $configuration, $migration);
return $plugin_id;
}
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Drupal\migrate_drupal\Plugin;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationInterface;
interface MigrateCckFieldPluginManagerInterface extends MigratePluginManagerInterface {
/**
* Get the plugin ID from the field type.
*
* @param string $field_type
* The field type being migrated.
* @param array $configuration
* (optional) An array of configuration relevant to the plugin instance.
* @param \Drupal\migrate\Plugin\MigrationInterface|null $migration
* (optional) The current migration instance.
*
* @return string
* The ID of the plugin for the field_type if available.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* If the plugin cannot be determined, such as if the field type is invalid.
*/
public function getPluginIdFromFieldType($field_type, array $configuration = [], MigrationInterface $migration = NULL);
}

View file

@ -2,13 +2,15 @@
namespace Drupal\migrate_drupal\Plugin\migrate;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrateDestinationPluginManager;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -40,7 +42,7 @@ class CckMigration extends Migration implements ContainerFactoryPluginInterface
/**
* The cckfield plugin manager.
*
* @var \Drupal\migrate\Plugin\MigratePluginManager
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -53,20 +55,20 @@ class CckMigration extends Migration implements ContainerFactoryPluginInterface
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\migrate\Plugin\MigratePluginManager $cck_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The cckfield plugin manager.
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* The migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $source_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
* The source migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $process_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
* The process migration plugin manager.
* @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
* The destination migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $idmap_plugin_manager
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
* The ID map migration plugin manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigratePluginManager $cck_manager, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $source_plugin_manager, MigratePluginManager $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManager $idmap_plugin_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateCckFieldPluginManagerInterface $cck_manager, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration_plugin_manager, $source_plugin_manager, $process_plugin_manager, $destination_plugin_manager, $idmap_plugin_manager);
$this->cckPluginManager = $cck_manager;
}
@ -106,12 +108,19 @@ class CckMigration extends Migration implements ContainerFactoryPluginInterface
}
foreach ($source_plugin as $row) {
$field_type = $row->getSourceProperty('type');
if (!isset($this->processedFieldTypes[$field_type]) && $this->cckPluginManager->hasDefinition($field_type)) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this);
}
catch (PluginNotFoundException $ex) {
continue;
}
if (!isset($this->processedFieldTypes[$field_type])) {
$this->processedFieldTypes[$field_type] = TRUE;
// Allow the cckfield plugin to alter the migration as necessary so
// that it knows how to handle fields of this type.
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, [], $this);
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, [], $this);
}
call_user_func([$this->cckPluginCache[$field_type], $this->pluginDefinition['cck_plugin_method']], $this);
}

View file

@ -3835,6 +3835,18 @@ $connection->insert('field_data_body')
'body_summary' => '',
'body_format' => 'filtered_html',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.",
'body_summary' => '',
'body_format' => 'filtered_html',
))
->execute();
$connection->schema()->createTable('field_data_comment_body', array(
@ -4930,6 +4942,18 @@ $connection->insert('field_data_field_link')
'field_link_title' => 'Home',
'field_link_attributes' => 'a:0:{}',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'field_link_url' => '<front>',
'field_link_title' => 'Home',
'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}',
))
->execute();
$connection->schema()->createTable('field_data_field_long_text', array(
@ -5167,6 +5191,16 @@ $connection->insert('field_data_field_tags')
'delta' => '0',
'field_tags_tid' => '9',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'field_tags_tid' => '9',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@ -5177,6 +5211,16 @@ $connection->insert('field_data_field_tags')
'delta' => '1',
'field_tags_tid' => '14',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '1',
'field_tags_tid' => '14',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@ -5187,6 +5231,16 @@ $connection->insert('field_data_field_tags')
'delta' => '2',
'field_tags_tid' => '17',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '2',
'field_tags_tid' => '17',
))
->execute();
$connection->schema()->createTable('field_data_field_term_reference', array(
@ -5603,6 +5657,18 @@ $connection->insert('field_revision_body')
'body_summary' => '',
'body_format' => 'filtered_html',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.",
'body_summary' => '',
'body_format' => 'filtered_html',
))
->execute();
$connection->schema()->createTable('field_revision_comment_body', array(
@ -6710,6 +6776,18 @@ $connection->insert('field_revision_field_link')
'field_link_title' => 'Home',
'field_link_attributes' => 'a:0:{}',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'field_link_url' => '<front>',
'field_link_title' => 'Home',
'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}',
))
->execute();
$connection->schema()->createTable('field_revision_field_long_text', array(
@ -6950,6 +7028,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '0',
'field_tags_tid' => '9',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '0',
'field_tags_tid' => '9',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@ -6960,6 +7048,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '1',
'field_tags_tid' => '14',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '1',
'field_tags_tid' => '14',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@ -6970,6 +7068,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '2',
'field_tags_tid' => '17',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => '0',
'entity_id' => '3',
'revision_id' => '3',
'language' => 'und',
'delta' => '2',
'field_tags_tid' => '17',
))
->execute();
$connection->schema()->createTable('field_revision_field_term_reference', array(
@ -29674,7 +29782,23 @@ $connection->insert('node')
'comment' => '2',
'promote' => '1',
'sticky' => '0',
'tnid' => '0',
'tnid' => '2',
'translate' => '0',
))
->values(array(
'nid' => '3',
'vid' => '3',
'type' => 'article',
'language' => 'is',
'title' => 'is - The thing about Deep Space 9',
'uid' => '1',
'status' => '1',
'created' => '1471428152',
'changed' => '1471428152',
'comment' => '2',
'promote' => '1',
'sticky' => '0',
'tnid' => '2',
'translate' => '0',
))
->execute();
@ -29813,6 +29937,14 @@ $connection->insert('node_comment_statistics')
'last_comment_uid' => '1',
'comment_count' => '1',
))
->values(array(
'nid' => '3',
'cid' => '0',
'last_comment_timestamp' => '1471428152',
'last_comment_name' => NULL,
'last_comment_uid' => '1',
'comment_count' => '0',
))
->execute();
$connection->schema()->createTable('node_counter', array(
@ -29864,6 +29996,18 @@ $connection->insert('node_counter')
'daycount' => '0',
'timestamp' => '1421727536',
))
->values(array(
'nid' => '2',
'totalcount' => '1',
'daycount' => '1',
'timestamp' => '1471428059',
))
->values(array(
'nid' => '3',
'totalcount' => '1',
'daycount' => '1',
'timestamp' => '1471428153',
))
->execute();
$connection->schema()->createTable('node_revision', array(
@ -29972,6 +30116,18 @@ $connection->insert('node_revision')
'promote' => '1',
'sticky' => '0',
))
->values(array(
'nid' => '3',
'vid' => '3',
'uid' => '1',
'title' => 'is - The thing about Deep Space 9',
'log' => '',
'timestamp' => '1471428152',
'status' => '1',
'comment' => '2',
'promote' => '1',
'sticky' => '0',
))
->execute();
$connection->schema()->createTable('node_type', array(
@ -40112,6 +40268,24 @@ $connection->insert('taxonomy_index')
'sticky' => '0',
'created' => '1441306772',
))
->values(array(
'nid' => '3',
'tid' => '9',
'sticky' => '0',
'created' => '1471428152',
))
->values(array(
'nid' => '3',
'tid' => '14',
'sticky' => '0',
'created' => '1471428152',
))
->values(array(
'nid' => '3',
'tid' => '17',
'sticky' => '0',
'created' => '1471428152',
))
->execute();
$connection->schema()->createTable('taxonomy_term_data', array(
@ -41342,7 +41516,7 @@ $connection->insert('variable')
))
->values(array(
'name' => 'language_content_type_article',
'value' => 's:1:"0";',
'value' => 's:1:"2";',
))
->values(array(
'name' => 'language_content_type_blog',
@ -41442,7 +41616,7 @@ $connection->insert('variable')
))
->values(array(
'name' => 'menu_override_parent_selector',
'value' => 'b:1;',
'value' => 'b:0;',
))
->values(array(
'name' => 'menu_parent_article',

View file

@ -22,32 +22,33 @@ class MigrateCckFieldPluginManagerTest extends MigrateDrupalTestBase {
public function testPluginSelection() {
$plugin_manager = \Drupal::service('plugin.manager.migrate.cckfield');
$this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance('filefield', ['core' => 6])));
$plugin_id = $plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 6]);
$this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance($plugin_id, ['core' => 6])));
try {
// If this test passes, createInstance will raise a
// If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
$plugin_manager->createInstance('filefield', ['core' => 7]);
$plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {
$this->assertIdentical($e->getMessage(), "Plugin ID 'filefield' was not found.");
}
$this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\ImageField', get_class($plugin_manager->createInstance('image', ['core' => 7])));
$this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\FileField', get_class($plugin_manager->createInstance('file', ['core' => 7])));
$this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6FileField', get_class($plugin_manager->createInstance('file', ['core' => 6])));
$this->assertIdentical('image', $plugin_manager->getPluginIdFromFieldType('image', ['core' => 7]));
$this->assertIdentical('file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 7]));
$this->assertIdentical('d6_file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 6]));
$this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 6])));
$this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 7])));
$this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 6]));
$this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 7]));
// Test fallback when no core version is specified.
$this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6NoCoreVersionSpecified', get_class($plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 6])));
$this->assertIdentical('d6_no_core_version_specified', $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 6]));
try {
// If this test passes, createInstance will raise a
// If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
$plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 7]);
$plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {

View file

@ -48,9 +48,9 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
'file' => 1,
'filter_format' => 7,
'image_style' => 6,
'language_content_settings' => 1,
'language_content_settings' => 2,
'migration' => 59,
'node' => 2,
'node' => 3,
'node_type' => 6,
'rdf_mapping' => 5,
'search_page' => 2,

View file

@ -1213,7 +1213,7 @@ function _node_access_rebuild_batch_operation(&$context) {
// Initiate multistep processing.
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
$context['sandbox']['max'] = \Drupal::entityQuery('node')->count()->execute();
$context['sandbox']['max'] = \Drupal::entityQuery('node')->accessCheck(FALSE)->count()->execute();
}
// Process the next 20 nodes.

View file

@ -127,7 +127,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$node = $this->entityManager()->getTranslationFromContext($node);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer, $this->currentUser());
$page = $node_view_controller->view($node);
unset($page['nodes'][$node->id()]['#cache']);
return $page;

View file

@ -4,12 +4,50 @@ namespace Drupal\node\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a controller to render a single node.
*/
class NodeViewController extends EntityViewController {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Creates an NodeViewController object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user. For backwards compatibility this is optional, however
* this will be removed before Drupal 9.0.0.
*/
public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AccountInterface $current_user = NULL) {
parent::__construct($entity_manager, $renderer);
$this->currentUser = $current_user ?: \Drupal::currentUser();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('renderer'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
@ -17,27 +55,44 @@ class NodeViewController extends EntityViewController {
$build = parent::view($node, $view_mode, $langcode);
foreach ($node->uriRelationships() as $rel) {
$url = $node->toUrl($rel);
// Add link relationships if the user is authenticated or if the anonymous
// user has access. Access checking must be done for anonymous users to
// avoid traffic to inaccessible pages from web crawlers. For
// authenticated users, showing the links in HTML head does not impact
// user experience or security, since the routes are access checked when
// visited and only visible via view source. This prevents doing
// potentially expensive and hard to cache access checks on every request.
// This means that the page will vary by user.permissions. We also rely on
// the access checking fallback to ensure the correct cacheability
// metadata if we have to check access.
if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node->url($rel),
'href' => $url->toString(),
),
TRUE,
);
}
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node->url($rel, array('alias' => TRUE)),
'href' => $url->setOption('alias', TRUE)->toString(),
),
TRUE,
);
}
}
// Given this varies by $this->currentUser->isAuthenticated(), add a cache
// context based on the anonymous role.
$build['#cache']['contexts'][] = 'user.roles:anonymous';
return $build;
}

View file

@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,7 +34,7 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -49,12 +50,12 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
* @param bool $translations
* Whether or not to include translations.
*/
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager, $translations) {
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager, $translations) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
$this->includeTranslations = $translations;
@ -128,14 +129,15 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
if ($this->cckPluginManager->hasDefinition($info['type'])) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 6], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 6], $migration);
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 6], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
else {
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}

View file

@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,7 +34,7 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -42,10 +43,10 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
*/
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager) {
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
}
@ -98,14 +99,15 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
if ($this->cckPluginManager->hasDefinition($field_type)) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 7], $migration);
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
else {
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}

View file

@ -2,6 +2,8 @@
namespace Drupal\node\Tests;
use Drupal\node\Entity\NodeType;
/**
* Ensures that node access rebuild functions work correctly even
* when other modules implements hook_node_grants().
@ -11,20 +13,27 @@ namespace Drupal\node\Tests;
class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
/**
* A user to test the rebuild nodes feature.
* A user to create nodes that only it has access to.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user to test the rebuild nodes feature which can't access the nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'bypass node access'));
$this->drupalLogin($admin_user);
$this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
$this->drupalLogin($this->adminUser);
$this->webUser = $this->drupalCreateUser();
}
@ -34,25 +43,54 @@ class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
*/
public function testNodeAccessRebuildNodeGrants() {
\Drupal::service('module_installer')->install(['node_access_test']);
\Drupal::state()->set('node_access_test.private', TRUE);
node_access_test_add_field(NodeType::load('page'));
$this->resetAll();
$node = $this->drupalCreateNode(array(
// Create 30 nodes so that _node_access_rebuild_batch_operation() has to run
// more than once.
for ($i = 0; $i < 30; $i++) {
$nodes[] = $this->drupalCreateNode(array(
'uid' => $this->webUser->id(),
'private' => [['value' => 1]]
));
}
/** @var \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage */
$grant_storage = \Drupal::service('node.grant_storage');
// Default realm access and node records are present.
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
foreach ($nodes as $node) {
$this->assertTrue($node->private->value);
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.');
$this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.');
}
$this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record');
$this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status/rebuild');
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('The content access permissions have been rebuilt.'));
// Test if the rebuild has been successful.
// Test if the rebuild by user that cannot bypass node access and does not
// have access to the nodes has been successful.
$this->assertFalse($this->adminUser->hasPermission('bypass node access'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
// Test an anonymous node access rebuild from code.
$this->drupalLogout();
node_access_rebuild();
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
}

View file

@ -1,36 +0,0 @@
<?php
namespace Drupal\node\Tests;
/**
* Ensures that node access rebuild functions work correctly.
*
* @group node
*/
class NodeAccessRebuildTest extends NodeTestBase {
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
$this->drupalLogin($web_user);
$this->webUser = $web_user;
}
/**
* Tests rebuilding the node access permissions table.
*/
function testNodeAccessRebuild() {
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('Content permissions have been rebuilt.'));
}
}

View file

@ -38,6 +38,16 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
return $node;
}
/**
* {@inheritdoc}
*/
protected function getDefaultCacheContexts() {
$defaults = parent::getDefaultCacheContexts();
// @see \Drupal\node\Controller\NodeViewController::view()
$defaults[] = 'user.roles:anonymous';
return $defaults;
}
/**
* {@inheritdoc}
*/

View file

@ -18,14 +18,49 @@ class NodeViewTest extends NodeTestBase {
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are checked for access for anonymous users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertFalse($result, 'Edit form not present for anonymous users without access.');
$this->drupalLogin($this->createUser(['access content']));
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are present regardless of access for authenticated users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertEqual($result[0]['href'], $node->url('version-history'));
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
// Give anonymous users access to edit the node. Do this through the UI to
// ensure caches are handled properly.
$this->drupalLogin($this->rootUser);
$edit = [
'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE
];
$this->drupalPostForm('admin/people/permissions', $edit, 'Save permissions');
$this->drupalLogout();
// Anonymous user's should now see the edit-form link but not the
// version-history link.
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
}
/**

View file

@ -47,7 +47,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
'd7_taxonomy_vocabulary',
'd7_field',
'd7_field_instance',
'd7_node:test_content_type',
'd7_node',
'd7_node:article',
]);
}

View file

@ -50,7 +50,7 @@ function hook_test_group_finished() {
* $results The results of the test as gathered by
* \Drupal\simpletest\WebTestBase.
*
* @see \Drupal\simpletest\WebTestBase->results()
* @see \Drupal\simpletest\WebTestBase::results()
*/
function hook_test_finished($results) {
}

View file

@ -33,6 +33,11 @@ class BrowserTestBaseTest extends BrowserTestBase {
// Test page contains some text.
$this->assertSession()->pageTextContains('Test page text.');
// Check that returned plain text is correct.
$text = $this->getTextContent();
$this->assertContains('Test page text.', $text);
$this->assertNotContains('</html>', $text);
// Response includes cache tags that we can assert.
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Tags', 'rendered');

View file

@ -27,7 +27,7 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$view_mode = $this->selectViewMode($entity_type);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$entity_cache_contexts = $this->getDefaultCacheContexts();
// Generate the standardized entity cache tags.
$cache_tag = $this->entity->getCacheTags();
@ -141,4 +141,14 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$this->assertResponse(404);
}
/**
* Gets the default cache contexts for rendered entities.
*
* @return array
* The default cache contexts for rendered entities.
*/
protected function getDefaultCacheContexts() {
return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Utility\Crypt;
use Drupal\simpletest\InstallerTestBase;
/**
* Tests the installer when a config_directory set up but does not exist.
*
* @group Installer
*/
class InstallerConfigDirectorySetNoDirectoryErrorTest extends InstallerTestBase {
/**
* The directory where the sync directory should be created during install.
*
* @var string
*/
protected $configDirectory;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->configDirectory = $this->publicFilesDirectory . '/config_' . Crypt::randomBytesBase64();
$this->settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) array(
'value' => $this->configDirectory . '/sync',
'required' => TRUE,
);
// Create the files directory early so we can test the error case.
mkdir($this->publicFilesDirectory);
// Create a file so the directory can not be created.
file_put_contents($this->configDirectory, 'Test');
parent::setUp();
}
/**
* Installer step: Configure settings.
*/
protected function setUpSettings() {
// This step should not appear as we had a failure prior to the settings
// screen.
}
/**
* @{inheritdoc}
*/
protected function setUpSite() {
// This step should not appear as we had a failure prior to the settings
// screen.
}
/**
* Verifies that installation failed.
*/
public function testError() {
$this->assertText("An automated attempt to create the directory {$this->configDirectory}/sync failed, possibly due to a permissions problem.");
$this->assertFalse(file_exists($this->configDirectory . '/sync') && is_dir($this->configDirectory . '/sync'), "The directory {$this->configDirectory}/sync does not exist.");
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Utility\Crypt;
use Drupal\simpletest\InstallerTestBase;
/**
* Tests the installer when a config_directory set up but does not exist.
*
* @group Installer
*/
class InstallerConfigDirectorySetNoDirectoryTest extends InstallerTestBase {
/**
* The sync directory created during the install.
*
* @var string
*/
protected $syncDirectory;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->syncDirectory = $this->publicFilesDirectory . '/config_' . Crypt::randomBytesBase64() . '/sync';
$this->settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) array(
'value' => $this->syncDirectory,
'required' => TRUE,
);
// Other directories will be created too.
$this->settings['config_directories']['custom'] = (object) array(
'value' => $this->publicFilesDirectory . '/config_custom',
'required' => TRUE,
);
parent::setUp();
}
/**
* Verifies that installation succeeded.
*/
public function testInstaller() {
$this->assertUrl('user/1');
$this->assertResponse(200);
$this->assertTrue(file_exists($this->syncDirectory) && is_dir($this->syncDirectory), "The directory {$this->syncDirectory} exists.");
$this->assertTrue(file_exists($this->publicFilesDirectory . '/config_custom') && is_dir($this->publicFilesDirectory . '/config_custom'), "The directory {$this->publicFilesDirectory}/custom_config exists.");
}
}

View file

@ -551,10 +551,20 @@ function system_requirements($phase) {
if (!empty($GLOBALS['config_directories'])) {
foreach (array_keys(array_filter($GLOBALS['config_directories'])) as $type) {
$directory = config_get_config_directory($type);
// If we're installing Drupal try and create the config sync directory.
if (!is_dir($directory) && $phase == 'install') {
file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
}
if (!is_dir($directory)) {
if ($phase == 'install') {
$description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', array('%directory' => $directory, ':handbook_url' => 'https://www.drupal.org/server-permissions'));
}
else {
$description = t('The directory %directory does not exist.', array('%directory' => $directory));
}
$requirements['config directory ' . $type] = array(
'title' => t('Configuration directory: %type', ['%type' => $type]),
'description' => t('The directory %directory does not exist.', array('%directory' => $directory)),
'description' => $description,
'severity' => REQUIREMENT_ERROR,
);
}

View file

@ -0,0 +1,121 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_view_long
label: test_entity_reference_entity_test_view_long
module: views
description: ''
tag: ''
base_table: entity_test_mul_changed_property
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test_mul_changed_property
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: field_test_data_with_a_long_name
filters: { }
sorts:
id:
id: id
table: entity_test_mul_changed_property
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_test_data_with_a_long_name:
id: field_test_data_with_a_long_name_data
table: entity_test_mul_changed__field_test_data_with_a_long_name
field: field_test_data_with_a_long_name
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
max-age: 0

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