Update to Drupal 8.1.8. For more information, see https://www.drupal.org/project/drupal/releases/8.1.8
This commit is contained in:
parent
e9f047ccf8
commit
f9f23cdf38
312 changed files with 6751 additions and 1546 deletions
|
@ -12,6 +12,12 @@
|
|||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {{ content|without('field_example') }} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* - title_attributes: Same as attributes, except applied to the main title
|
||||
* tag that appears in the template.
|
||||
* - title_prefix: Additional output populated by modules, intended to be
|
||||
* displayed in front of the main title tag that appears in the template.
|
||||
* - title_suffix: Additional output populated by modules, intended to be
|
||||
* displayed after the main title tag that appears in the template.
|
||||
*
|
||||
* @see template_preprocess_aggregator_feed()
|
||||
*
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {{ content|without('field_example') }} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* - attributes: HTML attributes for the wrapper.
|
||||
* - title_prefix: Additional output populated by modules, intended to be
|
||||
* displayed in front of the main title tag that appears in the template.
|
||||
* - title_suffix: Additional output populated by modules, intended to be
|
||||
* displayed after the main title tag that appears in the template.
|
||||
*
|
||||
* @see template_preprocess_aggregator_item()
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\tests\aggregator\Kernel\Migrate;
|
||||
namespace Drupal\Tests\aggregator\Kernel\Migrate;
|
||||
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
|
||||
|
|
|
@ -44,7 +44,8 @@ class BanIpManager implements BanIpManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function banIp($ip) {
|
||||
$this->connection->insert('ban_ip')
|
||||
$this->connection->merge('ban_ip')
|
||||
->key(array('ip' => $ip))
|
||||
->fields(array('ip' => $ip))
|
||||
->execute();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace Drupal\Tests\ban\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\ban\BanIpManager;
|
||||
|
||||
/**
|
||||
* Tests IP address banning.
|
||||
|
@ -29,7 +31,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
|
||||
// Ban a valid IP address.
|
||||
$edit = array();
|
||||
$edit['ip'] = '192.168.1.1';
|
||||
$edit['ip'] = '1.2.3.3';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
|
||||
$this->assertTrue($ip, 'IP address found in database.');
|
||||
|
@ -37,7 +39,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
|
||||
// Try to block an IP address that's already blocked.
|
||||
$edit = array();
|
||||
$edit['ip'] = '192.168.1.1';
|
||||
$edit['ip'] = '1.2.3.3';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$this->assertText(t('This IP address is already banned.'));
|
||||
|
||||
|
@ -73,6 +75,28 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
// $edit['ip'] = \Drupal::request()->getClientIP();
|
||||
// $this->drupalPostForm('admin/config/people/ban', $edit, t('Save'));
|
||||
// $this->assertText(t('You may not ban your own IP address.'));
|
||||
|
||||
// Test duplicate ip address are not present in the 'blocked_ips' table.
|
||||
// when they are entered programmatically.
|
||||
$connection = Database::getConnection();
|
||||
$banIp = new BanIpManager($connection);
|
||||
$ip = '1.0.0.0';
|
||||
$banIp->banIp($ip);
|
||||
$banIp->banIp($ip);
|
||||
$banIp->banIp($ip);
|
||||
$query = db_select('ban_ip', 'bip');
|
||||
$query->fields('bip', array('iid'));
|
||||
$query->condition('bip.ip', $ip);
|
||||
$ip_count = $query->execute()->fetchAll();
|
||||
$this->assertEqual(1, count($ip_count));
|
||||
$ip = '';
|
||||
$banIp->banIp($ip);
|
||||
$banIp->banIp($ip);
|
||||
$query = db_select('ban_ip', 'bip');
|
||||
$query->fields('bip', array('iid'));
|
||||
$query->condition('bip.ip', $ip);
|
||||
$ip_count = $query->execute()->fetchAll();
|
||||
$this->assertEqual(1, count($ip_count));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,7 +89,10 @@
|
|||
}, interval);
|
||||
}
|
||||
|
||||
var interval = 200;
|
||||
// The frequency with which to check for newly arrived BigPipe placeholders.
|
||||
// Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
|
||||
// more would cause the user to see content appear noticeably slower.
|
||||
var interval = drupalSettings.bigPipeInterval || 50;
|
||||
// The internal ID to contain the watcher service.
|
||||
var timeoutID;
|
||||
|
||||
|
|
|
@ -126,9 +126,15 @@ class BigPipe implements BigPipeInterface {
|
|||
// Reopen it for the duration that we are rendering placeholders.
|
||||
$this->session->start();
|
||||
|
||||
list($pre_body, $post_body) = explode('</body>', $content, 2);
|
||||
// Find the closing </body> tag and get the strings before and after. But be
|
||||
// careful to use the latest occurrence of the string "</body>", to ensure
|
||||
// that strings in inline JavaScript or CDATA sections aren't used instead.
|
||||
$parts = explode('</body>', $content);
|
||||
$post_body = array_pop($parts);
|
||||
$pre_body = implode('', $parts);
|
||||
|
||||
$this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets);
|
||||
$this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body), $cumulative_assets);
|
||||
$this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body, $placeholders), $cumulative_assets);
|
||||
$this->sendPostBody($post_body);
|
||||
|
||||
// Close the session again.
|
||||
|
@ -528,6 +534,9 @@ EOF;
|
|||
*
|
||||
* @param string $html
|
||||
* HTML markup.
|
||||
* @param array $placeholders
|
||||
* Associative array; the BigPipe placeholders. Keys are the BigPipe
|
||||
* placeholder IDs.
|
||||
*
|
||||
* @return array
|
||||
* Indexed array; the order in which the BigPipe placeholders must be sent.
|
||||
|
@ -535,18 +544,45 @@ EOF;
|
|||
* placeholders are kept: if the same placeholder occurs multiple times, we
|
||||
* only keep the first occurrence.
|
||||
*/
|
||||
protected function getPlaceholderOrder($html) {
|
||||
protected function getPlaceholderOrder($html, $placeholders) {
|
||||
$fragments = explode('<div data-big-pipe-placeholder-id="', $html);
|
||||
array_shift($fragments);
|
||||
$order = [];
|
||||
$placeholder_ids = [];
|
||||
|
||||
foreach ($fragments as $fragment) {
|
||||
$t = explode('"></div>', $fragment, 2);
|
||||
$placeholder = $t[0];
|
||||
$order[] = $placeholder;
|
||||
$placeholder_id = $t[0];
|
||||
$placeholder_ids[] = $placeholder_id;
|
||||
}
|
||||
$placeholder_ids = array_unique($placeholder_ids);
|
||||
|
||||
// The 'status messages' placeholder needs to be special cased, because it
|
||||
// depends on global state that can be modified when other placeholders are
|
||||
// being rendered: any code can add messages to render.
|
||||
// This violates the principle that each lazy builder must be able to render
|
||||
// itself in isolation, and therefore in any order. However, we cannot
|
||||
// change the way drupal_set_message() works in the Drupal 8 cycle. So we
|
||||
// have to accommodate its special needs.
|
||||
// Allowing placeholders to be rendered in a particular order (in this case:
|
||||
// last) would violate this isolation principle. Thus a monopoly is granted
|
||||
// to this one special case, with this hard-coded solution.
|
||||
// @see \Drupal\Core\Render\Element\StatusMessages
|
||||
// @see \Drupal\Core\Render\Renderer::replacePlaceholders()
|
||||
// @see https://www.drupal.org/node/2712935#comment-11368923
|
||||
$message_placeholder_ids = [];
|
||||
foreach ($placeholders as $placeholder_id => $placeholder_element) {
|
||||
if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
|
||||
$message_placeholder_ids[] = $placeholder_id;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($order);
|
||||
// Return placeholder IDs in DOM order, but with the 'status messages'
|
||||
// placeholders at the end, if they are present.
|
||||
$ordered_placeholder_ids = array_merge(
|
||||
array_diff($placeholder_ids, $message_placeholder_ids),
|
||||
array_intersect($placeholder_ids, $message_placeholder_ids)
|
||||
);
|
||||
return $ordered_placeholder_ids;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class BigPipePlaceholderTestCases {
|
|||
|
||||
// 1. Real-world example of HTML placeholder.
|
||||
$status_messages = new BigPipePlaceholderTestCase(
|
||||
[], //['#type' => 'status_messages'],
|
||||
['#type' => 'status_messages'],
|
||||
'<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
|
||||
[
|
||||
'#lazy_builder' => [
|
||||
|
@ -110,7 +110,7 @@ class BigPipePlaceholderTestCases {
|
|||
'command' => 'insert',
|
||||
'method' => 'replaceWith',
|
||||
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"]',
|
||||
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n \n",
|
||||
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n ",
|
||||
'settings' => NULL,
|
||||
],
|
||||
];
|
||||
|
|
|
@ -170,6 +170,11 @@ class BigPipeTest extends WebTestBase {
|
|||
$cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
|
||||
$cases['exception__lazy_builder']->bigPipePlaceholderId => NULL,
|
||||
$cases['exception__embedded_response']->bigPipePlaceholderId => NULL,
|
||||
], [
|
||||
0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
|
||||
// The 'html' case contains the 'status messages' placeholder, which is
|
||||
// always rendered last.
|
||||
1 => $cases['html']->bigPipePlaceholderId,
|
||||
]);
|
||||
|
||||
$this->assertRaw('</body>', 'Closing body tag present.');
|
||||
|
@ -183,7 +188,7 @@ class BigPipeTest extends WebTestBase {
|
|||
$records = db_query('SELECT * FROM {watchdog} ORDER BY wid DESC LIMIT 2')->fetchAll();
|
||||
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
|
||||
$this->assertTrue(FALSE !== strpos((string) unserialize($records[0]->variables)['@message'], 'Oh noes!'));
|
||||
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
|
||||
$this->assertEqual(RfcLogLevel::ERROR, $records[1]->severity);
|
||||
$this->assertTrue(FALSE !== strpos((string) unserialize($records[1]->variables)['@message'], 'You are not allowed to say llamas are not cool!'));
|
||||
|
||||
// Verify that 4xx responses work fine. (4xx responses are handled by
|
||||
|
@ -250,7 +255,7 @@ class BigPipeTest extends WebTestBase {
|
|||
$this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.');
|
||||
|
||||
$this->pass('Verifying BigPipe assets are absent…', 'Debug');
|
||||
$this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings and BigPipe asset library absent.');
|
||||
$this->assertTrue(!isset($this->getDrupalSettings()['bigPipePlaceholderIds']) && empty($this->getDrupalSettings()['ajaxPageState']), 'BigPipe drupalSettings and BigPipe asset library absent.');
|
||||
$this->assertRaw('</body>', 'Closing body tag present.');
|
||||
|
||||
// Verify that 4xx responses work fine. (4xx responses are handled by
|
||||
|
@ -336,8 +341,11 @@ class BigPipeTest extends WebTestBase {
|
|||
*
|
||||
* @param array $expected_big_pipe_placeholders
|
||||
* Keys: BigPipe placeholder IDs. Values: expected AJAX response.
|
||||
* @param array $expected_big_pipe_placeholder_stream_order
|
||||
* Keys: BigPipe placeholder IDs. Values: expected AJAX response. Keys are
|
||||
* defined in the order that they are expected to be rendered & streamed.
|
||||
*/
|
||||
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders) {
|
||||
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
|
||||
$this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
|
||||
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
|
||||
$placeholder_positions = [];
|
||||
|
@ -364,9 +372,14 @@ class BigPipeTest extends WebTestBase {
|
|||
}
|
||||
ksort($placeholder_positions, SORT_NUMERIC);
|
||||
$this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
|
||||
$this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<div data-big-pipe-placeholder-id="', '/') . '/', $this->getRawContent()));
|
||||
$expected_big_pipe_placeholders_with_replacements = array_filter($expected_big_pipe_placeholders);
|
||||
$this->assertEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
|
||||
$placeholders = array_map(function(\SimpleXMLElement $element) { return (string) $element['data-big-pipe-placeholder-id']; }, $this->cssSelect('[data-big-pipe-placeholder-id]'));
|
||||
$this->assertEqual(count($expected_big_pipe_placeholders), count(array_unique($placeholders)));
|
||||
$expected_big_pipe_placeholders_with_replacements = [];
|
||||
foreach ($expected_big_pipe_placeholder_stream_order as $big_pipe_placeholder_id) {
|
||||
$expected_big_pipe_placeholders_with_replacements[$big_pipe_placeholder_id] = $expected_big_pipe_placeholders[$big_pipe_placeholder_id];
|
||||
}
|
||||
$this->assertEqual($expected_big_pipe_placeholders_with_replacements, array_filter($expected_big_pipe_placeholders));
|
||||
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
|
||||
$this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
|
||||
|
||||
$this->pass('Verifying BigPipe start/stop signals…', 'Debug');
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
name: 'BigPipe regression test'
|
||||
type: module
|
||||
description: 'Support module for BigPipe regression testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,6 @@
|
|||
big_pipe_regression_test.2678662:
|
||||
path: '/big_pipe_regression_test/2678662'
|
||||
defaults:
|
||||
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2678662'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\big_pipe_regression_test;
|
||||
|
||||
use Drupal\big_pipe\Render\BigPipeMarkup;
|
||||
|
||||
class BigPipeRegressionTestController {
|
||||
|
||||
const MARKER_2678662 = '<script>var hitsTheFloor = "</body>";</script>';
|
||||
|
||||
/**
|
||||
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleBodies_2678662()
|
||||
*/
|
||||
public function regression2678662() {
|
||||
return [
|
||||
'#markup' => BigPipeMarkup::create(self::MARKER_2678662),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace Drupal\Tests\big_pipe\FunctionalJavascript;
|
||||
|
||||
use Drupal\big_pipe\Render\BigPipe;
|
||||
use Drupal\big_pipe_regression_test\BigPipeRegressionTestController;
|
||||
use Drupal\comment\CommentInterface;
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
@ -27,13 +30,8 @@ class BigPipeRegressionTest extends JavascriptTestBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'comment',
|
||||
'big_pipe',
|
||||
'history',
|
||||
'editor',
|
||||
'ckeditor',
|
||||
'filter',
|
||||
'big_pipe_regression_test',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -53,6 +51,8 @@ class BigPipeRegressionTest extends JavascriptTestBase {
|
|||
* @see https://www.drupal.org/node/2698811
|
||||
*/
|
||||
public function testCommentForm_2698811() {
|
||||
$this->assertTrue($this->container->get('module_installer')->install(['comment', 'history', 'ckeditor'], TRUE), 'Installed modules.');
|
||||
|
||||
// Ensure an `article` node type exists.
|
||||
$this->createContentType(['type' => 'article']);
|
||||
$this->addDefaultCommentField('node', 'article');
|
||||
|
@ -114,4 +114,74 @@ JS;
|
|||
$this->assertJsCondition($javascript);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure BigPipe works despite inline JS containing the string "</body>".
|
||||
*
|
||||
* @see https://www.drupal.org/node/2678662
|
||||
*/
|
||||
public function testMultipleClosingBodies_2678662() {
|
||||
$this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser());
|
||||
$this->drupalGet(Url::fromRoute('big_pipe_regression_test.2678662'));
|
||||
|
||||
// Confirm that AJAX behaviors were instantiated, if not, this points to a
|
||||
// JavaScript syntax error.
|
||||
$javascript = <<<JS
|
||||
(function(){
|
||||
return Object.keys(Drupal.ajax.instances).length > 0;
|
||||
}());
|
||||
JS;
|
||||
$this->assertJsCondition($javascript);
|
||||
|
||||
// Besides verifying there is no JavaScript syntax error, also verify the
|
||||
// HTML structure.
|
||||
$this->assertSession()
|
||||
->responseContains(BigPipe::STOP_SIGNAL . "\n\n\n</body></html>", 'The BigPipe stop signal is present just before the closing </body> and </html> tags.');
|
||||
$js_code_until_closing_body_tag = substr(BigPipeRegressionTestController::MARKER_2678662, 0, strpos(BigPipeRegressionTestController::MARKER_2678662, '</body>'));
|
||||
$this->assertSession()
|
||||
->responseNotContains($js_code_until_closing_body_tag . "\n" . BigPipe::START_SIGNAL, 'The BigPipe start signal does NOT start at the closing </body> tag string in an inline script.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure messages set in placeholders always appear.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2712935
|
||||
*/
|
||||
public function testMessages_2712935() {
|
||||
$this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser());
|
||||
$messages_markup = '<div role="contentinfo" aria-label="Status message"';
|
||||
|
||||
$test_routes = [
|
||||
// Messages placeholder rendered first.
|
||||
'render_placeholder_message_test.first',
|
||||
// Messages placeholder rendered after one, before another.
|
||||
'render_placeholder_message_test.middle',
|
||||
// Messages placeholder rendered last.
|
||||
'render_placeholder_message_test.last',
|
||||
];
|
||||
|
||||
$assert = $this->assertSession();
|
||||
foreach ($test_routes as $route) {
|
||||
// Verify that we start off with zero messages queued.
|
||||
$this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
|
||||
$assert->responseNotContains($messages_markup);
|
||||
|
||||
// Verify the test case at this route behaves as expected.
|
||||
$this->drupalGet(Url::fromRoute($route));
|
||||
$assert->elementContains('css', 'p.logged-message:nth-of-type(1)', 'Message: P1');
|
||||
$assert->elementContains('css', 'p.logged-message:nth-of-type(2)', 'Message: P2');
|
||||
$assert->responseContains($messages_markup);
|
||||
$assert->elementExists('css', 'div[aria-label="Status message"] ul');
|
||||
$assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(1)', 'P1');
|
||||
$assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(2)', 'P2');
|
||||
|
||||
// Verify that we end with all messages printed, hence again zero queued.
|
||||
$this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
|
||||
$assert->responseNotContains($messages_markup);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -198,6 +198,10 @@
|
|||
far too wide focus indicator. This fixes that. */
|
||||
overflow: hidden;
|
||||
}
|
||||
.ckeditor-buttons .cke_button_icon img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ckeditor-buttons li .cke_ltr {
|
||||
direction: ltr;
|
||||
}
|
||||
|
|
|
@ -483,7 +483,7 @@
|
|||
* A HTML string for the button to toggle group names.
|
||||
*/
|
||||
Drupal.theme.ckeditorButtonGroupNamesToggle = function () {
|
||||
return '<a class="ckeditor-groupnames-toggle" role="button" aria-pressed="false"></a>';
|
||||
return '<button class="link ckeditor-groupnames-toggle" aria-pressed="false"></button>';
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -96,9 +96,16 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
|
|||
* #element_validate handler for the "styles" element in settingsForm().
|
||||
*/
|
||||
public function validateStylesValue(array $element, FormStateInterface $form_state) {
|
||||
if ($this->generateStylesSetSetting($element['#value']) === FALSE) {
|
||||
$styles_setting = $this->generateStylesSetSetting($element['#value']);
|
||||
if ($styles_setting === FALSE) {
|
||||
$form_state->setError($element, t('The provided list of styles is syntactically incorrect.'));
|
||||
}
|
||||
else {
|
||||
$style_names = array_map(function ($style) { return $style['name']; }, $styles_setting);
|
||||
if (count($style_names) !== count(array_unique($style_names))) {
|
||||
$form_state->setError($element, t('Each style must have a unique label.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* Tests administration of the CKEditor StylesCombo plugin.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorStylesComboAdminTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['filter', 'editor', 'ckeditor'];
|
||||
|
||||
/**
|
||||
* A user with the 'administer filters' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A random generated format machine name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->format = strtolower($this->randomMachineName());
|
||||
$filter_format = FilterFormat::create([
|
||||
'format' => $this->format,
|
||||
'name' => $this->randomString(),
|
||||
'filters' => [],
|
||||
]);
|
||||
$filter_format->save();
|
||||
$editor = Editor::create([
|
||||
'format' => $this->format,
|
||||
'editor' => 'ckeditor',
|
||||
]);
|
||||
$editor->save();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['administer filters']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests StylesCombo settings for an existing text format.
|
||||
*/
|
||||
function testExistingFormat() {
|
||||
$ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
$default_settings = $ckeditor->getDefaultSettings();
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
|
||||
// Ensure an Editor config entity exists, with the proper settings.
|
||||
$expected_settings = $default_settings;
|
||||
$editor = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Case 1: Configure the Styles plugin with different labels for each style,
|
||||
// and ensure the updated settings are saved.
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
$edit = [
|
||||
'editor[settings][plugins][stylescombo][styles]' => "h1.title|Title\np.callout|Callout\n\n",
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$expected_settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.callout|Callout\n\n";
|
||||
$editor = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Case 2: Configure the Styles plugin with same labels for each style, and
|
||||
// ensure that an error is displayed and that the updated settings are not
|
||||
// saved.
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
$edit = [
|
||||
'editor[settings][plugins][stylescombo][styles]' => "h1.title|Title\np.callout|Title\n\n",
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$this->assertRaw(t('Each style must have a unique label.'));
|
||||
$expected_settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.callout|Callout\n\n";
|
||||
$editor = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
}
|
||||
|
||||
}
|
31
core/modules/config/config.install
Normal file
31
core/modules/config/config.install
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the config module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function config_requirements($phase) {
|
||||
$requirements = [];
|
||||
try {
|
||||
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// system_requirements() guarantees that the CONFIG_SYNC_DIRECTORY exists
|
||||
// as the config.storage.staging service relies on it.
|
||||
$directory = FALSE;
|
||||
}
|
||||
// Ensure the configuration sync directory is writable. This is only a warning
|
||||
// because only configuration import from a tarball requires the folder to be
|
||||
// web writable.
|
||||
if ($phase !== 'install' && !is_writable($directory)) {
|
||||
$requirements['config directory ' . CONFIG_SYNC_DIRECTORY] = [
|
||||
'title' => t('The directory %directory is not writable.', ['%directory' => $directory]),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
];
|
||||
}
|
||||
return $requirements;
|
||||
}
|
|
@ -50,6 +50,11 @@ class ConfigImportForm extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
|
||||
$directory_is_writable = is_writable($directory);
|
||||
if (!$directory_is_writable) {
|
||||
drupal_set_message($this->t('The directory %directory is not writable.', ['%directory' => $directory]), 'error');
|
||||
}
|
||||
$form['import_tarball'] = array(
|
||||
'#type' => 'file',
|
||||
'#title' => $this->t('Configuration archive'),
|
||||
|
@ -59,6 +64,7 @@ class ConfigImportForm extends FormBase {
|
|||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Upload'),
|
||||
'#disabled' => !$directory_is_writable,
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,16 @@ class ConfigImportUploadTest extends WebTestBase {
|
|||
$edit = array('files[import_tarball]' => drupal_realpath($text_file->uri));
|
||||
$this->drupalPostForm('admin/config/development/configuration/full/import', $edit, t('Upload'));
|
||||
$this->assertText(t('Could not extract the contents of the tar file'));
|
||||
|
||||
// Make the sync directory read-only.
|
||||
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
|
||||
\Drupal::service('file_system')->chmod($directory, 0555);
|
||||
$this->drupalGet('admin/config/development/configuration/full/import');
|
||||
$this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory]));
|
||||
// Ensure submit button for \Drupal\config\Form\ConfigImportForm is
|
||||
// disabled.
|
||||
$submit_is_disabled = $this->cssSelect('form.config-import-form input[type="submit"]:disabled');
|
||||
$this->assertTrue(count($submit_is_disabled) === 1, 'The submit button is disabled.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class ConfigInstallWebTest extends WebTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes'));
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes', 'administer site configuration'));
|
||||
|
||||
// Ensure the global variable being asserted by this test does not exist;
|
||||
// a previous test executed in this request/process might have set it.
|
||||
|
@ -188,4 +188,22 @@ class ConfigInstallWebTest extends WebTestBase {
|
|||
$this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests config_requirements().
|
||||
*/
|
||||
public function testConfigModuleRequirements() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPostForm('admin/modules', array('modules[Core][config][enable]' => TRUE), t('Install'));
|
||||
|
||||
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
|
||||
file_unmanaged_delete_recursive($directory);
|
||||
$this->drupalGet('/admin/reports/status');
|
||||
$this->assertRaw(t('The directory %directory does not exist.', array('%directory' => $directory)));
|
||||
|
||||
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
|
||||
\Drupal::service('file_system')->chmod($directory, 0555);
|
||||
$this->drupalGet('/admin/reports/status');
|
||||
$this->assertRaw(t('The directory %directory is not writable.', ['%directory' => $directory]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ entity.config_test.edit_form:
|
|||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
entity.config_test.edit_form_config_test_no_status:
|
||||
entity.config_test_no_status.edit_form:
|
||||
path: '/admin/structure/config_test/manage/{config_test_no_status}'
|
||||
defaults:
|
||||
_entity_form: config_test_no_status
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\config_translation\Access;
|
||||
|
||||
use Drupal\config_translation\ConfigMapperInterface;
|
||||
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
@ -12,27 +14,68 @@ use Drupal\Core\Session\AccountInterface;
|
|||
class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Checks access to the overview based on permissions and translatability.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route_match to check against.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to check access for.
|
||||
* @param string $langcode
|
||||
* The language code of the target language.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(RouteMatchInterface $route_match, AccountInterface $account, $langcode = NULL) {
|
||||
// For the translation forms we have a target language, so we need some
|
||||
// checks in addition to the checks performed for the translation overview.
|
||||
$base_access = parent::access($route_match, $account);
|
||||
if ($base_access->isAllowed()) {
|
||||
$mapper = $this->getMapperFromRouteMatch($route_match);
|
||||
|
||||
try {
|
||||
$source_langcode = $mapper->getLangcode();
|
||||
$source_language = $this->languageManager->getLanguage($source_langcode);
|
||||
|
||||
$target_language = $this->languageManager->getLanguage($langcode);
|
||||
|
||||
// Make sure that the target language is not locked, and that the target
|
||||
// language is not the original submission language. Although technically
|
||||
// configuration can be overlaid with translations in the same language,
|
||||
// that is logically not a good idea.
|
||||
$access =
|
||||
!empty($target_language) &&
|
||||
!$target_language->isLocked() &&
|
||||
(empty($this->sourceLanguage) || ($target_language->getId() != $this->sourceLanguage->getId()));
|
||||
|
||||
return $base_access->andIf(AccessResult::allowedIf($access));
|
||||
return $this->doCheckAccess($account, $mapper, $source_language, $target_language);
|
||||
}
|
||||
return $base_access;
|
||||
catch (ConfigMapperLanguageException $exception) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access given an account, configuration mapper, and source language.
|
||||
*
|
||||
* In addition to the checks performed by
|
||||
* ConfigTranslationOverviewAccess::doCheckAccess() this makes sure the target
|
||||
* language is not locked and the target language is not the source language.
|
||||
*
|
||||
* Although technically configuration can be overlaid with translations in the
|
||||
* same language, that is logically not a good idea.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to check access for.
|
||||
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
|
||||
* The configuration mapper to check access for.
|
||||
* @param \Drupal\Core\Language\LanguageInterface|null $source_language
|
||||
* The source language to check for, if any.
|
||||
* @param \Drupal\Core\Language\LanguageInterface|null $target_language
|
||||
* The target language to check for, if any.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The result of the access check.
|
||||
*
|
||||
* @see \Drupal\config_translation\Access\ConfigTranslationOverviewAccess::doCheckAccess()
|
||||
*/
|
||||
protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL, $target_language = NULL) {
|
||||
$base_access_result = parent::doCheckAccess($account, $mapper, $source_language);
|
||||
|
||||
$access =
|
||||
$target_language &&
|
||||
!$target_language->isLocked() &&
|
||||
(!$source_language || ($target_language->getId() !== $source_language->getId()));
|
||||
|
||||
return $base_access_result->andIf(AccessResult::allowedIf($access));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\config_translation\Access;
|
||||
|
||||
use Drupal\config_translation\ConfigMapperInterface;
|
||||
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\config_translation\ConfigMapperManagerInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
@ -28,13 +30,6 @@ class ConfigTranslationOverviewAccess implements AccessInterface {
|
|||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The source language.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageInterface
|
||||
*/
|
||||
protected $sourceLanguage;
|
||||
|
||||
/**
|
||||
* Constructs a ConfigTranslationOverviewAccess object.
|
||||
*
|
||||
|
@ -54,28 +49,66 @@ class ConfigTranslationOverviewAccess implements AccessInterface {
|
|||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route_match to check against.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
* The account to check access for.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$route = $route_match->getRouteObject();
|
||||
$mapper = $this->getMapperFromRouteMatch($route_match);
|
||||
|
||||
/** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
|
||||
$mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id'));
|
||||
try {
|
||||
$langcode = $mapper->getLangcode();
|
||||
}
|
||||
catch (ConfigMapperLanguageException $exception) {
|
||||
// ConfigTranslationController shows a helpful message if the language
|
||||
// codes do not match, so do not let that prevent granting access.
|
||||
$langcode = 'en';
|
||||
}
|
||||
$source_language = $this->languageManager->getLanguage($langcode);
|
||||
|
||||
return $this->doCheckAccess($account, $mapper, $source_language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a configuration mapper using a route match.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match to populate the mapper with.
|
||||
*
|
||||
* @return \Drupal\config_translation\ConfigMapperInterface
|
||||
* The configuration mapper.
|
||||
*/
|
||||
protected function getMapperFromRouteMatch(RouteMatchInterface $route_match) {
|
||||
$mapper = $this->configMapperManager->createInstance($route_match->getRouteObject()
|
||||
->getDefault('plugin_id'));
|
||||
$mapper->populateFromRouteMatch($route_match);
|
||||
$this->sourceLanguage = $this->languageManager->getLanguage($mapper->getLangcode());
|
||||
return $mapper;
|
||||
}
|
||||
|
||||
// Allow access to the translation overview if the proper permission is
|
||||
// granted, the configuration has translatable pieces, and the source
|
||||
// language is not locked if it is present.
|
||||
$source_language_access = is_null($this->sourceLanguage) || !$this->sourceLanguage->isLocked();
|
||||
/**
|
||||
* Checks access given an account, configuration mapper, and source language.
|
||||
*
|
||||
* Grants access if the proper permission is granted to the account, the
|
||||
* configuration has translatable pieces, and the source language is not
|
||||
* locked given it is present.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to check access for.
|
||||
* @param \Drupal\config_translation\ConfigMapperInterface $mapper
|
||||
* The configuration mapper to check access for.
|
||||
* @param \Drupal\Core\Language\LanguageInterface|null $source_language
|
||||
* The source language to check for, if any.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The result of the access check.
|
||||
*/
|
||||
protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL) {
|
||||
$access =
|
||||
$account->hasPermission('translate configuration') &&
|
||||
$mapper->hasSchema() &&
|
||||
$mapper->hasTranslatable() &&
|
||||
$source_language_access;
|
||||
(!$source_language || !$source_language->isLocked());
|
||||
|
||||
return AccessResult::allowedIf($access)->cachePerPermissions();
|
||||
}
|
||||
|
|
|
@ -202,6 +202,17 @@ interface ConfigMapperInterface {
|
|||
*/
|
||||
public function getLangcode();
|
||||
|
||||
/**
|
||||
* Returns the language code of a configuration object given its name.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @return string
|
||||
* The language code of the configuration object.
|
||||
*/
|
||||
public function getLangcodeFromConfig($config_name);
|
||||
|
||||
/**
|
||||
* Sets the original language code.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\config_translation;
|
||||
|
||||
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
@ -380,22 +381,26 @@ class ConfigNamesMapper extends PluginBase implements ConfigMapperInterface, Con
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode() {
|
||||
$config_factory = $this->configFactory;
|
||||
$langcodes = array_map(function($name) use ($config_factory) {
|
||||
// Default to English if no language code was provided in the file.
|
||||
// Although it is a best practice to include a language code, if the
|
||||
// developer did not think about a multilingual use-case, we fall back
|
||||
// on assuming the file is English.
|
||||
return $config_factory->get($name)->get('langcode') ?: 'en';
|
||||
}, $this->getConfigNames());
|
||||
$langcodes = array_map([$this, 'getLangcodeFromConfig'], $this->getConfigNames());
|
||||
|
||||
if (count(array_unique($langcodes)) > 1) {
|
||||
throw new \RuntimeException('A config mapper can only contain configuration for a single language.');
|
||||
throw new ConfigMapperLanguageException('A config mapper can only contain configuration for a single language.');
|
||||
}
|
||||
|
||||
return reset($langcodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcodeFromConfig($config_name) {
|
||||
// Default to English if no language code was provided in the file.
|
||||
// Although it is a best practice to include a language code, if the
|
||||
// developer did not think about a multilingual use case, we fall back
|
||||
// on assuming the file is English.
|
||||
return $this->configFactory->get($config_name)->get('langcode') ?: 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
namespace Drupal\config_translation\Controller;
|
||||
|
||||
use Drupal\config_translation\ConfigMapperManagerInterface;
|
||||
use Drupal\config_translation\Exception\ConfigMapperLanguageException;
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
@ -63,6 +66,13 @@ class ConfigTranslationController extends ControllerBase {
|
|||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a ConfigTranslationController.
|
||||
*
|
||||
|
@ -78,14 +88,17 @@ class ConfigTranslationController extends ControllerBase {
|
|||
* The current user.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(ConfigMapperManagerInterface $config_mapper_manager, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, AccountInterface $account, LanguageManagerInterface $language_manager) {
|
||||
public function __construct(ConfigMapperManagerInterface $config_mapper_manager, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, AccountInterface $account, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
|
||||
$this->configMapperManager = $config_mapper_manager;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->router = $router;
|
||||
$this->pathProcessor = $path_processor;
|
||||
$this->account = $account;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,7 +111,8 @@ class ConfigTranslationController extends ControllerBase {
|
|||
$container->get('router'),
|
||||
$container->get('path_processor_manager'),
|
||||
$container->get('current_user'),
|
||||
$container->get('language_manager')
|
||||
$container->get('language_manager'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -127,7 +141,33 @@ class ConfigTranslationController extends ControllerBase {
|
|||
if (count($languages) == 1) {
|
||||
drupal_set_message($this->t('In order to translate configuration, the website must have at least two <a href=":url">languages</a>.', array(':url' => $this->url('entity.configurable_language.collection'))), 'warning');
|
||||
}
|
||||
$original_langcode = $mapper->getLangcode();
|
||||
|
||||
try {
|
||||
$original_langcode = $mapper->getLangcode();
|
||||
$operations_access = TRUE;
|
||||
}
|
||||
catch (ConfigMapperLanguageException $exception) {
|
||||
$items = [];
|
||||
foreach ($mapper->getConfigNames() as $config_name) {
|
||||
$langcode = $mapper->getLangcodeFromConfig($config_name);
|
||||
$items[] = $this->t('@name: @langcode', [
|
||||
'@name' => $config_name,
|
||||
'@langcode' => $langcode,
|
||||
]);
|
||||
}
|
||||
$message = [
|
||||
'message' => ['#markup' => $this->t('The configuration objects have different language codes so they cannot be translated:')],
|
||||
'items' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
],
|
||||
];
|
||||
drupal_set_message($this->renderer->renderPlain($message), 'warning');
|
||||
|
||||
$original_langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
|
||||
$operations_access = FALSE;
|
||||
}
|
||||
|
||||
if (!isset($languages[$original_langcode])) {
|
||||
// If the language is not configured on the site, create a dummy language
|
||||
// object for this listing only to ensure the user gets useful info.
|
||||
|
@ -205,6 +245,9 @@ class ConfigTranslationController extends ControllerBase {
|
|||
$page['languages'][$langcode]['operations'] = array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $operations,
|
||||
// Even if the mapper contains multiple language codes, the source
|
||||
// configuration can still be edited.
|
||||
'#access' => ($langcode == $original_langcode) || $operations_access,
|
||||
);
|
||||
}
|
||||
return $page;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\config_translation\Exception;
|
||||
|
||||
/**
|
||||
* Provides an exception for configuration mappers with multiple languages.
|
||||
*/
|
||||
class ConfigMapperLanguageException extends \RuntimeException {
|
||||
}
|
|
@ -138,7 +138,12 @@ abstract class ConfigTranslationFormBase extends FormBase implements BaseFormIdI
|
|||
|
||||
$this->mapper = $mapper;
|
||||
$this->language = $language;
|
||||
$this->sourceLanguage = $this->languageManager->getLanguage($this->mapper->getLangcode());
|
||||
|
||||
// ConfigTranslationFormAccess will not grant access if this raises an
|
||||
// exception, so we can call this without a try-catch block here.
|
||||
$langcode = $this->mapper->getLangcode();
|
||||
|
||||
$this->sourceLanguage = $this->languageManager->getLanguage($langcode);
|
||||
|
||||
// Get base language configuration to display in the form before setting the
|
||||
// language to use for the form. This avoids repetitively settings and
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace Drupal\content_translation;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
|
@ -74,11 +77,27 @@ class ContentTranslationUpdatesManager implements EventSubscriberInterface {
|
|||
$this->updateDefinitions($entity_types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for migration imports.
|
||||
*/
|
||||
public function onMigrateImport(MigrateImportEvent $event) {
|
||||
$migration = $event->getMigration();
|
||||
$configuration = $migration->getDestinationConfiguration();
|
||||
$entity_types = NestedArray::getValue($configuration, ['content_translation_update_definitions']);
|
||||
if ($entity_types) {
|
||||
$entity_types = array_intersect_key($this->entityManager->getDefinitions(), array_flip($entity_types));
|
||||
$this->updateDefinitions($entity_types);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::IMPORT][] = ['onConfigImporterImport', 60];
|
||||
if (class_exists('\Drupal\migrate\Event\MigrateEvents')) {
|
||||
$events[MigrateEvents::POST_IMPORT][] = ['onMigrateImport'];
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
|
|
|
@ -124,8 +124,8 @@ class DynamicPageCacheSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
public function onRouteMatch(GetResponseEvent $event) {
|
||||
// Don't cache the response if the Dynamic Page Cache request policies are
|
||||
// not met. Store the result in a request attribute, so that onResponse()
|
||||
// does not have to redo the request policy check.
|
||||
// not met. Store the result in a static keyed by current request, so that
|
||||
// onResponse() does not have to redo the request policy check.
|
||||
$request = $event->getRequest();
|
||||
$request_policy_result = $this->requestPolicy->check($request);
|
||||
$this->requestPolicyResults[$request] = $request_policy_result;
|
||||
|
|
|
@ -14,6 +14,7 @@ process:
|
|||
label:
|
||||
plugin: static_map
|
||||
source: view_mode
|
||||
bypass: true
|
||||
map:
|
||||
search_index: "Search index"
|
||||
search_result: "Search result"
|
||||
|
|
|
@ -28,8 +28,7 @@ class FieldInstancePerFormDisplay extends DrupalSqlBase {
|
|||
->fields('fc', array(
|
||||
'type',
|
||||
'module',
|
||||
))
|
||||
->condition('fci.entity_type', 'node');
|
||||
));
|
||||
$query->join('field_config', 'fc', 'fci.field_id = fc.id');
|
||||
return $query;
|
||||
}
|
||||
|
|
|
@ -201,8 +201,18 @@ class EntityReferenceFormatterTest extends EntityKernelTestBase {
|
|||
$this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter)));
|
||||
|
||||
// Test the second field item.
|
||||
$expected_rendered_name_field_2 = '
|
||||
<div class="field field--name-name field--type-string field--label-hidden field__item">' . $this->unsavedReferencedEntity->label() . '</div>
|
||||
';
|
||||
$expected_rendered_body_field_2 = '
|
||||
<div class="clearfix text-formatted field field--name-body field--type-text field--label-above">
|
||||
<div class="field__label">Body</div>
|
||||
<div class="field__item"><p>Hello, unsaved world!</p></div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$renderer->renderRoot($build[1]);
|
||||
$this->assertEqual($build[1]['#markup'], $this->unsavedReferencedEntity->label(), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
$this->assertEqual($build[1]['#markup'], 'default | ' . $this->unsavedReferencedEntity->label() . $expected_rendered_name_field_2 . $expected_rendered_body_field_2, sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -271,6 +281,7 @@ class EntityReferenceFormatterTest extends EntityKernelTestBase {
|
|||
* Tests the label formatter.
|
||||
*/
|
||||
public function testLabelFormatter() {
|
||||
$this->installEntitySchema('entity_test_label');
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$formatter = 'entity_reference_label';
|
||||
|
|
|
@ -6,6 +6,7 @@ use Drupal\comment\Entity\CommentType;
|
|||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
|
@ -88,6 +89,8 @@ class MigrateFieldFormatterSettingsTest extends MigrateDrupal7TestBase {
|
|||
'label' => $this->randomMachineName(),
|
||||
])->save();
|
||||
|
||||
Vocabulary::create(['vid' => 'test_vocabulary'])->save();
|
||||
|
||||
// Give one unfortunate field instance invalid display settings to ensure
|
||||
// that the migration provides an empty array as a default (thus avoiding
|
||||
// an "unsupported operand types" fatal).
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
|||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
|
@ -45,6 +46,7 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase {
|
|||
$this->createType('book');
|
||||
$this->createType('forum');
|
||||
$this->createType('test_content_type');
|
||||
Vocabulary::create(['vid' => 'test_vocabulary'])->save();
|
||||
$this->executeMigrations(['d7_field', 'd7_field_instance']);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ class MigrateFieldInstanceWidgetSettingsTest extends MigrateDrupal7TestBase {
|
|||
$this->executeMigrations([
|
||||
'd7_node_type',
|
||||
'd7_comment_type',
|
||||
'd7_taxonomy_vocabulary',
|
||||
'd7_field',
|
||||
'd7_field_instance',
|
||||
'd7_field_instance_widget_settings',
|
||||
|
@ -126,6 +127,16 @@ class MigrateFieldInstanceWidgetSettingsTest extends MigrateDrupal7TestBase {
|
|||
$this->assertComponent('node.test_content_type.default', 'field_term_reference', 'entity_reference_autocomplete', 14);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_text', 'text_textfield', 15);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_text_list', 'options_select', 11);
|
||||
|
||||
$this->assertEntity('user.user.default', 'user', 'user');
|
||||
$this->assertComponent('user.user.default', 'field_file', 'file_generic', 8);
|
||||
|
||||
$this->assertEntity('comment.comment_node_test_content_type.default', 'comment', 'comment_node_test_content_type');
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'comment_body', 'text_textarea', 0);
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'field_integer', 'number', 2);
|
||||
|
||||
$this->assertEntity('taxonomy_term.test_vocabulary.default', 'taxonomy_term', 'test_vocabulary');
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'field_integer', 'number', 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
|||
*/
|
||||
class MigrateViewModesTest extends MigrateDrupal7TestBase {
|
||||
|
||||
public static $modules = ['comment', 'node'];
|
||||
public static $modules = ['comment', 'node', 'taxonomy'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -52,6 +52,7 @@ class MigrateViewModesTest extends MigrateDrupal7TestBase {
|
|||
$this->assertEntity('comment.full', 'Full', 'comment');
|
||||
$this->assertEntity('node.teaser', 'Teaser', 'node');
|
||||
$this->assertEntity('node.full', 'Full', 'node');
|
||||
$this->assertEntity('node.custom', 'custom', 'node');
|
||||
$this->assertEntity('user.full', 'Full', 'user');
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,42 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
|
|||
'required' => FALSE,
|
||||
'global_settings' => array(),
|
||||
),
|
||||
array(
|
||||
'field_name' => 'field_file',
|
||||
'entity_type' => 'user',
|
||||
'bundle' => 'user',
|
||||
'widget_settings' => array(
|
||||
),
|
||||
'display_settings' => array(
|
||||
),
|
||||
'description' => '',
|
||||
'required' => FALSE,
|
||||
'global_settings' => array(),
|
||||
),
|
||||
array(
|
||||
'field_name' => 'field_integer',
|
||||
'entity_type' => 'comment',
|
||||
'bundle' => 'comment_node_test_content_type',
|
||||
'widget_settings' => array(
|
||||
),
|
||||
'display_settings' => array(
|
||||
),
|
||||
'description' => '',
|
||||
'required' => FALSE,
|
||||
'global_settings' => array(),
|
||||
),
|
||||
array(
|
||||
'field_name' => 'field_link',
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle' => 'test_vocabulary',
|
||||
'widget_settings' => array(
|
||||
),
|
||||
'display_settings' => array(
|
||||
),
|
||||
'description' => '',
|
||||
'required' => FALSE,
|
||||
'global_settings' => array(),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -51,6 +87,33 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
|
|||
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '33',
|
||||
'field_id' => '11',
|
||||
'field_name' => 'field_file',
|
||||
'entity_type' => 'user',
|
||||
'bundle' => 'user',
|
||||
'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '32',
|
||||
'field_id' => '14',
|
||||
'field_name' => 'field_integer',
|
||||
'entity_type' => 'comment',
|
||||
'bundle' => 'comment_node_test_content_type',
|
||||
'data' => 'a:7:{s:5:"label";s:7:"Integer";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:6:"number";s:6:"module";s:6:"number";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:5:{s:3:"min";s:0:"";s:3:"max";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:14:"number_integer";s:8:"settings";a:4:{s:18:"thousand_separator";s:1:" ";s:17:"decimal_separator";s:1:".";s:5:"scale";i:0;s:13:"prefix_suffix";b:1;}s:6:"module";s:6:"number";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '25',
|
||||
'field_id' => '15',
|
||||
'field_name' => 'field_link',
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle' => 'test_vocabulary',
|
||||
'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
|
||||
'deleted' => '0',
|
||||
),
|
||||
);
|
||||
$this->databaseContents['field_config'] = array(
|
||||
array(
|
||||
|
@ -68,6 +131,51 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase {
|
|||
'translatable' => '0',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '11',
|
||||
'field_name' => 'field_file',
|
||||
'type' => 'file',
|
||||
'module' => 'file',
|
||||
'active' => '1',
|
||||
'storage_type' => 'field_sql_storage',
|
||||
'storage_module' => 'field_sql_storage',
|
||||
'storage_active' => '1',
|
||||
'locked' => '0',
|
||||
'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:13:"display_field";i:0;s:15:"display_default";i:0;s:10:"uri_scheme";s:6:"public";}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:21:"field_data_field_file";a:3:{s:3:"fid";s:14:"field_file_fid";s:7:"display";s:18:"field_file_display";s:11:"description";s:22:"field_file_description";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:25:"field_revision_field_file";a:3:{s:3:"fid";s:14:"field_file_fid";s:7:"display";s:18:"field_file_display";s:11:"description";s:22:"field_file_description";}}}}}s:12:"foreign keys";a:1:{s:3:"fid";a:2:{s:5:"table";s:12:"file_managed";s:7:"columns";a:1:{s:3:"fid";s:3:"fid";}}}s:7:"indexes";a:1:{s:3:"fid";a:1:{i:0;s:3:"fid";}}s:2:"id";s:2:"11";}',
|
||||
'cardinality' => '1',
|
||||
'translatable' => '0',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '14',
|
||||
'field_name' => 'field_integer',
|
||||
'type' => 'number_integer',
|
||||
'module' => 'number',
|
||||
'active' => '1',
|
||||
'storage_type' => 'field_sql_storage',
|
||||
'storage_module' => 'field_sql_storage',
|
||||
'storage_active' => '1',
|
||||
'locked' => '0',
|
||||
'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:0:{}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:24:"field_data_field_integer";a:1:{s:5:"value";s:19:"field_integer_value";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:28:"field_revision_field_integer";a:1:{s:5:"value";s:19:"field_integer_value";}}}}}s:12:"foreign keys";a:0:{}s:7:"indexes";a:0:{}s:2:"id";s:2:"14";}',
|
||||
'cardinality' => '1',
|
||||
'translatable' => '0',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
'id' => '15',
|
||||
'field_name' => 'field_link',
|
||||
'type' => 'link_field',
|
||||
'module' => 'link',
|
||||
'active' => '1',
|
||||
'storage_type' => 'field_sql_storage',
|
||||
'storage_module' => 'field_sql_storage',
|
||||
'storage_active' => '1',
|
||||
'locked' => '0',
|
||||
'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:7:{s:10:"attributes";a:3:{s:6:"target";s:7:"default";s:5:"class";s:0:"";s:3:"rel";s:0:"";}s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:0:"";s:15:"title_maxlength";i:128;s:13:"enable_tokens";i:1;s:7:"display";a:1:{s:10:"url_cutoff";i:80;}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:21:"field_data_field_link";a:3:{s:3:"url";s:14:"field_link_url";s:5:"title";s:16:"field_link_title";s:10:"attributes";s:21:"field_link_attributes";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:25:"field_revision_field_link";a:3:{s:3:"url";s:14:"field_link_url";s:5:"title";s:16:"field_link_title";s:10:"attributes";s:21:"field_link_attributes";}}}}}s:12:"foreign keys";a:0:{}s:7:"indexes";a:0:{}s:2:"id";s:2:"15";}',
|
||||
'cardinality' => '1',
|
||||
'translatable' => '0',
|
||||
'deleted' => '0',
|
||||
),
|
||||
);
|
||||
|
||||
parent::setUp();
|
||||
|
|
|
@ -29,6 +29,10 @@ class ViewModeTest extends MigrateSqlSourceTestCase {
|
|||
'entity_type' => 'node',
|
||||
'view_mode' => 'teaser',
|
||||
),
|
||||
array(
|
||||
'entity_type' => 'node',
|
||||
'view_mode' => 'custom',
|
||||
),
|
||||
array(
|
||||
'entity_type' => 'user',
|
||||
'view_mode' => 'default',
|
||||
|
@ -50,7 +54,7 @@ class ViewModeTest extends MigrateSqlSourceTestCase {
|
|||
'field_name' => 'body',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'forum',
|
||||
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:1;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:11;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
|
||||
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:1;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:3:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:11;}s:6:"custom";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
|
||||
'deleted' => '0',
|
||||
),
|
||||
array(
|
||||
|
|
|
@ -325,25 +325,30 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
|
|||
* This validator is used only when cardinality not set to 1 or unlimited.
|
||||
*/
|
||||
public static function validateMultipleCount($element, FormStateInterface $form_state, $form) {
|
||||
$parents = $element['#parents'];
|
||||
$values = NestedArray::getValue($form_state->getValues(), $parents);
|
||||
$values = NestedArray::getValue($form_state->getValues(), $element['#parents']);
|
||||
|
||||
array_pop($parents);
|
||||
$current = count(Element::children(NestedArray::getValue($form, $parents))) - 1;
|
||||
$array_parents = $element['#array_parents'];
|
||||
array_pop($array_parents);
|
||||
$previously_uploaded_count = count(Element::children(NestedArray::getValue($form, $array_parents))) - 1;
|
||||
|
||||
$field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($element['#entity_type']);
|
||||
$field_storage = $field_storage_definitions[$element['#field_name']];
|
||||
$uploaded = count($values['fids']);
|
||||
$count = $uploaded + $current;
|
||||
if ($count > $field_storage->getCardinality()) {
|
||||
$keep = $uploaded - $count + $field_storage->getCardinality();
|
||||
$newly_uploaded_count = count($values['fids']);
|
||||
$total_uploaded_count = $newly_uploaded_count + $previously_uploaded_count;
|
||||
if ($total_uploaded_count > $field_storage->getCardinality()) {
|
||||
$keep = $newly_uploaded_count - $total_uploaded_count + $field_storage->getCardinality();
|
||||
$removed_files = array_slice($values['fids'], $keep);
|
||||
$removed_names = array();
|
||||
foreach ($removed_files as $fid) {
|
||||
$file = File::load($fid);
|
||||
$removed_names[] = $file->getFilename();
|
||||
}
|
||||
$args = array('%field' => $field_storage->getName(), '@max' => $field_storage->getCardinality(), '@count' => $uploaded, '%list' => implode(', ', $removed_names));
|
||||
$args = [
|
||||
'%field' => $field_storage->getName(),
|
||||
'@max' => $field_storage->getCardinality(),
|
||||
'@count' => $total_uploaded_count,
|
||||
'%list' => implode(', ', $removed_names),
|
||||
];
|
||||
$message = t('Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args);
|
||||
drupal_set_message($message, 'warning');
|
||||
$values['fids'] = array_slice($values['fids'], 0, $keep);
|
||||
|
|
|
@ -56,13 +56,13 @@ class FileFieldRSSContentTest extends FileFieldTestBase {
|
|||
// Check that the RSS enclosure appears in the RSS feed.
|
||||
$this->drupalGet('rss.xml');
|
||||
$uploaded_filename = str_replace('public://', '', $node_file->getFileUri());
|
||||
$test_element = sprintf(
|
||||
'<enclosure url="%s" length="%s" type="%s" />',
|
||||
$selector = sprintf(
|
||||
'enclosure[url="%s"][length="%s"][type="%s"]',
|
||||
file_create_url("public://$uploaded_filename", array('absolute' => TRUE)),
|
||||
$node_file->getSize(),
|
||||
$node_file->getMimeType()
|
||||
);
|
||||
$this->assertRaw($test_element, 'File field RSS enclosure is displayed when viewing the RSS feed.');
|
||||
$this->assertTrue(!empty($this->cssSelect($selector)), 'File field RSS enclosure is displayed when viewing the RSS feed.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ abstract class FileFieldTestBase extends WebTestBase {
|
|||
|
||||
/**
|
||||
* Retrieves a sample file of the specified type.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface
|
||||
*/
|
||||
function getTestFile($type_name, $size = NULL) {
|
||||
// Get a file to upload.
|
||||
|
|
|
@ -247,32 +247,39 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$this->assertTrue(empty($node->{$field_name}->target_id), 'Node was successfully saved without any files.');
|
||||
}
|
||||
|
||||
$upload_files = array($test_file, $test_file);
|
||||
$upload_files_node_creation = array($test_file, $test_file);
|
||||
// Try to upload multiple files, but fewer than the maximum.
|
||||
$nid = $this->uploadNodeFiles($upload_files, $field_name, $type_name);
|
||||
$nid = $this->uploadNodeFiles($upload_files_node_creation, $field_name, $type_name);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$this->assertEqual(count($node->{$field_name}), count($upload_files), 'Node was successfully saved with mulitple files.');
|
||||
$this->assertEqual(count($node->{$field_name}), count($upload_files_node_creation), 'Node was successfully saved with mulitple files.');
|
||||
|
||||
// Try to upload more files than allowed on revision.
|
||||
$this->uploadNodeFiles($upload_files, $field_name, $nid, 1);
|
||||
$args = array(
|
||||
$upload_files_node_revision = array($test_file, $test_file, $test_file, $test_file);
|
||||
$this->uploadNodeFiles($upload_files_node_revision, $field_name, $nid, 1);
|
||||
$args = [
|
||||
'%field' => $field_name,
|
||||
'@count' => $cardinality
|
||||
);
|
||||
$this->assertRaw(t('%field: this field cannot hold more than @count values.', $args));
|
||||
'@max' => $cardinality,
|
||||
'@count' => count($upload_files_node_creation) + count($upload_files_node_revision),
|
||||
'%list' => implode(', ', array_fill(0, 3, $test_file->getFilename())),
|
||||
];
|
||||
$this->assertRaw(t('Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args));
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$this->assertEqual(count($node->{$field_name}), count($upload_files), 'More files than allowed could not be saved to node.');
|
||||
$this->assertEqual(count($node->{$field_name}), $cardinality, 'More files than allowed could not be saved to node.');
|
||||
|
||||
// Try to upload exactly the allowed number of files on revision.
|
||||
$this->uploadNodeFile($test_file, $field_name, $nid, 1);
|
||||
// Try to upload exactly the allowed number of files on revision. Create an
|
||||
// empty node first, to fill it in its first revision.
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => $type_name
|
||||
]);
|
||||
$this->uploadNodeFile($test_file, $field_name, $node->id(), 1);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$this->assertEqual(count($node->{$field_name}), $cardinality, 'Node was successfully revised to maximum number of files.');
|
||||
|
||||
// Try to upload exactly the allowed number of files, new node.
|
||||
$upload_files[] = $test_file;
|
||||
$upload_files = array_fill(0, $cardinality, $test_file);
|
||||
$nid = $this->uploadNodeFiles($upload_files, $field_name, $type_name);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
id: d6_language_content_settings
|
||||
label: Drupal 6 language content settings
|
||||
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
|
@ -39,6 +40,8 @@ process:
|
|||
2: true
|
||||
destination:
|
||||
plugin: entity:language_content_settings
|
||||
content_translation_update_definitions:
|
||||
- node
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_node_type
|
||||
|
|
|
@ -39,6 +39,8 @@ process:
|
|||
2: true
|
||||
destination:
|
||||
plugin: entity:language_content_settings
|
||||
content_translation_update_definitions:
|
||||
- node
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_node_type
|
||||
|
|
|
@ -276,8 +276,8 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
|
|||
*/
|
||||
function purgeConfiguration() {
|
||||
// Ensure that we are getting the defined language negotiation information.
|
||||
// An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
|
||||
// \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
|
||||
// An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
|
||||
// \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
|
||||
// cached information.
|
||||
$this->negotiatorManager->clearCachedDefinitions();
|
||||
$this->languageManager->reset();
|
||||
|
@ -291,8 +291,8 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
|
|||
*/
|
||||
function updateConfiguration(array $types) {
|
||||
// Ensure that we are getting the defined language negotiation information.
|
||||
// An invocation of \Drupal\Core\Extension\ModuleHandler::install() or
|
||||
// \Drupal\Core\Extension\ModuleHandler::uninstall() could invalidate the
|
||||
// An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
|
||||
// \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
|
||||
// cached information.
|
||||
$this->negotiatorManager->clearCachedDefinitions();
|
||||
$this->languageManager->reset();
|
||||
|
|
|
@ -169,7 +169,7 @@ class LinkItem extends FieldItemBase implements LinkItemInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl() {
|
||||
return Url::fromUri($this->uri);
|
||||
return Url::fromUri($this->uri, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\link\Plugin\migrate\cckfield\d7;
|
||||
|
||||
use Drupal\link\Plugin\migrate\cckfield\LinkField as D6LinkField;
|
||||
|
||||
/**
|
||||
* @MigrateCckField(
|
||||
* id = "link_field",
|
||||
* core = {7},
|
||||
* type_map = {
|
||||
* "link_field" = "link"
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* This plugin provides the exact same functionality as the Drupal 6 "link"
|
||||
* plugin with the exception that the plugin ID "link_field" is used in the
|
||||
* field type map.
|
||||
*/
|
||||
class LinkField extends D6LinkField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldWidgetMap() {
|
||||
// By default, use the plugin ID for the widget types.
|
||||
return ['link_field' => 'link_default'];
|
||||
}
|
||||
|
||||
}
|
|
@ -78,6 +78,13 @@ class LinkItemTest extends FieldKernelTestBase {
|
|||
$entity->field_test->title = $title;
|
||||
$entity->field_test->first()->get('options')->set('query', $parsed_url['query']);
|
||||
$entity->field_test->first()->get('options')->set('attributes', array('class' => $class));
|
||||
$this->assertEquals([
|
||||
'query' => $parsed_url['query'],
|
||||
'attributes' => [
|
||||
'class' => $class,
|
||||
],
|
||||
'external' => TRUE,
|
||||
], $entity->field_test->first()->getUrl()->getOptions());
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ process:
|
|||
menu_name:
|
||||
-
|
||||
plugin: migration
|
||||
# The menu migration is in the system module.
|
||||
migration: menu
|
||||
source: menu_name
|
||||
-
|
||||
|
@ -20,7 +21,7 @@ process:
|
|||
management: admin
|
||||
bypass: true
|
||||
'link/uri':
|
||||
plugin: internal_uri
|
||||
plugin: link_uri
|
||||
source:
|
||||
- link_path
|
||||
'link/options': options
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\menu_link_content\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Process a path into an 'internal:' URI.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "internal_uri"
|
||||
* )
|
||||
*/
|
||||
class InternalUri extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($path) = $value;
|
||||
|
||||
if (parse_url($path, PHP_URL_SCHEME) === NULL) {
|
||||
return 'internal:/' . $path;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\menu_link_content\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Processes a link path into an 'internal:' or 'entity:' URI.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "link_uri"
|
||||
* )
|
||||
*/
|
||||
class LinkUri extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager, used to fetch entity link templates.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a LinkUri object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager, used to fetch entity link templates.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($path) = $value;
|
||||
$path = ltrim($path, '/');
|
||||
|
||||
if (parse_url($path, PHP_URL_SCHEME) === NULL) {
|
||||
$path = 'internal:/' . $path;
|
||||
|
||||
// Convert entity URIs to the entity scheme, if the path matches a route
|
||||
// of the form "entity.$entity_type_id.canonical".
|
||||
// @see \Drupal\Core\Url::fromEntityUri()
|
||||
$url = Url::fromUri($path);
|
||||
if ($url->isRouted()) {
|
||||
$route_name = $url->getRouteName();
|
||||
foreach (array_keys($this->entityTypeManager->getDefinitions()) as $entity_type_id) {
|
||||
if ($route_name == "entity.$entity_type_id.canonical" && isset($url->getRouteParameters()[$entity_type_id])) {
|
||||
return "entity:$entity_type_id/" . $url->getRouteParameters()[$entity_type_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\menu_link_content\Plugin\migrate\process\d6\LinkUri;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Path\PathValidator;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\menu_link_content\Plugin\migrate\process\d6\LinkUri.
|
||||
*
|
||||
* @group menu_link_content
|
||||
*
|
||||
* @coversDefaultClass \Drupal\menu_link_content\Plugin\migrate\process\d6\LinkUri
|
||||
*/
|
||||
class LinkUriTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The entity type manager prophecy used in the test.
|
||||
*
|
||||
* @var \Prophecy\Prophecy\ProphecyInterface|\Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The 'link_uri' process plugin being tested.
|
||||
*
|
||||
* @var \Drupal\menu_link_content\Plugin\migrate\process\d6\LinkUri
|
||||
*/
|
||||
protected $processPlugin;
|
||||
|
||||
/**
|
||||
* The path validator prophecy used in the test.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathValidator
|
||||
*/
|
||||
protected $pathValidator;
|
||||
|
||||
/**
|
||||
* The fake entity type ID used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'the_entity_type_id';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
|
||||
$this->entityTypeManager->getDefinitions()->willReturn([$this->entityTypeId => '']);
|
||||
$this->processPlugin = new LinkUri([], 'link_uri', [], $this->entityTypeManager->reveal());
|
||||
|
||||
// Url::fromInternalUri() accesses the path validator from the global
|
||||
// container.
|
||||
// @see \Drupal\Core\Url::fromInternalUri()
|
||||
$this->pathValidator = $this->prophesize(PathValidator::class);
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('path.validator', $this->pathValidator->reveal());
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests LinkUri::transform().
|
||||
*
|
||||
* @param array $value
|
||||
* The value to pass to LinkUri::transform().
|
||||
* @param string $expected
|
||||
* The expected return value of LinkUri::transform().
|
||||
* @param \Drupal\Core\Url $url
|
||||
* (optional) The URL that the path validator prophecy will return.
|
||||
*
|
||||
* @dataProvider providerTestTransform
|
||||
*
|
||||
* @covers ::transform
|
||||
*/
|
||||
public function testTransform(array $value, $expected, Url $url = NULL) {
|
||||
$migrate_executable = $this->prophesize(MigrateExecutableInterface::class);
|
||||
$row = $this->prophesize(Row::class);
|
||||
|
||||
if ($url) {
|
||||
$this->pathValidator->getUrlIfValidWithoutAccessCheck(reset($value))->willReturn($url);
|
||||
}
|
||||
|
||||
$actual = $this->processPlugin->transform($value, $migrate_executable->reveal(), $row->reveal(), 'link/uri');
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test cases for LinkUriTest::testTransform().
|
||||
*
|
||||
* @return array
|
||||
* An array of test cases, each which the following values:
|
||||
* - The value array to pass to LinkUri::transform().
|
||||
* - The expected path returned by LinkUri::transform().
|
||||
* - (optional) A URL object that the path validator prophecy will return.
|
||||
*/
|
||||
public function providerTestTransform() {
|
||||
$tests = [];
|
||||
|
||||
$value = ['http://example.com'];
|
||||
$expected = 'http://example.com';
|
||||
$tests['with_scheme'] = [$value, $expected];
|
||||
|
||||
$value = ['/test'];
|
||||
$expected = 'internal:/test';
|
||||
$tests['leading_slash'] = [$value, $expected];
|
||||
|
||||
$value = ['test'];
|
||||
$expected = 'internal:/test';
|
||||
$tests['without_scheme'] = [$value, $expected];
|
||||
|
||||
$url = Url::fromRoute('route_name');
|
||||
$tests['with_route'] = [$value, $expected, $url];
|
||||
|
||||
$url = Url::fromRoute('entity.not_an_entity_type_id.canonical');
|
||||
$tests['without_entity_type'] = [$value, $expected, $url];
|
||||
|
||||
$url = Url::fromRoute('entity.the_entity_type_id.canonical');
|
||||
$tests['without_route_parameter'] = [$value, $expected, $url];
|
||||
|
||||
$url = Url::fromRoute('entity.the_entity_type_id.canonical', ['the_entity_type_id' => 'the_entity_id']);
|
||||
$expected = 'entity:the_entity_type_id/the_entity_id';
|
||||
$tests['entity_path'] = [$value, $expected, $url];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
# The menu migration is in the system module and the menu_links migration is in the menu_link_content module.
|
||||
id: menu_settings
|
||||
label: Menu UI configuration
|
||||
migration_tags:
|
||||
|
|
|
@ -94,8 +94,10 @@ abstract class DestinationBase extends PluginBase implements MigrateDestinationI
|
|||
*
|
||||
* @param array $id_map
|
||||
* The map row data for the item.
|
||||
* @param int $update_action
|
||||
* The rollback action to take if we are updating an existing item.
|
||||
*/
|
||||
protected function setRollbackAction(array $id_map) {
|
||||
protected function setRollbackAction(array $id_map, $update_action = MigrateIdMapInterface::ROLLBACK_PRESERVE) {
|
||||
// If the entity we're updating was previously migrated by us, preserve the
|
||||
// existing rollback action.
|
||||
if (isset($id_map['sourceid1'])) {
|
||||
|
@ -104,7 +106,7 @@ abstract class DestinationBase extends PluginBase implements MigrateDestinationI
|
|||
// Otherwise, we're updating an entity which already existed on the
|
||||
// destination and want to make sure we do not delete it on rollback.
|
||||
else {
|
||||
$this->rollbackAction = MigrateIdMapInterface::ROLLBACK_PRESERVE;
|
||||
$this->rollbackAction = $update_action;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,7 +124,8 @@ abstract class Entity extends DestinationBase implements ContainerFactoryPluginI
|
|||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$entity_id = reset($old_destination_id_values) ?: $this->getEntityId($row);
|
||||
if (!empty($entity_id) && ($entity = $this->storage->load($entity_id))) {
|
||||
$this->updateEntity($entity, $row);
|
||||
// Allow updateEntity() to change the entity.
|
||||
$entity = $this->updateEntity($entity, $row) ?: $entity;
|
||||
}
|
||||
else {
|
||||
// Attempt to ensure we always have a bundle.
|
||||
|
|
|
@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityInterface;
|
|||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
|
@ -85,7 +86,12 @@ class EntityContentBase extends Entity {
|
|||
if (!$entity) {
|
||||
throw new MigrateException('Unable to get entity');
|
||||
}
|
||||
return $this->save($entity, $old_destination_id_values);
|
||||
|
||||
$ids = $this->save($entity, $old_destination_id_values);
|
||||
if (!empty($this->configuration['translations'])) {
|
||||
$ids[] = $entity->language()->getId();
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,12 +110,32 @@ class EntityContentBase extends Entity {
|
|||
return array($entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'integer';
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
if ($key = $this->getKey('langcode')) {
|
||||
$ids[$key]['type'] = 'string';
|
||||
}
|
||||
else {
|
||||
throw new MigrateException('This entity type does not support translation.');
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
@ -120,8 +146,29 @@ class EntityContentBase extends Entity {
|
|||
* The entity to update.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*
|
||||
* @return NULL|\Drupal\Core\Entity\EntityInterface
|
||||
* An updated entity, or NULL if it's the same as the one passed in.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
// By default, an update will be preserved.
|
||||
$rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
|
||||
|
||||
// Make sure we have the right translation.
|
||||
if ($this->isTranslationDestination()) {
|
||||
$property = $this->storage->getEntityType()->getKey('langcode');
|
||||
if ($row->hasDestinationProperty($property)) {
|
||||
$language = $row->getDestinationProperty($property);
|
||||
if (!$entity->hasTranslation($language)) {
|
||||
$entity->addTranslation($language);
|
||||
|
||||
// We're adding a translation, so delete it on rollback.
|
||||
$rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
}
|
||||
$entity = $entity->getTranslation($language);
|
||||
}
|
||||
}
|
||||
|
||||
// If the migration has specified a list of properties to be overwritten,
|
||||
// clone the row with an empty set of destination values, and re-add only
|
||||
// the specified properties.
|
||||
|
@ -140,7 +187,10 @@ class EntityContentBase extends Entity {
|
|||
}
|
||||
}
|
||||
|
||||
$this->setRollbackAction($row->getIdMap());
|
||||
$this->setRollbackAction($row->getIdMap(), $rollback_action);
|
||||
|
||||
// We might have a different (translated) entity, so return it.
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,4 +235,32 @@ class EntityContentBase extends Entity {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
// Attempt to remove the translation.
|
||||
$entity = $this->storage->load(reset($destination_identifier));
|
||||
if ($entity && $entity instanceof TranslatableInterface) {
|
||||
if ($key = $this->getKey('langcode')) {
|
||||
if (isset($destination_identifier[$key])) {
|
||||
$langcode = $destination_identifier[$key];
|
||||
if ($entity->hasTranslation($langcode)) {
|
||||
// Make sure we don't remove the default translation.
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
if (!$translation->isDefaultTranslation()) {
|
||||
$entity->removeTranslation($langcode);
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
parent::rollback($destination_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class Migration extends ProcessPluginBase implements ContainerFactoryPluginInter
|
|||
$source_id_values[$migration_id] = $value;
|
||||
}
|
||||
// Break out of the loop as soon as a destination ID is found.
|
||||
if ($destination_ids = $migration->getIdMap()->lookupDestinationID($source_id_values[$migration_id])) {
|
||||
if ($destination_ids = $migration->getIdMap()->lookupDestinationId($source_id_values[$migration_id])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ class Migration extends ProcessPluginBase implements ContainerFactoryPluginInter
|
|||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
protected function skipOnEmpty($value) {
|
||||
protected function skipOnEmpty(array $value) {
|
||||
if (!array_filter($value)) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
|
|
41
core/modules/migrate/src/Plugin/migrate/process/Substr.php
Normal file
41
core/modules/migrate/src/Plugin/migrate/process/Substr.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* This plugin returns a substring of the current value.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "substr"
|
||||
* )
|
||||
*/
|
||||
class Substr extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$start = isset($this->configuration['start']) ? $this->configuration['start'] : 0;
|
||||
if (!is_int($start)) {
|
||||
throw new MigrateException('The start position configuration value should be an integer. Omit this key to capture from the beginning of the string.');
|
||||
}
|
||||
$length = isset($this->configuration['length']) ? $this->configuration['length'] : NULL;
|
||||
if (!is_null($length) && !is_int($length)) {
|
||||
throw new MigrateException('The character length configuration value should be an integer. Omit this key to capture from the start position to the end of the string.');
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
throw new MigrateException('The input value must be a string.');
|
||||
}
|
||||
|
||||
// Use optional start or length to return a portion of $value.
|
||||
$new_value = Unicode::substr($value, $start, $length);
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
name: 'Migration external translated test'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- node
|
||||
- migrate
|
|
@ -0,0 +1,19 @@
|
|||
id: external_translated_test_node
|
||||
label: External translated content
|
||||
source:
|
||||
plugin: migrate_external_translated_test
|
||||
default_lang: true
|
||||
constants:
|
||||
type: external_test
|
||||
process:
|
||||
type: constants/type
|
||||
title: title
|
||||
langcode:
|
||||
plugin: static_map
|
||||
source: lang
|
||||
map:
|
||||
English: en
|
||||
French: fr
|
||||
Spanish: es
|
||||
destination:
|
||||
plugin: entity:node
|
|
@ -0,0 +1,27 @@
|
|||
id: external_translated_test_node_translation
|
||||
label: External translated content translations
|
||||
source:
|
||||
plugin: migrate_external_translated_test
|
||||
default_lang: false
|
||||
constants:
|
||||
type: external_test
|
||||
process:
|
||||
nid:
|
||||
plugin: migration
|
||||
source: name
|
||||
migration: external_translated_test_node
|
||||
type: constants/type
|
||||
title: title
|
||||
langcode:
|
||||
plugin: static_map
|
||||
source: lang
|
||||
map:
|
||||
English: en
|
||||
French: fr
|
||||
Spanish: es
|
||||
destination:
|
||||
plugin: entity:node
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- external_translated_test_node
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate_external_translated_test\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
||||
|
||||
/**
|
||||
* A simple migrate source for our tests.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "migrate_external_translated_test"
|
||||
* )
|
||||
*/
|
||||
class MigrateExternalTranslatedTestSource extends SourcePluginBase {
|
||||
|
||||
/**
|
||||
* The data to import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $import = [
|
||||
['name' => 'cat', 'title' => 'Cat', 'lang' => 'English'],
|
||||
['name' => 'cat', 'title' => 'Chat', 'lang' => 'French'],
|
||||
['name' => 'cat', 'title' => 'Gato', 'lang' => 'Spanish'],
|
||||
['name' => 'dog', 'title' => 'Dog', 'lang' => 'English'],
|
||||
['name' => 'dog', 'title' => 'Chien', 'lang' => 'French'],
|
||||
['name' => 'monkey', 'title' => 'Monkey', 'lang' => 'English'],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'name' => $this->t('Unique name'),
|
||||
'title' => $this->t('Title'),
|
||||
'lang' => $this->t('Language'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['name']['type'] = 'string';
|
||||
if (!$this->configuration['default_lang']) {
|
||||
$ids['lang']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$data = [];
|
||||
|
||||
// Keep the rows with the right languages.
|
||||
$want_default = $this->configuration['default_lang'];
|
||||
foreach ($this->import as $row) {
|
||||
$is_english = $row['lang'] == 'English';
|
||||
if ($want_default == $is_english) {
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return new \ArrayIterator($data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Tests the EntityContentBase destination.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateEntityContentBaseTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['migrate', 'user', 'language', 'entity_test'];
|
||||
|
||||
/**
|
||||
* The storage for entity_test_mul.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* A content migrate destination.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('en')->save();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
$this->storage = $this->container->get('entity.manager')->getStorage('entity_test_mul');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the existing translations of an entity.
|
||||
*
|
||||
* @param int $id
|
||||
* The entity ID.
|
||||
* @param string $default
|
||||
* The expected default translation language code.
|
||||
* @param string[] $others
|
||||
* The expected other translation language codes.
|
||||
*/
|
||||
protected function assertTranslations($id, $default, $others = []) {
|
||||
$entity = $this->storage->load($id);
|
||||
$this->assertTrue($entity, "Entity exists");
|
||||
$this->assertEquals($default, $entity->language()->getId(), "Entity default translation");
|
||||
$translations = array_keys($entity->getTranslationLanguages(FALSE));
|
||||
sort($others);
|
||||
sort($translations);
|
||||
$this->assertEquals($others, $translations, "Entity translations");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the destination plugin to test.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
*/
|
||||
protected function createDestination(array $configuration) {
|
||||
$this->destination = new EntityContentBase(
|
||||
$configuration,
|
||||
'fake_plugin_id',
|
||||
[],
|
||||
$this->getMock(MigrationInterface::class),
|
||||
$this->storage,
|
||||
[],
|
||||
$this->container->get('entity.manager'),
|
||||
$this->container->get('plugin.manager.field.field_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test importing and rolling back translated entities.
|
||||
*/
|
||||
public function testTranslated() {
|
||||
// Create a destination.
|
||||
$this->createDestination(['translations' => TRUE]);
|
||||
|
||||
// Create some pre-existing entities.
|
||||
$this->storage->create(['id' => 1, 'langcode' => 'en'])->save();
|
||||
$this->storage->create(['id' => 2, 'langcode' => 'fr'])->save();
|
||||
$translated = $this->storage->create(['id' => 3, 'langcode' => 'en']);
|
||||
$translated->save();
|
||||
$translated->addTranslation('fr')->save();
|
||||
|
||||
// Pre-assert that things are as expected.
|
||||
$this->assertTranslations(1, 'en');
|
||||
$this->assertTranslations(2, 'fr');
|
||||
$this->assertTranslations(3, 'en', ['fr']);
|
||||
$this->assertFalse($this->storage->load(4));
|
||||
|
||||
$destination_rows = [
|
||||
// Existing default translation.
|
||||
['id' => 1, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE],
|
||||
// New translation.
|
||||
['id' => 2, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE],
|
||||
// Existing non-default translation.
|
||||
['id' => 3, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE],
|
||||
// Brand new row.
|
||||
['id' => 4, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE],
|
||||
];
|
||||
$rollback_actions = [];
|
||||
|
||||
// Import some rows.
|
||||
foreach ($destination_rows as $idx => $destination_row) {
|
||||
$row = new Row([], []);
|
||||
foreach ($destination_row as $key => $value) {
|
||||
$row->setDestinationProperty($key, $value);
|
||||
}
|
||||
$this->destination->import($row);
|
||||
|
||||
// Check that the rollback action is correct, and save it.
|
||||
$this->assertEquals($destination_row['action'], $this->destination->rollbackAction());
|
||||
$rollback_actions[$idx] = $this->destination->rollbackAction();
|
||||
}
|
||||
|
||||
$this->assertTranslations(1, 'en');
|
||||
$this->assertTranslations(2, 'fr', ['en']);
|
||||
$this->assertTranslations(3, 'en', ['fr']);
|
||||
$this->assertTranslations(4, 'fr');
|
||||
|
||||
// Rollback the rows.
|
||||
foreach ($destination_rows as $idx => $destination_row) {
|
||||
if ($rollback_actions[$idx] == MigrateIdMapInterface::ROLLBACK_DELETE) {
|
||||
$this->destination->rollback($destination_row);
|
||||
}
|
||||
}
|
||||
|
||||
// No change, update of existing translation.
|
||||
$this->assertTranslations(1, 'en');
|
||||
// Remove added translation.
|
||||
$this->assertTranslations(2, 'fr');
|
||||
// No change, update of existing translation.
|
||||
$this->assertTranslations(3, 'en', ['fr']);
|
||||
// No change, can't remove default translation.
|
||||
$this->assertTranslations(4, 'fr');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* Tests migrating non-Drupal translated content.
|
||||
*
|
||||
* Ensure it's possible to migrate in translations, even if there's no nid or
|
||||
* tnid property on the source.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateExternalTranslatedTest extends MigrateTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @todo: Remove migrate_drupal when https://www.drupal.org/node/2560795 is
|
||||
* fixed.
|
||||
*/
|
||||
public static $modules = ['system', 'user', 'language', 'node', 'field', 'migrate_drupal', 'migrate_external_translated_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', ['sequences']);
|
||||
$this->installSchema('node', array('node_access'));
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
// Create some languages.
|
||||
ConfigurableLanguage::createFromLangcode('en')->save();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
|
||||
// Create a content type.
|
||||
NodeType::create([
|
||||
'type' => 'external_test',
|
||||
'name' => 'Test node type',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test importing and rolling back our data.
|
||||
*/
|
||||
public function testMigrations() {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$this->assertEquals(0, count($storage->loadMultiple()));
|
||||
|
||||
// Run the migrations.
|
||||
$migration_ids = ['external_translated_test_node', 'external_translated_test_node_translation'];
|
||||
$this->executeMigrations($migration_ids);
|
||||
$this->assertEquals(3, count($storage->loadMultiple()));
|
||||
|
||||
$node = $storage->load(1);
|
||||
$this->assertEquals('en', $node->language()->getId());
|
||||
$this->assertEquals('Cat', $node->title->value);
|
||||
$this->assertEquals('Chat', $node->getTranslation('fr')->title->value);
|
||||
$this->assertEquals('Gato', $node->getTranslation('es')->title->value);
|
||||
|
||||
$node = $storage->load(2);
|
||||
$this->assertEquals('en', $node->language()->getId());
|
||||
$this->assertEquals('Dog', $node->title->value);
|
||||
$this->assertEquals('Chien', $node->getTranslation('fr')->title->value);
|
||||
$this->assertFalse($node->hasTranslation('es'), "No spanish translation for node 2");
|
||||
|
||||
$node = $storage->load(3);
|
||||
$this->assertEquals('en', $node->language()->getId());
|
||||
$this->assertEquals('Monkey', $node->title->value);
|
||||
$this->assertFalse($node->hasTranslation('fr'), "No french translation for node 3");
|
||||
$this->assertFalse($node->hasTranslation('es'), "No spanish translation for node 3");
|
||||
|
||||
$this->assertNull($storage->load(4), "No node 4 migrated");
|
||||
|
||||
// Roll back the migrations.
|
||||
foreach ($migration_ids as $migration_id) {
|
||||
$migration = $this->getMigration($migration_id);
|
||||
$executable = new MigrateExecutable($migration, $this);
|
||||
$executable->rollback();
|
||||
}
|
||||
|
||||
$this->assertEquals(0, count($storage->loadMultiple()));
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityType;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
|
@ -97,6 +98,33 @@ class EntityContentBaseTest extends UnitTestCase {
|
|||
$destination->import(new Row([], []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that translation destination fails for untranslatable entities.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
* @expectedExceptionMessage This entity type does not support translation
|
||||
*/
|
||||
public function testUntranslatable() {
|
||||
// An entity type without a language.
|
||||
$entity_type = $this->prophesize(ContentEntityType::class);
|
||||
$entity_type->getKey('langcode')->willReturn('');
|
||||
$entity_type->getKey('id')->willReturn('id');
|
||||
|
||||
$this->storage->getEntityType()->willReturn($entity_type->reveal());
|
||||
|
||||
$destination = new EntityTestDestination(
|
||||
[ 'translations' => TRUE ],
|
||||
'',
|
||||
[],
|
||||
$this->migration->reveal(),
|
||||
$this->storage->reveal(),
|
||||
[],
|
||||
$this->entityManager->reveal(),
|
||||
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal()
|
||||
);
|
||||
$destination->getIds();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\migrate\Unit\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\process\Migration;
|
||||
use Drupal\migrate\Plugin\MigrateDestinationInterface;
|
||||
|
@ -86,4 +87,51 @@ class MigrationTest extends MigrateProcessTestCase {
|
|||
$this->assertEquals(2, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that processing is skipped when the input value is empty.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
public function testSkipOnEmpty() {
|
||||
$migration_plugin = $this->prophesize(MigrationInterface::class);
|
||||
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
|
||||
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
|
||||
|
||||
$configuration = [
|
||||
'migration' => 'foobaz',
|
||||
];
|
||||
$migration_plugin->id()->willReturn(uniqid());
|
||||
$migration = new Migration($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
|
||||
$migration->transform(0, $this->migrateExecutable, $this->row, 'foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a successful lookup.
|
||||
*/
|
||||
public function testSuccessfulLookup() {
|
||||
$migration_plugin = $this->prophesize(MigrationInterface::class);
|
||||
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
|
||||
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
|
||||
|
||||
$configuration = [
|
||||
'migration' => 'foobaz',
|
||||
];
|
||||
$migration_plugin->id()->willReturn(uniqid());
|
||||
|
||||
$id_map = $this->prophesize(MigrateIdMapInterface::class);
|
||||
$id_map->lookupDestinationId([1])->willReturn([3]);
|
||||
$migration_plugin->getIdMap()->willReturn($id_map->reveal());
|
||||
|
||||
$migration_plugin_manager->createInstances(['foobaz'])
|
||||
->willReturn(['foobaz' => $migration_plugin->reveal()]);
|
||||
|
||||
$migrationStorage = $this->prophesize(EntityStorageInterface::class);
|
||||
$migrationStorage
|
||||
->loadMultiple(['foobaz'])
|
||||
->willReturn([$migration_plugin->reveal()]);
|
||||
|
||||
$migration = new Migration($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
|
||||
$this->assertSame(3, $migration->transform(1, $this->migrateExecutable, $this->row, 'foo'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
92
core/modules/migrate/tests/src/Unit/process/SubstrTest.php
Normal file
92
core/modules/migrate/tests/src/Unit/process/SubstrTest.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\process;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\process\Substr;
|
||||
|
||||
/**
|
||||
* Tests the substr plugin.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\Substr
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class SubstrTest extends MigrateProcessTestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Substr plugin based on providerTestSubstr() values.
|
||||
*
|
||||
* @dataProvider providerTestSubstr
|
||||
*/
|
||||
public function testSubstr($start = NULL, $length = NULL, $expected = NULL) {
|
||||
$configuration['start'] = $start;
|
||||
$configuration['length'] = $length;
|
||||
$this->plugin = new Substr($configuration, 'map', []);
|
||||
$value = $this->plugin->transform('Captain Janeway', $this->migrateExecutable, $this->row, 'destinationproperty');
|
||||
$this->assertSame($expected, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testSubstr().
|
||||
*/
|
||||
public function providerTestSubstr() {
|
||||
return [
|
||||
// Tests with valid start and length values.
|
||||
[0, 7, 'Captain'],
|
||||
// Tests with valid start > 0 and valid length.
|
||||
[6, 3, 'n J'],
|
||||
// Tests with valid start < 0 and valid length.
|
||||
[-7, 4, 'Jane'],
|
||||
// Tests without start value and valid length value.
|
||||
[NULL, 7, 'Captain'],
|
||||
// Tests with valid start value and no length value.
|
||||
[1, NULL, 'aptain Janeway'],
|
||||
// Tests without both start and length values.
|
||||
[NULL, NULL, 'Captain Janeway'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid input type.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
* @expectedExceptionMessage The input value must be a string.
|
||||
*/
|
||||
public function testSubstrFail() {
|
||||
$configuration = [];
|
||||
$this->plugin = new Substr($configuration, 'map', []);
|
||||
$this->plugin->transform(['Captain Janeway'], $this->migrateExecutable, $this->row, 'destinationproperty');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the start parameter is an integer.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
* @expectedExceptionMessage The start position configuration value should be an integer. Omit this key to capture from the beginning of the string.
|
||||
*/
|
||||
public function testStartIsString() {
|
||||
$configuration['start'] = '2';
|
||||
$this->plugin = new Substr($configuration, 'map', []);
|
||||
$this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the length parameter is an integer.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
* @expectedExceptionMessage The character length configuration value should be an integer. Omit this key to capture from the start position to the end of the string.
|
||||
*/
|
||||
public function testLengthIsString() {
|
||||
$configuration['length'] = '1';
|
||||
$this->plugin = new Substr($configuration, 'map', []);
|
||||
$this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
|
||||
}
|
||||
|
||||
}
|
|
@ -37,6 +37,13 @@ class CckMigration extends Migration implements ContainerFactoryPluginInterface
|
|||
*/
|
||||
protected $cckPluginCache;
|
||||
|
||||
/**
|
||||
* The cckfield plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $cckPluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a CckMigration.
|
||||
*
|
||||
|
|
|
@ -8034,7 +8034,17 @@ $connection->insert('history')
|
|||
->values(array(
|
||||
'uid' => '1',
|
||||
'nid' => '9',
|
||||
'timestamp' => '1457655127',
|
||||
'timestamp' => '1468384961',
|
||||
))
|
||||
->values(array(
|
||||
'uid' => '1',
|
||||
'nid' => '12',
|
||||
'timestamp' => '1468384823',
|
||||
))
|
||||
->values(array(
|
||||
'uid' => '1',
|
||||
'nid' => '13',
|
||||
'timestamp' => '1468384931',
|
||||
))
|
||||
->execute();
|
||||
|
||||
|
@ -34709,7 +34719,7 @@ $connection->insert('menu_router')
|
|||
'access_callback' => 'user_access',
|
||||
'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}',
|
||||
'page_callback' => 'drupal_get_form',
|
||||
'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'fit' => '15',
|
||||
'number_parts' => '4',
|
||||
'tab_parent' => '',
|
||||
|
@ -34731,7 +34741,7 @@ $connection->insert('menu_router')
|
|||
'access_callback' => 'user_access',
|
||||
'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}',
|
||||
'page_callback' => 'drupal_get_form',
|
||||
'page_arguments' => 'a:2:{i:0;s:24:"node_type_delete_confirm";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'page_arguments' => 'a:2:{i:0;s:24:"node_type_delete_confirm";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'fit' => '31',
|
||||
'number_parts' => '5',
|
||||
'tab_parent' => '',
|
||||
|
@ -34841,7 +34851,7 @@ $connection->insert('menu_router')
|
|||
'access_callback' => 'user_access',
|
||||
'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}',
|
||||
'page_callback' => 'drupal_get_form',
|
||||
'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}',
|
||||
'fit' => '31',
|
||||
'number_parts' => '5',
|
||||
'tab_parent' => 'admin/content/node-type/company',
|
||||
|
@ -41334,6 +41344,40 @@ $connection->insert('node')
|
|||
'tnid' => '0',
|
||||
'translate' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '10',
|
||||
'vid' => '13',
|
||||
'type' => 'page',
|
||||
'language' => 'en',
|
||||
'title' => 'The Real McCoy',
|
||||
'uid' => '1',
|
||||
'status' => '1',
|
||||
'created' => '1444238800',
|
||||
'changed' => '1444238808',
|
||||
'comment' => '2',
|
||||
'promote' => '1',
|
||||
'moderate' => '0',
|
||||
'sticky' => '0',
|
||||
'tnid' => '10',
|
||||
'translate' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '11',
|
||||
'vid' => '14',
|
||||
'type' => 'page',
|
||||
'language' => 'fr',
|
||||
'title' => 'Le Vrai McCoy',
|
||||
'uid' => '1',
|
||||
'status' => '1',
|
||||
'created' => '1444239050',
|
||||
'changed' => '1444239050',
|
||||
'comment' => '2',
|
||||
'promote' => '1',
|
||||
'moderate' => '0',
|
||||
'sticky' => '0',
|
||||
'tnid' => '10',
|
||||
'translate' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_access', array(
|
||||
|
@ -41463,6 +41507,13 @@ $connection->insert('node_comment_statistics')
|
|||
'last_comment_uid',
|
||||
'comment_count',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '0',
|
||||
'last_comment_timestamp' => '1468384735',
|
||||
'last_comment_name' => NULL,
|
||||
'last_comment_uid' => '1',
|
||||
'comment_count' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '1',
|
||||
'last_comment_timestamp' => '1388271197',
|
||||
|
@ -41479,7 +41530,14 @@ $connection->insert('node_comment_statistics')
|
|||
))
|
||||
->values(array(
|
||||
'nid' => '9',
|
||||
'last_comment_timestamp' => '1444671588',
|
||||
'last_comment_timestamp' => '1444238800',
|
||||
'last_comment_name' => NULL,
|
||||
'last_comment_uid' => '1',
|
||||
'comment_count' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '10',
|
||||
'last_comment_timestamp' => '1444239050',
|
||||
'last_comment_name' => NULL,
|
||||
'last_comment_uid' => '1',
|
||||
'comment_count' => '0',
|
||||
|
@ -41727,6 +41785,28 @@ $connection->insert('node_revisions')
|
|||
'timestamp' => '1444671588',
|
||||
'format' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '10',
|
||||
'vid' => '13',
|
||||
'uid' => '1',
|
||||
'title' => 'The Real McCoy',
|
||||
'body' => "In the original, Queen's English.",
|
||||
'teaser' => "In the original, Queen's English.",
|
||||
'log' => '',
|
||||
'timestamp' => '1444238808',
|
||||
'format' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'nid' => '11',
|
||||
'vid' => '14',
|
||||
'uid' => '1',
|
||||
'title' => 'Le Vrai McCoy',
|
||||
'body' => 'Ooh là là!',
|
||||
'teaser' => 'Ooh là là!',
|
||||
'log' => '',
|
||||
'timestamp' => '1444239050',
|
||||
'format' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_type', array(
|
||||
|
@ -41861,9 +41941,9 @@ $connection->insert('node_type')
|
|||
'title_label' => 'Name',
|
||||
'has_body' => '1',
|
||||
'body_label' => 'Description',
|
||||
'min_word_count' => '20',
|
||||
'min_word_count' => '0',
|
||||
'custom' => '0',
|
||||
'modified' => '0',
|
||||
'modified' => '1',
|
||||
'locked' => '0',
|
||||
'orig_type' => 'company',
|
||||
))
|
||||
|
@ -44465,8 +44545,8 @@ $connection->insert('users')
|
|||
'signature' => '',
|
||||
'signature_format' => '0',
|
||||
'created' => '0',
|
||||
'access' => '1458198052',
|
||||
'login' => '1458193160',
|
||||
'access' => '1468384823',
|
||||
'login' => '1468384420',
|
||||
'status' => '1',
|
||||
'timezone' => NULL,
|
||||
'language' => '',
|
||||
|
@ -44809,13 +44889,17 @@ $connection->insert('variable')
|
|||
'name' => 'comment_article',
|
||||
'value' => 's:1:"2";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_company',
|
||||
'value' => 's:1:"2";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_controls_article',
|
||||
'value' => 'i:3;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_controls_company',
|
||||
'value' => 'i:3;',
|
||||
'value' => 's:1:"3";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_controls_employee',
|
||||
|
@ -44855,7 +44939,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_mode_company',
|
||||
'value' => 'i:4;',
|
||||
'value' => 's:1:"4";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_mode_employee',
|
||||
|
@ -44895,7 +44979,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_order_company',
|
||||
'value' => 'i:1;',
|
||||
'value' => 's:1:"1";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_order_employee',
|
||||
|
@ -44935,7 +45019,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_per_page_company',
|
||||
'value' => 'i:50;',
|
||||
'value' => 's:2:"50";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_default_per_page_employee',
|
||||
|
@ -44975,7 +45059,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_form_location_company',
|
||||
'value' => 'i:0;',
|
||||
'value' => 's:1:"0";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_form_location_employee',
|
||||
|
@ -45019,7 +45103,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_preview_company',
|
||||
'value' => 'i:1;',
|
||||
'value' => 's:1:"1";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_preview_employee',
|
||||
|
@ -45063,7 +45147,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'comment_subject_field_company',
|
||||
'value' => 'i:1;',
|
||||
'value' => 's:1:"1";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'comment_subject_field_employee',
|
||||
|
@ -45425,6 +45509,10 @@ $connection->insert('variable')
|
|||
'name' => 'event_nodeapi_article',
|
||||
'value' => 's:5:"never";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'event_nodeapi_company',
|
||||
'value' => 's:5:"never";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'event_nodeapi_event',
|
||||
'value' => 's:3:"all";',
|
||||
|
@ -45487,7 +45575,11 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'form_build_id_article',
|
||||
'value' => 's:48:"form-mXZfFJxcCFGB80PPYtNOuwYbho6-xKTvrRLb3TAMkic";',
|
||||
'value' => 's:48:"form-t2zKJflpBD4rpYoGQH33ckjjWAYdo5lF3Hl1O_YnWyE";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'form_build_id_company',
|
||||
'value' => 's:48:"form-jFw2agRukPxjG5dG-N6joZLyoxXmCoxTzua0HUciqK0";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'forum_block_num_0',
|
||||
|
@ -45589,6 +45681,10 @@ $connection->insert('variable')
|
|||
'name' => 'node_options_book',
|
||||
'value' => 'a:1:{i:0;s:6:"status";}',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'node_options_company',
|
||||
'value' => 'a:2:{i:0;s:6:"status";i:1;s:7:"promote";}',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'node_options_forum',
|
||||
'value' => 'a:1:{i:0;s:6:"status";}',
|
||||
|
@ -45785,6 +45881,10 @@ $connection->insert('variable')
|
|||
'name' => 'upload_article',
|
||||
'value' => 'b:0;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'upload_company',
|
||||
'value' => 's:1:"1";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'upload_page',
|
||||
'value' => 'b:1;',
|
||||
|
|
|
@ -3474,7 +3474,7 @@ $connection->insert('field_config_instance')
|
|||
'field_name' => 'body',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
|
||||
'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:3:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"custom";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
|
||||
'deleted' => '0',
|
||||
))
|
||||
->values(array(
|
||||
|
@ -3720,6 +3720,24 @@ $connection->insert('field_config_instance')
|
|||
'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}',
|
||||
'deleted' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '34',
|
||||
'field_id' => '15',
|
||||
'field_name' => 'field_link',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
|
||||
'deleted' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'id' => '35',
|
||||
'field_id' => '14',
|
||||
'field_name' => 'field_integer',
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle' => 'test_vocabulary',
|
||||
'data' => 'a:7:{s:5:"label";s:7:"Integer";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:6:"number";s:6:"module";s:6:"number";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:5:{s:3:"min";s:0:"";s:3:"max";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:14:"number_integer";s:8:"settings";a:4:{s:18:"thousand_separator";s:0:"";s:17:"decimal_separator";s:1:".";s:5:"scale";i:0;s:13:"prefix_suffix";b:1;}s:6:"module";s:6:"number";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
|
||||
'deleted' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('field_data_body', array(
|
||||
|
@ -4900,6 +4918,18 @@ $connection->insert('field_data_field_link')
|
|||
'field_link_title' => 'Click Here',
|
||||
'field_link_attributes' => 'a:1:{s:5:"title";s:10:"Click Here";}',
|
||||
))
|
||||
->values(array(
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
'deleted' => '0',
|
||||
'entity_id' => '2',
|
||||
'revision_id' => '2',
|
||||
'language' => 'und',
|
||||
'delta' => '0',
|
||||
'field_link_url' => '<front>',
|
||||
'field_link_title' => 'Home',
|
||||
'field_link_attributes' => 'a:0:{}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('field_data_field_long_text', array(
|
||||
|
@ -6668,6 +6698,18 @@ $connection->insert('field_revision_field_link')
|
|||
'field_link_title' => 'Click Here',
|
||||
'field_link_attributes' => 'a:1:{s:5:"title";s:10:"Click Here";}',
|
||||
))
|
||||
->values(array(
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
'deleted' => '0',
|
||||
'entity_id' => '2',
|
||||
'revision_id' => '2',
|
||||
'language' => 'und',
|
||||
'delta' => '0',
|
||||
'field_link_url' => '<front>',
|
||||
'field_link_title' => 'Home',
|
||||
'field_link_attributes' => 'a:0:{}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('field_revision_field_long_text', array(
|
||||
|
|
|
@ -4,7 +4,10 @@ namespace Drupal\Tests\migrate_drupal\Kernel\d6;
|
|||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\user\Entity\User;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
* @group migrate_drupal
|
||||
|
@ -85,4 +88,40 @@ class EntityContentBaseTest extends MigrateDrupal6TestBase {
|
|||
$this->assertIdentical('proto@zo.an', $account->getInitialEmail());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that translation destination fails for untranslatable entities.
|
||||
*/
|
||||
public function testUntranslatable() {
|
||||
$this->enableModules(['language_test']);
|
||||
$this->installEntitySchema('no_language_entity_test');
|
||||
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([
|
||||
'source' => [
|
||||
'plugin' => 'embedded_data',
|
||||
'ids' => ['id' => ['type' => 'integer']],
|
||||
'data_rows' => [['id' => 1]],
|
||||
],
|
||||
'process' => [
|
||||
'id' => 'id',
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'entity:no_language_entity_test',
|
||||
'translations' => TRUE,
|
||||
],
|
||||
]);
|
||||
|
||||
$message = $this->prophesize(MigrateMessageInterface::class);
|
||||
// Match the expected message. Can't use default argument types, because
|
||||
// we need to convert to string from TranslatableMarkup.
|
||||
$argument = Argument::that(function($msg) {
|
||||
return strpos((string) $msg, "This entity type does not support translation") !== FALSE;
|
||||
});
|
||||
$message->display($argument, Argument::any())
|
||||
->shouldBeCalled();
|
||||
|
||||
$executable = new MigrateExecutable($migration, $message->reveal());
|
||||
$executable->import();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,17 +89,24 @@ abstract class MigrateDrupal6TestBase extends MigrateDrupalTestBase {
|
|||
/**
|
||||
* Executes all content migrations.
|
||||
*
|
||||
* @param bool $include_revisions
|
||||
* If TRUE, migrates node revisions.
|
||||
* @param array $include
|
||||
* Extra things to include as part of the migrations. Values may be
|
||||
* 'revisions' or 'translations'.
|
||||
*/
|
||||
protected function migrateContent($include_revisions = FALSE) {
|
||||
protected function migrateContent($include = []) {
|
||||
if (in_array('translations', $include)) {
|
||||
$this->executeMigrations(['language']);
|
||||
}
|
||||
$this->migrateUsers(FALSE);
|
||||
$this->migrateFields();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->executeMigrations(['d6_node_settings', 'd6_node']);
|
||||
|
||||
if ($include_revisions) {
|
||||
if (in_array('translations', $include)) {
|
||||
$this->executeMigrations(['translations']);
|
||||
}
|
||||
if (in_array('revisions', $include)) {
|
||||
$this->executeMigrations(['d6_node_revision']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,14 @@ class MigrateUpgradeForm extends ConfirmFormBase {
|
|||
'source_module' => 'image',
|
||||
'destination_module' => 'image',
|
||||
],
|
||||
'd6_language_content_settings' => [
|
||||
'source_module' => 'locale',
|
||||
'destination_module' => 'language',
|
||||
],
|
||||
'd7_language_content_settings' => [
|
||||
'source_module' => 'locale',
|
||||
'destination_module' => 'language',
|
||||
],
|
||||
'd7_language_negotiation_settings' => [
|
||||
'source_module' => 'locale',
|
||||
'destination_module' => 'language',
|
||||
|
@ -290,6 +298,10 @@ class MigrateUpgradeForm extends ConfirmFormBase {
|
|||
'source_module' => 'node',
|
||||
'destination_module' => 'node',
|
||||
],
|
||||
'd6_node_translation' => [
|
||||
'source_module' => 'node',
|
||||
'destination_module' => 'node',
|
||||
],
|
||||
'd6_node_revision' => [
|
||||
'source_module' => 'node',
|
||||
'destination_module' => 'node',
|
||||
|
|
|
@ -30,7 +30,7 @@ abstract class MigrateUpgradeTestBase extends WebTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['migrate_drupal_ui', 'telephone'];
|
||||
public static $modules = ['language', 'content_translation', 'migrate_drupal_ui', 'telephone'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -40,14 +40,16 @@ class MigrateUpgrade6Test extends MigrateUpgradeTestBase {
|
|||
'comment' => 3,
|
||||
'comment_type' => 2,
|
||||
'contact_form' => 5,
|
||||
'configurable_language' => 5,
|
||||
'editor' => 2,
|
||||
'field_config' => 62,
|
||||
'field_config' => 63,
|
||||
'field_storage_config' => 43,
|
||||
'file' => 7,
|
||||
'filter_format' => 7,
|
||||
'image_style' => 5,
|
||||
'language_content_settings' => 2,
|
||||
'migration' => 105,
|
||||
'node' => 9,
|
||||
'node' => 10,
|
||||
'node_type' => 11,
|
||||
'rdf_mapping' => 5,
|
||||
'search_page' => 2,
|
||||
|
@ -57,7 +59,7 @@ class MigrateUpgrade6Test extends MigrateUpgradeTestBase {
|
|||
'menu' => 8,
|
||||
'taxonomy_term' => 6,
|
||||
'taxonomy_vocabulary' => 6,
|
||||
'tour' => 1,
|
||||
'tour' => 4,
|
||||
'user' => 7,
|
||||
'user_role' => 6,
|
||||
'menu_link_content' => 4,
|
||||
|
|
|
@ -39,13 +39,16 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
|
|||
'block_content_type' => 1,
|
||||
'comment' => 1,
|
||||
'comment_type' => 7,
|
||||
// Module 'language' comes with 'en', 'und', 'zxx'. Migration adds 'is'.
|
||||
'configurable_language' => 4,
|
||||
'contact_form' => 3,
|
||||
'editor' => 2,
|
||||
'field_config' => 41,
|
||||
'field_storage_config' => 31,
|
||||
'field_config' => 43,
|
||||
'field_storage_config' => 32,
|
||||
'file' => 1,
|
||||
'filter_format' => 7,
|
||||
'image_style' => 6,
|
||||
'language_content_settings' => 1,
|
||||
'migration' => 59,
|
||||
'node' => 2,
|
||||
'node_type' => 6,
|
||||
|
@ -57,16 +60,16 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
|
|||
'menu' => 10,
|
||||
'taxonomy_term' => 18,
|
||||
'taxonomy_vocabulary' => 3,
|
||||
'tour' => 1,
|
||||
'tour' => 4,
|
||||
'user' => 3,
|
||||
'user_role' => 4,
|
||||
'menu_link_content' => 9,
|
||||
'view' => 12,
|
||||
'date_format' => 11,
|
||||
'entity_form_display' => 15,
|
||||
'entity_form_display' => 16,
|
||||
'entity_form_mode' => 1,
|
||||
'entity_view_display' => 22,
|
||||
'entity_view_mode' => 10,
|
||||
'entity_view_display' => 24,
|
||||
'entity_view_mode' => 11,
|
||||
'base_field_override' => 7,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
|||
source:
|
||||
plugin: d6_node
|
||||
process:
|
||||
nid: nid
|
||||
# In D6, nodes always have a tnid, but it's zero for untranslated nodes.
|
||||
# We normalize it to equal the nid in that case.
|
||||
# @see \Drupal\node\Plugin\migrate\source\d6\Node::prepareRow().
|
||||
nid: tnid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: default_value
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
id: d6_node_translation
|
||||
label: Node translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
deriver: Drupal\node\Plugin\migrate\D6NodeDeriver
|
||||
source:
|
||||
plugin: d6_node
|
||||
translations: true
|
||||
process:
|
||||
nid: tnid
|
||||
type: type
|
||||
langcode:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: "und"
|
||||
title: title
|
||||
uid: node_uid
|
||||
status: status
|
||||
created: created
|
||||
changed: changed
|
||||
promote: promote
|
||||
sticky: sticky
|
||||
'body/format':
|
||||
plugin: migration
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
'body/summary': teaser
|
||||
revision_uid: revision_uid
|
||||
revision_log: log
|
||||
revision_timestamp: timestamp
|
||||
|
||||
# unmapped d6 fields.
|
||||
# translate
|
||||
# moderate
|
||||
# comment
|
||||
|
||||
destination:
|
||||
plugin: entity:node
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_user
|
||||
- d6_node_type
|
||||
- d6_node_settings
|
||||
- d6_filter_format
|
||||
- language
|
||||
optional:
|
||||
- d6_field_instance_widget_settings
|
||||
- d6_field_formatter_settings
|
||||
- d6_upload_field_instance
|
||||
provider: migrate_drupal
|
|
@ -8,6 +8,7 @@ use Drupal\Core\Datetime\DateFormatterInterface;
|
|||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeStorageInterface;
|
||||
use Drupal\node\NodeTypeInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -125,6 +126,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);
|
||||
$page = $node_view_controller->view($node);
|
||||
unset($page['nodes'][$node->id()]['#cache']);
|
||||
|
@ -170,12 +172,9 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
$delete_permission = (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'));
|
||||
|
||||
$rows = array();
|
||||
|
||||
$vids = $node_storage->revisionIds($node);
|
||||
|
||||
$latest_revision = TRUE;
|
||||
|
||||
foreach (array_reverse($vids) as $vid) {
|
||||
foreach ($this->_getRevisionIds($node, $node_storage) as $vid) {
|
||||
/** @var \Drupal\node\NodeInterface $revision */
|
||||
$revision = $node_storage->loadRevision($vid);
|
||||
// Only show revisions that are affected by the language that is being
|
||||
|
@ -263,6 +262,8 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
),
|
||||
);
|
||||
|
||||
$build['pager'] = array('#type' => 'pager');
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
|
@ -279,4 +280,25 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
return $this->t('Create @name', array('@name' => $node_type->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of node revision IDs for a specific node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface
|
||||
* The node entity.
|
||||
* @param \Drupal\node\NodeStorageInterface $node_storage
|
||||
* The node storage handler.
|
||||
*
|
||||
* @return int[]
|
||||
* Node revision IDs (in descending order).
|
||||
*/
|
||||
protected function _getRevisionIds(NodeInterface $node, NodeStorageInterface $node_storage) {
|
||||
$result = $node_storage->getQuery()
|
||||
->allRevisions()
|
||||
->condition($node->getEntityType()->getKey('id'), $node->id())
|
||||
->sort($node->getEntityType()->getKey('revision'), 'DESC')
|
||||
->pager(50)
|
||||
->execute();
|
||||
return array_keys($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -146,10 +146,21 @@ class NodeViewBuilder extends EntityViewBuilder {
|
|||
/** @var \Drupal\node\NodeInterface $entity */
|
||||
parent::alterBuild($build, $entity, $display, $view_mode);
|
||||
if ($entity->id()) {
|
||||
$build['#contextual_links']['node'] = array(
|
||||
'route_parameters' => array('node' => $entity->id()),
|
||||
'metadata' => array('changed' => $entity->getChangedTime()),
|
||||
);
|
||||
if ($entity->isDefaultRevision()) {
|
||||
$build['#contextual_links']['node'] = [
|
||||
'route_parameters' => ['node' => $entity->id()],
|
||||
'metadata' => ['changed' => $entity->getChangedTime()],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$build['#contextual_links']['node_revision'] = [
|
||||
'route_parameters' => [
|
||||
'node' => $entity->id(),
|
||||
'node_revision' => $entity->getRevisionId(),
|
||||
],
|
||||
'metadata' => ['changed' => $entity->getChangedTime()],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,13 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
|||
*/
|
||||
protected $cckPluginManager;
|
||||
|
||||
/**
|
||||
* Whether or not to include translations.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $includeTranslations;
|
||||
|
||||
/**
|
||||
* D6NodeDeriver constructor.
|
||||
*
|
||||
|
@ -44,19 +51,24 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
|||
* The base plugin ID for the plugin ID.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
|
||||
* The CCK plugin manager.
|
||||
* @param bool $translations
|
||||
* Whether or not to include translations.
|
||||
*/
|
||||
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager) {
|
||||
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager, $translations) {
|
||||
$this->basePluginId = $base_plugin_id;
|
||||
$this->cckPluginManager = $cck_manager;
|
||||
$this->includeTranslations = $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
// Translations don't make sense unless we have content_translation.
|
||||
return new static(
|
||||
$base_plugin_id,
|
||||
$container->get('plugin.manager.migrate.cckfield')
|
||||
$container->get('plugin.manager.migrate.cckfield'),
|
||||
$container->get('module_handler')->moduleExists('content_translation')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -72,6 +84,11 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
|||
* @see \Drupal\Component\Plugin\Derivative\DeriverBase::getDerivativeDefinition()
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if ($base_plugin_definition['id'] == 'd6_node_translation' && !$this->includeTranslations) {
|
||||
// Refuse to generate anything.
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
// Read all CCK field instance definitions in the source database.
|
||||
$fields = array();
|
||||
try {
|
||||
|
@ -100,9 +117,10 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
|||
$values['source']['node_type'] = $node_type;
|
||||
$values['destination']['default_bundle'] = $node_type;
|
||||
|
||||
// If this migration is based on the d6_node_revision migration, it
|
||||
// should explicitly depend on the corresponding d6_node variant.
|
||||
if ($base_plugin_definition['id'] == 'd6_node_revision') {
|
||||
// If this migration is based on the d6_node_revision migration or
|
||||
// is for translations of nodes, it should explicitly depend on the
|
||||
// corresponding d6_node variant.
|
||||
if (in_array($base_plugin_definition['id'], ['d6_node_revision', 'd6_node_translation'])) {
|
||||
$values['migration_dependencies']['required'][] = 'd6_node:' . $node_type;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\node\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
|
@ -37,9 +38,11 @@ class Node extends DrupalSqlBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Select node in its last revision.
|
||||
$query = $this->select('node_revisions', 'nr')
|
||||
->fields('n', array(
|
||||
$query = $this->select('node_revisions', 'nr');
|
||||
$query->innerJoin('node', 'n', static::JOIN);
|
||||
$this->handleTranslations($query);
|
||||
|
||||
$query->fields('n', array(
|
||||
'nid',
|
||||
'type',
|
||||
'language',
|
||||
|
@ -54,17 +57,16 @@ class Node extends DrupalSqlBase {
|
|||
'translate',
|
||||
))
|
||||
->fields('nr', array(
|
||||
'vid',
|
||||
'title',
|
||||
'body',
|
||||
'teaser',
|
||||
'log',
|
||||
'timestamp',
|
||||
'format',
|
||||
'vid',
|
||||
));
|
||||
$query->addField('n', 'uid', 'node_uid');
|
||||
$query->addField('nr', 'uid', 'revision_uid');
|
||||
$query->innerJoin('node', 'n', static::JOIN);
|
||||
|
||||
if (isset($this->configuration['node_type'])) {
|
||||
$query->condition('n.type', $this->configuration['node_type']);
|
||||
|
@ -123,6 +125,11 @@ class Node extends DrupalSqlBase {
|
|||
}
|
||||
}
|
||||
|
||||
// Make sure we always have a translation set.
|
||||
if ($row->getSourceProperty('tnid') == 0) {
|
||||
$row->setSourceProperty('tnid', $row->getSourceProperty('nid'));
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
|
@ -251,4 +258,22 @@ class Node extends DrupalSqlBase {
|
|||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt our query for translations.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface
|
||||
* The generated query.
|
||||
*/
|
||||
protected function handleTranslations(SelectInterface $query) {
|
||||
// Check whether or not we want translations.
|
||||
if (empty($this->configuration['translations'])) {
|
||||
// No translations: Yield untranslated nodes, or default translations.
|
||||
$query->where('n.tnid = 0 OR n.tnid = n.nid');
|
||||
}
|
||||
else {
|
||||
// Translations: Yield only non-default translations.
|
||||
$query->where('n.tnid <> 0 AND n.tnid <> n.nid');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\node\Plugin\migrate\source\d6;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
|
||||
/**
|
||||
* Drupal 6 node revision source from database.
|
||||
|
@ -37,4 +38,11 @@ class NodeRevision extends Node {
|
|||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function handleTranslations(SelectInterface $query) {
|
||||
// @todo in https://www.drupal.org/node/2746541
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,7 +63,9 @@ class Path extends FieldPluginBase {
|
|||
*/
|
||||
public function render(ResultRow $values) {
|
||||
$nid = $this->getValue($values, 'nid');
|
||||
return \Drupal::url('entity.node.canonical', ['node' => $nid], ['absolute' => $this->options['absolute']]);
|
||||
return array(
|
||||
'#markup' => \Drupal::url('entity.node.canonical', ['node' => $nid], ['absolute' => $this->options['absolute']]),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ use Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeTestBase;
|
|||
*/
|
||||
class MigrateNodeRevisionTest extends MigrateNodeTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\node\Tests;
|
||||
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* Ensures that data added to nodes by other modules appears in RSS feeds.
|
||||
*
|
||||
|
@ -60,4 +62,47 @@ class NodeRSSContentTest extends NodeTestBase {
|
|||
$this->assertNoText($rss_only_content, 'Node content designed for RSS does not appear when viewing node.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests relative, root-relative, protocol-relative and absolute URLs.
|
||||
*/
|
||||
public function testUrlHandling() {
|
||||
// Only the plain_text text format is available by default, which escapes
|
||||
// all HTML.
|
||||
FilterFormat::create([
|
||||
'format' => 'full_html',
|
||||
'name' => 'Full HTML',
|
||||
'filters' => [],
|
||||
])->save();
|
||||
|
||||
$defaults = [
|
||||
'type' => 'article',
|
||||
'promote' => 1,
|
||||
];
|
||||
$this->drupalCreateNode($defaults + [
|
||||
'body' => [
|
||||
'value' => '<p><a href="' . file_url_transform_relative(file_create_url('public://root-relative')) . '">Root-relative URL</a></p>',
|
||||
'format' => 'full_html',
|
||||
],
|
||||
]);
|
||||
$protocol_relative_url = substr(file_create_url('public://protocol-relative'), strlen(\Drupal::request()->getScheme() . ':'));
|
||||
$this->drupalCreateNode($defaults + [
|
||||
'body' => [
|
||||
'value' => '<p><a href="' . $protocol_relative_url . '">Protocol-relative URL</a></p>',
|
||||
'format' => 'full_html',
|
||||
],
|
||||
]);
|
||||
$absolute_url = file_create_url('public://absolute');
|
||||
$this->drupalCreateNode($defaults + [
|
||||
'body' => [
|
||||
'value' => '<p><a href="' . $absolute_url . '">Absolute URL</a></p>',
|
||||
'format' => 'full_html',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->drupalGet('rss.xml');
|
||||
$this->assertRaw(file_create_url('public://root-relative'), 'Root-relative URL is transformed to absolute.');
|
||||
$this->assertRaw($protocol_relative_url, 'Protocol-relative URL is left untouched.');
|
||||
$this->assertRaw($absolute_url, 'Absolute URL is left untouched.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\node\Tests;
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Create a node with revisions and test viewing, saving, reverting, and
|
||||
* deleting revisions for user with access to all.
|
||||
|
@ -15,7 +17,7 @@ class NodeRevisionsAllTest extends NodeTestBase {
|
|||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
|
||||
// Create and log in user.
|
||||
$web_user = $this->drupalCreateUser(
|
||||
array(
|
||||
|
@ -45,17 +47,7 @@ class NodeRevisionsAllTest extends NodeTestBase {
|
|||
for ($i = 0; $i < $revision_count; $i++) {
|
||||
$logs[] = $node->revision_log = $this->randomMachineName(32);
|
||||
|
||||
// Create revision with a random title and body and update variables.
|
||||
$node->title = $this->randomMachineName();
|
||||
$node->body = array(
|
||||
'value' => $this->randomMachineName(32),
|
||||
'format' => filter_default_format(),
|
||||
);
|
||||
$node->setNewRevision();
|
||||
$node->save();
|
||||
|
||||
$node_storage->resetCache(array($node->id()));
|
||||
$node = $node_storage->load($node->id()); // Make sure we get revision information.
|
||||
$node = $this->createNodeRevision($node);
|
||||
$nodes[] = clone $node;
|
||||
}
|
||||
|
||||
|
@ -63,6 +55,28 @@ class NodeRevisionsAllTest extends NodeTestBase {
|
|||
$this->revisionLogs = $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new revision for a given node.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* A node object.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* A node object with up to date revision information.
|
||||
*/
|
||||
protected function createNodeRevision(NodeInterface $node) {
|
||||
// Create revision with a random title and body and update variables.
|
||||
$node->title = $this->randomMachineName();
|
||||
$node->body = array(
|
||||
'value' => $this->randomMachineName(32),
|
||||
'format' => filter_default_format(),
|
||||
);
|
||||
$node->setNewRevision();
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks node revision operations.
|
||||
*/
|
||||
|
@ -145,6 +159,29 @@ class NodeRevisionsAllTest extends NodeTestBase {
|
|||
'%title' => $nodes[2]->getTitle(),
|
||||
'%revision-date' => format_date($old_revision_date),
|
||||
)));
|
||||
|
||||
// Create 50 more revisions in order to trigger paging on the revisions
|
||||
// overview screen.
|
||||
$node = $nodes[0];
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
$logs[] = $node->revision_log = $this->randomMachineName(32);
|
||||
|
||||
$node = $this->createNodeRevision($node);
|
||||
$nodes[] = clone $node;
|
||||
}
|
||||
|
||||
$this->drupalGet('node/' . $node->id() . '/revisions');
|
||||
|
||||
// Check that the pager exists.
|
||||
$this->assertRaw('page=1');
|
||||
|
||||
// Check that the last revision is displayed on the first page.
|
||||
$this->assertText(end($logs));
|
||||
|
||||
// Go to the second page and check that one of the initial three revisions
|
||||
// is displayed.
|
||||
$this->clickLink(t('Page 2'));
|
||||
$this->assertText($logs[2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use Drupal\field\Entity\FieldStorageConfig;
|
|||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
|
||||
/**
|
||||
* Create a node with revisions and test viewing, saving, reverting, and
|
||||
|
@ -34,7 +35,7 @@ class NodeRevisionsTest extends NodeTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('node', 'datetime', 'language', 'content_translation');
|
||||
public static $modules = ['node', 'contextual', 'datetime', 'language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -71,6 +72,7 @@ class NodeRevisionsTest extends NodeTestBase {
|
|||
'delete page revisions',
|
||||
'edit any page content',
|
||||
'delete any page content',
|
||||
'access contextual links',
|
||||
'translate any entity',
|
||||
'administer content types',
|
||||
)
|
||||
|
@ -152,6 +154,18 @@ class NodeRevisionsTest extends NodeTestBase {
|
|||
// Confirm that this is the default revision.
|
||||
$this->assertTrue($node->isDefaultRevision(), 'Third node revision is the default one.');
|
||||
|
||||
// Confirm that the "Edit" and "Delete" contextual links appear for the
|
||||
// default revision.
|
||||
$ids = ['node:node=' . $node->id() . ':changed=' . $node->getChangedTime()];
|
||||
$json = $this->renderContextualLinks($ids, 'node/' . $node->id());
|
||||
$this->verbose($json[$ids[0]]);
|
||||
|
||||
$expected = '<li class="entitynodeedit-form"><a href="' . base_path() . 'node/' . $node->id() . '/edit">Edit</a></li>';
|
||||
$this->assertTrue(strstr($json[$ids[0]], $expected), 'The "Edit" contextual link is shown for the default revision.');
|
||||
$expected = '<li class="entitynodedelete-form"><a href="' . base_path() . 'node/' . $node->id() . '/delete">Delete</a></li>';
|
||||
$this->assertTrue(strstr($json[$ids[0]], $expected), 'The "Delete" contextual link is shown for the default revision.');
|
||||
|
||||
|
||||
// Confirm that revisions revert properly.
|
||||
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionid() . "/revert", array(), t('Revert'));
|
||||
$this->assertRaw(t('@type %title has been reverted to the revision from %revision-date.',
|
||||
|
@ -165,6 +179,16 @@ class NodeRevisionsTest extends NodeTestBase {
|
|||
$node = node_revision_load($node->getRevisionId());
|
||||
$this->assertFalse($node->isDefaultRevision(), 'Third node revision is not the default one.');
|
||||
|
||||
// Confirm that "Edit" and "Delete" contextual links don't appear for
|
||||
// non-default revision.
|
||||
$ids = ['node_revision::node=' . $node->id() . '&node_revision=' . $node->getRevisionId() . ':'];
|
||||
$json = $this->renderContextualLinks($ids, 'node/' . $node->id() . '/revisions/' . $node->getRevisionId() . '/view');
|
||||
$this->verbose($json[$ids[0]]);
|
||||
|
||||
$this->assertFalse(strstr($json[$ids[0]], '<li class="entitynodeedit-form">'), 'The "Edit" contextual link is not shown for a non-default revision.');
|
||||
$this->assertFalse(strstr($json[$ids[0]], '<li class="entitynodedelete-form">'), 'The "Delete" contextual link is not shown for a non-default revision.');
|
||||
|
||||
|
||||
// Confirm revisions delete properly.
|
||||
$this->drupalPostForm("node/" . $node->id() . "/revisions/" . $nodes[1]->getRevisionId() . "/delete", array(), t('Delete'));
|
||||
$this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.',
|
||||
|
@ -317,6 +341,27 @@ class NodeRevisionsTest extends NodeTestBase {
|
|||
$this->assertTrue(empty($node_revision->revision_log->value), 'After a new node revision is saved with an empty log message, the log message for the node is empty.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets server-rendered contextual links for the given contextual links IDs.
|
||||
*
|
||||
* @param string[] $ids
|
||||
* An array of contextual link IDs.
|
||||
* @param string $current_path
|
||||
* The Drupal path for the page for which the contextual links are rendered.
|
||||
*
|
||||
* @return string
|
||||
* The decoded JSON response body.
|
||||
*/
|
||||
protected function renderContextualLinks(array $ids, $current_path) {
|
||||
$post = array();
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$post['ids[' . $i . ']'] = $ids[$i];
|
||||
}
|
||||
$response = $this->drupalPost('contextual/render', 'application/json', $post, ['query' => ['destination' => $current_path]]);
|
||||
|
||||
return Json::decode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the revision translations are correctly reverted.
|
||||
*/
|
||||
|
|
|
@ -445,4 +445,53 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that revision translations are rendered properly.
|
||||
*/
|
||||
public function testRevisionTranslationRendering() {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
|
||||
// Create a node.
|
||||
$nid = $this->createEntity(['title' => 'First rev en title'], 'en');
|
||||
$node = $storage->load($nid);
|
||||
$original_revision_id = $node->getRevisionId();
|
||||
|
||||
// Add a French translation.
|
||||
$translation = $node->addTranslation('fr');
|
||||
$translation->title = 'First rev fr title';
|
||||
$translation->setNewRevision(FALSE);
|
||||
$translation->save();
|
||||
|
||||
// Create a new revision.
|
||||
$node->title = 'Second rev en title';
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
|
||||
// Get an English view of this revision.
|
||||
$original_revision = $storage->loadRevision($original_revision_id);
|
||||
$original_revision_url = $original_revision->toUrl('revision')->toString();
|
||||
|
||||
// Should be different from regular node URL.
|
||||
$this->assertNotIdentical($original_revision_url, $original_revision->toUrl()->toString());
|
||||
$this->drupalGet($original_revision_url);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Contents should be in English, of correct revision.
|
||||
$this->assertText('First rev en title');
|
||||
$this->assertNoText('First rev fr title');
|
||||
|
||||
// Get a French view.
|
||||
$url_fr = $original_revision->getTranslation('fr')->toUrl('revision')->toString();
|
||||
|
||||
// Should have different URL from English.
|
||||
$this->assertNotIdentical($url_fr, $original_revision->toUrl()->toString());
|
||||
$this->assertNotIdentical($url_fr, $original_revision_url);
|
||||
$this->drupalGet($url_fr);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Contents should be in French, of correct revision.
|
||||
$this->assertText('First rev fr title');
|
||||
$this->assertNoText('First rev en title');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ class NodeTypeTranslationTest extends WebTestBase {
|
|||
* @var array
|
||||
*/
|
||||
public static $modules = array(
|
||||
'block',
|
||||
'config_translation',
|
||||
'field_ui',
|
||||
'node',
|
||||
);
|
||||
|
||||
|
@ -53,6 +55,8 @@ class NodeTypeTranslationTest extends WebTestBase {
|
|||
|
||||
$admin_permissions = array(
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer languages',
|
||||
'administer site configuration',
|
||||
'administer themes',
|
||||
'translate configuration',
|
||||
|
@ -144,6 +148,29 @@ class NodeTypeTranslationTest extends WebTestBase {
|
|||
$this->assertText('Edited title');
|
||||
$this->drupalGet("$langcode/node/add/$type");
|
||||
$this->assertText('Translated title');
|
||||
|
||||
// Add an e-mail field.
|
||||
$this->drupalPostForm("admin/structure/types/manage/$type/fields/add-field", array('new_storage_type' => 'email', 'label' => 'Email', 'field_name' => 'email'), 'Save and continue');
|
||||
$this->drupalPostForm(NULL, array(), 'Save field settings');
|
||||
$this->drupalPostForm(NULL, array(), 'Save settings');
|
||||
|
||||
$type = Unicode::strtolower($this->randomMachineName(16));
|
||||
$name = $this->randomString();
|
||||
$this->drupalCreateContentType(array('type' => $type, 'name' => $name));
|
||||
|
||||
// Set tabs.
|
||||
$this->drupalPlaceBlock('local_tasks_block', array('primary' => TRUE));
|
||||
|
||||
// Change default language.
|
||||
$this->drupalPostForm('admin/config/regional/language', array('site_default_language' => 'es'), 'Save configuration');
|
||||
|
||||
// Try re-using the email field.
|
||||
$this->drupalGet("es/admin/structure/types/manage/$type/fields/add-field");
|
||||
$this->drupalPostForm(NULL, array('existing_storage_name' => 'field_email', 'existing_storage_label' => 'Email'), 'Save and continue');
|
||||
$this->assertResponse(200);
|
||||
$this->drupalGet("es/admin/structure/types/manage/$type/fields/node.$type.field_email/translate");
|
||||
$this->assertResponse(200);
|
||||
$this->assertText("The configuration objects have different language codes so they cannot be translated");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
77
core/modules/node/src/Tests/Views/PathPluginTest.php
Normal file
77
core/modules/node/src/Tests/Views/PathPluginTest.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\node\Tests\Views;
|
||||
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the node row plugin.
|
||||
*
|
||||
* @group node
|
||||
*/
|
||||
class PathPluginTest extends NodeTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_node_path_plugin');
|
||||
|
||||
/**
|
||||
* Contains all nodes used by this test.
|
||||
*
|
||||
* @var Node[]
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'article'));
|
||||
|
||||
// Create two nodes.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$this->nodes[] = $this->drupalCreateNode(
|
||||
array(
|
||||
'type' => 'article',
|
||||
'body' => array(
|
||||
array(
|
||||
'value' => $this->randomMachineName(42),
|
||||
'format' => filter_default_format(),
|
||||
'summary' => $this->randomMachineName(),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the node path plugin.
|
||||
*/
|
||||
public function testPathPlugin() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$view = Views::getView('test_node_path_plugin');
|
||||
$view->initDisplay();
|
||||
$view->setDisplay('page_1');
|
||||
$view->initStyle();
|
||||
$view->rowPlugin->options['view_mode'] = 'full';
|
||||
|
||||
// Test with view_mode full.
|
||||
$output = $view->preview();
|
||||
$output = $renderer->renderRoot($output);
|
||||
foreach ($this->nodes as $node) {
|
||||
$this->assertTrue(strpos($output, 'This is <strong>not escaped</strong> and this is ' . $node->link('the link')) !== FALSE, 'Make sure path field rewriting is not escaped.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
89
core/modules/node/src/Tests/Views/RevisionLinkTest.php
Normal file
89
core/modules/node/src/Tests/Views/RevisionLinkTest.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\node\Tests\Views;
|
||||
|
||||
/**
|
||||
* Tests the different revision link handlers.
|
||||
*
|
||||
* @group node
|
||||
*
|
||||
* @see \Drupal\node\Plugin\views\field\RevisionLink
|
||||
* @see \Drupal\node\Plugin\views\field\RevisionLinkDelete
|
||||
* @see \Drupal\node\Plugin\views\field\RevisionLinkRevert
|
||||
*/
|
||||
class RevisionLinkTest extends NodeTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_node_revision_links'];
|
||||
|
||||
/**
|
||||
* Tests revision links.
|
||||
*/
|
||||
public function testRevisionLinks() {
|
||||
// Create one user which can view/revert and delete and one which can only
|
||||
// do one of them.
|
||||
$this->drupalCreateContentType(['name' => 'page', 'type' => 'page']);
|
||||
$account = $this->drupalCreateUser(['revert all revisions', 'view all revisions', 'delete all revisions', 'edit any page content', 'delete any page content']);
|
||||
$this->drupalLogin($account);
|
||||
// Create two nodes, one without an additional revision and one with a
|
||||
// revision.
|
||||
$nodes = [
|
||||
$this->drupalCreateNode(),
|
||||
$this->drupalCreateNode(),
|
||||
];
|
||||
|
||||
$first_revision = $nodes[1]->getRevisionId();
|
||||
// Create revision of the node.
|
||||
$nodes[1]->setNewRevision();
|
||||
$nodes[1]->save();
|
||||
$second_revision = $nodes[1]->getRevisionId();
|
||||
|
||||
$this->drupalGet('test-node-revision-links');
|
||||
$this->assertResponse(200, 'Test view can be accessed in the path expected');
|
||||
// The first node revision should link to the node directly as you get an
|
||||
// access denied if you link to the revision.
|
||||
$url = $nodes[0]->urlInfo()->toString();
|
||||
$this->assertLinkByHref($url);
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/view');
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/delete');
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $nodes[0]->getRevisionId() . '/revert');
|
||||
|
||||
// For the second node the current revision got set to the last revision, so
|
||||
// the first one should also link to the node page itself.
|
||||
$url = $nodes[1]->urlInfo()->toString();
|
||||
$this->assertLinkByHref($url);
|
||||
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/view');
|
||||
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/delete');
|
||||
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/revert');
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/view');
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/delete');
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $second_revision . '/revert');
|
||||
|
||||
$accounts = [
|
||||
'view' => $this->drupalCreateUser(['view all revisions']),
|
||||
'revert' => $this->drupalCreateUser(['revert all revisions', 'edit any page content']),
|
||||
'delete' => $this->drupalCreateUser(['delete all revisions', 'delete any page content']),
|
||||
];
|
||||
|
||||
$url = $nodes[1]->urlInfo()->toString();
|
||||
// Render the view with users which can only delete/revert revisions.
|
||||
foreach ($accounts as $allowed_operation => $account) {
|
||||
$this->drupalLogin($account);
|
||||
$this->drupalGet('test-node-revision-links');
|
||||
// Check expected links.
|
||||
foreach (['revert', 'delete'] as $operation) {
|
||||
if ($operation == $allowed_operation) {
|
||||
$this->assertLinkByHref($url . '/revisions/' . $first_revision . '/' . $operation);
|
||||
}
|
||||
else {
|
||||
$this->assertNoLinkByHref($url . '/revisions/' . $first_revision . '/' . $operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_node_path_plugin
|
||||
label: test_node_path_plugin
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
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
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
path:
|
||||
id: path
|
||||
table: node
|
||||
field: path
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: true
|
||||
text: 'This is <strong>not escaped</strong> and this is <a href="{{ path }}" hreflang="en">the link</a>.'
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
absolute: false
|
||||
entity_type: node
|
||||
plugin_id: node_path
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
title: test_node_path_plugin
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: test-node-path-plugin
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
|
@ -0,0 +1,224 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: test_node_revision_links
|
||||
label: test_node_revision_links
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_revision
|
||||
base_field: vid
|
||||
core: '8'
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: none
|
||||
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: none
|
||||
options:
|
||||
items_per_page: 0
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
link_to_revision:
|
||||
id: link_to_revision
|
||||
table: node_field_revision
|
||||
field: link_to_revision
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
text: 'Link to revision'
|
||||
entity_type: node
|
||||
plugin_id: node_revision_link
|
||||
delete_revision:
|
||||
id: delete_revision
|
||||
table: node_field_revision
|
||||
field: delete_revision
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
text: 'Link to delete revision'
|
||||
entity_type: node
|
||||
plugin_id: node_revision_link_delete
|
||||
revert_revision:
|
||||
id: revert_revision
|
||||
table: node_field_revision
|
||||
field: revert_revision
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
text: 'Link to delete revision'
|
||||
entity_type: node
|
||||
plugin_id: node_revision_link_revert
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: test_node_revision_links
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: test-node-revision-links
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\node\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Test D6NodeDeriver.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateNodeDeriverTest extends MigrateDrupal6TestBase {
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->pluginManager = $this->container->get('plugin.manager.migration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test node translation migrations with translation disabled.
|
||||
*/
|
||||
public function testNoTranslations() {
|
||||
// Without content_translation, there should be no translation migrations.
|
||||
$migrations = $this->pluginManager->createInstances('d6_node_translation');
|
||||
$this->assertSame([], $migrations,
|
||||
"No node translation migrations without content_translation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test node translation migrations with translation enabled.
|
||||
*/
|
||||
public function testTranslations() {
|
||||
// With content_translation, there should be translation migrations for
|
||||
// each content type.
|
||||
$this->enableModules(['language', 'content_translation']);
|
||||
$migrations = $this->pluginManager->createInstances('d6_node_translation');
|
||||
$this->assertArrayHasKey('d6_node_translation:story', $migrations,
|
||||
"Node translation migrations exist after content_translation installed");
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,11 @@ class MigrateNodeTest extends MigrateNodeTestBase {
|
|||
|
||||
use FileMigrationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['language', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -23,7 +28,7 @@ class MigrateNodeTest extends MigrateNodeTestBase {
|
|||
parent::setUp();
|
||||
$this->setUpMigratedFiles();
|
||||
$this->installSchema('file', ['file_usage']);
|
||||
$this->executeMigrations(['d6_node']);
|
||||
$this->executeMigrations(['language', 'd6_node', 'd6_node_translation']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,6 +90,15 @@ class MigrateNodeTest extends MigrateNodeTestBase {
|
|||
$this->assertSame('Buy it now', $node->field_test_link->title);
|
||||
$this->assertSame(['attributes' => ['target' => '_blank']], $node->field_test_link->options);
|
||||
|
||||
// Test that translations are working.
|
||||
$node = Node::load(10);
|
||||
$this->assertIdentical('en', $node->langcode->value);
|
||||
$this->assertIdentical('The Real McCoy', $node->title->value);
|
||||
$this->assertTrue($node->hasTranslation('fr'), "Node 10 has french translation");
|
||||
|
||||
// Node 11 is a translation of node 10, and should not be imported separately.
|
||||
$this->assertNull(Node::load(11), "Node 11 doesn't exist in D8, it was a translation");
|
||||
|
||||
// Rerun migration with two source database changes.
|
||||
// 1. Add an invalid link attributes and a different URL and
|
||||
// title. If only the attributes are changed the error does not occur.
|
||||
|
|
|
@ -44,6 +44,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
|
|||
'd7_user',
|
||||
'd7_node_type',
|
||||
'd7_comment_type',
|
||||
'd7_taxonomy_vocabulary',
|
||||
'd7_field',
|
||||
'd7_field_instance',
|
||||
'd7_node:test_content_type',
|
||||
|
@ -139,9 +140,13 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
|
|||
$this->assertIdentical('title text', $node->field_images->title);
|
||||
$this->assertIdentical('93', $node->field_images->width);
|
||||
$this->assertIdentical('93', $node->field_images->height);
|
||||
$this->assertIdentical('http://google.com', $node->field_link->uri);
|
||||
$this->assertIdentical('Click Here', $node->field_link->title);
|
||||
|
||||
$node = Node::load(2);
|
||||
$this->assertIdentical("...is that it's the absolute best show ever. Trust me, I would know.", $node->body->value);
|
||||
$this->assertIdentical('internal:/', $node->field_link->uri);
|
||||
$this->assertIdentical('Home', $node->field_link->title);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class NodeByNodeTypeTest extends MigrateSqlSourceTestCase {
|
|||
'promote' => 1,
|
||||
'moderate' => 0,
|
||||
'sticky' => 0,
|
||||
'tnid' => 0,
|
||||
'tnid' => 1,
|
||||
'translate' => 0,
|
||||
// Node revision fields.
|
||||
'body' => 'body for node 1',
|
||||
|
@ -64,7 +64,7 @@ class NodeByNodeTypeTest extends MigrateSqlSourceTestCase {
|
|||
'promote' => 1,
|
||||
'moderate' => 0,
|
||||
'sticky' => 0,
|
||||
'tnid' => 0,
|
||||
'tnid' => 2,
|
||||
'translate' => 0,
|
||||
// Node revision fields.
|
||||
'body' => 'body for node 2',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue