Skip to content
......@@ -36,3 +36,4 @@ process:
destination:
plugin: config
config_name: system.site
translations: true
......@@ -66,3 +66,4 @@ process:
destination:
plugin: config
config_name: user.mail
translations: true
......@@ -27,3 +27,4 @@ process:
destination:
plugin: config
config_name: user.settings
translations: true
......@@ -2,6 +2,7 @@
namespace Drupal\datetime;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
......@@ -40,14 +41,28 @@ public function getValue($langcode = NULL) {
return $this->date;
}
/** @var \Drupal\Core\Field\FieldItemInterface $item */
$item = $this->getParent();
$value = $item->{($this->definition->getSetting('date source'))};
$storage_format = $item->getFieldDefinition()->getSetting('datetime_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
$datetime_type = $item->getFieldDefinition()->getSetting('datetime_type');
$storage_format = $datetime_type === DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
try {
$date = DrupalDateTime::createFromFormat($storage_format, $value, DATETIME_STORAGE_TIMEZONE);
if ($date instanceof DrupalDateTime && !$date->hasErrors()) {
$this->date = $date;
// If the format did not include an explicit time portion, then the
// time will be set from the current time instead. For consistency, we
// set the time to 12:00:00 UTC for date-only fields. This is used so
// that the local date portion is the same, across nearly all time
// zones.
// @see datetime_date_default_time()
// @see http://php.net/manual/en/datetime.createfromformat.php
// @todo Update comment and/or code per the chosen solution in
// https://www.drupal.org/node/2830094
if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATE) {
$this->date->setTime(12, 0, 0);
}
}
}
catch (\Exception $e) {
......
<?php
namespace Drupal\Tests\datetime_range\Kernel;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
/**
* Test datetime range field type via API.
*
* @group datetime
*/
class DateRangeItemTest extends FieldKernelTestBase {
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
public static $modules = [
'datetime',
'datetime_range',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => Unicode::strtolower($this->randomMachineName()),
'entity_type' => 'entity_test',
'type' => 'daterange',
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
]);
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
'required' => TRUE,
]);
$this->field->save();
$display_options = [
'type' => 'daterange_default',
'label' => 'hidden',
'settings' => [
'format_type' => 'fallback',
'separator' => 'UNTRANSLATED',
],
];
EntityViewDisplay::create([
'targetEntityType' => $this->field->getTargetEntityTypeId(),
'bundle' => $this->field->getTargetBundle(),
'mode' => 'default',
'status' => TRUE,
])->setComponent($this->fieldStorage->getName(), $display_options)
->save();
}
/**
* Tests the field configured for date-only.
*/
public function testDateOnly() {
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
$field_name = $this->fieldStorage->getName();
// Create an entity.
$entity = EntityTest::create([
'name' => $this->randomString(),
$field_name => [
'value' => '2016-09-21',
'end_value' => '2016-09-21',
],
]);
// Dates are saved without a time value. When they are converted back into
// a \Drupal\datetime\DateTimeComputed object they should all have the same
// time.
$start_date = $entity->{$field_name}->start_date;
sleep(1);
$end_date = $entity->{$field_name}->end_date;
$this->assertEquals($start_date->getTimestamp(), $end_date->getTimestamp());
}
}
......@@ -519,8 +519,8 @@ function editor_file_download($uri) {
if ($file->isPermanent()) {
$referencing_entity_is_accessible = FALSE;
$references = empty($usage_list['editor']) ? [] : $usage_list['editor'];
foreach ($references as $entity_type => $entity_ids) {
$referencing_entities = entity_load_multiple($entity_type, $entity_ids);
foreach ($references as $entity_type => $entity_ids_usage_count) {
$referencing_entities = entity_load_multiple($entity_type, array_keys($entity_ids_usage_count));
/** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */
foreach ($referencing_entities as $referencing_entity) {
if ($referencing_entity->access('view', NULL, TRUE)->isAllowed()) {
......
......@@ -68,9 +68,18 @@ function testEditorPrivateFileReferenceFilter() {
$file->setPermanent();
$file->save();
// Create some nodes to ensure file usage count does not match the ID's
// of the nodes we are going to check.
for ($i = 0; $i < 5; $i++) {
$this->drupalCreateNode([
'type' => 'page',
'uid' => $author->id(),
]);
}
// Create a node with its body field properly pointing to the just-created
// file.
$node = $this->drupalCreateNode([
$published_node = $this->drupalCreateNode([
'type' => 'page',
'body' => [
'value' => '<img alt="alt" data-entity-type="file" data-entity-uuid="' . $file->uuid() . '" src="' . $src . '" />',
......@@ -79,19 +88,44 @@ function testEditorPrivateFileReferenceFilter() {
'uid' => $author->id(),
]);
// Create an unpublished node with its body field properly pointing to the
// just-created file.
$unpublished_node = $this->drupalCreateNode([
'type' => 'page',
'status' => NODE_NOT_PUBLISHED,
'body' => [
'value' => '<img alt="alt" data-entity-type="file" data-entity-uuid="' . $file->uuid() . '" src="' . $src . '" />',
'format' => 'private_images',
],
'uid' => $author->id(),
]);
// Do the actual test. The image should be visible for anonymous users,
// because they can view the referencing entity.
$this->drupalGet($node->toUrl());
// because they can view the published node. Even though they can't view
// the unpublished node.
$this->drupalGet($published_node->toUrl());
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet($unpublished_node->toUrl());
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(200);
// When the published node is also unpublished, the image should also
// become inaccessible to anonymous users.
$published_node->setPublished(FALSE)->save();
$this->drupalGet($published_node->toUrl());
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(403);
// Disallow anonymous users to view the entity, which then should also
// disallow them to view the image.
$published_node->setPublished(TRUE)->save();
Role::load(RoleInterface::ANONYMOUS_ID)
->revokePermission('access content')
->save();
$this->drupalGet($node->toUrl());
$this->drupalGet($published_node->toUrl());
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet($src);
$this->assertSession()->statusCodeEquals(403);
......
......@@ -136,7 +136,7 @@ public function testUpdateImportSourceRemote() {
// Check the status on the Available translation status page.
$this->assertRaw('<label for="edit-langcodes-de" class="visually-hidden">Update German</label>', 'German language found');
$this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found');
$this->assertText('Contributed module one (' . format_date($this->timestampNow, 'html_date') . ')', 'Updates for Contrib module one');
$this->assertText('Contributed module one (' . format_date($this->timestampNew, 'html_date') . ')', 'Updates for Contrib module one');
$this->assertText('Contributed module two (' . format_date($this->timestampNew, 'html_date') . ')', 'Updates for Contrib module two');
// Execute the translation update.
......
......@@ -54,13 +54,16 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
* The migration entity.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Language\ConfigurableLanguageManagerInterface $language_manager
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->config = $config_factory->getEditable($configuration['config_name']);
$this->language_manager = $language_manager;
if ($this->isTranslationDestination()) {
$this->supportsRollback = TRUE;
}
}
/**
......@@ -81,7 +84,7 @@ public static function create(ContainerInterface $container, array $configuratio
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
if ($row->hasDestinationProperty('langcode')) {
if ($this->isTranslationDestination()) {
$this->config = $this->language_manager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $this->config->getName());
}
......@@ -91,7 +94,11 @@ public function import(Row $row, array $old_destination_id_values = array()) {
}
}
$this->config->save();
return [$this->config->getName()];
$ids[] = $this->config->getName();
if ($this->isTranslationDestination()) {
$ids[] = $row->getDestinationProperty('langcode');
}
return $ids;
}
/**
......@@ -106,6 +113,9 @@ public function fields(MigrationInterface $migration = NULL) {
*/
public function getIds() {
$ids['config_name']['type'] = 'string';
if ($this->isTranslationDestination()) {
$ids['langcode']['type'] = 'string';
}
return $ids;
}
......@@ -118,4 +128,25 @@ public function calculateDependencies() {
return $this->dependencies;
}
/**
* Get whether this destination is for translations.
*
* @return bool
* Whether this destination is for translations.
*/
protected function isTranslationDestination() {
return !empty($this->configuration['translations']);
}
/**
* {@inheritdoc}
*/
public function rollback(array $destination_identifier) {
if ($this->isTranslationDestination()) {
$language = $destination_identifier['langcode'];
$config = $this->language_manager->getLanguageConfigOverride($language, $this->config->getName());
$config->delete();
}
}
}
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\MigrateExecutable;
/**
* Tests rolling back of configuration objects.
*
* @group migrate
*/
class MigrateConfigRollbackTest extends MigrateTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'language', 'config_translation'];
/**
* Tests rolling back configuration.
*/
public function testConfigRollback() {
// Use system.site configuration to demonstrate importing and rolling back
// configuration.
$variable = [
[
'id' => 'site_name',
'site_name' => 'Some site',
'site_slogan' => 'Awesome slogan',
],
];
$ids = [
'id' =>
[
'type' => 'string'
],
];
$definition = [
'id' => 'config',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $variable,
'ids' => $ids,
],
'process' => [
'name' => 'site_name',
'slogan' => 'site_slogan',
],
'destination' => [
'plugin' => 'config',
'config_name' => 'system.site',
],
];
/** @var \Drupal\migrate\Plugin\Migration $config_migration */
$config_migration = \Drupal::service('plugin.manager.migration')
->createStubMigration($definition);
$config_id_map = $config_migration->getIdMap();
// Rollback is not enabled for configuration translations.
$this->assertFalse($config_migration->getDestinationPlugin()->supportsRollback());
// Import and validate config entities were created.
$config_executable = new MigrateExecutable($config_migration, $this);
$config_executable->import();
$config = $this->config('system.site');
$this->assertSame('Some site', $config->get('name'));
$this->assertSame('Awesome slogan', $config->get('slogan'));
$map_row = $config_id_map->getRowBySource(['id' => $variable[0]['id']]);
$this->assertNotNull($map_row['destid1']);
// Rollback and verify the configuration changes are still there.
$config_executable->rollback();
$config = $this->config('system.site');
$this->assertSame('Some site', $config->get('name'));
$this->assertSame('Awesome slogan', $config->get('slogan'));
// Confirm the map row is deleted.
$map_row = $config_id_map->getRowBySource(['id' => $variable[0]['id']]);
$this->assertNull($map_row['destid1']);
// We use system configuration to demonstrate importing and rolling back
// configuration translations.
$i18n_variable = [
[
'id' => 'site_name',
'language' => 'fr',
'site_name' => 'fr - Some site',
'site_slogan' => 'fr - Awesome slogan',
],
[
'id' => 'site_name',
'language' => 'is',
'site_name' => 'is - Some site',
'site_slogan' => 'is - Awesome slogan',
],
];
$ids = [
'id' =>
[
'type' => 'string'
],
'language' =>
[
'type' => 'string'
]
];
$definition = [
'id' => 'i18n_config',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $i18n_variable,
'ids' => $ids,
],
'process' => [
'langcode' => 'language',
'name' => 'site_name',
'slogan' => 'site_slogan',
],
'destination' => [
'plugin' => 'config',
'config_name' => 'system.site',
'translations' => 'true',
],
];
$config_migration = \Drupal::service('plugin.manager.migration')
->createStubMigration($definition);
$config_id_map = $config_migration->getIdMap();
// Rollback is enabled for configuration translations.
$this->assertTrue($config_migration->getDestinationPlugin()->supportsRollback());
// Import and validate config entities were created.
$config_executable = new MigrateExecutable($config_migration, $this);
$config_executable->import();
$language_manager = \Drupal::service('language_manager');
foreach ($i18n_variable as $row) {
$langcode = $row['language'];
/** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */
$config_translation = $language_manager->getLanguageConfigOverride($langcode, 'system.site');
$this->assertSame($row['site_name'], $config_translation->get('name'));
$this->assertSame($row['site_slogan'], $config_translation->get('slogan'));
$map_row = $config_id_map->getRowBySource(['id' => $row['id'], 'language' => $row['language']]);
$this->assertNotNull($map_row['destid1']);
}
// Rollback and verify the translation have been removed.
$config_executable->rollback();
foreach ($i18n_variable as $row) {
$langcode = $row['language'];
$config_translation = $language_manager->getLanguageConfigOverride($langcode, 'system.site');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('slogan'));
// Confirm the map row is deleted.
$map_row = $config_id_map->getRowBySource(['id' => $row['id'], 'language' => $langcode]);
$this->assertFalse($map_row);
}
// Test that the configuration is still present.
$config = $this->config('system.site');
$this->assertSame('Some site', $config->get('name'));
$this->assertSame('Awesome slogan', $config->get('slogan'));
}
}
......@@ -3,7 +3,7 @@
namespace Drupal\Tests\migrate\Kernel\process;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\KernelTests\Core\File\FileTestBase;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\migrate\process\FileCopy;
use Drupal\migrate\Row;
......@@ -13,7 +13,7 @@
*
* @group migrate
*/
class CopyFileTest extends FileTestBase {
class CopyFileTest extends KernelTestBase {
/**
* {@inheritdoc}
......@@ -180,4 +180,39 @@ protected function doImport($source_path, $destination_path, $configuration = []
return $result;
}
/**
* Create a file and return the URI of it.
*
* @param $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
* @return
* File URI.
*/
protected function createUri($filepath = NULL, $contents = NULL, $scheme = NULL) {
if (!isset($filepath)) {
// Prefix with non-latin characters to ensure that all file-related
// tests work with international filenames.
$filepath = 'Файл для тестирования ' . $this->randomMachineName();
}
if (empty($scheme)) {
$scheme = file_default_scheme();
}
$filepath = $scheme . '://' . $filepath;
if (empty($contents)) {
$contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
}
file_put_contents($filepath, $contents);
$this->assertFileExists($filepath, t('The test file exists on the disk.'));
return $filepath;
}
}
......@@ -44,9 +44,6 @@ public function testImport() {
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue(FALSE));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
......@@ -94,9 +91,6 @@ public function testLanguageImport() {
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue($source));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
......@@ -110,9 +104,9 @@ public function testLanguageImport() {
->method('getLanguageConfigOverride')
->with('mi', 'd8_config')
->will($this->returnValue($config));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination = new Config(array('config_name' => 'd8_config', 'translations' => 'true'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination_id = $destination->import($row);
$this->assertEquals($destination_id, ['d8_config']);
$this->assertEquals($destination_id, ['d8_config', 'mi']);
}
}
......@@ -4485,6 +4485,18 @@
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->values(array(
'entity_type' => 'user',
'bundle' => 'user',
'deleted' => '0',
'entity_id' => '2',
'revision_id' => '2',
'language' => 'und',
'delta' => '0',
'field_file_fid' => '2',
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->execute();
 
$connection->schema()->createTable('field_data_field_float', array(
......@@ -6674,6 +6686,18 @@
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->values(array(
'entity_type' => 'user',
'bundle' => 'user',
'deleted' => '0',
'entity_id' => '2',
'revision_id' => '2',
'language' => 'und',
'delta' => '0',
'field_file_fid' => '2',
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->execute();
 
$connection->schema()->createTable('field_revision_field_float', array(
......@@ -8307,6 +8331,16 @@
'status' => '1',
'timestamp' => '1421727515',
))
->values(array(
'fid' => '2',
'uid' => '1',
'filename' => 'ds9.txt',
'uri' => 'public://ds9.txt',
'filemime' => 'text/plain',
'filesize' => '4720',
'status' => '1',
'timestamp' => '1421727516',
))
->execute();
 
$connection->schema()->createTable('file_usage', array(
......@@ -8375,6 +8409,13 @@
'id' => '1',
'count' => '1',
))
->values(array(
'fid' => '2',
'module' => 'file',
'type' => 'user',
'id' => '2',
'count' => '1',
))
->execute();
 
$connection->schema()->createTable('filter', array(
......@@ -45,7 +45,7 @@ protected function getEntityCounts() {
'editor' => 2,
'field_config' => 49,
'field_storage_config' => 37,
'file' => 1,
'file' => 2,
'filter_format' => 7,
'image_style' => 6,
'language_content_settings' => 2,
......
__ ___ ___
,' ,' | | `. `.
,' ,' |===| `. `.
/ // |___| \\ \
/ // |___| \\ \
//// |___| \\\\
/ / || || \ \
/ / || || \ \
/| | || || | |\
|| | | : o : | | ||
| \| | .===. | |/ |
| |\ /| (___) |\ /| |
|__||.\ .-. // /,_._,\ \\ .-. /.||__|
|__||_.\ `-.\ //_ [:(|):] _\\ /.-' /._||__|
__/| ||___`._____ ___\\__/___/_ ||| _\___\__//___ _____.'___||_ |\__
/___//__________/.-/_____________|.-.|_____________\-.\__________\\___\
\___\\__\\\_____\`-\__\\\\__\____|_-_|____/_//_____/-'/__//______//__//
\|__||__..' // \ _ \__|||__/ _ / \\ `..__||__|/
|__||_./ .-'/ \\ |(|)| // \`-. \..||__|
| || / `-' \\ \'/ // `-' \ || |
| |/ \| :(-): |/ \| |
| /| | : o : | |\ |
|| | | |___| | | ||
\| | || || | |/
\ \ || || / /
\ \ ||___|| / /
\\\\ |___| ////
\ \\ |___| // /
\ \\ | | // /
`. `. |===| ,' ,'
`._`. |___| ,'_,'
_ _
_____---' \_n_/ `---_____
_ / ... ----------- ... \ _
( )-' . '::.\__ V __/.::' . `-( )
_ .-' ':::. ____ \ / ____ .:::' `-. _
,-'.`' __.--' \ | | / `--.__ `'.`-.
/ ''::.. \ || || / ..::'' \
/ ..... ,'\,' ||_|| `./`. ..... \
/ :::::' ,' | | `. '::::: \
| '::: ,' | | `. :::' |
_/ :: / |___| \ :: \_
(/ / ,-' `-. \ \)
_/ `. ,-' ooo oo `-. ,' \_
| /`./ ,-'\ /`-. \.'\ |
| .: / / \ \_ __.---.__ _/ / \ \ :. |
.' :;: | _ / o \[ ' \ / ` ]/ \ _ | ::: `.
| ':: | ( `-. / | | \ ,-' ) | ::' |
|: ': | `-./ / ___ \ \,-' | :' :|
.':: ' | | / `-. .-' . `-. .-' \ o | | ' ::`.
| ::. | | o ,| `-. / \`_|_'/ \ .-' |. o | | .:: |
\ |_ |____| | ` / \ ' | |____| _| /
(| _] ]____[ |- ( (O) ) -| ]____[ [_ |)
/.: | | | | . \_ _/ . | | | | :.\
| :' | | o `|-,-' \ /..|..\ / `-.-|' o | |. ': |
`. :: | | o \ .-' `-.___.-' `-. / o | | :: .'
| .:: | _.\ \| | | \/ /._ | ::. |
| ': | _.-' \ o \ | | / o / `-._ | :' |
`. __ |__.-'_ _.\ /[_.__ | | __._]\ o /._ _`-.__| __ .'
| \ \_.--''.' .-' \ / / `---' \ \ / `-. `.``--__/ / |
|_\ __ ,',-' `-./ o \,-' `-.`. __ /_|
\\ / .',' `-. oo .-. oo ,-' `.`. \ //
(\\ \ \ `-._| |_,-' / / //)
\\ ) \ (_) / ( //
/ \/ `. ,' \/ \
\ .::. `. ,' .::: /
\ ':::. `-./`. .'\.-' '''''' /
\ ''' /_ _ _\ ::.. /
`-.'::' `--.______| |______.--' ,-'
`-'`-._ .. .: .: _,-'`'
(_)-. ::. '':::: :::::: : ,-(_)
\_____ '' _ _ ' _____/
---._/ u \_.---
Used with permission from:
Orbital Space Station (Terok Nor - Deep Space 9) - Joe Reiss
https://startrekasciiart.blogspot.co.uk/2011/05/deep-space-nine.html
......@@ -73,6 +73,11 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
$view_mode = $node->preview_view_mode;
$query_options = array('query' => array('uuid' => $node->uuid()));
$query = $this->getRequest()->query;
if ($query->has('destination')) {
$query_options['query']['destination'] = $query->get('destination');
}
$form['backlink'] = array(
'#type' => 'link',
'#title' => $this->t('Back to content editing'),
......@@ -80,9 +85,11 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
'#options' => array('attributes' => array('class' => array('node-preview-backlink'))) + $query_options,
);
$view_mode_options = $this->entityManager->getViewModeOptionsByBundle('node', $node->bundle());
// Always show full as an option, even if the display is not enabled.
$view_mode_options = ['full' => $this->t('Full')] + $this->entityManager->getViewModeOptionsByBundle('node', $node->bundle());
// Unset view modes that are not used in the front end.
unset($view_mode_options['default']);
unset($view_mode_options['rss']);
unset($view_mode_options['search_index']);
......@@ -116,10 +123,18 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.node.preview', array(
$route_parameters = [
'node_preview' => $form_state->getValue('uuid'),
'view_mode_id' => $form_state->getValue('view_mode'),
));
];
$options = [];
$query = $this->getRequest()->query;
if ($query->has('destination')) {
$options['query']['destination'] = $query->get('destination');
$query->remove('destination');
}
$form_state->setRedirect('entity.node.preview', $route_parameters, $options);
}
}
......@@ -22,6 +22,9 @@ class NodeForm extends ContentEntityForm {
/**
* Whether this node has been previewed or not.
*
* @deprecated Scheduled for removal in Drupal 8.3.x. Use the form state
* property 'has_been_previewed' instead.
*/
protected $hasBeenPreviewed = FALSE;
......@@ -65,6 +68,8 @@ protected function prepareEntity() {
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$this->hasBeenPreviewed = $form_state->get('has_been_previewed') ?: FALSE;
// Try to restore from temp store, this must be done before calling
// parent::form().
$store = $this->tempStoreFactory->get('node_preview');
......@@ -89,6 +94,7 @@ public function form(array $form, FormStateInterface $form_state) {
$this->entity = $preview->getFormObject()->getEntity();
$this->entity->in_preview = NULL;
$form_state->set('has_been_previewed', TRUE);
$this->hasBeenPreviewed = TRUE;
}
......@@ -231,7 +237,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
$node = $this->entity;
$preview_mode = $node->type->entity->getPreviewMode();
$element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || $this->hasBeenPreviewed;
$element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || $form_state->get('has_been_previewed');
// If saving is an option, privileged users get dedicated form submit
// buttons to adjust the publishing status while saving in one go.
......@@ -338,10 +344,19 @@ public function preview(array $form, FormStateInterface $form_state) {
$store = $this->tempStoreFactory->get('node_preview');
$this->entity->in_preview = TRUE;
$store->set($this->entity->uuid(), $form_state);
$form_state->setRedirect('entity.node.preview', array(
$route_parameters = [
'node_preview' => $this->entity->uuid(),
'view_mode_id' => 'default',
));
'view_mode_id' => 'full',
];
$options = [];
$query = $this->getRequest()->query;
if ($query->has('destination')) {
$options['query']['destination'] = $query->get('destination');
$query->remove('destination');
}
$form_state->setRedirect('entity.node.preview', $route_parameters, $options);
}
/**
......
......@@ -6,6 +6,7 @@
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
......@@ -195,7 +196,7 @@ function testPagePreview() {
->save();
$view_mode_edit = array('view_mode' => 'teaser');
$this->drupalPostForm('node/preview/' . $uuid . '/default', $view_mode_edit, t('Switch'));
$this->drupalPostForm('node/preview/' . $uuid . '/full', $view_mode_edit, t('Switch'));
$this->assertRaw('view-mode-teaser', 'View mode teaser class found.');
$this->assertNoText($edit[$body_key], 'Body not displayed.');
......@@ -292,6 +293,29 @@ function testPagePreview() {
$this->clickLink(t('Back to content editing'));
$this->assertRaw('edit-submit');
// Check that destination is remembered when clicking on preview. When going
// back to the edit form and clicking save, we should go back to the
// original destination, if set.
$destination = 'node';
$this->drupalPostForm($node->toUrl('edit-form'), [], t('Preview'), ['query' => ['destination' => $destination]]);
$parameters = ['node_preview' => $node->uuid(), 'view_mode_id' => 'full'];
$options = ['absolute' => TRUE, 'query' => ['destination' => $destination]];
$this->assertUrl(Url::fromRoute('entity.node.preview', $parameters, $options));
$this->drupalPostForm(NULL, ['view_mode' => 'teaser'], t('Switch'));
$this->clickLink(t('Back to content editing'));
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertUrl($destination);
// Check that preview page works as expected without a destination set.
$this->drupalPostForm($node->toUrl('edit-form'), [], t('Preview'));
$parameters = ['node_preview' => $node->uuid(), 'view_mode_id' => 'full'];
$this->assertUrl(Url::fromRoute('entity.node.preview', $parameters, ['absolute' => TRUE]));
$this->drupalPostForm(NULL, ['view_mode' => 'teaser'], t('Switch'));
$this->clickLink(t('Back to content editing'));
$this->drupalPostForm(NULL, [], t('Save'));
$this->assertUrl($node->toUrl());
$this->assertResponse(200);
// Assert multiple items can be added and are not lost when previewing.
$test_image_1 = current($this->drupalGetTestFiles('image', 39325));
$edit_image_1['files[field_image_0][]'] = drupal_realpath($test_image_1->uri);
......@@ -418,7 +442,7 @@ function testPagePreviewWithRevisions() {
/** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
$controller_resolver = \Drupal::service('controller_resolver');
$node_preview_controller = $controller_resolver->getControllerFromDefinition('\Drupal\node\Controller\NodePreviewController::view');
$node_preview_controller($node, 'default');
$node_preview_controller($node, 'full');
}
/**
......@@ -439,7 +463,7 @@ public function testSimultaneousPreview() {
$edit2 = array($title_key => 'Another page title');
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit2, t('Preview'));
$this->assertUrl(\Drupal::url('entity.node.preview', ['node_preview' => $node->uuid(), 'view_mode_id' => 'default'], ['absolute' => TRUE]));
$this->assertUrl(\Drupal::url('entity.node.preview', ['node_preview' => $node->uuid(), 'view_mode_id' => 'full'], ['absolute' => TRUE]));
$this->assertText($edit2[$title_key]);
}
......
/**
* @file
* CSS for Offcanvas tray.
*
* @todo Move CSS into core dialog library https://www.drupal.org/node/2784443.
*/
/* Position the dialog-offcanvas tray container outside the right of the viewport. */
.ui-dialog-offcanvas {
box-sizing: border-box;
height: 100%;
overflow: visible;
}
/* Wrap the form that's inside the dialog-offcanvas tray. */
.ui-dialog-offcanvas .ui-dialog-content {
padding: 0 20px;
/* Prevent horizontal scrollbar. */
overflow-x: hidden;
overflow-y: auto;
}
[dir="rtl"] .ui-dialog-offcanvas .ui-dialog-content {
text-align: right;
}
/**
* @file
* Motion effects for off-canvas tray dialog.
*
* Motion effects are in a separate file so that they can be easily turned off
* to improve performance if desired.
*
* @todo Move motion effects file into a core Off-Canvas library and add a
* configuration option for browser rendering performance to disable this
* file: https://www.drupal.org/node/2784443.
*/
/* Transition the dialog-offcanvas tray container, with 2s delay to match main canvas speed. */
.ui-dialog-offcanvas .ui-dialog-content {
-webkit-transition: all .7s ease 2s;
-moz-transition: all .7s ease 2s;
transition: all .7s ease 2s;
}
@media (max-width: 700px) {
.ui-dialog-offcanvas .ui-dialog-content {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}
}
.dialog-offcanvas__main-canvas {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}