composer update
This commit is contained in:
parent
f6abc3dce2
commit
71dfaca858
1753 changed files with 45274 additions and 14619 deletions
|
@ -70,9 +70,9 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']])->fetchField();
|
||||
$this->assertTrue(!empty($fid), 'The feed found in database.');
|
||||
return Feed::load($fid);
|
||||
$fids = \Drupal::entityQuery('aggregator_feed')->condition('title', $edit['title[0][value]'])->condition('url', $edit['url[0][value]'])->execute();
|
||||
$this->assertNotEmpty($fids, 'The feed found in database.');
|
||||
return Feed::load(array_values($fids)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,10 +179,10 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
$this->clickLink('Update items');
|
||||
|
||||
// Ensure we have the right number of items.
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()]);
|
||||
$iids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
|
||||
$feed->items = [];
|
||||
foreach ($result as $item) {
|
||||
$feed->items[] = $item->iid;
|
||||
foreach ($iids as $iid) {
|
||||
$feed->items[] = $iid;
|
||||
}
|
||||
|
||||
if ($expected_count !== NULL) {
|
||||
|
@ -211,11 +211,12 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* Expected number of feed items.
|
||||
*/
|
||||
public function updateAndDelete(FeedInterface $feed, $expected_count) {
|
||||
$count_query = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->count();
|
||||
$this->updateFeedItems($feed, $expected_count);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$count = $count_query->execute();
|
||||
$this->assertTrue($count);
|
||||
$this->deleteFeedItems($feed);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$count = $count_query->execute();
|
||||
$this->assertTrue($count == 0);
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,7 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* TRUE if feed is unique.
|
||||
*/
|
||||
public function uniqueFeed($feed_name, $feed_url) {
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed_name, ':url' => $feed_url])->fetchField();
|
||||
$result = \Drupal::entityQuery('aggregator_feed')->condition('title', $feed_name)->condition('url', $feed_url)->count()->execute();
|
||||
return (1 == $result);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,31 +20,23 @@ class AggregatorCronTest extends AggregatorTestBase {
|
|||
// Create feed and test basic updating on cron.
|
||||
$this->createSampleNodes();
|
||||
$feed = $this->createFeed();
|
||||
$count_query = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->count();
|
||||
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->assertEqual(5, $count_query->execute());
|
||||
$this->deleteFeedItems($feed);
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->assertEqual(0, $count_query->execute());
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->assertEqual(5, $count_query->execute());
|
||||
|
||||
// Test feed locking when queued for update.
|
||||
$this->deleteFeedItems($feed);
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields([
|
||||
'queued' => REQUEST_TIME,
|
||||
])
|
||||
->execute();
|
||||
$feed->setQueuedTime(REQUEST_TIME)->save();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields([
|
||||
'queued' => 0,
|
||||
])
|
||||
->execute();
|
||||
$this->assertEqual(0, $count_query->execute());
|
||||
$feed->setQueuedTime(0)->save();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->assertEqual(5, $count_query->execute());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,9 +67,9 @@ abstract class AggregatorTestBase extends BrowserTestBase {
|
|||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']])->fetchField();
|
||||
$this->assertTrue(!empty($fid), 'The feed found in database.');
|
||||
return Feed::load($fid);
|
||||
$fids = \Drupal::entityQuery('aggregator_feed')->condition('title', $edit['title[0][value]'])->condition('url', $edit['url[0][value]'])->execute();
|
||||
$this->assertNotEmpty($fids, 'The feed found in database.');
|
||||
return Feed::load(array_values($fids)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,10 +176,10 @@ abstract class AggregatorTestBase extends BrowserTestBase {
|
|||
$this->clickLink('Update items');
|
||||
|
||||
// Ensure we have the right number of items.
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()]);
|
||||
$iids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
|
||||
$feed->items = [];
|
||||
foreach ($result as $item) {
|
||||
$feed->items[] = $item->iid;
|
||||
foreach ($iids as $iid) {
|
||||
$feed->items[] = $iid;
|
||||
}
|
||||
|
||||
if ($expected_count !== NULL) {
|
||||
|
@ -208,11 +208,12 @@ abstract class AggregatorTestBase extends BrowserTestBase {
|
|||
* Expected number of feed items.
|
||||
*/
|
||||
public function updateAndDelete(FeedInterface $feed, $expected_count) {
|
||||
$count_query = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->count();
|
||||
$this->updateFeedItems($feed, $expected_count);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$count = $count_query->execute();
|
||||
$this->assertTrue($count);
|
||||
$this->deleteFeedItems($feed);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$count = $count_query->execute();
|
||||
$this->assertTrue($count == 0);
|
||||
}
|
||||
|
||||
|
@ -228,7 +229,7 @@ abstract class AggregatorTestBase extends BrowserTestBase {
|
|||
* TRUE if feed is unique.
|
||||
*/
|
||||
public function uniqueFeed($feed_name, $feed_url) {
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed_name, ':url' => $feed_url])->fetchField();
|
||||
$result = \Drupal::entityQuery('aggregator_feed')->condition('title', $feed_name)->condition('url', $feed_url)->count()->execute();
|
||||
return (1 == $result);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,8 +43,8 @@ class DeleteFeedTest extends AggregatorTestBase {
|
|||
$this->assertResponse(404, 'Deleted feed source does not exist.');
|
||||
|
||||
// Check database for feed.
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed1->label(), ':url' => $feed1->getUrl()])->fetchField();
|
||||
$this->assertFalse($result, 'Feed not found in database');
|
||||
$result = \Drupal::entityQuery('aggregator_feed')->condition('title', $feed1->label())->condition('url', $feed1->getUrl())->count()->execute();
|
||||
$this->assertEquals(0, $result, 'Feed not found in database');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\Tests\aggregator\Functional;
|
|||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
|
||||
/**
|
||||
* Tests the built-in feed parser with valid feed samples.
|
||||
|
@ -57,16 +58,17 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
$this->assertText('Atom-Powered Robots Run Amok');
|
||||
$this->assertLinkByHref('http://example.org/2003/12/13/atom03');
|
||||
$this->assertText('Some text.');
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', [':link' => 'http://example.org/2003/12/13/atom03'])->fetchField(), 'Atom entry id element is parsed correctly.');
|
||||
$iids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/13/atom03')->execute();
|
||||
$item = Item::load(array_values($iids)[0]);
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', $item->getGuid(), 'Atom entry id element is parsed correctly.');
|
||||
|
||||
// Check for second feed entry.
|
||||
$this->assertText('We tried to stop them, but we failed.');
|
||||
$this->assertLinkByHref('http://example.org/2003/12/14/atom03');
|
||||
$this->assertText('Some other text.');
|
||||
$db_guid = db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', [
|
||||
':link' => 'http://example.org/2003/12/14/atom03',
|
||||
])->fetchField();
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $db_guid, 'Atom entry id element is parsed correctly.');
|
||||
$iids = \Drupal::entityQuery('aggregator_item')->condition('link', 'http://example.org/2003/12/14/atom03')->execute();
|
||||
$item = Item::load(array_values($iids)[0]);
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $item->getGuid(), 'Atom entry id element is parsed correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
|
||||
/**
|
||||
* Tests OPML import.
|
||||
*
|
||||
|
@ -44,7 +46,8 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
* Submits form filled with invalid fields.
|
||||
*/
|
||||
public function validateImportFormFields() {
|
||||
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$count_query = \Drupal::entityQuery('aggregator_feed')->count();
|
||||
$before = $count_query->execute();
|
||||
|
||||
$edit = [];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
|
@ -62,7 +65,7 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$after = $count_query->execute();
|
||||
$this->assertEqual($before, $after, 'No feeds were added during the three last form submissions.');
|
||||
}
|
||||
|
||||
|
@ -70,7 +73,8 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
* Submits form with invalid, empty, and valid OPML files.
|
||||
*/
|
||||
protected function submitImportForm() {
|
||||
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$count_query = \Drupal::entityQuery('aggregator_feed')->count();
|
||||
$before = $count_query->execute();
|
||||
|
||||
$form['files[upload]'] = $this->getInvalidOpml();
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $form, t('Import'));
|
||||
|
@ -80,10 +84,12 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$after = $count_query->execute();
|
||||
$this->assertEqual($before, $after, 'No feeds were added during the two last form submissions.');
|
||||
|
||||
db_delete('aggregator_feed')->execute();
|
||||
foreach (Feed::loadMultiple() as $feed) {
|
||||
$feed->delete();
|
||||
}
|
||||
|
||||
$feeds[0] = $this->getFeedEditArray();
|
||||
$feeds[1] = $this->getFeedEditArray();
|
||||
|
@ -96,15 +102,15 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
$this->assertRaw(t('A feed with the URL %url already exists.', ['%url' => $feeds[0]['url[0][value]']]), 'Verifying that a duplicate URL was identified');
|
||||
$this->assertRaw(t('A feed named %title already exists.', ['%title' => $feeds[1]['title[0][value]']]), 'Verifying that a duplicate title was identified');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$after = $count_query->execute();
|
||||
$this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
|
||||
|
||||
$feeds_from_db = db_query("SELECT title, url, refresh FROM {aggregator_feed}");
|
||||
$feed_entities = Feed::loadMultiple();
|
||||
$refresh = TRUE;
|
||||
foreach ($feeds_from_db as $feed) {
|
||||
$title[$feed->url] = $feed->title;
|
||||
$url[$feed->title] = $feed->url;
|
||||
$refresh = $refresh && $feed->refresh == 900;
|
||||
foreach ($feed_entities as $feed_entity) {
|
||||
$title[$feed_entity->getUrl()] = $feed_entity->label();
|
||||
$url[$feed_entity->label()] = $feed_entity->getUrl();
|
||||
$refresh = $refresh && $feed_entity->getRefreshRate() == 900;
|
||||
}
|
||||
|
||||
$this->assertEqual($title[$feeds[0]['url[0][value]']], $feeds[0]['title[0][value]'], 'First feed was added correctly.');
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
|
||||
/**
|
||||
* Update feed items from a feed.
|
||||
|
@ -43,26 +44,24 @@ class UpdateFeedItemTest extends AggregatorTestBase {
|
|||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", [':url' => $edit['url[0][value]']])->fetchField();
|
||||
$feed = Feed::load($fid);
|
||||
$fids = \Drupal::entityQuery('aggregator_feed')->condition('url', $edit['url[0][value]'])->execute();
|
||||
$feed = Feed::load(array_values($fids)[0]);
|
||||
|
||||
$feed->refreshItems();
|
||||
$before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$iids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
|
||||
$before = Item::load(array_values($iids)[0])->getPostedTime();
|
||||
|
||||
// Sleep for 3 second.
|
||||
sleep(3);
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields([
|
||||
'checked' => 0,
|
||||
'hash' => '',
|
||||
'etag' => '',
|
||||
'modified' => 0,
|
||||
])
|
||||
->execute();
|
||||
$feed
|
||||
->setLastCheckedTime(0)
|
||||
->setHash('')
|
||||
->setEtag('')
|
||||
->setLastModified(0)
|
||||
->save();
|
||||
$feed->refreshItems();
|
||||
|
||||
$after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$after = Item::load(array_values($iids)[0])->getPostedTime();
|
||||
$this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (@before === @after)', ['@before' => $before, '@after' => $after]));
|
||||
|
||||
// Make sure updating items works even after uninstalling a module
|
||||
|
|
|
@ -93,7 +93,8 @@
|
|||
*/
|
||||
Drupal.behaviors.blockHighlightPlacement = {
|
||||
attach(context, settings) {
|
||||
if (settings.blockPlacement) {
|
||||
// Ensure that the block we are attempting to scroll to actually exists.
|
||||
if (settings.blockPlacement && $('.js-block-placed').length) {
|
||||
$(context)
|
||||
.find('[data-drupal-selector="edit-blocks"]')
|
||||
.once('block-highlight')
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
Drupal.behaviors.blockHighlightPlacement = {
|
||||
attach: function attach(context, settings) {
|
||||
if (settings.blockPlacement) {
|
||||
if (settings.blockPlacement && $('.js-block-placed').length) {
|
||||
$(context).find('[data-drupal-selector="edit-blocks"]').once('block-highlight').each(function () {
|
||||
var $container = $(this);
|
||||
|
||||
|
|
|
@ -193,6 +193,9 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
|
|||
if ($this->request->query->has('block-placement')) {
|
||||
$placement = $this->request->query->get('block-placement');
|
||||
$form['#attached']['drupalSettings']['blockPlacement'] = $placement;
|
||||
// Remove the block placement from the current request so that it is not
|
||||
// passed on to any redirect destinations.
|
||||
$this->request->query->remove('block-placement');
|
||||
}
|
||||
|
||||
// Loop over each region and build blocks.
|
||||
|
@ -378,9 +381,6 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
|
|||
$entity->save();
|
||||
}
|
||||
$this->messenger->addStatus($this->t('The block settings have been updated.'));
|
||||
|
||||
// Remove any previously set block placement.
|
||||
$this->request->query->remove('block-placement');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\block\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\block\Plugin\migrate\source\Block;
|
||||
|
||||
/**
|
||||
* Gets i18n block data from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_block_translation",
|
||||
* source_module = "i18n_block"
|
||||
* )
|
||||
*/
|
||||
class BlockTranslation extends Block {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Let the parent set the block table to use, but do not use the parent
|
||||
// query. Instead build a query so can use an inner join to the selected
|
||||
// block table.
|
||||
parent::query();
|
||||
$query = $this->select('i18n_string', 'i18n')
|
||||
->fields('i18n')
|
||||
->fields('b', [
|
||||
'bid',
|
||||
'module',
|
||||
'delta',
|
||||
'theme',
|
||||
'status',
|
||||
'weight',
|
||||
'region',
|
||||
'custom',
|
||||
'visibility',
|
||||
'pages',
|
||||
'title',
|
||||
'cache',
|
||||
'i18n_mode',
|
||||
])
|
||||
->fields('lt', [
|
||||
'lid',
|
||||
'translation',
|
||||
'language',
|
||||
'plid',
|
||||
'plural',
|
||||
'i18n_status',
|
||||
])
|
||||
->condition('i18n_mode', 1);
|
||||
$query->leftjoin($this->blockTable, 'b', ('b.delta = i18n.objectid'));
|
||||
$query->leftjoin('locales_target', 'lt', 'lt.lid = i18n.lid');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'bid' => $this->t('The block numeric identifier.'),
|
||||
'module' => $this->t('The module providing the block.'),
|
||||
'delta' => $this->t("The block's delta."),
|
||||
'theme' => $this->t('Which theme the block is placed in.'),
|
||||
'status' => $this->t('Block enabled status'),
|
||||
'weight' => $this->t('Block weight within region'),
|
||||
'region' => $this->t('Theme region within which the block is set'),
|
||||
'visibility' => $this->t('Visibility'),
|
||||
'pages' => $this->t('Pages list.'),
|
||||
'title' => $this->t('Block title.'),
|
||||
'cache' => $this->t('Cache rule.'),
|
||||
'i18n_mode' => $this->t('Multilingual mode'),
|
||||
'lid' => $this->t('Language string ID'),
|
||||
'textgroup' => $this->t('A module defined group of translations'),
|
||||
'context' => $this->t('Full string ID for quick search: type:objectid:property.'),
|
||||
'objectid' => $this->t('Object ID'),
|
||||
'type' => $this->t('Object type for this string'),
|
||||
'property' => $this->t('Object property for this string'),
|
||||
'objectindex' => $this->t('Integer value of Object ID'),
|
||||
'format' => $this->t('The {filter_format}.format of the string'),
|
||||
'translation' => $this->t('Translation'),
|
||||
'language' => $this->t('Language code'),
|
||||
'plid' => $this->t('Parent lid'),
|
||||
'plural' => $this->t('Plural index number'),
|
||||
'i18n_status' => $this->t('Translation needs update'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['delta']['type'] = 'string';
|
||||
$ids['delta']['alias'] = 'b';
|
||||
$ids['language']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\block_test\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
|
||||
/**
|
||||
* Provides a context-aware block that uses a not-passed, non-required context.
|
||||
*
|
||||
* @Block(
|
||||
* id = "test_context_aware_no_valid_context_options",
|
||||
* admin_label = @Translation("Test context-aware block - no valid context options"),
|
||||
* context_definitions = {
|
||||
* "email" = @ContextDefinition("email", required = FALSE)
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class TestContextAwareNoValidContextOptionsBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'#markup' => 'Rendered block with no valid context options',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
namespace Drupal\Tests\block\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
|
@ -239,6 +241,13 @@ class BlockUiTest extends BrowserTestBase {
|
|||
$this->assertText('User context found.');
|
||||
$this->assertRaw($expected_text);
|
||||
|
||||
// Test context mapping form element is not visible if there are no valid
|
||||
// context options for the block (the test_context_aware_no_valid_context_options
|
||||
// block has one context defined which is not available for it on the
|
||||
// Block Layout interface).
|
||||
$this->drupalGet('admin/structure/block/add/test_context_aware_no_valid_context_options/classy');
|
||||
$this->assertSession()->fieldNotExists('edit-settings-context-mapping-email');
|
||||
|
||||
// Test context mapping allows empty selection for optional contexts.
|
||||
$this->drupalGet('admin/structure/block/manage/testcontextawareblock');
|
||||
$edit = [
|
||||
|
@ -281,6 +290,24 @@ class BlockUiTest extends BrowserTestBase {
|
|||
* Tests the block placement indicator.
|
||||
*/
|
||||
public function testBlockPlacementIndicator() {
|
||||
// Test the block placement indicator with using the domain as URL language
|
||||
// indicator. This causes destination query parameters to be absolute URLs.
|
||||
\Drupal::service('module_installer')->install(['language', 'locale']);
|
||||
$this->container = \Drupal::getContainer();
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
$config = $this->config('language.types');
|
||||
$config->set('negotiation.language_interface.enabled', [
|
||||
LanguageNegotiationUrl::METHOD_ID => -10,
|
||||
]);
|
||||
$config->save();
|
||||
$config = $this->config('language.negotiation');
|
||||
$config->set('url.source', LanguageNegotiationUrl::CONFIG_DOMAIN);
|
||||
$config->set('url.domains', [
|
||||
'en' => \Drupal::request()->getHost(),
|
||||
'it' => 'it.example.com',
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// Select the 'Powered by Drupal' block to be placed.
|
||||
$block = [];
|
||||
$block['id'] = strtolower($this->randomMachineName());
|
||||
|
@ -289,11 +316,30 @@ class BlockUiTest extends BrowserTestBase {
|
|||
|
||||
// After adding a block, it will indicate which block was just added.
|
||||
$this->drupalPostForm('admin/structure/block/add/system_powered_by_block', $block, t('Save block'));
|
||||
$this->assertUrl('admin/structure/block/list/classy?block-placement=' . Html::getClass($block['id']));
|
||||
$this->assertSession()->addressEquals('admin/structure/block/list/classy?block-placement=' . Html::getClass($block['id']));
|
||||
|
||||
// Resaving the block page will remove the block indicator.
|
||||
// Resaving the block page will remove the block placement indicator.
|
||||
$this->drupalPostForm(NULL, [], t('Save blocks'));
|
||||
$this->assertUrl('admin/structure/block/list/classy');
|
||||
$this->assertSession()->addressEquals('admin/structure/block/list/classy');
|
||||
|
||||
// Place another block and test the remove functionality works with the
|
||||
// block placement indicator. Click the first 'Place block' link to bring up
|
||||
// the list of blocks to place in the first available region.
|
||||
$this->clickLink('Place block');
|
||||
// Select the first available block.
|
||||
$this->clickLink('Place block');
|
||||
$block = [];
|
||||
$block['id'] = strtolower($this->randomMachineName());
|
||||
$block['theme'] = 'classy';
|
||||
$this->submitForm([], 'Save block');
|
||||
$this->assertSession()->addressEquals('admin/structure/block/list/classy?block-placement=' . Html::getClass($block['id']));
|
||||
|
||||
// Removing a block will remove the block placement indicator.
|
||||
$this->clickLink('Remove');
|
||||
$this->submitForm([], 'Remove');
|
||||
// @todo https://www.drupal.org/project/drupal/issues/2980527 this should be
|
||||
// 'admin/structure/block/list/classy' but there is a bug.
|
||||
$this->assertSession()->addressEquals('admin/structure/block');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\block\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of i18n block translations.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateBlockContentTranslationTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'text',
|
||||
'aggregator',
|
||||
'book',
|
||||
'block',
|
||||
'comment',
|
||||
'forum',
|
||||
'views',
|
||||
'block_content',
|
||||
'config_translation',
|
||||
'content_translation',
|
||||
'language',
|
||||
'statistics',
|
||||
'taxonomy',
|
||||
// Required for translation migrations.
|
||||
'migrate_drupal_multilingual',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['block']);
|
||||
$this->installConfig(['block_content']);
|
||||
$this->installEntitySchema('block_content');
|
||||
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd7_filter_format',
|
||||
'block_content_type',
|
||||
'block_content_body_field',
|
||||
'd7_custom_block',
|
||||
'd7_user_role',
|
||||
'd7_block',
|
||||
'd7_block_translation',
|
||||
]);
|
||||
block_rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the migration of block title translation.
|
||||
*/
|
||||
public function testBlockContentTranslation() {
|
||||
/** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
$config = $language_manager->getLanguageConfigOverride('fr', 'block.block.bartik_user_login');
|
||||
$this->assertSame('fr - User login title', $config->get('settings.label'));
|
||||
}
|
||||
|
||||
}
|
|
@ -112,10 +112,10 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
|
|||
public function testBlockMigration() {
|
||||
$this->assertEntity('bartik_system_main', 'system_main_block', [], '', 'content', 'bartik', 0, '', '0');
|
||||
$this->assertEntity('bartik_search_form', 'search_form_block', [], '', 'sidebar_first', 'bartik', -1, '', '0');
|
||||
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0, '', '0');
|
||||
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0, 'User login title', 'visible');
|
||||
$this->assertEntity('bartik_system_powered_by', 'system_powered_by_block', [], '', 'footer_fifth', 'bartik', 10, '', '0');
|
||||
$this->assertEntity('seven_system_main', 'system_main_block', [], '', 'content', 'seven', 0, '', '0');
|
||||
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10, '', '0');
|
||||
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10, 'User login title', 'visible');
|
||||
|
||||
// The d7_custom_block migration should have migrated a block containing a
|
||||
// mildly amusing limerick. We'll need its UUID to determine
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\block\Kernel\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
|
||||
|
||||
/**
|
||||
* Tests i18n block source plugin.
|
||||
*
|
||||
* @covers \Drupal\block\Plugin\migrate\source\d7\BlockTranslation
|
||||
*
|
||||
* @group content_translation
|
||||
*/
|
||||
class BlockTranslationTest extends MigrateSqlSourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'migrate_drupal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function providerSource() {
|
||||
|
||||
// The source data.
|
||||
$tests[0]['source_data']['block'] = [
|
||||
[
|
||||
'bid' => 1,
|
||||
'module' => 'system',
|
||||
'delta' => 'main',
|
||||
'theme' => 'bartik',
|
||||
'status' => 1,
|
||||
'weight' => 0,
|
||||
'region' => 'content',
|
||||
'custom' => '0',
|
||||
'visibility' => 0,
|
||||
'pages' => '',
|
||||
'title' => '',
|
||||
'cache' => -1,
|
||||
'i18n_mode' => 0,
|
||||
],
|
||||
[
|
||||
'bid' => 2,
|
||||
'module' => 'system',
|
||||
'delta' => 'navigation',
|
||||
'theme' => 'bartik',
|
||||
'status' => 1,
|
||||
'weight' => 0,
|
||||
'region' => 'sidebar_first',
|
||||
'custom' => '0',
|
||||
'visibility' => 0,
|
||||
'pages' => '',
|
||||
'title' => 'Navigation',
|
||||
'cache' => -1,
|
||||
'i18n_mode' => 1,
|
||||
],
|
||||
];
|
||||
$tests[0]['source_data']['block_role'] = [
|
||||
[
|
||||
'module' => 'block',
|
||||
'delta' => 1,
|
||||
'rid' => 2,
|
||||
],
|
||||
[
|
||||
'module' => 'block',
|
||||
'delta' => 2,
|
||||
'rid' => 2,
|
||||
],
|
||||
[
|
||||
'module' => 'block',
|
||||
'delta' => 2,
|
||||
'rid' => 100,
|
||||
],
|
||||
];
|
||||
$tests[0]['source_data']['i18n_string'] = [
|
||||
[
|
||||
'lid' => 1,
|
||||
'textgroup' => 'block',
|
||||
'context' => '1',
|
||||
'objectid' => 'navigation',
|
||||
'type' => 'system',
|
||||
'property' => 'title',
|
||||
'objectindex' => 0,
|
||||
'format' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$tests[0]['source_data']['locales_target'] = [
|
||||
[
|
||||
'lid' => 1,
|
||||
'translation' => 'fr - Navigation',
|
||||
'language' => 'fr',
|
||||
'plid' => 0,
|
||||
'plural' => 0,
|
||||
'i18n_status' => 0,
|
||||
],
|
||||
];
|
||||
$tests[0]['source_data']['role'] = [
|
||||
[
|
||||
'rid' => 2,
|
||||
'name' => 'authenticated user',
|
||||
],
|
||||
];
|
||||
$tests[0]['source_data']['system'] = [
|
||||
[
|
||||
'filename' => 'modules/system/system.module',
|
||||
'name' => 'system',
|
||||
'type' => 'module',
|
||||
'owner' => '',
|
||||
'status' => '1',
|
||||
'throttle' => '0',
|
||||
'bootstrap' => '0',
|
||||
'schema_version' => '7055',
|
||||
'weight' => '0',
|
||||
'info' => 'a:0:{}',
|
||||
],
|
||||
];
|
||||
// The expected results.
|
||||
$tests[0]['expected_data'] = [
|
||||
[
|
||||
'bid' => 2,
|
||||
'module' => 'system',
|
||||
'delta' => 'navigation',
|
||||
'theme' => 'bartik',
|
||||
'status' => 1,
|
||||
'weight' => 0,
|
||||
'region' => 'sidebar_first',
|
||||
'custom' => '0',
|
||||
'visibility' => 0,
|
||||
'pages' => '',
|
||||
'title' => 'Navigation',
|
||||
'cache' => -1,
|
||||
'i18n_mode' => 1,
|
||||
'lid' => 1,
|
||||
'translation' => 'fr - Navigation',
|
||||
'language' => 'fr',
|
||||
'plid' => 0,
|
||||
'plural' => 0,
|
||||
'i18n_status' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
|
@ -293,8 +293,8 @@ class BlockContentAccessHandlerTest extends KernelTestBase {
|
|||
'forbidden',
|
||||
],
|
||||
];
|
||||
return $cases;
|
||||
}
|
||||
return $cases;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,6 +46,17 @@ class MigrateCommentTypeTest extends MigrateDrupal7TestBase {
|
|||
* Tests the migrated comment types.
|
||||
*/
|
||||
public function testMigration() {
|
||||
$comment_fields = [
|
||||
'comment' => 'Default comment setting',
|
||||
'comment_default_mode' => 'Default display mode',
|
||||
'comment_default_per_page' => 'Default comments per page',
|
||||
'comment_anonymous' => 'Anonymous commenting',
|
||||
'comment_subject_field' => 'Comment subject field',
|
||||
'comment_preview' => 'Preview comment',
|
||||
'comment_form_location' => 'Location of comment submission form',
|
||||
];
|
||||
$this->assertArraySubset($comment_fields, $this->migration->getSourcePlugin()->fields());
|
||||
|
||||
$this->assertEntity('comment_node_article', 'Article comment');
|
||||
$this->assertEntity('comment_node_blog', 'Blog entry comment');
|
||||
$this->assertEntity('comment_node_book', 'Book page comment');
|
||||
|
|
|
@ -184,11 +184,16 @@ class EntityOperations implements ContainerInjectionInterface {
|
|||
// Sync translations.
|
||||
if ($entity->getEntityType()->hasKey('langcode')) {
|
||||
$entity_langcode = $entity->language()->getId();
|
||||
if (!$content_moderation_state->hasTranslation($entity_langcode)) {
|
||||
$content_moderation_state->addTranslation($entity_langcode);
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
$content_moderation_state->langcode = $entity_langcode;
|
||||
}
|
||||
if ($content_moderation_state->language()->getId() !== $entity_langcode) {
|
||||
$content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
|
||||
else {
|
||||
if (!$content_moderation_state->hasTranslation($entity_langcode)) {
|
||||
$content_moderation_state->addTranslation($entity_langcode);
|
||||
}
|
||||
if ($content_moderation_state->language()->getId() !== $entity_langcode) {
|
||||
$content_moderation_state = $content_moderation_state->getTranslation($entity_langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ class ModerationStateFieldItemList extends FieldItemList {
|
|||
if ($entity->getEntityType()->hasKey('langcode')) {
|
||||
$langcode = $entity->language()->getId();
|
||||
if (!$content_moderation_state->hasTranslation($langcode)) {
|
||||
$content_moderation_state->addTranslation($langcode);
|
||||
$content_moderation_state->addTranslation($langcode, $content_moderation_state->toArray());
|
||||
}
|
||||
if ($content_moderation_state->language()->getId() !== $langcode) {
|
||||
$content_moderation_state = $content_moderation_state->getTranslation($langcode);
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\content_moderation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
|
||||
|
||||
/**
|
||||
* Test content_moderation functionality with content_translation.
|
||||
*
|
||||
* @group content_moderation
|
||||
*/
|
||||
class ModerationContentTranslationTest extends BrowserTestBase {
|
||||
|
||||
use ContentModerationTestTrait;
|
||||
|
||||
/**
|
||||
* A user with permission to bypass access content.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'locale',
|
||||
'content_translation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->rootUser);
|
||||
// Create an Article content type.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article'])->save();
|
||||
$edit = [
|
||||
'predefined_langcode' => 'fr',
|
||||
];
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, 'Add language');
|
||||
// Enable content translation on articles.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$edit = [
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][article][translatable]' => TRUE,
|
||||
'settings[node][article][settings][language][language_alterable]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save configuration');
|
||||
// Adding languages requires a container rebuild in the test running
|
||||
// environment so that multilingual services are used.
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests existing translations being edited after enabling content moderation.
|
||||
*/
|
||||
public function testModerationWithExistingContent() {
|
||||
// Create a published article in English.
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published English node',
|
||||
'langcode[0][value]' => 'en',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('Article Published English node has been created.');
|
||||
$english_node = $this->drupalGetNodeByTitle('Published English node');
|
||||
|
||||
// Add a French translation.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink('Add');
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published French node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save (this translation)');
|
||||
$this->assertSession()->pageTextContains('Article Published French node has been updated.');
|
||||
|
||||
// Install content moderation and enable moderation on Article node type.
|
||||
\Drupal::service('module_installer')->install(['content_moderation']);
|
||||
$workflow = $this->createEditorialWorkflow();
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
|
||||
$workflow->save();
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
// Edit the English node.
|
||||
$this->drupalGet('node/' . $english_node->id() . '/edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published English new node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Article Published English new node has been updated.');
|
||||
// Edit the French translation.
|
||||
$this->drupalGet('fr/node/' . $english_node->id() . '/edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [
|
||||
'title[0][value]' => 'Published French new node',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save (this translation)');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Article Published French new node has been updated.');
|
||||
}
|
||||
|
||||
}
|
|
@ -296,7 +296,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
// Create a French translation.
|
||||
$french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
|
||||
$french_node->setUnpublished();
|
||||
// Revision 1 (fr).
|
||||
// Revision 2 (fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->value);
|
||||
|
@ -305,7 +305,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
// Move English node to create another draft.
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$english_node->moderation_state->value = 'draft';
|
||||
// Revision 2 (en, fr).
|
||||
// Revision 3 (en, fr).
|
||||
$english_node->save();
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
|
@ -316,7 +316,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
|
||||
// Publish the French node.
|
||||
$french_node->moderation_state->value = 'published';
|
||||
// Revision 3 (en, fr).
|
||||
// Revision 4 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadEntity($french_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
|
@ -327,7 +327,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
|
||||
// Publish the English node.
|
||||
$english_node->moderation_state->value = 'published';
|
||||
// Revision 4 (en, fr).
|
||||
// Revision 5 (en, fr).
|
||||
$english_node->save();
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
|
@ -336,15 +336,15 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
$french_node->moderation_state->value = 'draft';
|
||||
// Revision 5 (en, fr).
|
||||
// Revision 6 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadEntity($english_node, 5)->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($english_node, 6)->getTranslation('fr');
|
||||
$this->assertFalse($french_node->isPublished());
|
||||
$this->assertTrue($french_node->getTranslation('en')->isPublished());
|
||||
|
||||
// Republish the French node.
|
||||
$french_node->moderation_state->value = 'published';
|
||||
// Revision 6 (en, fr).
|
||||
// Revision 7 (en, fr).
|
||||
$french_node->save();
|
||||
$french_node = $this->reloadEntity($english_node)->getTranslation('fr');
|
||||
$this->assertTrue($french_node->isPublished());
|
||||
|
@ -353,7 +353,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$content_moderation_state = ContentModerationState::load(1);
|
||||
$content_moderation_state->set('moderation_state', 'draft');
|
||||
$content_moderation_state->setNewRevision(TRUE);
|
||||
// Revision 7 (en, fr).
|
||||
// Revision 8 (en, fr).
|
||||
$content_moderation_state->save();
|
||||
$english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
|
||||
|
||||
|
@ -366,12 +366,12 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
$content_moderation_state = $content_moderation_state->getTranslation('fr');
|
||||
$content_moderation_state->set('moderation_state', 'draft');
|
||||
$content_moderation_state->setNewRevision(TRUE);
|
||||
// Revision 8 (en, fr).
|
||||
// Revision 9 (en, fr).
|
||||
$content_moderation_state->save();
|
||||
|
||||
$english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
|
||||
$this->assertEquals('draft', $english_node->moderation_state->value);
|
||||
$french_node = $this->reloadEntity($english_node, '8')->getTranslation('fr');
|
||||
$french_node = $this->reloadEntity($english_node, '9')->getTranslation('fr');
|
||||
$this->assertEquals('draft', $french_node->moderation_state->value);
|
||||
// Switching the moderation state to an unpublished state should update the
|
||||
// entity.
|
||||
|
@ -380,7 +380,7 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
// Get the default english node.
|
||||
$english_node = $this->reloadEntity($english_node);
|
||||
$this->assertTrue($english_node->isPublished());
|
||||
$this->assertEquals(6, $english_node->getRevisionId());
|
||||
$this->assertEquals(7, $english_node->getRevisionId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -416,25 +416,83 @@ class ContentModerationStateTest extends KernelTestBase {
|
|||
|
||||
/**
|
||||
* Tests that entities with special languages can be moderated.
|
||||
*
|
||||
* @dataProvider moderationWithSpecialLanguagesTestCases
|
||||
*/
|
||||
public function testModerationWithSpecialLanguages() {
|
||||
public function testModerationWithSpecialLanguages($original_language, $updated_language) {
|
||||
$workflow = $this->createEditorialWorkflow();
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
|
||||
$workflow->save();
|
||||
|
||||
// Create a test entity.
|
||||
$entity = EntityTestRev::create([
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
'langcode' => $original_language,
|
||||
]);
|
||||
$entity->save();
|
||||
$this->assertEquals('draft', $entity->moderation_state->value);
|
||||
|
||||
$entity->moderation_state->value = 'published';
|
||||
$entity->langcode = $updated_language;
|
||||
$entity->save();
|
||||
|
||||
$this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cases for ::testModerationWithSpecialLanguages().
|
||||
*/
|
||||
public function moderationWithSpecialLanguagesTestCases() {
|
||||
return [
|
||||
'Not specified to not specified' => [
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
],
|
||||
'English to not specified' => [
|
||||
'en',
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
],
|
||||
'Not specified to english' => [
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
'en',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test changing the language of content without adding a translation.
|
||||
*/
|
||||
public function testChangingContentLangcode() {
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
NodeType::create([
|
||||
'type' => 'test_type',
|
||||
])->save();
|
||||
$workflow = $this->createEditorialWorkflow();
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
|
||||
$workflow->save();
|
||||
|
||||
$entity = Node::create([
|
||||
'title' => 'Test node',
|
||||
'langcode' => 'en',
|
||||
'type' => 'test_type',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertCount(1, $entity->getTranslationLanguages());
|
||||
$this->assertCount(1, $content_moderation_state->getTranslationLanguages());
|
||||
$this->assertEquals('en', $entity->langcode->value);
|
||||
$this->assertEquals('en', $content_moderation_state->langcode->value);
|
||||
|
||||
$entity->langcode = 'fr';
|
||||
$entity->save();
|
||||
|
||||
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
|
||||
$this->assertCount(1, $entity->getTranslationLanguages());
|
||||
$this->assertCount(1, $content_moderation_state->getTranslationLanguages());
|
||||
$this->assertEquals('fr', $entity->langcode->value);
|
||||
$this->assertEquals('fr', $content_moderation_state->langcode->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a non-translatable entity type with a langcode can be moderated.
|
||||
*/
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
namespace Drupal\Tests\content_moderation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
|
||||
use Drupal\workflows\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
|
||||
|
@ -64,6 +66,8 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
$this->testNode->save();
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
|
||||
$this->testNode = Node::load($this->testNode->id());
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,4 +336,37 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the field item list when used with existing unmoderated content.
|
||||
*/
|
||||
public function testWithExistingUnmoderatedContent() {
|
||||
$node = Node::create([
|
||||
'title' => 'Test title',
|
||||
'type' => 'unmoderated',
|
||||
]);
|
||||
$node->save();
|
||||
$translation = $node->addTranslation('de', $node->toArray());
|
||||
$translation->title = 'Translated';
|
||||
$translation->save();
|
||||
|
||||
$workflow = Workflow::load('editorial');
|
||||
$workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'unmoderated');
|
||||
$workflow->save();
|
||||
|
||||
// After enabling moderation, both the original node and translation should
|
||||
// have a published moderation state.
|
||||
$node = Node::load($node->id());
|
||||
$translation = $node->getTranslation('de');
|
||||
$this->assertEquals('published', $node->moderation_state->value);
|
||||
$this->assertEquals('published', $translation->moderation_state->value);
|
||||
|
||||
// After the node has been updated, both the original node and translation
|
||||
// should still have a value.
|
||||
$node->title = 'Updated title';
|
||||
$node->save();
|
||||
$translation = $node->getTranslation('de');
|
||||
$this->assertEquals('published', $node->moderation_state->value);
|
||||
$this->assertEquals('published', $translation->moderation_state->value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
id: d6_taxonomy_term_localized_translation
|
||||
label: Taxonomy localized term translations
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d6_term_localized_translation
|
||||
translations: true
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the tid field to allow incremental migrations.
|
||||
tid: tid
|
||||
langcode: language
|
||||
vid:
|
||||
plugin: migration
|
||||
migration: d6_taxonomy_vocabulary
|
||||
source: vid
|
||||
name:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- name_translated
|
||||
- name
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
description:
|
||||
-
|
||||
plugin: callback
|
||||
source:
|
||||
- description_translated
|
||||
- description
|
||||
callable: array_filter
|
||||
-
|
||||
plugin: callback
|
||||
callable: current
|
||||
destination:
|
||||
plugin: entity:taxonomy_term
|
||||
translations: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_taxonomy_term
|
|
@ -0,0 +1,77 @@
|
|||
id: d7_block_translation
|
||||
label: Block translation
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
- Multilingual
|
||||
source:
|
||||
plugin: d7_block_translation
|
||||
constants:
|
||||
dest_label: 'settings/label'
|
||||
process:
|
||||
multilingual:
|
||||
plugin: skip_on_empty
|
||||
source: i18n_mode
|
||||
method: row
|
||||
langcode: language
|
||||
property: constants/dest_label
|
||||
translation: translation
|
||||
id:
|
||||
-
|
||||
plugin: migration_lookup
|
||||
migration: d7_block
|
||||
source:
|
||||
- module
|
||||
- delta
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
# The plugin process is copied from d7_block.yml
|
||||
plugin:
|
||||
-
|
||||
plugin: static_map
|
||||
bypass: true
|
||||
source:
|
||||
- module
|
||||
- delta
|
||||
map:
|
||||
book:
|
||||
navigation: book_navigation
|
||||
comment:
|
||||
recent: views_block:comments_recent-block_1
|
||||
forum:
|
||||
active: forum_active_block
|
||||
new: forum_new_block
|
||||
# locale:
|
||||
# 0: language_block
|
||||
node:
|
||||
syndicate: node_syndicate_block
|
||||
search:
|
||||
form: search_form_block
|
||||
statistics:
|
||||
popular: statistics_popular_block
|
||||
system:
|
||||
main: system_main_block
|
||||
'powered-by': system_powered_by_block
|
||||
user:
|
||||
login: user_login_block
|
||||
# 1: system_menu_block:tools
|
||||
new: views_block:who_s_new-block_1
|
||||
online: views_block:who_s_online-who_s_online_block
|
||||
-
|
||||
plugin: block_plugin_id
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
# The theme process is copied from d7_block.yml
|
||||
theme:
|
||||
plugin: block_theme
|
||||
source:
|
||||
- theme
|
||||
- default_theme
|
||||
- admin_theme
|
||||
destination:
|
||||
plugin: entity:block
|
||||
migration_dependencies:
|
||||
optional:
|
||||
- d7_block
|
|
@ -108,7 +108,12 @@ class MigrateTaxonomyTermTranslationTest extends MigrateDrupal6TestBase {
|
|||
|
||||
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
|
||||
$term = $this->treeData[$vid][$tid];
|
||||
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
|
||||
// PostgreSQL, MySQL and SQLite may not return the parent terms in the same
|
||||
// order so sort before testing.
|
||||
sort($parent_ids);
|
||||
$actual_terms = array_filter($term->parents);
|
||||
sort($actual_terms);
|
||||
$this->assertEquals($parent_ids, $actual_terms, "Term $tid has correct parents in taxonomy tree");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\datetime\Functional\Views;
|
||||
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
|
||||
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
|
||||
/**
|
||||
* Tests Views filters for datetime fields.
|
||||
*
|
||||
* @group datetime
|
||||
*/
|
||||
class FilterDateTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Name of the field.
|
||||
*
|
||||
* Note, this is used in the default test view.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = 'field_date';
|
||||
|
||||
/**
|
||||
* Nodes to test.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface[]
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'datetime',
|
||||
'datetime_test',
|
||||
'node',
|
||||
'views',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $testViews = ['test_filter_datetime'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Create nodes with relative dates of yesterday, today, and tomorrow.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$now = \Drupal::time()->getRequestTime();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(['administer views']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
|
||||
// Add a date field to page nodes.
|
||||
$fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'datetime',
|
||||
'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME],
|
||||
]);
|
||||
$fieldStorage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $fieldStorage,
|
||||
'bundle' => 'page',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Create some nodes.
|
||||
$dates = [
|
||||
// Tomorrow.
|
||||
DrupalDateTime::createFromTimestamp($now + 86400, DateTimeItemInterface::STORAGE_TIMEZONE)->format(DateTimeItemInterface::DATE_STORAGE_FORMAT),
|
||||
// Today.
|
||||
DrupalDateTime::createFromTimestamp($now, DateTimeItemInterface::STORAGE_TIMEZONE)->format(DateTimeItemInterface::DATE_STORAGE_FORMAT),
|
||||
// Yesterday.
|
||||
DrupalDateTime::createFromTimestamp($now - 86400, DateTimeItemInterface::STORAGE_TIMEZONE)->format(DateTimeItemInterface::DATE_STORAGE_FORMAT),
|
||||
];
|
||||
|
||||
$this->nodes = [];
|
||||
foreach ($dates as $date) {
|
||||
$this->nodes[] = $this->drupalCreateNode([
|
||||
$this->fieldName => [
|
||||
'value' => $date,
|
||||
],
|
||||
]);
|
||||
}
|
||||
// Add a node where the date field is empty.
|
||||
$this->nodes[] = $this->drupalCreateNode();
|
||||
|
||||
// Views needs to be aware of the new field.
|
||||
$this->container->get('views.views_data')->clear();
|
||||
|
||||
// Load test views.
|
||||
ViewTestData::createTestViews(get_class($this), ['datetime_test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exposed grouped filters.
|
||||
*/
|
||||
public function testExposedGroupedFilters() {
|
||||
// Expose the empty and not empty operators in a grouped filter.
|
||||
$this->drupalPostForm('admin/structure/views/nojs/handler/test_filter_datetime/default/filter/' . $this->fieldName . '_value', [], t('Expose filter'));
|
||||
$this->drupalPostForm(NULL, [], 'Grouped filters');
|
||||
|
||||
$edit = [];
|
||||
$edit['options[group_info][group_items][1][title]'] = 'empty';
|
||||
$edit['options[group_info][group_items][1][operator]'] = 'empty';
|
||||
$edit['options[group_info][group_items][2][title]'] = 'not empty';
|
||||
$edit['options[group_info][group_items][2][operator]'] = 'not empty';
|
||||
|
||||
$this->drupalPostForm(NULL, $edit, 'Apply');
|
||||
|
||||
// Test that the exposed filter works as expected.
|
||||
$path = 'test_filter_datetime-path';
|
||||
$this->drupalPostForm('admin/structure/views/view/test_filter_datetime/edit', [], 'Add Page');
|
||||
$this->drupalPostForm('admin/structure/views/nojs/display/test_filter_datetime/page_1/path', ['path' => $path], 'Apply');
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
|
||||
$this->drupalGet($path);
|
||||
|
||||
// Filter the Preview by 'empty'.
|
||||
$this->getSession()->getPage()->findField($this->fieldName . '_value')->selectOption(1);
|
||||
$this->getSession()->getPage()->pressButton('Apply');
|
||||
$results = $this->cssSelect('.view-content .field-content');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
// Filter the Preview by 'not empty'.
|
||||
$this->getSession()->getPage()->findField($this->fieldName . '_value')->selectOption(2);
|
||||
$this->getSession()->getPage()->pressButton('Apply');
|
||||
$results = $this->cssSelect('.view-content .field-content');
|
||||
$this->assertEquals(3, count($results));
|
||||
}
|
||||
|
||||
}
|
|
@ -71,6 +71,15 @@ class FilterDateTest extends DateTimeHandlerTestBase {
|
|||
$node->save();
|
||||
$this->nodes[] = $node;
|
||||
}
|
||||
|
||||
// Add a node where the date field is empty.
|
||||
$node = Node::create([
|
||||
'title' => $this->randomMachineName(8),
|
||||
'type' => 'page',
|
||||
'field_date' => [],
|
||||
]);
|
||||
$node->save();
|
||||
$this->nodes[] = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,6 +139,30 @@ class FilterDateTest extends DateTimeHandlerTestBase {
|
|||
];
|
||||
$this->assertIdenticalResultset($view, $expected_result, $this->map);
|
||||
$view->destroy();
|
||||
|
||||
// Test the empty operator.
|
||||
$view->initHandlers();
|
||||
$view->filter[$field]->operator = 'empty';
|
||||
$view->setDisplay('default');
|
||||
$this->executeView($view);
|
||||
$expected_result = [
|
||||
['nid' => $this->nodes[3]->id()],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected_result, $this->map);
|
||||
$view->destroy();
|
||||
|
||||
// Test the not empty operator.
|
||||
$view->initHandlers();
|
||||
$view->filter[$field]->operator = 'not empty';
|
||||
$view->setDisplay('default');
|
||||
$this->executeView($view);
|
||||
$expected_result = [
|
||||
['nid' => $this->nodes[0]->id()],
|
||||
['nid' => $this->nodes[1]->id()],
|
||||
['nid' => $this->nodes[2]->id()],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected_result, $this->map);
|
||||
$view->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\field\Tests\EntityReference;
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field_ui\Tests\FieldUiTestTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends WebTestBase {
|
||||
class EntityReferenceAdminTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
|
@ -65,145 +66,6 @@ class EntityReferenceAdminTest extends WebTestBase {
|
|||
*/
|
||||
public function testFieldAdminHandler() {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
// First step: 'Add new field' on the 'Manage fields' page.
|
||||
$this->drupalGet($bundle_path . '/fields/add-field');
|
||||
|
||||
// Check if the commonly referenced entity types appear in the list.
|
||||
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:node');
|
||||
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:user');
|
||||
|
||||
$this->drupalPostForm(NULL, [
|
||||
'label' => 'Test label',
|
||||
'field_name' => 'test',
|
||||
'new_storage_type' => 'entity_reference',
|
||||
], t('Save and continue'));
|
||||
|
||||
// Node should be selected by default.
|
||||
$this->assertFieldByName('settings[target_type]', 'node');
|
||||
|
||||
// Check that all entity types can be referenced.
|
||||
$this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityManager()->getDefinitions()));
|
||||
|
||||
// Second step: 'Field settings' form.
|
||||
$this->drupalPostForm(NULL, [], t('Save field settings'));
|
||||
|
||||
// The base handler should be selected by default.
|
||||
$this->assertFieldByName('settings[handler]', 'default:node');
|
||||
|
||||
// The base handler settings should be displayed.
|
||||
$entity_type_id = 'node';
|
||||
// Check that the type label is correctly displayed.
|
||||
$this->assertText('Content type');
|
||||
$bundles = $this->container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertFieldByName('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
}
|
||||
|
||||
reset($bundles);
|
||||
|
||||
// Test the sort settings.
|
||||
// Option 0: no sort.
|
||||
$this->assertFieldByName('settings[handler_settings][sort][field]', '_none');
|
||||
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
|
||||
// Option 1: sort by field.
|
||||
$this->drupalPostAjaxForm(NULL, ['settings[handler_settings][sort][field]' => 'nid'], 'settings[handler_settings][sort][field]');
|
||||
$this->assertFieldByName('settings[handler_settings][sort][direction]', 'ASC');
|
||||
|
||||
// Test that a non-translatable base field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='nid']");
|
||||
// Test that a translatable base field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='title']");
|
||||
// Test that a configurable field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='body.value']");
|
||||
|
||||
// Set back to no sort.
|
||||
$this->drupalPostAjaxForm(NULL, ['settings[handler_settings][sort][field]' => '_none'], 'settings[handler_settings][sort][field]');
|
||||
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
|
||||
|
||||
// Third step: confirm.
|
||||
$this->drupalPostForm(NULL, [
|
||||
'required' => '1',
|
||||
'settings[handler_settings][target_bundles][' . key($bundles) . ']' => key($bundles),
|
||||
], t('Save settings'));
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', 'Test label', 'Field was created and appears in the overview page.');
|
||||
|
||||
// Check that the field settings form can be submitted again, even when the
|
||||
// field is required.
|
||||
// The first 'Edit' link is for the Body field.
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->drupalPostForm(NULL, [], t('Save settings'));
|
||||
|
||||
// Switch the target type to 'taxonomy_term' and check that the settings
|
||||
// specific to its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'taxonomy_term',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertFieldByName('settings[handler_settings][auto_create]');
|
||||
|
||||
// Switch the target type to 'user' and check that the settings specific to
|
||||
// its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'user',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertFieldByName('settings[handler_settings][filter][type]', '_none');
|
||||
|
||||
// Switch the target type to 'node'.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'node',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
|
||||
// Try to select the views handler.
|
||||
$edit = [
|
||||
'settings[handler]' => 'views',
|
||||
];
|
||||
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
|
||||
$this->assertRaw(t('No eligible views were found. <a href=":create">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href=":existing">existing view</a>.', [
|
||||
':create' => \Drupal::url('views_ui.add'),
|
||||
':existing' => \Drupal::url('entity.view.collection'),
|
||||
]));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save settings'));
|
||||
// If no eligible view is available we should see a message.
|
||||
$this->assertText('The views entity selection mode requires a view.');
|
||||
|
||||
// Enable the entity_reference_test module which creates an eligible view.
|
||||
$this->container->get('module_installer')->install(['entity_reference_test']);
|
||||
$this->resetAll();
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
|
||||
$edit = [
|
||||
'settings[handler_settings][view][view_and_display]' => 'test_entity_reference:entity_reference_1',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save settings'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Switch the target type to 'entity_test'.
|
||||
$edit = [
|
||||
'settings[target_type]' => 'entity_test',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$edit = [
|
||||
'settings[handler]' => 'views',
|
||||
];
|
||||
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
|
||||
$edit = [
|
||||
'required' => FALSE,
|
||||
'settings[handler_settings][view][view_and_display]' => 'test_entity_reference_entity_test:entity_reference_1',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save settings'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Create a new view and display it as a entity reference.
|
||||
$edit = [
|
||||
'id' => 'node_test_view',
|
||||
|
@ -253,7 +115,7 @@ class EntityReferenceAdminTest extends WebTestBase {
|
|||
$edit = [
|
||||
'settings[handler]' => 'views',
|
||||
];
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'settings[handler]');
|
||||
$this->drupalPostForm(NULL, $edit, t('Change handler'));
|
||||
$edit = [
|
||||
'required' => FALSE,
|
||||
'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
|
||||
|
@ -275,7 +137,7 @@ class EntityReferenceAdminTest extends WebTestBase {
|
|||
// Try to add a new node and fill the entity reference field.
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$result = $this->xpath('//input[@name="field_test_entity_ref_field[0][target_id]" and contains(@data-autocomplete-path, "/entity_reference_autocomplete/node/views/")]');
|
||||
$target_url = $this->getAbsoluteUrl($result[0]['data-autocomplete-path']);
|
||||
$target_url = $this->getAbsoluteUrl($result[0]->getAttribute('data-autocomplete-path'));
|
||||
$this->drupalGet($target_url, ['query' => ['q' => 'Foo']]);
|
||||
$this->assertRaw($node1->getTitle() . ' (' . $node1->id() . ')');
|
||||
$this->assertRaw($node2->getTitle() . ' (' . $node2->id() . ')');
|
||||
|
@ -446,7 +308,8 @@ class EntityReferenceAdminTest extends WebTestBase {
|
|||
'settings[handler_settings][target_bundles][' . $vocabularies[1]->id() . ']' => TRUE,
|
||||
];
|
||||
// Enable the second vocabulary as a target bundle.
|
||||
$this->drupalPostAjaxForm($path, $edit, key($edit));
|
||||
$this->drupalPostForm($path, $edit, 'Save settings');
|
||||
$this->drupalGet($path);
|
||||
// Expect a select element with the two vocabularies as options.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[0]->id() . "']");
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[1]->id() . "']");
|
||||
|
@ -513,49 +376,23 @@ class EntityReferenceAdminTest extends WebTestBase {
|
|||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertFieldSelectOptions($name, array $expected_options) {
|
||||
$xpath = $this->buildXPathQuery('//select[@name=:name]', [':name' => $name]);
|
||||
$fields = $this->xpath($xpath);
|
||||
if ($fields) {
|
||||
$field = $fields[0];
|
||||
$options = $this->getAllOptionsList($field);
|
||||
|
||||
$options = $field->findAll('xpath', 'option');
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getValue();
|
||||
});
|
||||
sort($options);
|
||||
sort($expected_options);
|
||||
|
||||
return $this->assertIdentical($options, $expected_options);
|
||||
$this->assertIdentical($options, $expected_options);
|
||||
}
|
||||
else {
|
||||
return $this->fail('Unable to find field ' . $name);
|
||||
$this->fail('Unable to find field ' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all options from a select element.
|
||||
*
|
||||
* @param \SimpleXMLElement $element
|
||||
* The select element field information.
|
||||
*
|
||||
* @return array
|
||||
* An array of option values as strings.
|
||||
*/
|
||||
protected function getAllOptionsList(\SimpleXMLElement $element) {
|
||||
$options = [];
|
||||
// Add all options items.
|
||||
foreach ($element->option as $option) {
|
||||
$options[] = (string) $option['value'];
|
||||
}
|
||||
|
||||
// Loops trough all the option groups
|
||||
foreach ($element->optgroup as $optgroup) {
|
||||
$options = array_merge($this->getAllOptionsList($optgroup), $options);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\EntityReference;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable path module to ensure that the selection handler does not fail for
|
||||
* entities with a path field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node', 'field_ui', 'path', 'taxonomy', 'block', 'views_ui'];
|
||||
|
||||
/**
|
||||
* The name of the content type created for testing purposes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
// Create a content type, with underscores.
|
||||
$type_name = strtolower($this->randomMachineName(8)) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer views',
|
||||
'create ' . $type_name . ' content',
|
||||
'edit own ' . $type_name . ' content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Entity Reference Admin UI.
|
||||
*/
|
||||
public function testFieldAdminHandler() {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// First step: 'Add new field' on the 'Manage fields' page.
|
||||
$this->drupalGet($bundle_path . '/fields/add-field');
|
||||
|
||||
// Check if the commonly referenced entity types appear in the list.
|
||||
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:node');
|
||||
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:user');
|
||||
|
||||
$page->findField('new_storage_type')->setValue('entity_reference');
|
||||
$assert_session->waitForField('label')->setValue('Test');
|
||||
$machine_name = $assert_session->waitForElement('xpath', '//*[@id="edit-label-machine-name-suffix"]/span[2]/span[contains(text(), "field_test")]');
|
||||
$this->assertNotEmpty($machine_name);
|
||||
$page->pressButton('Save and continue');
|
||||
|
||||
// Node should be selected by default.
|
||||
$this->assertFieldByName('settings[target_type]', 'node');
|
||||
|
||||
// Check that all entity types can be referenced.
|
||||
$this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityManager()->getDefinitions()));
|
||||
|
||||
// Second step: 'Field settings' form.
|
||||
$this->drupalPostForm(NULL, [], t('Save field settings'));
|
||||
|
||||
// The base handler should be selected by default.
|
||||
$this->assertFieldByName('settings[handler]', 'default:node');
|
||||
|
||||
// The base handler settings should be displayed.
|
||||
$entity_type_id = 'node';
|
||||
// Check that the type label is correctly displayed.
|
||||
$assert_session->pageTextContains('Content type');
|
||||
$bundles = $this->container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertFieldByName('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
}
|
||||
|
||||
reset($bundles);
|
||||
|
||||
// Test the sort settings.
|
||||
// Option 0: no sort.
|
||||
$this->assertFieldByName('settings[handler_settings][sort][field]', '_none');
|
||||
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
|
||||
// Option 1: sort by field.
|
||||
$page->findField('settings[handler_settings][sort][field]')->setValue('nid');
|
||||
$assert_session->waitForField('settings[handler_settings][sort][direction]');
|
||||
$this->assertFieldByName('settings[handler_settings][sort][direction]', 'ASC');
|
||||
|
||||
// Test that a non-translatable base field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='nid']");
|
||||
// Test that a translatable base field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='title']");
|
||||
// Test that a configurable field is a sort option.
|
||||
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='body.value']");
|
||||
|
||||
// Set back to no sort.
|
||||
$page->findField('settings[handler_settings][sort][field]')->setValue('_none');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
|
||||
|
||||
// Third step: confirm.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . key($bundles) . ']')->setValue(key($bundles));
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->drupalPostForm(NULL, [
|
||||
'required' => '1',
|
||||
], t('Save settings'));
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', 'Test', 'Field was created and appears in the overview page.');
|
||||
|
||||
// Check that the field settings form can be submitted again, even when the
|
||||
// field is required.
|
||||
// The first 'Edit' link is for the Body field.
|
||||
$this->clickLink(t('Edit'), 1);
|
||||
$this->drupalPostForm(NULL, [], t('Save settings'));
|
||||
|
||||
// Switch the target type to 'taxonomy_term' and check that the settings
|
||||
// specific to its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'taxonomy_term',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertFieldByName('settings[handler_settings][auto_create]');
|
||||
|
||||
// Switch the target type to 'user' and check that the settings specific to
|
||||
// its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'user',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertFieldByName('settings[handler_settings][filter][type]', '_none');
|
||||
|
||||
// Switch the target type to 'node'.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$edit = [
|
||||
'settings[target_type]' => 'node',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
|
||||
// Try to select the views handler.
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$views_text = (string) new FormattableMarkup('No eligible views were found. <a href=":create">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href=":existing">existing view</a>.', [
|
||||
':create' => \Drupal::url('views_ui.add'),
|
||||
':existing' => \Drupal::url('entity.view.collection'),
|
||||
]);
|
||||
$assert_session->waitForElement('xpath', '//a[contains(text(), "Create a view")]');
|
||||
$assert_session->responseContains($views_text);
|
||||
|
||||
$this->drupalPostForm(NULL, [], t('Save settings'));
|
||||
// If no eligible view is available we should see a message.
|
||||
$assert_session->pageTextContains('The views entity selection mode requires a view.');
|
||||
|
||||
// Enable the entity_reference_test module which creates an eligible view.
|
||||
$this->container->get('module_installer')
|
||||
->install(['entity_reference_test']);
|
||||
$this->resetAll();
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session
|
||||
->waitForField('settings[handler_settings][view][view_and_display]')
|
||||
->setValue('test_entity_reference:entity_reference_1');
|
||||
$this->drupalPostForm(NULL, [], t('Save settings'));
|
||||
$assert_session->pageTextContains('Saved Test configuration.');
|
||||
|
||||
// Switch the target type to 'entity_test'.
|
||||
$edit = [
|
||||
'settings[target_type]' => 'entity_test',
|
||||
];
|
||||
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session
|
||||
->waitForField('settings[handler_settings][view][view_and_display]')
|
||||
->setValue('test_entity_reference_entity_test:entity_reference_1');
|
||||
$edit = [
|
||||
'required' => FALSE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save settings'));
|
||||
$assert_session->pageTextContains('Saved Test configuration.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*/
|
||||
protected function assertFieldSelectOptions($name, array $expected_options) {
|
||||
$xpath = $this->buildXPathQuery('//select[@name=:name]', [':name' => $name]);
|
||||
$fields = $this->xpath($xpath);
|
||||
if ($fields) {
|
||||
$field = $fields[0];
|
||||
$options = $field->findAll('xpath', 'option');
|
||||
$optgroups = $field->findAll('xpath', 'optgroup');
|
||||
foreach ($optgroups as $optgroup) {
|
||||
$options = array_merge($options, $optgroup->findAll('xpath', 'option'));
|
||||
}
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getAttribute('value');
|
||||
});
|
||||
|
||||
sort($options);
|
||||
sort($expected_options);
|
||||
|
||||
$this->assertIdentical($options, $expected_options);
|
||||
}
|
||||
else {
|
||||
$this->fail('Unable to find field ' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,13 @@ class StringFormatterTest extends KernelTestBase {
|
|||
*/
|
||||
public static $modules = ['field', 'text', 'entity_test', 'system', 'filter', 'user'];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -79,6 +86,8 @@ class StringFormatterTest extends KernelTestBase {
|
|||
'settings' => [],
|
||||
]);
|
||||
$this->display->save();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +154,7 @@ class StringFormatterTest extends KernelTestBase {
|
|||
$value2 = $this->randomMachineName();
|
||||
$entity->{$this->fieldName}->value = $value2;
|
||||
$entity->save();
|
||||
$entity_new_revision = \Drupal::entityManager()->getStorage('entity_test_rev')->loadRevision($old_revision_id);
|
||||
$entity_new_revision = $this->entityTypeManager->getStorage('entity_test_rev')->loadRevision($old_revision_id);
|
||||
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertLink($value2, 0);
|
||||
|
@ -154,6 +163,19 @@ class StringFormatterTest extends KernelTestBase {
|
|||
$this->renderEntityFields($entity_new_revision, $this->display);
|
||||
$this->assertLink($value, 0);
|
||||
$this->assertLinkByHref('/entity_test_rev/' . $entity_new_revision->id() . '/revision/' . $entity_new_revision->getRevisionId() . '/view');
|
||||
|
||||
// Check that linking to a revisionable entity works if the entity type does
|
||||
// not specify a 'revision' link template.
|
||||
$entity_type = clone $this->entityTypeManager->getDefinition('entity_test_rev');
|
||||
$link_templates = $entity_type->getLinkTemplates();
|
||||
unset($link_templates['revision']);
|
||||
$entity_type->set('links', $link_templates);
|
||||
\Drupal::state()->set('entity_test_rev.entity_type', $entity_type);
|
||||
$this->entityTypeManager->clearCachedDefinitions();
|
||||
|
||||
$this->renderEntityFields($entity_new_revision, $this->display);
|
||||
$this->assertLink($value, 0);
|
||||
$this->assertLinkByHref($entity->url('canonical'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
|
|||
/**
|
||||
* Tests adding a new field.
|
||||
*
|
||||
* @todo Assert properties can bet set in the form and read back in
|
||||
* @todo Assert properties can be set in the form and read back in
|
||||
* $field_storage and $fields.
|
||||
*/
|
||||
public function createField() {
|
||||
|
|
|
@ -23,7 +23,7 @@ use Drupal\Core\Template\Attribute;
|
|||
/**
|
||||
* The regex pattern used when checking for insecure file types.
|
||||
*/
|
||||
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(php|pl|py|cgi|asp|js)(\.|$)/i');
|
||||
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i');
|
||||
|
||||
// Load all Field module hooks for File.
|
||||
require_once __DIR__ . '/file.field.inc';
|
||||
|
@ -859,7 +859,6 @@ function _file_save_upload_from_form(array $element, FormStateInterface $form_st
|
|||
* @todo: move this logic to a service in https://www.drupal.org/node/2244513.
|
||||
*/
|
||||
function file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
$user = \Drupal::currentUser();
|
||||
static $upload_cache;
|
||||
|
||||
$all_files = \Drupal::request()->files->get('files', []);
|
||||
|
@ -887,176 +886,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
|
||||
$files = [];
|
||||
foreach ($uploaded_files as $i => $file_info) {
|
||||
// Check for file upload errors and return FALSE for this file if a lower
|
||||
// level system error occurred. For a complete list of errors:
|
||||
// See http://php.net/manual/features.file-upload.errors.php.
|
||||
switch ($file_info->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
case UPLOAD_ERR_OK:
|
||||
// Final check that this is a valid upload, if it isn't, use the
|
||||
// default error handler.
|
||||
if (is_uploaded_file($file_info->getRealPath())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Unknown error
|
||||
default:
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
}
|
||||
// Begin building file entity.
|
||||
$values = [
|
||||
'uid' => $user->id(),
|
||||
'status' => 0,
|
||||
'filename' => $file_info->getClientOriginalName(),
|
||||
'uri' => $file_info->getRealPath(),
|
||||
'filesize' => $file_info->getSize(),
|
||||
];
|
||||
$values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']);
|
||||
$file = File::create($values);
|
||||
|
||||
$extensions = '';
|
||||
if (isset($validators['file_validate_extensions'])) {
|
||||
if (isset($validators['file_validate_extensions'][0])) {
|
||||
// Build the list of non-munged extensions if the caller provided them.
|
||||
$extensions = $validators['file_validate_extensions'][0];
|
||||
}
|
||||
else {
|
||||
// If 'file_validate_extensions' is set and the list is empty then the
|
||||
// caller wants to allow any extension. In this case we have to remove the
|
||||
// validator or else it will reject all extensions.
|
||||
unset($validators['file_validate_extensions']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No validator was provided, so add one using the default list.
|
||||
// Build a default non-munged safe list for file_munge_filename().
|
||||
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
$validators['file_validate_extensions'] = [];
|
||||
$validators['file_validate_extensions'][0] = $extensions;
|
||||
}
|
||||
|
||||
if (!empty($extensions)) {
|
||||
// Munge the filename to protect against possible malicious extension
|
||||
// hiding within an unknown file type (ie: filename.html.foo).
|
||||
$file->setFilename(file_munge_filename($file->getFilename(), $extensions));
|
||||
}
|
||||
|
||||
// Rename potentially executable files, to help prevent exploits (i.e. will
|
||||
// rename filename.php.foo and filename.php to filename.php.foo.txt and
|
||||
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
||||
// evaluates to TRUE.
|
||||
if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
|
||||
$file->setMimeType('text/plain');
|
||||
// The destination filename will also later be used to create the URI.
|
||||
$file->setFilename($file->getFilename() . '.txt');
|
||||
// The .txt extension may not be in the allowed list of extensions. We have
|
||||
// to add it here or else the file upload will fail.
|
||||
if (!empty($extensions)) {
|
||||
$validators['file_validate_extensions'][0] .= ' txt';
|
||||
\Drupal::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()]));
|
||||
}
|
||||
}
|
||||
|
||||
// If the destination is not provided, use the temporary directory.
|
||||
if (empty($destination)) {
|
||||
$destination = 'temporary://';
|
||||
}
|
||||
|
||||
// Assert that the destination contains a valid stream.
|
||||
$destination_scheme = file_uri_scheme($destination);
|
||||
if (!file_stream_wrapper_valid_scheme($destination_scheme)) {
|
||||
\Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
$file->source = $form_field_name;
|
||||
// A file URI may already have a trailing slash or look like "public://".
|
||||
if (substr($destination, -1) != '/') {
|
||||
$destination .= '/';
|
||||
}
|
||||
$file->destination = file_destination($destination . $file->getFilename(), $replace);
|
||||
// If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and
|
||||
// there's an existing file so we need to bail.
|
||||
if ($file->destination === FALSE) {
|
||||
\Drupal::messenger()->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add in our check of the file name length.
|
||||
$validators['file_validate_name_length'] = [];
|
||||
|
||||
// Call the validation functions specified by this function's caller.
|
||||
$errors = file_validate($file, $validators);
|
||||
|
||||
// Check for errors.
|
||||
if (!empty($errors)) {
|
||||
$message = [
|
||||
'error' => [
|
||||
'#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]),
|
||||
],
|
||||
'item_list' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $errors,
|
||||
],
|
||||
];
|
||||
// @todo Add support for render arrays in
|
||||
// \Drupal\Core\Messenger\MessengerInterface::addMessage()?
|
||||
// @see https://www.drupal.org/node/2505497.
|
||||
\Drupal::messenger()->addError(\Drupal::service('renderer')->renderPlain($message));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
$file->setFileUri($file->destination);
|
||||
if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) {
|
||||
\Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.'));
|
||||
\Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]);
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the permissions on the new file.
|
||||
drupal_chmod($file->getFileUri());
|
||||
|
||||
// If we are replacing an existing file re-use its database record.
|
||||
// @todo Do not create a new entity in order to update it. See
|
||||
// https://www.drupal.org/node/2241865.
|
||||
if ($replace == FILE_EXISTS_REPLACE) {
|
||||
$existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]);
|
||||
if (count($existing_files)) {
|
||||
$existing = reset($existing_files);
|
||||
$file->fid = $existing->id();
|
||||
$file->setOriginalId($existing->id());
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it this far it's safe to record this file in the database.
|
||||
$file->save();
|
||||
$files[$i] = $file;
|
||||
// Allow an anonymous user who creates a non-public file to see it. See
|
||||
// \Drupal\file\FileAccessControlHandler::checkAccess().
|
||||
if ($user->isAnonymous() && $destination_scheme !== 'public') {
|
||||
$session = \Drupal::request()->getSession();
|
||||
$allowed_temp_files = $session->get('anonymous_allowed_file_ids', []);
|
||||
$allowed_temp_files[$file->id()] = $file->id();
|
||||
$session->set('anonymous_allowed_file_ids', $allowed_temp_files);
|
||||
}
|
||||
$files[$i] = _file_save_upload_single($file_info, $form_field_name, $validators, $destination, $replace);
|
||||
}
|
||||
|
||||
// Add files to the cache.
|
||||
|
@ -1065,6 +895,201 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
return isset($delta) ? $files[$delta] : $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a file upload to a new location.
|
||||
*
|
||||
* @param \SplFileInfo $file_info
|
||||
* The file upload to save.
|
||||
* @param string $form_field_name
|
||||
* A string that is the associative array key of the upload form element in
|
||||
* the form array.
|
||||
* @param array $validators
|
||||
* (optional) An associative array of callback functions used to validate the
|
||||
* file.
|
||||
* @param bool $destination
|
||||
* (optional) A string containing the URI that the file should be copied to.
|
||||
* @param int $replace
|
||||
* (optional) The replace behavior when the destination file already exists.
|
||||
*
|
||||
* @return \Drupal\file\FileInterface|false
|
||||
* The created file entity or FALSE if the uploaded file not saved.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*
|
||||
* @internal
|
||||
* This method should only be called from file_save_upload(). Use that method
|
||||
* instead.
|
||||
*
|
||||
* @see file_save_upload()
|
||||
*/
|
||||
function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
|
||||
$user = \Drupal::currentUser();
|
||||
// Check for file upload errors and return FALSE for this file if a lower
|
||||
// level system error occurred. For a complete list of errors:
|
||||
// See http://php.net/manual/features.file-upload.errors.php.
|
||||
switch ($file_info->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]));
|
||||
return FALSE;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]));
|
||||
return FALSE;
|
||||
|
||||
case UPLOAD_ERR_OK:
|
||||
// Final check that this is a valid upload, if it isn't, use the
|
||||
// default error handler.
|
||||
if (is_uploaded_file($file_info->getRealPath())) {
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Unknown error
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]));
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
// Begin building file entity.
|
||||
$values = [
|
||||
'uid' => $user->id(),
|
||||
'status' => 0,
|
||||
'filename' => $file_info->getClientOriginalName(),
|
||||
'uri' => $file_info->getRealPath(),
|
||||
'filesize' => $file_info->getSize(),
|
||||
];
|
||||
$values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']);
|
||||
$file = File::create($values);
|
||||
|
||||
$extensions = '';
|
||||
if (isset($validators['file_validate_extensions'])) {
|
||||
if (isset($validators['file_validate_extensions'][0])) {
|
||||
// Build the list of non-munged extensions if the caller provided them.
|
||||
$extensions = $validators['file_validate_extensions'][0];
|
||||
}
|
||||
else {
|
||||
// If 'file_validate_extensions' is set and the list is empty then the
|
||||
// caller wants to allow any extension. In this case we have to remove the
|
||||
// validator or else it will reject all extensions.
|
||||
unset($validators['file_validate_extensions']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No validator was provided, so add one using the default list.
|
||||
// Build a default non-munged safe list for file_munge_filename().
|
||||
$extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
$validators['file_validate_extensions'] = [];
|
||||
$validators['file_validate_extensions'][0] = $extensions;
|
||||
}
|
||||
|
||||
if (!empty($extensions)) {
|
||||
// Munge the filename to protect against possible malicious extension
|
||||
// hiding within an unknown file type (ie: filename.html.foo).
|
||||
$file->setFilename(file_munge_filename($file->getFilename(), $extensions));
|
||||
}
|
||||
|
||||
// Rename potentially executable files, to help prevent exploits (i.e. will
|
||||
// rename filename.php.foo and filename.php to filename.php.foo.txt and
|
||||
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
||||
// evaluates to TRUE.
|
||||
if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
|
||||
$file->setMimeType('text/plain');
|
||||
// The destination filename will also later be used to create the URI.
|
||||
$file->setFilename($file->getFilename() . '.txt');
|
||||
// The .txt extension may not be in the allowed list of extensions. We have
|
||||
// to add it here or else the file upload will fail.
|
||||
if (!empty($extensions)) {
|
||||
$validators['file_validate_extensions'][0] .= ' txt';
|
||||
\Drupal::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()]));
|
||||
}
|
||||
}
|
||||
|
||||
// If the destination is not provided, use the temporary directory.
|
||||
if (empty($destination)) {
|
||||
$destination = 'temporary://';
|
||||
}
|
||||
|
||||
// Assert that the destination contains a valid stream.
|
||||
$destination_scheme = file_uri_scheme($destination);
|
||||
if (!file_stream_wrapper_valid_scheme($destination_scheme)) {
|
||||
\Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$file->source = $form_field_name;
|
||||
// A file URI may already have a trailing slash or look like "public://".
|
||||
if (substr($destination, -1) != '/') {
|
||||
$destination .= '/';
|
||||
}
|
||||
$file->destination = file_destination($destination . $file->getFilename(), $replace);
|
||||
// If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and
|
||||
// there's an existing file so we need to bail.
|
||||
if ($file->destination === FALSE) {
|
||||
\Drupal::messenger()->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination]));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Add in our check of the file name length.
|
||||
$validators['file_validate_name_length'] = [];
|
||||
|
||||
// Call the validation functions specified by this function's caller.
|
||||
$errors = file_validate($file, $validators);
|
||||
|
||||
// Check for errors.
|
||||
if (!empty($errors)) {
|
||||
$message = [
|
||||
'error' => [
|
||||
'#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]),
|
||||
],
|
||||
'item_list' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $errors,
|
||||
],
|
||||
];
|
||||
// @todo Add support for render arrays in
|
||||
// \Drupal\Core\Messenger\MessengerInterface::addMessage()?
|
||||
// @see https://www.drupal.org/node/2505497.
|
||||
\Drupal::messenger()->addError(\Drupal::service('renderer')->renderPlain($message));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$file->setFileUri($file->destination);
|
||||
if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) {
|
||||
\Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.'));
|
||||
\Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Set the permissions on the new file.
|
||||
drupal_chmod($file->getFileUri());
|
||||
|
||||
// If we are replacing an existing file re-use its database record.
|
||||
// @todo Do not create a new entity in order to update it. See
|
||||
// https://www.drupal.org/node/2241865.
|
||||
if ($replace == FILE_EXISTS_REPLACE) {
|
||||
$existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]);
|
||||
if (count($existing_files)) {
|
||||
$existing = reset($existing_files);
|
||||
$file->fid = $existing->id();
|
||||
$file->setOriginalId($existing->id());
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it this far it's safe to record this file in the database.
|
||||
$file->save();
|
||||
|
||||
// Allow an anonymous user who creates a non-public file to see it. See
|
||||
// \Drupal\file\FileAccessControlHandler::checkAccess().
|
||||
if ($user->isAnonymous() && $destination_scheme !== 'public') {
|
||||
$session = \Drupal::request()->getSession();
|
||||
$allowed_temp_files = $session->get('anonymous_allowed_file_ids', []);
|
||||
$allowed_temp_files[$file->id()] = $file->id();
|
||||
$session->set('anonymous_allowed_file_ids', $allowed_temp_files);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the preferred upload progress implementation.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests multiple file upload.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class MultipleFileUploadTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin = $this->drupalCreateUser(['administer themes']);
|
||||
$this->drupalLogin($admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple file field with all file extensions.
|
||||
*/
|
||||
public function testMultipleFileFieldWithAllFileExtensions() {
|
||||
$theme = 'test_theme_settings';
|
||||
\Drupal::service('theme_handler')->install([$theme]);
|
||||
$this->drupalGet("admin/appearance/settings/$theme");
|
||||
|
||||
$edit = [];
|
||||
// Create few files with non-typical extensions.
|
||||
foreach (['file1.wtf', 'file2.wtf'] as $i => $file) {
|
||||
$file_path = $this->root . "/sites/default/files/simpletest/$file";
|
||||
file_put_contents($file_path, 'File with non-default extension.', FILE_APPEND | LOCK_EX);
|
||||
$edit["files[multi_file][$i]"] = $file_path;
|
||||
}
|
||||
|
||||
// @todo: Replace after https://www.drupal.org/project/drupal/issues/2917885
|
||||
$this->drupalGet("admin/appearance/settings/$theme");
|
||||
$submit_xpath = $this->assertSession()->buttonExists('Save configuration')->getXpath();
|
||||
$client = $this->getSession()->getDriver()->getClient();
|
||||
$form = $client->getCrawler()->filterXPath($submit_xpath)->form();
|
||||
$client->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $edit);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertNotContains('Only files with the following extensions are allowed', $page->getContent());
|
||||
$this->assertContains('The configuration options have been saved.', $page->getContent());
|
||||
$this->assertContains('file1.wtf', $page->getContent());
|
||||
$this->assertContains('file2.wtf', $page->getContent());
|
||||
}
|
||||
|
||||
}
|
|
@ -90,11 +90,11 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl
|
|||
protected $initialized = FALSE;
|
||||
|
||||
/**
|
||||
* Whether already in the process of language initialization.
|
||||
* Whether language types are in the process of language initialization.
|
||||
*
|
||||
* @var bool
|
||||
* @var bool[]
|
||||
*/
|
||||
protected $initializing = FALSE;
|
||||
protected $initializing = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -213,12 +213,12 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl
|
|||
$this->negotiatedLanguages[$type] = $this->getDefaultLanguage();
|
||||
|
||||
if ($this->negotiator && $this->isMultilingual()) {
|
||||
if (!$this->initializing) {
|
||||
$this->initializing = TRUE;
|
||||
if (!isset($this->initializing[$type])) {
|
||||
$this->initializing[$type] = TRUE;
|
||||
$negotiation = $this->negotiator->initializeType($type);
|
||||
$this->negotiatedLanguages[$type] = reset($negotiation);
|
||||
$this->negotiatedMethods[$type] = key($negotiation);
|
||||
$this->initializing = FALSE;
|
||||
unset($this->initializing[$type]);
|
||||
}
|
||||
// If the current interface language needs to be retrieved during
|
||||
// initialization we return the system language. This way string
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\language\Functional;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests Language Negotiation.
|
||||
*
|
||||
* Uses different negotiators for content and interface.
|
||||
*
|
||||
* @group language
|
||||
*/
|
||||
class ConfigurableLanguageManagerTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
'locale',
|
||||
'block',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $this->createUser([], '', TRUE);
|
||||
$this->drupalLogin($user);
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
|
||||
// Create a page node type and make it translatable.
|
||||
NodeType::create([
|
||||
'type' => 'page',
|
||||
'name' => t('Page'),
|
||||
])->save();
|
||||
|
||||
$config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'page');
|
||||
$config->setDefaultLangcode('en')
|
||||
->setLanguageAlterable(TRUE)
|
||||
->save();
|
||||
|
||||
// Create a Node with title 'English' and translate it to Spanish.
|
||||
$node = Node::create([
|
||||
'type' => 'page',
|
||||
'title' => 'English',
|
||||
]);
|
||||
$node->save();
|
||||
$node->addTranslation('es', ['title' => 'Español']);
|
||||
$node->save();
|
||||
|
||||
// Enable both language_interface and language_content language negotiation.
|
||||
\Drupal::getContainer()->get('language_negotiator')->updateConfiguration([
|
||||
'language_interface',
|
||||
'language_content',
|
||||
]);
|
||||
|
||||
// Set the preferred language of the user for admin pages to English.
|
||||
$user->set('preferred_admin_langcode', 'en')->save();
|
||||
|
||||
// Make sure node edit pages are administration pages.
|
||||
$this->config('node.settings')->set('use_admin_theme', '1')->save();
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
|
||||
// Place a Block with a translatable string on the page.
|
||||
$this->placeBlock('system_powered_by_block', ['region' => 'content']);
|
||||
|
||||
// Load the Spanish Node page once, to register the translatable string.
|
||||
$this->drupalGet('/es/node/1');
|
||||
|
||||
// Translate the Powered by string.
|
||||
/** @var \Drupal\locale\StringStorageInterface $string_storage */
|
||||
$string_storage = \Drupal::getContainer()->get('locale.storage');
|
||||
$source = $string_storage->findString(['source' => 'Powered by <a href=":poweredby">Drupal</a>']);
|
||||
$string_storage->createTranslation([
|
||||
'lid' => $source->lid,
|
||||
'language' => 'es',
|
||||
'translation' => 'Funciona con ...',
|
||||
])->save();
|
||||
// Invalidate caches so that the new translation will be used.
|
||||
Cache::invalidateTags(['rendered', 'locale']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translation with URL and Preferred Admin Language negotiators.
|
||||
*
|
||||
* The interface language uses the preferred language for admin pages of the
|
||||
* user and after that the URL. The Content uses just the URL.
|
||||
*/
|
||||
public function testUrlContentTranslationWithPreferredAdminLanguage() {
|
||||
$assert_session = $this->assertSession();
|
||||
// Set the interface language to use the preferred administration language
|
||||
// and then the URL.
|
||||
/** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
|
||||
$language_negotiator = \Drupal::getContainer()->get('language_negotiator');
|
||||
$language_negotiator->saveConfiguration('language_interface', [
|
||||
'language-user-admin' => 1,
|
||||
'language-url' => 2,
|
||||
'language-selected' => 3,
|
||||
]);
|
||||
// Set Content Language Negotiator to use just the URL.
|
||||
$language_negotiator->saveConfiguration('language_content', [
|
||||
'language-url' => 4,
|
||||
'language-selected' => 5,
|
||||
]);
|
||||
|
||||
// See if the full view of the node in english is present and the
|
||||
// string in the Powered By Block is in English.
|
||||
$this->drupalGet('/node/1');
|
||||
$assert_session->pageTextContains('English');
|
||||
$assert_session->pageTextContains('Powered by');
|
||||
|
||||
// Load the spanish node page again and see if both the node and the string
|
||||
// are translated.
|
||||
$this->drupalGet('/es/node/1');
|
||||
$assert_session->pageTextContains('Español');
|
||||
$assert_session->pageTextContains('Funciona con');
|
||||
$assert_session->pageTextNotContains('Powered by');
|
||||
|
||||
// Check if the Powered by string is shown in English on an
|
||||
// administration page, and the node content is shown in Spanish.
|
||||
$this->drupalGet('/es/node/1/edit');
|
||||
$assert_session->pageTextContains('Español');
|
||||
$assert_session->pageTextContains('Powered by');
|
||||
$assert_session->pageTextNotContains('Funciona con');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translation with URL and Session Language Negotiators.
|
||||
*/
|
||||
public function testUrlContentTranslationWithSessionLanguage() {
|
||||
$assert_session = $this->assertSession();
|
||||
/** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */
|
||||
$language_negotiator = \Drupal::getContainer()->get('language_negotiator');
|
||||
// Set Interface Language Negotiator to Session.
|
||||
$language_negotiator->saveConfiguration('language_interface', [
|
||||
'language-session' => 1,
|
||||
'language-url' => 2,
|
||||
'language-selected' => 3,
|
||||
]);
|
||||
|
||||
// Set Content Language Negotiator to URL.
|
||||
$language_negotiator->saveConfiguration('language_content', [
|
||||
'language-url' => 4,
|
||||
'language-selected' => 5,
|
||||
]);
|
||||
|
||||
// See if the full view of the node in english is present and the
|
||||
// string in the Powered By Block is in English.
|
||||
$this->drupalGet('/node/1');
|
||||
$assert_session->pageTextContains('English');
|
||||
$assert_session->pageTextContains('Powered by');
|
||||
|
||||
// The language session variable has not been set yet, so
|
||||
// The string should be in Spanish.
|
||||
$this->drupalGet('/es/node/1');
|
||||
$assert_session->pageTextContains('Español');
|
||||
$assert_session->pageTextNotContains('Powered by');
|
||||
$assert_session->pageTextContains('Funciona con');
|
||||
|
||||
// Set the session language to Spanish but load the English node page.
|
||||
$this->drupalGet('/node/1', ['query' => ['language' => 'es']]);
|
||||
$assert_session->pageTextContains('English');
|
||||
$assert_session->pageTextNotContains('Español');
|
||||
$assert_session->pageTextContains('Funciona con');
|
||||
$assert_session->pageTextNotContains('Powered by');
|
||||
|
||||
// Set the session language to English but load the node page in Spanish.
|
||||
$this->drupalGet('/es/node/1', ['query' => ['language' => 'en']]);
|
||||
$assert_session->pageTextNotContains('English');
|
||||
$assert_session->pageTextContains('Español');
|
||||
$assert_session->pageTextNotContains('Funciona con');
|
||||
$assert_session->pageTextContains('Powered by');
|
||||
}
|
||||
|
||||
}
|
|
@ -45,9 +45,9 @@ class MigrateLanguageContentTaxonomyVocabularySettingsTest extends MigrateDrupal
|
|||
// Set language to vocabulary.
|
||||
$this->assertLanguageContentSettings($target_entity, 'vocabulary_2_i_1_', 'fr', FALSE, ['enabled' => FALSE]);
|
||||
// Localize terms.
|
||||
$this->assertLanguageContentSettings($target_entity, 'vocabulary_3_i_2_', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => TRUE]);
|
||||
$this->assertLanguageContentSettings($target_entity, 'vocabulary_3_i_2_', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => FALSE]);
|
||||
// None translation enabled.
|
||||
$this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_than', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
|
||||
$this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_than', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => TRUE]);
|
||||
$this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
|
||||
$this->assertLanguageContentSettings($target_entity, 'forums', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
|
||||
$this->assertLanguageContentSettings($target_entity, 'type', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]);
|
||||
|
|
|
@ -85,3 +85,32 @@
|
|||
display: block;
|
||||
padding-top: 0.55em;
|
||||
}
|
||||
|
||||
#drupal-off-canvas .inline-block-create-button {
|
||||
display: block;
|
||||
padding: 24px;
|
||||
padding-left: 44px;
|
||||
font-size: 16px;
|
||||
color: #eee;
|
||||
background: url(../../../misc/icons/bebebe/plus.svg) transparent 16px no-repeat;
|
||||
}
|
||||
|
||||
#drupal-off-canvas .inline-block-create-button,
|
||||
#drupal-off-canvas .inline-block-list__item {
|
||||
margin: 0 -20px;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#drupal-off-canvas .inline-block-create-button:hover,
|
||||
#drupal-off-canvas .inline-block-list__item:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
#drupal-off-canvas .inline-block-list {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#drupal-off-canvas .inline-block-list__item {
|
||||
display: block;
|
||||
padding: 15px 0 15px 25px;
|
||||
}
|
||||
|
|
|
@ -9,3 +9,5 @@ dependencies:
|
|||
- drupal:contextual
|
||||
# @todo Discuss removing in https://www.drupal.org/project/drupal/issues/2935999.
|
||||
- drupal:field_ui
|
||||
# @todo Discuss removing in https://www.drupal.org/project/drupal/issues/3003610.
|
||||
- drupal:block
|
||||
|
|
|
@ -19,6 +19,7 @@ use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
|
|||
use Drupal\layout_builder\InlineBlockEntityOperations;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
|
@ -62,8 +63,8 @@ function layout_builder_entity_type_alter(array &$entity_types) {
|
|||
function layout_builder_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) {
|
||||
// Hides the Layout Builder field. It is rendered directly in
|
||||
// \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
|
||||
unset($form['fields']['layout_builder__layout']);
|
||||
$key = array_search('layout_builder__layout', $form['#fields']);
|
||||
unset($form['fields'][OverridesSectionStorage::FIELD_NAME]);
|
||||
$key = array_search(OverridesSectionStorage::FIELD_NAME, $form['#fields']);
|
||||
if ($key !== FALSE) {
|
||||
unset($form['#fields'][$key]);
|
||||
}
|
||||
|
@ -177,7 +178,7 @@ function layout_builder_cron() {
|
|||
function layout_builder_plugin_filter_block_alter(array &$definitions, array $extra, $consumer) {
|
||||
// @todo Determine the 'inline_block' blocks should be allowed outside
|
||||
// of layout_builder https://www.drupal.org/node/2979142.
|
||||
if ($consumer !== 'layout_builder') {
|
||||
if ($consumer !== 'layout_builder' || !isset($extra['list']) || $extra['list'] !== 'inline_blocks') {
|
||||
foreach ($definitions as $id => $definition) {
|
||||
if ($definition['id'] === 'inline_block') {
|
||||
unset($definitions[$id]);
|
||||
|
@ -202,3 +203,21 @@ function layout_builder_block_content_access(EntityInterface $entity, $operation
|
|||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
|
||||
*/
|
||||
function layout_builder_plugin_filter_block__block_ui_alter(array &$definitions, array $extra) {
|
||||
foreach ($definitions as $id => $definition) {
|
||||
// Filter out any layout_builder definition with required contexts.
|
||||
if ($definition['provider'] === 'layout_builder' && !empty($definition['context'])) {
|
||||
/** @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context */
|
||||
foreach ($definition['context'] as $context) {
|
||||
if ($context->isRequired()) {
|
||||
unset($definitions[$id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,19 @@ layout_builder.add_block:
|
|||
section_storage:
|
||||
layout_builder_tempstore: TRUE
|
||||
|
||||
layout_builder.choose_inline_block:
|
||||
path: '/layout_builder/choose/inline-block/{section_storage_type}/{section_storage}/{delta}/{region}'
|
||||
defaults:
|
||||
_controller: '\Drupal\layout_builder\Controller\ChooseBlockController::inlineBlockList'
|
||||
_title: 'Add a new Inline Block'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
section_storage:
|
||||
layout_builder_tempstore: TRUE
|
||||
|
||||
layout_builder.update_block:
|
||||
path: '/layout_builder/update/block/{section_storage_type}/{section_storage}/{delta}/{region}/{uuid}'
|
||||
defaults:
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Drupal\layout_builder\Controller;
|
|||
use Drupal\Core\Ajax\AjaxHelperTrait;
|
||||
use Drupal\Core\Block\BlockManagerInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
|
||||
|
@ -29,14 +30,24 @@ class ChooseBlockController implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* ChooseBlockController constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
|
||||
* The block manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(BlockManagerInterface $block_manager) {
|
||||
public function __construct(BlockManagerInterface $block_manager, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->blockManager = $block_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,7 +55,8 @@ class ChooseBlockController implements ContainerInjectionInterface {
|
|||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.block')
|
||||
$container->get('plugin.manager.block'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -63,8 +75,43 @@ class ChooseBlockController implements ContainerInjectionInterface {
|
|||
*/
|
||||
public function build(SectionStorageInterface $section_storage, $delta, $region) {
|
||||
$build['#title'] = $this->t('Choose a block');
|
||||
$build['#type'] = 'container';
|
||||
$build['#attributes']['class'][] = 'block-categories';
|
||||
if ($this->entityTypeManager->hasDefinition('block_content_type') && $types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple()) {
|
||||
if (count($types) === 1) {
|
||||
$type = reset($types);
|
||||
$plugin_id = 'inline_block:' . $type->id();
|
||||
if ($this->blockManager->hasDefinition($plugin_id)) {
|
||||
$url = Url::fromRoute('layout_builder.add_block', [
|
||||
'section_storage_type' => $section_storage->getStorageType(),
|
||||
'section_storage' => $section_storage->getStorageId(),
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
'plugin_id' => $plugin_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$url = Url::fromRoute('layout_builder.choose_inline_block', [
|
||||
'section_storage_type' => $section_storage->getStorageType(),
|
||||
'section_storage' => $section_storage->getStorageId(),
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
]);
|
||||
}
|
||||
if (isset($url)) {
|
||||
$build['add_block'] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $url,
|
||||
'#title' => $this->t('Create @entity_type', [
|
||||
'@entity_type' => $this->entityTypeManager->getDefinition('block_content')->getSingularLabel(),
|
||||
]),
|
||||
'#attributes' => $this->getAjaxAttributes(),
|
||||
];
|
||||
$build['add_block']['#attributes']['class'][] = 'inline-block-create-button';
|
||||
}
|
||||
}
|
||||
|
||||
$block_categories['#type'] = 'container';
|
||||
$block_categories['#attributes']['class'][] = 'block-categories';
|
||||
|
||||
// @todo Explicitly cast delta to an integer, remove this in
|
||||
// https://www.drupal.org/project/drupal/issues/2984509.
|
||||
|
@ -75,35 +122,116 @@ class ChooseBlockController implements ContainerInjectionInterface {
|
|||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
]);
|
||||
foreach ($this->blockManager->getGroupedDefinitions($definitions) as $category => $blocks) {
|
||||
$build[$category]['#type'] = 'details';
|
||||
$build[$category]['#open'] = TRUE;
|
||||
$build[$category]['#title'] = $category;
|
||||
$build[$category]['links'] = [
|
||||
'#theme' => 'links',
|
||||
];
|
||||
foreach ($blocks as $block_id => $block) {
|
||||
$link = [
|
||||
'title' => $block['admin_label'],
|
||||
'url' => Url::fromRoute('layout_builder.add_block',
|
||||
[
|
||||
'section_storage_type' => $section_storage->getStorageType(),
|
||||
'section_storage' => $section_storage->getStorageId(),
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
'plugin_id' => $block_id,
|
||||
]
|
||||
),
|
||||
];
|
||||
if ($this->isAjax()) {
|
||||
$link['attributes']['class'][] = 'use-ajax';
|
||||
$link['attributes']['data-dialog-type'][] = 'dialog';
|
||||
$link['attributes']['data-dialog-renderer'][] = 'off_canvas';
|
||||
}
|
||||
$build[$category]['links']['#links'][] = $link;
|
||||
$grouped_definitions = $this->blockManager->getGroupedDefinitions($definitions);
|
||||
foreach ($grouped_definitions as $category => $blocks) {
|
||||
$block_categories[$category]['#type'] = 'details';
|
||||
$block_categories[$category]['#open'] = TRUE;
|
||||
$block_categories[$category]['#title'] = $category;
|
||||
$block_categories[$category]['links'] = $this->getBlockLinks($section_storage, $delta, $region, $blocks);
|
||||
}
|
||||
$build['block_categories'] = $block_categories;
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the UI for choosing a new inline block.
|
||||
*
|
||||
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
|
||||
* The section storage.
|
||||
* @param int $delta
|
||||
* The delta of the section to splice.
|
||||
* @param string $region
|
||||
* The region the block is going in.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
public function inlineBlockList(SectionStorageInterface $section_storage, $delta, $region) {
|
||||
$definitions = $this->blockManager->getFilteredDefinitions('layout_builder', $this->getAvailableContexts($section_storage), [
|
||||
'section_storage' => $section_storage,
|
||||
'region' => $region,
|
||||
'list' => 'inline_blocks',
|
||||
]);
|
||||
$blocks = $this->blockManager->getGroupedDefinitions($definitions);
|
||||
$build = [];
|
||||
if (isset($blocks['Inline blocks'])) {
|
||||
$build['links'] = $this->getBlockLinks($section_storage, $delta, $region, $blocks['Inline blocks']);
|
||||
$build['links']['#attributes']['class'][] = 'inline-block-list';
|
||||
foreach ($build['links']['#links'] as &$link) {
|
||||
$link['attributes']['class'][] = 'inline-block-list__item';
|
||||
}
|
||||
$build['back_button'] = [
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('layout_builder.choose_block',
|
||||
[
|
||||
'section_storage_type' => $section_storage->getStorageType(),
|
||||
'section_storage' => $section_storage->getStorageId(),
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
]
|
||||
),
|
||||
'#title' => $this->t('Back'),
|
||||
'#attributes' => $this->getAjaxAttributes(),
|
||||
];
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a render array of block links.
|
||||
*
|
||||
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
|
||||
* The section storage.
|
||||
* @param int $delta
|
||||
* The delta of the section to splice.
|
||||
* @param string $region
|
||||
* The region the block is going in.
|
||||
* @param array $blocks
|
||||
* The information for each block.
|
||||
*
|
||||
* @return array
|
||||
* The block links render array.
|
||||
*/
|
||||
protected function getBlockLinks(SectionStorageInterface $section_storage, $delta, $region, array $blocks) {
|
||||
$links = [];
|
||||
foreach ($blocks as $block_id => $block) {
|
||||
$link = [
|
||||
'title' => $block['admin_label'],
|
||||
'url' => Url::fromRoute('layout_builder.add_block',
|
||||
[
|
||||
'section_storage_type' => $section_storage->getStorageType(),
|
||||
'section_storage' => $section_storage->getStorageId(),
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
'plugin_id' => $block_id,
|
||||
]
|
||||
),
|
||||
'attributes' => $this->getAjaxAttributes(),
|
||||
];
|
||||
|
||||
$links[] = $link;
|
||||
}
|
||||
return [
|
||||
'#theme' => 'links',
|
||||
'#links' => $links,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dialog attributes if an ajax request.
|
||||
*
|
||||
* @return array
|
||||
* The attributes array.
|
||||
*/
|
||||
protected function getAjaxAttributes() {
|
||||
if ($this->isAjax()) {
|
||||
return [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'off_canvas',
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\layout_builder\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxHelperTrait;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
@ -24,6 +25,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
|||
|
||||
use LayoutBuilderContextTrait;
|
||||
use StringTranslationTrait;
|
||||
use AjaxHelperTrait;
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
|
@ -90,6 +92,11 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
|||
$this->prepareLayout($section_storage, $is_rebuilding);
|
||||
|
||||
$output = [];
|
||||
if ($this->isAjax()) {
|
||||
$output['status_messages'] = [
|
||||
'#type' => 'status_messages',
|
||||
];
|
||||
}
|
||||
$count = 0;
|
||||
for ($i = 0; $i < $section_storage->count(); $i++) {
|
||||
$output[] = $this->buildAddSectionLink($section_storage, $count);
|
||||
|
@ -114,6 +121,11 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
|||
* Indicates if the layout is rebuilding.
|
||||
*/
|
||||
protected function prepareLayout(SectionStorageInterface $section_storage, $is_rebuilding) {
|
||||
// If the layout has pending changes, add a warning.
|
||||
if ($this->layoutTempstoreRepository->has($section_storage)) {
|
||||
$this->messenger->addWarning($this->t('You have unsaved changes.'));
|
||||
}
|
||||
|
||||
// Only add sections if the layout is new and empty.
|
||||
if (!$is_rebuilding && $section_storage->count() === 0) {
|
||||
$sections = [];
|
||||
|
@ -269,7 +281,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
|||
],
|
||||
'remove' => [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Remove section'),
|
||||
'#title' => $this->t('Remove section <span class="visually-hidden">@section</span>', ['@section' => $delta + 1]),
|
||||
'#url' => Url::fromRoute('layout_builder.remove_section', [
|
||||
'section_storage_type' => $storage_type,
|
||||
'section_storage' => $storage_id,
|
||||
|
|
|
@ -9,6 +9,7 @@ use Drupal\Core\Plugin\Context\EntityContext;
|
|||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\Section;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
|
||||
|
@ -110,10 +111,10 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
|||
$bundle = $this->getTargetBundle();
|
||||
|
||||
if ($new_value) {
|
||||
$this->addSectionField($entity_type_id, $bundle, 'layout_builder__layout');
|
||||
$this->addSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
|
||||
}
|
||||
else {
|
||||
$this->removeSectionField($entity_type_id, $bundle, 'layout_builder__layout');
|
||||
$this->removeSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,8 +275,8 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
|||
* The sections.
|
||||
*/
|
||||
protected function getRuntimeSections(FieldableEntityInterface $entity) {
|
||||
if ($this->isOverridable() && !$entity->get('layout_builder__layout')->isEmpty()) {
|
||||
return $entity->get('layout_builder__layout')->getSections();
|
||||
if ($this->isOverridable() && !$entity->get(OverridesSectionStorage::FIELD_NAME)->isEmpty()) {
|
||||
return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections();
|
||||
}
|
||||
|
||||
return $this->getSections();
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Drupal\layout_builder\EventSubscriber;
|
|||
use Drupal\block_content\Access\RefinableDependentAccessInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\PreviewFallbackInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
|
||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
|
||||
|
@ -88,6 +90,14 @@ class BlockComponentRenderArray implements EventSubscriberInterface {
|
|||
if ($access->isAllowed()) {
|
||||
$event->addCacheableDependency($block);
|
||||
|
||||
$content = $block->build();
|
||||
$is_content_empty = Element::isEmpty($content);
|
||||
$is_placeholder_ready = $event->inPreview() && $block instanceof PreviewFallbackInterface;
|
||||
// If the content is empty and no placeholder is available, return.
|
||||
if ($is_content_empty && !$is_placeholder_ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
$build = [
|
||||
// @todo Move this to BlockBase in https://www.drupal.org/node/2931040.
|
||||
'#theme' => 'block',
|
||||
|
@ -96,8 +106,11 @@ class BlockComponentRenderArray implements EventSubscriberInterface {
|
|||
'#base_plugin_id' => $block->getBaseId(),
|
||||
'#derivative_plugin_id' => $block->getDerivativeId(),
|
||||
'#weight' => $event->getComponent()->getWeight(),
|
||||
'content' => $block->build(),
|
||||
'content' => $content,
|
||||
];
|
||||
if ($is_content_empty && $is_placeholder_ready) {
|
||||
$build['content']['#markup'] = $block->getPreviewFallbackString();
|
||||
}
|
||||
$event->setBuild($build);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Drupal\Core\Field\FieldDefinitionInterface;
|
|||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\field_ui\Form\EntityViewDisplayEditForm;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\SectionStorageInterface;
|
||||
|
||||
/**
|
||||
|
@ -48,8 +49,8 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
|||
$form = parent::form($form, $form_state);
|
||||
|
||||
// Remove the Layout Builder field from the list.
|
||||
$form['#fields'] = array_diff($form['#fields'], ['layout_builder__layout']);
|
||||
unset($form['fields']['layout_builder__layout']);
|
||||
$form['#fields'] = array_diff($form['#fields'], [OverridesSectionStorage::FIELD_NAME]);
|
||||
unset($form['fields'][OverridesSectionStorage::FIELD_NAME]);
|
||||
|
||||
$is_enabled = $this->entity->isLayoutBuilderEnabled();
|
||||
if ($is_enabled) {
|
||||
|
@ -133,7 +134,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
|||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($display->getTargetEntityTypeId());
|
||||
$query = $this->entityTypeManager->getStorage($display->getTargetEntityTypeId())->getQuery()
|
||||
->exists('layout_builder__layout');
|
||||
->exists(OverridesSectionStorage::FIELD_NAME);
|
||||
if ($bundle_key = $entity_type->getKey('bundle')) {
|
||||
$query->condition($bundle_key, $display->getTargetBundle());
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
|||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
|
||||
/**
|
||||
* Methods to help with entities using the layout builder.
|
||||
|
@ -65,7 +66,7 @@ trait LayoutEntityHelperTrait {
|
|||
return $entity->getSections();
|
||||
}
|
||||
elseif ($this->isEntityUsingFieldOverride($entity)) {
|
||||
return $entity->get('layout_builder__layout')->getSections();
|
||||
return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -102,7 +103,7 @@ trait LayoutEntityHelperTrait {
|
|||
* TRUE if the entity is using a field for a layout override.
|
||||
*/
|
||||
protected function isEntityUsingFieldOverride(EntityInterface $entity) {
|
||||
return $entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout');
|
||||
return $entity instanceof FieldableEntityInterface && $entity->hasField(OverridesSectionStorage::FIELD_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,15 @@ class LayoutTempstoreRepository implements LayoutTempstoreRepositoryInterface {
|
|||
return $section_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has(SectionStorageInterface $section_storage) {
|
||||
$id = $section_storage->getStorageId();
|
||||
$tempstore = $this->getTempstore($section_storage)->get($id);
|
||||
return !empty($tempstore['section_storage']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,17 @@ interface LayoutTempstoreRepositoryInterface {
|
|||
*/
|
||||
public function set(SectionStorageInterface $section_storage);
|
||||
|
||||
/**
|
||||
* Checks for the existence of a tempstore version of a section storage.
|
||||
*
|
||||
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
|
||||
* The section storage to check for in tempstore.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there is a tempstore version of this section storage.
|
||||
*/
|
||||
public function has(SectionStorageInterface $section_storage);
|
||||
|
||||
/**
|
||||
* Removes the tempstore version of a section storage.
|
||||
*
|
||||
|
|
|
@ -130,13 +130,22 @@ class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface,
|
|||
// render array. If the hook is invoked the placeholder will be
|
||||
// replaced.
|
||||
// @see ::replaceFieldPlaceholder()
|
||||
'#markup' => new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]),
|
||||
'#markup' => $this->getPreviewFallbackString(),
|
||||
];
|
||||
}
|
||||
CacheableMetadata::createFromObject($this)->applyTo($build);
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPreviewFallbackString() {
|
||||
$entity = $this->getEntity();
|
||||
$extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
|
||||
return new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all placeholders for a given field.
|
||||
*
|
||||
|
|
|
@ -17,7 +17,6 @@ use Drupal\Core\Field\FormatterPluginManager;
|
|||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
@ -160,13 +159,17 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta
|
|||
$build = [];
|
||||
$this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]);
|
||||
}
|
||||
if (!empty($entity->in_preview) && !Element::getVisibleChildren($build)) {
|
||||
$build['content']['#markup'] = new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
|
||||
}
|
||||
CacheableMetadata::createFromObject($this)->applyTo($build);
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPreviewFallbackString() {
|
||||
return new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -106,7 +106,7 @@ class FieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface
|
|||
$derivative['default_formatter'] = $field_type_definition['default_formatter'];
|
||||
}
|
||||
|
||||
$derivative['category'] = $this->t('@entity', ['@entity' => $entity_type_labels[$entity_type_id]]);
|
||||
$derivative['category'] = $this->t('@entity fields', ['@entity' => $entity_type_labels[$entity_type_id]]);
|
||||
|
||||
$derivative['admin_label'] = $field_definition->getLabel();
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
namespace Drupal\layout_builder\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\SectionStorageLocalTaskProviderInterface;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -28,14 +28,24 @@ class LayoutBuilderLocalTaskDeriver extends DeriverBase implements ContainerDeri
|
|||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The section storage manager.
|
||||
*
|
||||
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
||||
*/
|
||||
protected $sectionStorageManager;
|
||||
|
||||
/**
|
||||
* Constructs a new LayoutBuilderLocalTaskDeriver.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
||||
* The section storage manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, SectionStorageManagerInterface $section_storage_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->sectionStorageManager = $section_storage_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +53,8 @@ class LayoutBuilderLocalTaskDeriver extends DeriverBase implements ContainerDeri
|
|||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('plugin.manager.layout_builder.section_storage')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -51,84 +62,13 @@ class LayoutBuilderLocalTaskDeriver extends DeriverBase implements ContainerDeri
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->getEntityTypesForOverrides() as $entity_type_id => $entity_type) {
|
||||
// Overrides.
|
||||
$this->derivatives["layout_builder.overrides.$entity_type_id.view"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 15,
|
||||
'title' => $this->t('Layout'),
|
||||
'base_route' => "entity.$entity_type_id.canonical",
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
$this->derivatives["layout_builder.overrides.$entity_type_id.save"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.save",
|
||||
'title' => $this->t('Save Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
$this->derivatives["layout_builder.overrides.$entity_type_id.cancel"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.cancel",
|
||||
'title' => $this->t('Cancel Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 5,
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
// @todo This link should be conditionally displayed, see
|
||||
// https://www.drupal.org/node/2917777.
|
||||
$this->derivatives["layout_builder.overrides.$entity_type_id.revert"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.revert",
|
||||
'title' => $this->t('Revert to defaults'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 10,
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
foreach ($this->sectionStorageManager->getDefinitions() as $plugin_id => $definition) {
|
||||
$section_storage = $this->sectionStorageManager->loadEmpty($plugin_id);
|
||||
if ($section_storage instanceof SectionStorageLocalTaskProviderInterface) {
|
||||
$this->derivatives += $section_storage->buildLocalTasks($base_plugin_definition);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getEntityTypesForDefaults() as $entity_type_id => $entity_type) {
|
||||
// Defaults.
|
||||
$this->derivatives["layout_builder.defaults.$entity_type_id.view"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.view",
|
||||
'title' => $this->t('Manage layout'),
|
||||
'base_route' => "layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
$this->derivatives["layout_builder.defaults.$entity_type_id.save"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.save",
|
||||
'title' => $this->t('Save Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
$this->derivatives["layout_builder.defaults.$entity_type_id.cancel"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.cancel",
|
||||
'title' => $this->t('Cancel Layout'),
|
||||
'weight' => 5,
|
||||
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
}
|
||||
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of entity types relevant for defaults.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
* An array of entity types.
|
||||
*/
|
||||
protected function getEntityTypesForDefaults() {
|
||||
return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->get('field_ui_base_route');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of entity types relevant for overrides.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
* An array of entity types.
|
||||
*/
|
||||
protected function getEntityTypesForOverrides() {
|
||||
return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ use Symfony\Component\Routing\RouteCollection;
|
|||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*/
|
||||
class DefaultsSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, DefaultsSectionStorageInterface {
|
||||
class DefaultsSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, DefaultsSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
|
@ -196,6 +196,32 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildLocalTasks($base_plugin_definition) {
|
||||
$local_tasks = [];
|
||||
foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$local_tasks["layout_builder.defaults.$entity_type_id.view"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.view",
|
||||
'title' => $this->t('Manage layout'),
|
||||
'base_route' => "layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
$local_tasks["layout_builder.defaults.$entity_type_id.save"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.save",
|
||||
'title' => $this->t('Save Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
$local_tasks["layout_builder.defaults.$entity_type_id.cancel"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.defaults.$entity_type_id.cancel",
|
||||
'title' => $this->t('Cancel Layout'),
|
||||
'weight' => 5,
|
||||
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
|
||||
];
|
||||
}
|
||||
return $local_tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of relevant entity types.
|
||||
*
|
||||
|
|
|
@ -30,7 +30,14 @@ use Symfony\Component\Routing\RouteCollection;
|
|||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*/
|
||||
class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface {
|
||||
class OverridesSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, OverridesSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
|
||||
|
||||
/**
|
||||
* The field name used by this storage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FIELD_NAME = 'layout_builder__layout';
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
|
@ -127,8 +134,8 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
|||
if (strpos($id, '.') !== FALSE) {
|
||||
list($entity_type_id, $entity_id) = explode('.', $id, 2);
|
||||
$entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
|
||||
if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) {
|
||||
return $entity->get('layout_builder__layout');
|
||||
if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) {
|
||||
return $entity->get(static::FIELD_NAME);
|
||||
}
|
||||
}
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType()));
|
||||
|
@ -157,6 +164,45 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildLocalTasks($base_plugin_definition) {
|
||||
$local_tasks = [];
|
||||
foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$local_tasks["layout_builder.overrides.$entity_type_id.view"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 15,
|
||||
'title' => $this->t('Layout'),
|
||||
'base_route' => "entity.$entity_type_id.canonical",
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
$local_tasks["layout_builder.overrides.$entity_type_id.save"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.save",
|
||||
'title' => $this->t('Save Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
$local_tasks["layout_builder.overrides.$entity_type_id.cancel"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.cancel",
|
||||
'title' => $this->t('Cancel Layout'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 5,
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
// @todo This link should be conditionally displayed, see
|
||||
// https://www.drupal.org/node/2917777.
|
||||
$local_tasks["layout_builder.overrides.$entity_type_id.revert"] = $base_plugin_definition + [
|
||||
'route_name' => "layout_builder.overrides.$entity_type_id.revert",
|
||||
'title' => $this->t('Revert to defaults'),
|
||||
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
|
||||
'weight' => 10,
|
||||
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
|
||||
];
|
||||
}
|
||||
return $local_tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this entity type's ID is stored as an integer.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
|
||||
|
||||
/**
|
||||
* Allows section storage plugins to provide local tasks.
|
||||
*
|
||||
* @see \Drupal\layout_builder\Plugin\Derivative\LayoutBuilderLocalTaskDeriver
|
||||
* @see \Drupal\layout_builder\SectionStorageInterface
|
||||
*
|
||||
* @internal
|
||||
* Layout Builder is currently experimental and should only be leveraged by
|
||||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*/
|
||||
interface SectionStorageLocalTaskProviderInterface {
|
||||
|
||||
/**
|
||||
* Provides the local tasks dynamically for Layout Builder plugins.
|
||||
*
|
||||
* @param mixed $base_plugin_definition
|
||||
* The definition of the base plugin.
|
||||
*
|
||||
* @return array
|
||||
* An array of full derivative definitions keyed on derivative ID.
|
||||
*/
|
||||
public function buildLocalTasks($base_plugin_definition);
|
||||
|
||||
}
|
|
@ -356,4 +356,13 @@ class Section {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method: Implements a deep clone.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->components as $uuid => $component) {
|
||||
$this->components[$uuid] = clone $component;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,4 +111,17 @@ trait SectionStorageTrait {
|
|||
return isset($this->getSections()[$delta]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method: Implements a deep clone.
|
||||
*/
|
||||
public function __clone() {
|
||||
$sections = $this->getSections();
|
||||
|
||||
foreach ($sections as $delta => $item) {
|
||||
$sections[$delta] = clone $item;
|
||||
}
|
||||
|
||||
$this->setSections($sections);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# See \Drupal\layout_builder_fieldblock_test\Plugin\Block\FieldBlock.
|
||||
block.settings.field_block_test:*:*:*:
|
||||
type: block.settings.field_block:*:*:*
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Layout Builder test'
|
||||
type: module
|
||||
description: 'Support module for testing layout building.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\layout_builder_fieldblock_test\Plugin\Block;
|
||||
|
||||
use Drupal\layout_builder\Plugin\Block\FieldBlock as LayoutBuilderFieldBlock;
|
||||
|
||||
/**
|
||||
* Provides test field block to test with Block UI.
|
||||
*
|
||||
* \Drupal\Tests\layout_builder\FunctionalJavascript\FieldBlockTest provides
|
||||
* test coverage of complex AJAX interactions within certain field blocks.
|
||||
* layout_builder_plugin_filter_block__block_ui_alter() removes certain blocks
|
||||
* with 'layout_builder' as the provider. To make these blocks available during
|
||||
* testing, this plugin uses the same deriver but each derivative will have a
|
||||
* different provider.
|
||||
*
|
||||
* @Block(
|
||||
* id = "field_block_test",
|
||||
* deriver = "\Drupal\layout_builder\Plugin\Derivative\FieldBlockDeriver",
|
||||
* )
|
||||
*
|
||||
* @see \Drupal\Tests\layout_builder\FunctionalJavascript\FieldBlockTest
|
||||
* @see layout_builder_plugin_filter_block__block_ui_alter()
|
||||
*/
|
||||
class FieldBlock extends LayoutBuilderFieldBlock {
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ class LayoutBuilderTest extends BrowserTestBase {
|
|||
'layout_builder_views_test',
|
||||
'layout_test',
|
||||
'block',
|
||||
'block_test',
|
||||
'node',
|
||||
'layout_builder_test',
|
||||
];
|
||||
|
@ -90,7 +91,7 @@ class LayoutBuilderTest extends BrowserTestBase {
|
|||
// The body field is only present once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
// The extra field is only present once.
|
||||
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
|
||||
$assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field');
|
||||
// Save the defaults.
|
||||
$assert_session->linkExists('Save Layout');
|
||||
$this->clickLink('Save Layout');
|
||||
|
@ -105,7 +106,7 @@ class LayoutBuilderTest extends BrowserTestBase {
|
|||
// The body field is only present once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
// The extra field is only present once.
|
||||
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
|
||||
$assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field');
|
||||
|
||||
// Add a new block.
|
||||
$assert_session->linkExists('Add Block');
|
||||
|
@ -316,6 +317,11 @@ class LayoutBuilderTest extends BrowserTestBase {
|
|||
$page->fillField('id', 'myothermenu');
|
||||
$page->pressButton('Save');
|
||||
|
||||
$page->clickLink('Add link');
|
||||
$page->fillField('title[0][value]', 'My link');
|
||||
$page->fillField('link[0][uri]', '/');
|
||||
$page->pressButton('Save');
|
||||
|
||||
$this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
|
@ -514,13 +520,68 @@ class LayoutBuilderTest extends BrowserTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Asserts that a text string only appears once on the page.
|
||||
* Tests the usage of placeholders for empty blocks.
|
||||
*
|
||||
* @param string $needle
|
||||
* The string to look for.
|
||||
* @see \Drupal\Core\Block\BlockPluginInterface::getPlaceholderString()
|
||||
* @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
|
||||
*/
|
||||
protected function assertTextAppearsOnce($needle) {
|
||||
$this->assertEquals(1, substr_count($this->getSession()->getPage()->getContent(), $needle), "'$needle' only appears once on the page.");
|
||||
public function testBlockPlaceholder() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
]));
|
||||
|
||||
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
|
||||
|
||||
// Customize the default view mode.
|
||||
$this->drupalGet("$field_ui_prefix/display-layout/default");
|
||||
|
||||
// Add a block whose content is controlled by state and is empty by default.
|
||||
$this->clickLink('Add Block');
|
||||
$this->clickLink('Test block caching');
|
||||
$page->fillField('settings[label]', 'The block label');
|
||||
$page->pressButton('Add Block');
|
||||
|
||||
$block_content = 'I am content';
|
||||
$placeholder_content = 'Placeholder for the "The block label" block';
|
||||
|
||||
// The block placeholder is displayed and there is no content.
|
||||
$assert_session->pageTextContains($placeholder_content);
|
||||
$assert_session->pageTextNotContains($block_content);
|
||||
|
||||
// Set block content and reload the page.
|
||||
\Drupal::state()->set('block_test.content', $block_content);
|
||||
$this->getSession()->reload();
|
||||
|
||||
// The block placeholder is no longer displayed and the content is visible.
|
||||
$assert_session->pageTextNotContains($placeholder_content);
|
||||
$assert_session->pageTextContains($block_content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Block UI when Layout Builder is installed.
|
||||
*/
|
||||
public function testBlockUiListing() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer blocks',
|
||||
]));
|
||||
|
||||
$this->drupalGet('admin/structure/block');
|
||||
$page->clickLink('Place block');
|
||||
|
||||
// Ensure that blocks expected to appear are available.
|
||||
$assert_session->pageTextContains('Test HTML block');
|
||||
$assert_session->pageTextContains('Block test');
|
||||
// Ensure that blocks not expected to appear are not available.
|
||||
$assert_session->pageTextNotContains('Body');
|
||||
$assert_session->pageTextNotContains('Content fields');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\Tests\layout_builder\Functional;
|
|||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\Section;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
@ -20,13 +21,6 @@ class LayoutSectionTest extends BrowserTestBase {
|
|||
*/
|
||||
public static $modules = ['field_ui', 'layout_builder', 'node', 'block_test'];
|
||||
|
||||
/**
|
||||
* The name of the layout section field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = 'layout_builder__layout';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -226,7 +220,7 @@ class LayoutSectionTest extends BrowserTestBase {
|
|||
]);
|
||||
$entity->addTranslation('es', [
|
||||
'title' => 'Translated node title',
|
||||
$this->fieldName => [
|
||||
OverridesSectionStorage::FIELD_NAME => [
|
||||
[
|
||||
'section' => new Section('layout_twocol', [], [
|
||||
'foo' => new SectionComponent('foo', 'first', [
|
||||
|
@ -373,7 +367,7 @@ class LayoutSectionTest extends BrowserTestBase {
|
|||
'value' => 'The node body',
|
||||
],
|
||||
],
|
||||
$this->fieldName => $section_values,
|
||||
OverridesSectionStorage::FIELD_NAME => $section_values,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,14 @@ class FieldBlockTest extends WebDriverTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'datetime', 'layout_builder', 'user'];
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'datetime',
|
||||
'layout_builder',
|
||||
'user',
|
||||
// See \Drupal\layout_builder_fieldblock_test\Plugin\Block\FieldBlock.
|
||||
'layout_builder_fieldblock_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -67,7 +74,7 @@ class FieldBlockTest extends WebDriverTestBase {
|
|||
$assert_session->pageTextNotContains('Initial email');
|
||||
|
||||
$assert_session->pageTextContains('Date field');
|
||||
$block_url = 'admin/structure/block/add/field_block%3Auser%3Auser%3Afield_date/classy';
|
||||
$block_url = 'admin/structure/block/add/field_block_test%3Auser%3Auser%3Afield_date/classy';
|
||||
$assert_session->linkByHrefExists($block_url);
|
||||
|
||||
$this->drupalGet($block_url);
|
||||
|
|
|
@ -192,8 +192,8 @@ class InlineBlockPrivateFilesTest extends InlineBlockTestBase {
|
|||
$page = $this->getSession()->getPage();
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-categories details:contains(Create new block)'));
|
||||
$this->clickLink('Basic block');
|
||||
$this->assertNotEmpty($assert_session->waitForLink('Create custom block'));
|
||||
$this->clickLink('Create custom block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->fieldValueEquals('Title', '');
|
||||
$page->findField('Title')->setValue($title);
|
||||
|
|
|
@ -428,4 +428,74 @@ class InlineBlockTest extends InlineBlockTestBase {
|
|||
$assert_session->pageTextNotContains('You are not authorized to access this page');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the workflow for adding an inline block depending on number of types.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
* @throws \Behat\Mink\Exception\ExpectationException
|
||||
*/
|
||||
public function testAddWorkFlow() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$type_storage = $this->container->get('entity_type.manager')->getStorage('block_content_type');
|
||||
foreach ($type_storage->loadByProperties() as $type) {
|
||||
$type->delete();
|
||||
}
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
// Enable layout builder and overrides.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
|
||||
$layout_default_path = 'admin/structure/types/manage/bundle_with_section_field/display-layout/default';
|
||||
$this->drupalGet($layout_default_path);
|
||||
// Add a basic block with the body field set.
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// Confirm that with no block content types the link does not appear.
|
||||
$assert_session->linkNotExists('Create custom block');
|
||||
|
||||
$this->createBlockContentType('basic', 'Basic block');
|
||||
|
||||
$this->drupalGet($layout_default_path);
|
||||
// Add a basic block with the body field set.
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// Confirm with only 1 type the "Create custom block" link goes directly t
|
||||
// block add form.
|
||||
$assert_session->linkNotExists('Basic block');
|
||||
$this->clickLink('Create custom block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->fieldExists('Title');
|
||||
|
||||
$this->createBlockContentType('advanced', 'Advanced block');
|
||||
|
||||
$this->drupalGet($layout_default_path);
|
||||
// Add a basic block with the body field set.
|
||||
$page->clickLink('Add Block');
|
||||
// Confirm that, when more than 1 type exists, "Create custom block" shows a
|
||||
// list of block types.
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->linkNotExists('Basic block');
|
||||
$assert_session->linkNotExists('Advanced block');
|
||||
$this->clickLink('Create custom block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->fieldNotExists('Title');
|
||||
$assert_session->linkExists('Basic block');
|
||||
$assert_session->linkExists('Advanced block');
|
||||
|
||||
$this->clickLink('Advanced block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->fieldExists('Title');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,13 +71,7 @@ abstract class InlineBlockTestBase extends WebDriverTestBase {
|
|||
],
|
||||
],
|
||||
]);
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'Basic block',
|
||||
'revision' => 1,
|
||||
]);
|
||||
$bundle->save();
|
||||
block_content_add_body_field($bundle->id());
|
||||
$this->createBlockContentType('basic', 'Basic block');
|
||||
|
||||
$this->blockStorage = $this->container->get('entity_type.manager')->getStorage('block_content');
|
||||
}
|
||||
|
@ -146,8 +140,8 @@ abstract class InlineBlockTestBase extends WebDriverTestBase {
|
|||
$page = $this->getSession()->getPage();
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-categories details:contains(Create new block)'));
|
||||
$this->clickLink('Basic block');
|
||||
$this->assertNotEmpty($assert_session->waitForLink('Create custom block'));
|
||||
$this->clickLink('Create custom block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$textarea = $assert_session->waitForElement('css', '[name="settings[block_form][body][0][value]"]');
|
||||
$this->assertNotEmpty($textarea);
|
||||
|
@ -219,4 +213,22 @@ abstract class InlineBlockTestBase extends WebDriverTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block content type.
|
||||
*
|
||||
* @param string $id
|
||||
* The block type id.
|
||||
* @param string $label
|
||||
* The block type label.
|
||||
*/
|
||||
protected function createBlockContentType($id, $label) {
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'revision' => 1,
|
||||
]);
|
||||
$bundle->save();
|
||||
block_content_add_body_field($bundle->id());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Layout Builder UI.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class LayoutBuilderUiTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* Path prefix for the field UI for the test bundle.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FIELD_UI_PREFIX = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
|
||||
public static $modules = [
|
||||
'layout_builder',
|
||||
'block',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// @todo The Layout Builder UI relies on local tasks; fix in
|
||||
// https://www.drupal.org/project/drupal/issues/2917777.
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
|
||||
$this->createContentType(['type' => 'bundle_with_section_field']);
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the message indicating unsaved changes.
|
||||
*/
|
||||
public function testUnsavedChangesMessage() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Enable layout builder.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
|
||||
// Make and then cancel changes.
|
||||
$this->assertModifiedLayout(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
$page->clickLink('Cancel Layout');
|
||||
$assert_session->pageTextNotContains('You have unsaved changes.');
|
||||
|
||||
// Make and then save changes.
|
||||
$this->assertModifiedLayout(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
$page->clickLink('Save Layout');
|
||||
$assert_session->pageTextNotContains('You have unsaved changes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that modifying a layout works as expected.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to a Layout Builder UI page.
|
||||
*/
|
||||
protected function assertModifiedLayout($path) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalGet($path);
|
||||
$page->clickLink('Add Section');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextNotContains('You have unsaved changes.');
|
||||
$page->clickLink('One column');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextContainsOnce('You have unsaved changes.');
|
||||
|
||||
// Reload the page.
|
||||
$this->drupalGet($path);
|
||||
$assert_session->pageTextContainsOnce('You have unsaved changes.');
|
||||
}
|
||||
|
||||
}
|
|
@ -230,11 +230,10 @@ class FieldBlockTest extends EntityKernelTestBase {
|
|||
* @covers ::build
|
||||
* @dataProvider providerTestBuild
|
||||
*/
|
||||
public function testBuild(PromiseInterface $promise, $in_preview, $expected_markup, $log_message = '', $log_arguments = []) {
|
||||
public function testBuild(PromiseInterface $promise, $expected_markup, $log_message = '', $log_arguments = []) {
|
||||
$entity = $this->prophesize(FieldableEntityInterface::class);
|
||||
$field = $this->prophesize(FieldItemListInterface::class);
|
||||
$entity->get('the_field_name')->willReturn($field->reveal());
|
||||
$entity->in_preview = $in_preview;
|
||||
$field->view(Argument::type('array'))->will($promise);
|
||||
|
||||
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
|
||||
|
@ -269,40 +268,20 @@ class FieldBlockTest extends EntityKernelTestBase {
|
|||
*/
|
||||
public function providerTestBuild() {
|
||||
$data = [];
|
||||
$data['array, no preview'] = [
|
||||
$data['array'] = [
|
||||
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
|
||||
FALSE,
|
||||
'The field value',
|
||||
];
|
||||
$data['array, preview'] = [
|
||||
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
|
||||
TRUE,
|
||||
'The field value',
|
||||
];
|
||||
$data['empty array, no preview'] = [
|
||||
$data['empty array'] = [
|
||||
new ReturnPromise([[]]),
|
||||
FALSE,
|
||||
'',
|
||||
];
|
||||
$data['empty array, preview'] = [
|
||||
new ReturnPromise([[]]),
|
||||
TRUE,
|
||||
'Placeholder for the "The Field Label" field',
|
||||
];
|
||||
$data['exception, no preview'] = [
|
||||
$data['exception'] = [
|
||||
new ThrowPromise(new \Exception('The exception message')),
|
||||
FALSE,
|
||||
'',
|
||||
'The field "%field" failed to render with the error of "%error".',
|
||||
['%field' => 'the_field_name', '%error' => 'The exception message'],
|
||||
];
|
||||
$data['exception, preview'] = [
|
||||
new ThrowPromise(new \Exception('The exception message')),
|
||||
TRUE,
|
||||
'Placeholder for the "The Field Label" field',
|
||||
'The field "%field" failed to render with the error of "%error".',
|
||||
['%field' => 'the_field_name', '%error' => 'The exception message'],
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\layout_builder\Kernel;
|
||||
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\Section;
|
||||
|
||||
/**
|
||||
|
@ -56,7 +57,7 @@ class LayoutBuilderFieldLayoutCompatibilityTest extends LayoutBuilderCompatibili
|
|||
// Add a layout override.
|
||||
$this->enableOverrides();
|
||||
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
|
||||
$field_list = $this->entity->get('layout_builder__layout');
|
||||
$field_list = $this->entity->get(OverridesSectionStorage::FIELD_NAME);
|
||||
$field_list->appendSection(new Section('layout_onecol'));
|
||||
$this->entity->save();
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\Tests\layout_builder\Kernel;
|
|||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
use Drupal\layout_builder\Section;
|
||||
|
||||
/**
|
||||
|
@ -35,7 +36,7 @@ class LayoutBuilderInstallTest extends LayoutBuilderCompatibilityTestBase {
|
|||
// Add a layout override.
|
||||
$this->enableOverrides();
|
||||
$this->entity = $this->reloadEntity($this->entity);
|
||||
$this->entity->get('layout_builder__layout')->appendSection(new Section('layout_onecol'));
|
||||
$this->entity->get(OverridesSectionStorage::FIELD_NAME)->appendSection(new Section('layout_onecol'));
|
||||
$this->entity->save();
|
||||
|
||||
// The rendered entity has now changed. The non-configurable field is shown
|
||||
|
@ -50,7 +51,7 @@ class LayoutBuilderInstallTest extends LayoutBuilderCompatibilityTestBase {
|
|||
$this->assertNotEmpty($this->cssSelect('.layout--onecol'));
|
||||
|
||||
// Removing the layout restores the original rendering of the entity.
|
||||
$this->entity->get('layout_builder__layout')->removeSection(0);
|
||||
$this->entity->get(OverridesSectionStorage::FIELD_NAME)->removeSection(0);
|
||||
$this->entity->save();
|
||||
$this->assertFieldAttributes($this->entity, $expected_fields);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\Tests\layout_builder\Kernel;
|
|||
|
||||
use Drupal\entity_test\Entity\EntityTestBaseFieldDisplay;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||
|
||||
/**
|
||||
* Tests the field type for Layout Sections.
|
||||
|
@ -42,10 +43,10 @@ class LayoutSectionItemListTest extends SectionStorageTestBase {
|
|||
}, $section_data);
|
||||
$entity = EntityTestBaseFieldDisplay::create([
|
||||
'name' => 'The test entity',
|
||||
'layout_builder__layout' => $section_data,
|
||||
OverridesSectionStorage::FIELD_NAME => $section_data,
|
||||
]);
|
||||
$entity->save();
|
||||
return $entity->get('layout_builder__layout');
|
||||
return $entity->get(OverridesSectionStorage::FIELD_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -137,6 +137,17 @@ abstract class SectionStorageTestBase extends EntityKernelTestBase {
|
|||
$this->assertSections($expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests __clone().
|
||||
*/
|
||||
public function testClone() {
|
||||
$this->assertSame([], $this->sectionStorage->getSection(0)->getLayoutSettings());
|
||||
|
||||
$new_section_storage = clone $this->sectionStorage;
|
||||
$new_section_storage->getSection(0)->setLayoutSettings(['asdf' => 'qwer']);
|
||||
$this->assertSame([], $this->sectionStorage->getSection(0)->getLayoutSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field list has the expected sections.
|
||||
*
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Core\Cache\Cache;
|
|||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
|
||||
use Drupal\Core\Render\PreviewFallbackInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
|
||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
|
||||
|
@ -252,6 +253,98 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
|||
$this->assertEquals($expected_cache, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*/
|
||||
public function testOnBuildRenderInPreviewEmptyBuild() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class)->willImplement(PreviewFallbackInterface::class);
|
||||
|
||||
$block->access($this->account->reveal(), TRUE)->shouldNotBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
$block->getCacheTags()->willReturn(['test']);
|
||||
$block->getCacheMaxAge()->willReturn(Cache::PERMANENT);
|
||||
$block->getConfiguration()->willReturn([]);
|
||||
$block->getPluginId()->willReturn('block_plugin_id');
|
||||
$block->getBaseId()->willReturn('block_plugin_id');
|
||||
$block->getDerivativeId()->willReturn(NULL);
|
||||
$placeholder_string = 'The placeholder string';
|
||||
$block->getPreviewFallbackString()->willReturn($placeholder_string);
|
||||
|
||||
$block_content = [];
|
||||
$block->build()->willReturn($block_content);
|
||||
$this->blockManager->createInstance('some_block_id', ['id' => 'some_block_id'])->willReturn($block->reveal());
|
||||
|
||||
$component = new SectionComponent('some-uuid', 'some-region', ['id' => 'some_block_id']);
|
||||
$event = new SectionComponentBuildRenderArrayEvent($component, [], TRUE);
|
||||
|
||||
$subscriber = new BlockComponentRenderArray($this->account->reveal());
|
||||
|
||||
$expected_build = [
|
||||
'#theme' => 'block',
|
||||
'#weight' => 0,
|
||||
'#configuration' => [],
|
||||
'#plugin_id' => 'block_plugin_id',
|
||||
'#base_plugin_id' => 'block_plugin_id',
|
||||
'#derivative_plugin_id' => NULL,
|
||||
'content' => $block_content,
|
||||
];
|
||||
$expected_build['content']['#markup'] = $placeholder_string;
|
||||
|
||||
$expected_cache = $expected_build + [
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => ['test'],
|
||||
'max-age' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$subscriber->onBuildRender($event);
|
||||
$result = $event->getBuild();
|
||||
$this->assertEquals($expected_build, $result);
|
||||
$event->getCacheableMetadata()->applyTo($result);
|
||||
$this->assertEquals($expected_cache, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*/
|
||||
public function testOnBuildRenderEmptyBuild() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
$access_result = AccessResult::allowed();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result)->shouldBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
$block->getCacheTags()->willReturn(['test']);
|
||||
$block->getCacheMaxAge()->willReturn(Cache::PERMANENT);
|
||||
$block->getConfiguration()->willReturn([]);
|
||||
$block->getPluginId()->willReturn('block_plugin_id');
|
||||
$block->getBaseId()->willReturn('block_plugin_id');
|
||||
$block->getDerivativeId()->willReturn(NULL);
|
||||
|
||||
$block->build()->willReturn([]);
|
||||
$this->blockManager->createInstance('some_block_id', ['id' => 'some_block_id'])->willReturn($block->reveal());
|
||||
|
||||
$component = new SectionComponent('some-uuid', 'some-region', ['id' => 'some_block_id']);
|
||||
$event = new SectionComponentBuildRenderArrayEvent($component, [], FALSE);
|
||||
|
||||
$subscriber = new BlockComponentRenderArray($this->account->reveal());
|
||||
|
||||
$expected_build = [];
|
||||
|
||||
$expected_cache = $expected_build + [
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => ['test'],
|
||||
'max-age' => -1,
|
||||
],
|
||||
];
|
||||
|
||||
$subscriber->onBuildRender($event);
|
||||
$result = $event->getBuild();
|
||||
$this->assertEquals($expected_build, $result);
|
||||
$event->getCacheableMetadata()->applyTo($result);
|
||||
$this->assertEquals($expected_cache, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,7 @@ class LayoutTempstoreRepositoryTest extends UnitTestCase {
|
|||
|
||||
/**
|
||||
* @covers ::get
|
||||
* @covers ::has
|
||||
*/
|
||||
public function testGetEmptyTempstore() {
|
||||
$section_storage = $this->prophesize(SectionStorageInterface::class);
|
||||
|
@ -30,12 +31,15 @@ class LayoutTempstoreRepositoryTest extends UnitTestCase {
|
|||
|
||||
$repository = new LayoutTempstoreRepository($tempstore_factory->reveal());
|
||||
|
||||
$this->assertFalse($repository->has($section_storage->reveal()));
|
||||
|
||||
$result = $repository->get($section_storage->reveal());
|
||||
$this->assertSame($section_storage->reveal(), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
* @covers ::has
|
||||
*/
|
||||
public function testGetLoadedTempstore() {
|
||||
$section_storage = $this->prophesize(SectionStorageInterface::class);
|
||||
|
@ -50,6 +54,8 @@ class LayoutTempstoreRepositoryTest extends UnitTestCase {
|
|||
|
||||
$repository = new LayoutTempstoreRepository($tempstore_factory->reveal());
|
||||
|
||||
$this->assertTrue($repository->has($section_storage->reveal()));
|
||||
|
||||
$result = $repository->get($section_storage->reveal());
|
||||
$this->assertSame($tempstore_section_storage->reveal(), $result);
|
||||
$this->assertNotSame($section_storage->reveal(), $result);
|
||||
|
|
|
@ -112,13 +112,13 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
|||
$entity_storage = $this->prophesize(EntityStorageInterface::class);
|
||||
|
||||
$entity_without_layout = $this->prophesize(FieldableEntityInterface::class);
|
||||
$entity_without_layout->hasField('layout_builder__layout')->willReturn(FALSE);
|
||||
$entity_without_layout->get('layout_builder__layout')->shouldNotBeCalled();
|
||||
$entity_without_layout->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(FALSE);
|
||||
$entity_without_layout->get(OverridesSectionStorage::FIELD_NAME)->shouldNotBeCalled();
|
||||
$entity_storage->load('entity_without_layout')->willReturn($entity_without_layout->reveal());
|
||||
|
||||
$entity_with_layout = $this->prophesize(FieldableEntityInterface::class);
|
||||
$entity_with_layout->hasField('layout_builder__layout')->willReturn(TRUE);
|
||||
$entity_with_layout->get('layout_builder__layout')->willReturn('the_return_value');
|
||||
$entity_with_layout->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(TRUE);
|
||||
$entity_with_layout->get(OverridesSectionStorage::FIELD_NAME)->willReturn('the_return_value');
|
||||
$entity_storage->load('entity_with_layout')->willReturn($entity_with_layout->reveal());
|
||||
|
||||
$this->entityTypeManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal());
|
||||
|
|
|
@ -231,6 +231,7 @@ class SectionRenderTest extends UnitTestCase {
|
|||
* @covers ::toRenderArray
|
||||
*/
|
||||
public function testContextAwareBlock() {
|
||||
$block_content = ['#markup' => 'The block content.'];
|
||||
$render_array = [
|
||||
'#theme' => 'block',
|
||||
'#weight' => 0,
|
||||
|
@ -238,7 +239,7 @@ class SectionRenderTest extends UnitTestCase {
|
|||
'#plugin_id' => 'block_plugin_id',
|
||||
'#base_plugin_id' => 'block_plugin_id',
|
||||
'#derivative_plugin_id' => NULL,
|
||||
'content' => [],
|
||||
'content' => $block_content,
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => [],
|
||||
|
@ -251,7 +252,7 @@ class SectionRenderTest extends UnitTestCase {
|
|||
|
||||
$access_result = AccessResult::allowed();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result);
|
||||
$block->build()->willReturn([]);
|
||||
$block->build()->willReturn($block_content);
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
$block->getCacheTags()->willReturn([]);
|
||||
$block->getCacheMaxAge()->willReturn(Cache::PERMANENT);
|
||||
|
|
|
@ -185,20 +185,11 @@ class Media extends EditorialContentEntityBase implements MediaInterface {
|
|||
// Set the thumbnail alt.
|
||||
$media_source = $this->getSource();
|
||||
$plugin_definition = $media_source->getPluginDefinition();
|
||||
|
||||
$this->thumbnail->alt = '';
|
||||
if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) {
|
||||
$this->thumbnail->alt = $media_source->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']);
|
||||
}
|
||||
else {
|
||||
$this->thumbnail->alt = $this->t('Thumbnail', [], ['langcode' => $this->langcode->value]);
|
||||
}
|
||||
|
||||
// Set the thumbnail title.
|
||||
if (!empty($plugin_definition['thumbnail_title_metadata_attribute'])) {
|
||||
$this->thumbnail->title = $media_source->getMetadata($this, $plugin_definition['thumbnail_title_metadata_attribute']);
|
||||
}
|
||||
else {
|
||||
$this->thumbnail->title = $this->label();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -304,6 +304,17 @@ class MediaTypeForm extends EntityForm {
|
|||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
|
||||
// If the media source has not been chosen yet, turn the submit button into
|
||||
// a button. This rebuilds the form with the media source's configuration
|
||||
// form visible, instead of saving the media type. This allows users to
|
||||
// create a media type without JavaScript enabled. With JavaScript enabled,
|
||||
// this rebuild occurs during an AJAX request.
|
||||
// @see \Drupal\media\MediaTypeForm::ajaxHandlerData()
|
||||
if (empty($this->getEntity()->get('source'))) {
|
||||
$actions['submit']['#type'] = 'button';
|
||||
}
|
||||
|
||||
$actions['submit']['#value'] = $this->t('Save');
|
||||
$actions['delete']['#value'] = $this->t('Delete');
|
||||
$actions['delete']['#access'] = $this->entity->access('delete');
|
||||
|
|
|
@ -7,7 +7,6 @@ use Drupal\Core\Cache\CacheBackendInterface;
|
|||
use Drupal\Core\Cache\UseCacheBackendTrait;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
|
||||
/**
|
||||
* Fetches and caches oEmbed resources.
|
||||
|
@ -69,8 +68,7 @@ class ResourceFetcher implements ResourceFetcherInterface {
|
|||
$content = (string) $response->getBody();
|
||||
|
||||
if (strstr($format, 'text/xml') || strstr($format, 'application/xml')) {
|
||||
$encoder = new XmlEncoder();
|
||||
$data = $encoder->decode($content, 'xml');
|
||||
$data = $this->parseResourceXml($content, $url);
|
||||
}
|
||||
elseif (strstr($format, 'text/javascript') || strstr($format, 'application/json')) {
|
||||
$data = Json::decode($content);
|
||||
|
@ -194,4 +192,42 @@ class ResourceFetcher implements ResourceFetcherInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses XML resource data.
|
||||
*
|
||||
* @param string $data
|
||||
* The raw XML for the resource.
|
||||
* @param string $url
|
||||
* The resource URL.
|
||||
*
|
||||
* @return array
|
||||
* The parsed resource data.
|
||||
*
|
||||
* @throws \Drupal\media\OEmbed\ResourceException
|
||||
* If the resource data could not be parsed.
|
||||
*/
|
||||
protected function parseResourceXml($data, $url) {
|
||||
// Enable userspace error handling.
|
||||
$was_using_internal_errors = libxml_use_internal_errors(TRUE);
|
||||
libxml_clear_errors();
|
||||
|
||||
$content = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
// Restore the previous error handling behavior.
|
||||
libxml_use_internal_errors($was_using_internal_errors);
|
||||
|
||||
$error = libxml_get_last_error();
|
||||
if ($error) {
|
||||
libxml_clear_errors();
|
||||
throw new ResourceException($error->message, $url);
|
||||
}
|
||||
elseif ($content === FALSE) {
|
||||
throw new ResourceException('The fetched resource could not be parsed.', $url);
|
||||
}
|
||||
|
||||
// Convert XML to JSON so that the parsed resource has a consistent array
|
||||
// structure, regardless of any XML attributes or quirks of the XML parser.
|
||||
$data = Json::encode($content);
|
||||
return Json::decode($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* label = @Translation("Image"),
|
||||
* description = @Translation("Use local images for reusable media."),
|
||||
* allowed_field_types = {"image"},
|
||||
* default_thumbnail_filename = "no-thumbnail.png"
|
||||
* default_thumbnail_filename = "no-thumbnail.png",
|
||||
* thumbnail_alt_metadata_attribute = "thumbnail_alt_value"
|
||||
* )
|
||||
*/
|
||||
class Image extends File {
|
||||
|
@ -138,6 +139,9 @@ class Image extends File {
|
|||
|
||||
case 'thumbnail_uri':
|
||||
return $uri;
|
||||
|
||||
case 'thumbnail_alt_value':
|
||||
return $media->get($this->configuration['source_field'])->alt ?: parent::getMetadata($media, $name);
|
||||
}
|
||||
|
||||
return parent::getMetadata($media, $name);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<oembed>
|
||||
<type>video</type>
|
||||
<version>1.0</version>
|
||||
<version type="float">1.0</version>
|
||||
<title>Let's Not Get a Drink Sometime</title>
|
||||
<https/>
|
||||
<author_name>CollegeHumor</author_name>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\media\Functional;
|
||||
|
||||
use Drupal\media\Entity\MediaType;
|
||||
|
||||
/**
|
||||
* Ensures that media UI works correctly without JavaScript.
|
||||
*
|
||||
* @group media
|
||||
*/
|
||||
class MediaTypeCreationTest extends MediaFunctionalTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'media_test_source',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests the media type creation form with only the mandatory options.
|
||||
*/
|
||||
public function testMediaTypeCreationForm() {
|
||||
$machine_name = mb_strtolower($this->randomMachineName());
|
||||
|
||||
$this->drupalGet('/admin/structure/media/add');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->fieldExists('label')->setValue($this->randomString());
|
||||
$this->assertSession()->fieldExists('id')->setValue($machine_name);
|
||||
$this->assertSession()->selectExists('source')->selectOption('test');
|
||||
$this->assertSession()->buttonExists('Save')->press();
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->fieldValueEquals('Test config value', 'This is default value.');
|
||||
$this->assertSession()->buttonExists('Save')->press();
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->addressEquals('admin/structure/media');
|
||||
|
||||
$this->assertInstanceOf(MediaType::class, MediaType::load($machine_name));
|
||||
}
|
||||
|
||||
}
|
|
@ -248,7 +248,7 @@ class MediaUiFunctionalTest extends MediaFunctionalTestBase {
|
|||
// Unlimited value field.
|
||||
'unlimited_value:single_type:create_list' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE],
|
||||
// Unlimited value field with the tags widget.
|
||||
'unlimited_value:single_type:create_list' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
|
||||
'unlimited_value:single_type:create_list:tags' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -167,13 +167,13 @@ abstract class MediaResourceTestBase extends EntityResourceTestBase {
|
|||
],
|
||||
'thumbnail' => [
|
||||
[
|
||||
'alt' => 'Thumbnail',
|
||||
'alt' => '',
|
||||
'width' => 180,
|
||||
'height' => 180,
|
||||
'target_id' => (int) $thumbnail->id(),
|
||||
'target_type' => 'file',
|
||||
'target_uuid' => $thumbnail->uuid(),
|
||||
'title' => 'Llama',
|
||||
'title' => NULL,
|
||||
'url' => $thumbnail->url(),
|
||||
],
|
||||
],
|
||||
|
|
|
@ -59,6 +59,8 @@ class MediaSourceImageTest extends MediaSourceTestBase {
|
|||
|
||||
// Make sure the thumbnail is displayed from uploaded image.
|
||||
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'example_1.jpeg');
|
||||
// Ensure the thumbnail has the correct alt attribute.
|
||||
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'alt', 'Image Alt Text 1');
|
||||
|
||||
// Load the media and check that all fields are properly populated.
|
||||
$media = Media::load(1);
|
||||
|
|
|
@ -40,7 +40,6 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
'value' => 'Snowball',
|
||||
],
|
||||
'thumbnail_uri' => [
|
||||
'title' => 'Thumbnail',
|
||||
'value' => 'public://TheSisko.png',
|
||||
],
|
||||
]);
|
||||
|
@ -230,7 +229,7 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
|
||||
// Save a media item and make sure thumbnail was added.
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail1.jpg'],
|
||||
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
|
||||
]);
|
||||
/** @var \Drupal\media\MediaInterface $media */
|
||||
$media = Media::create([
|
||||
|
@ -242,45 +241,46 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
$this->assertSame('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not added to the media item.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
// We expect the title not to be present on the Thumbnail.
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Now change the metadata attribute and make sure that the thumbnail stays
|
||||
// the same.
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail2.jpg'],
|
||||
'thumbnail_uri' => ['value' => 'public://thumbnail2.jpg'],
|
||||
]);
|
||||
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not preserved.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Remove the thumbnail and make sure that it is auto-updated on save.
|
||||
$media->thumbnail->target_id = NULL;
|
||||
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media item.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Change the metadata attribute again, change the source field value too
|
||||
// and make sure that the thumbnail updates.
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail1.jpg'],
|
||||
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
|
||||
]);
|
||||
$media->field_media_test->value = 'some_new_value';
|
||||
$this->assertSame('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media item.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Change the thumbnail metadata attribute and make sure that the thumbnail
|
||||
// is set correctly.
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'thumbnail_uri' => ['title' => 'Should not be used', 'value' => 'public://thumbnail1.jpg'],
|
||||
'alternative_thumbnail_uri' => ['title' => 'Should be used', 'value' => 'public://thumbnail2.jpg'],
|
||||
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
|
||||
'alternative_thumbnail_uri' => ['value' => 'public://thumbnail2.jpg'],
|
||||
]);
|
||||
\Drupal::state()->set('media_source_test_definition', ['thumbnail_uri_metadata_attribute' => 'alternative_thumbnail_uri']);
|
||||
$media = Media::create([
|
||||
|
@ -293,14 +293,14 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'alternative_thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'Correct metadata attribute was not used for the thumbnail.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Enable queued thumbnails and make sure that the entity gets the default
|
||||
// thumbnail initially.
|
||||
\Drupal::state()->set('media_source_test_definition', []);
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'thumbnail_uri' => ['title' => 'Should not be used', 'value' => 'public://thumbnail1.jpg'],
|
||||
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
|
||||
]);
|
||||
$this->testMediaType->setQueueThumbnailDownloadsStatus(TRUE)->save();
|
||||
$media = Media::create([
|
||||
|
@ -311,8 +311,8 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
$this->assertSame('public://thumbnail1.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is not correct.');
|
||||
$media->save();
|
||||
$this->assertSame('public://media-icons/generic/generic.png', $media->thumbnail->entity->getFileUri(), 'Default thumbnail was not set initially.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Process the queue item and make sure that the thumbnail was updated too.
|
||||
$queue_name = 'media_entity_thumbnail';
|
||||
|
@ -330,18 +330,15 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
|
||||
$media = Media::load($media->id());
|
||||
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not updated by the queue.');
|
||||
$this->assertSame('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertSame('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('', $media->thumbnail->alt);
|
||||
|
||||
// Set alt and title metadata attributes and make sure they are used for the
|
||||
// thumbnail.
|
||||
// Set the alt metadata attribute and make sure it's used for the thumbnail.
|
||||
\Drupal::state()->set('media_source_test_definition', [
|
||||
'thumbnail_alt_metadata_attribute' => 'alt',
|
||||
'thumbnail_title_metadata_attribute' => 'title',
|
||||
]);
|
||||
\Drupal::state()->set('media_source_test_attributes', [
|
||||
'alt' => ['title' => 'Alt text', 'value' => 'This will be alt.'],
|
||||
'title' => ['title' => 'Title text', 'value' => 'This will be title.'],
|
||||
'alt' => ['value' => 'This will be alt.'],
|
||||
]);
|
||||
$media = Media::create([
|
||||
'bundle' => $this->testMediaType->id(),
|
||||
|
@ -350,8 +347,8 @@ class MediaSourceTest extends MediaKernelTestBase {
|
|||
]);
|
||||
$media->save();
|
||||
$this->assertSame('Boxer', $media->getName(), 'Correct name was not set on the media item.');
|
||||
$this->assertSame('This will be title.', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
|
||||
$this->assertSame('This will be alt.', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
|
||||
$this->assertEmpty($media->thumbnail->title);
|
||||
$this->assertSame('This will be alt.', $media->thumbnail->alt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -198,7 +198,7 @@ class MenuLinkContent extends MenuLinkBase implements ContainerFactoryPluginInte
|
|||
* The menu link ID.
|
||||
*/
|
||||
protected function getUuid() {
|
||||
$this->getDerivativeId();
|
||||
return $this->getDerivativeId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\menu_link_content\Unit;
|
||||
|
||||
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent
|
||||
*
|
||||
* @group Menu
|
||||
*/
|
||||
class MenuLinkPluginTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getUuid
|
||||
*/
|
||||
public function testGetInstanceReflection() {
|
||||
/** @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent $menu_link_content_plugin */
|
||||
$menu_link_content_plugin = $this->prophesize(MenuLinkContent::class);
|
||||
$menu_link_content_plugin->getDerivativeId()->willReturn('test_id');
|
||||
$menu_link_content_plugin = $menu_link_content_plugin->reveal();
|
||||
|
||||
$class = new \ReflectionClass(MenuLinkContent::class);
|
||||
$instance_method = $class->getMethod('getUuid');
|
||||
$instance_method->setAccessible(TRUE);
|
||||
|
||||
$this->assertEquals('test_id', $instance_method->invoke($menu_link_content_plugin));
|
||||
}
|
||||
|
||||
}
|
|
@ -208,7 +208,14 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
|||
$migration->set('requirements', $required_dependency_graph[$migration_id]['paths']);
|
||||
}
|
||||
}
|
||||
array_multisort($weights, SORT_DESC, SORT_NUMERIC, $migrations);
|
||||
// Sort weights, labels, and keys in the same order as each other.
|
||||
array_multisort(
|
||||
// Use the numerical weight as the primary sort.
|
||||
$weights, SORT_DESC, SORT_NUMERIC,
|
||||
// When migrations have the same weight, sort them alphabetically by ID.
|
||||
array_keys($migrations), SORT_ASC, SORT_NATURAL,
|
||||
$migrations
|
||||
);
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
|
|
@ -584,7 +584,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
|
||||
if (!empty($source_id_values)) {
|
||||
$var_dump = var_export($source_id_values, TRUE);
|
||||
throw new MigrateException(sprintf("Extra unknown items in source IDs: %s", $var_dump));
|
||||
throw new MigrateException(sprintf("Extra unknown items for map %s in source IDs: %s", $this->mapTableName(), $var_dump));
|
||||
}
|
||||
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
|
|
|
@ -82,7 +82,8 @@ use Drupal\migrate\Row;
|
|||
* @endcode
|
||||
*
|
||||
* If the source value was '2004-12-19T10:19:42-0600' the transformed value
|
||||
* would be 2004-12-19T10:19:42.
|
||||
* would be 2004-12-19T10:19:42. Set validate_format to false if your source
|
||||
* value is '0000-00-00 00:00:00'.
|
||||
*
|
||||
* @see \DateTime::createFromFormat()
|
||||
* @see \Drupal\Component\Datetime\DateTimePlus::__construct()
|
||||
|
@ -99,7 +100,7 @@ class FormatDate extends ProcessPluginBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (empty($value)) {
|
||||
if (empty($value) && $value !== '0' && $value !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
|
@ -353,7 +353,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
|||
|
||||
$row_data = $this->getIterator()->current() + $this->configuration;
|
||||
$this->fetchNextRow();
|
||||
$row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->getDestinationIds());
|
||||
$row = new Row($row_data, $this->getIds());
|
||||
|
||||
// Populate the source key for this row.
|
||||
$this->currentSourceIds = $row->getSourceIdValues();
|
||||
|
|
|
@ -108,6 +108,11 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->state = $state;
|
||||
// If we are using high water, but haven't yet set a high water mark, skip
|
||||
// joining the map table, as we want to get all available records.
|
||||
if ($this->getHighWaterProperty() && $this->getHighWater() === NULL) {
|
||||
$this->configuration['ignore_map'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,7 +327,9 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
if ($this->getHighWaterProperty()) {
|
||||
$high_water_field = $this->getHighWaterField();
|
||||
$high_water = $this->getHighWater();
|
||||
if ($high_water) {
|
||||
// We check against NULL because 0 is an acceptable value for the high
|
||||
// water mark.
|
||||
if ($high_water !== NULL) {
|
||||
$conditions->condition($high_water_field, $high_water, '>');
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
|
|
|
@ -136,6 +136,72 @@ class HighWaterTest extends MigrateTestBase {
|
|||
$this->assertNodeDoesNotExist('Item 3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the high water value can be 0.
|
||||
*/
|
||||
public function testZeroHighwater() {
|
||||
// Assert all of the nodes have been imported.
|
||||
$this->assertNodeExists('Item 1');
|
||||
$this->assertNodeExists('Item 2');
|
||||
$this->assertNodeExists('Item 3');
|
||||
$migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
|
||||
$source = $migration->getSourcePlugin();
|
||||
$source->rewind();
|
||||
$count = 0;
|
||||
while ($source->valid()) {
|
||||
$count++;
|
||||
$source->next();
|
||||
}
|
||||
|
||||
// Expect no rows as everything is below the high water mark.
|
||||
$this->assertSame(0, $count);
|
||||
|
||||
// Test resetting the high water mark to 0.
|
||||
$this->container->get('keyvalue')->get('migrate:high_water')->set('high_water_test', 0);
|
||||
$migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
|
||||
$source = $migration->getSourcePlugin();
|
||||
$source->rewind();
|
||||
$count = 0;
|
||||
while ($source->valid()) {
|
||||
$count++;
|
||||
$source->next();
|
||||
}
|
||||
$this->assertSame(3, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that deleting the high water value causes all rows to be reimported.
|
||||
*/
|
||||
public function testNullHighwater() {
|
||||
// Assert all of the nodes have been imported.
|
||||
$this->assertNodeExists('Item 1');
|
||||
$this->assertNodeExists('Item 2');
|
||||
$this->assertNodeExists('Item 3');
|
||||
$migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
|
||||
$source = $migration->getSourcePlugin();
|
||||
$source->rewind();
|
||||
$count = 0;
|
||||
while ($source->valid()) {
|
||||
$count++;
|
||||
$source->next();
|
||||
}
|
||||
|
||||
// Expect no rows as everything is below the high water mark.
|
||||
$this->assertSame(0, $count);
|
||||
|
||||
// Test resetting the high water mark.
|
||||
$this->container->get('keyvalue')->get('migrate:high_water')->delete('high_water_test');
|
||||
$migration = $this->container->get('plugin.manager.migration')->CreateInstance('high_water_test', []);
|
||||
$source = $migration->getSourcePlugin();
|
||||
$source->rewind();
|
||||
$count = 0;
|
||||
while ($source->valid()) {
|
||||
$count++;
|
||||
$source->next();
|
||||
}
|
||||
$this->assertSame(3, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests high water property of SqlBase when rows marked for update.
|
||||
*/
|
||||
|
|
|
@ -45,6 +45,11 @@ class MigrateEmbeddedDataTest extends KernelTestBase {
|
|||
$results = [];
|
||||
/** @var \Drupal\migrate\Row $row */
|
||||
foreach ($source as $row) {
|
||||
// The plugin should not mark any rows as stubs. We need to use
|
||||
// assertSame() here because assertFalse() will pass falsy values (e.g.,
|
||||
// empty arrays).
|
||||
$this->assertSame(FALSE, $row->isStub());
|
||||
|
||||
$data_row = $row->getSource();
|
||||
// The "data" row returned by getSource() also includes all source
|
||||
// configuration - we remove it so we see only the data itself.
|
||||
|
|
|
@ -529,14 +529,14 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
|
|||
$this->fail('Too many source IDs should throw');
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertEquals("Extra unknown items in source IDs: array (\n 0 => 3,\n)", $e->getMessage());
|
||||
$this->assertEquals("Extra unknown items for map migrate_map_sql_idmap_test in source IDs: array (\n 0 => 3,\n)", $e->getMessage());
|
||||
}
|
||||
try {
|
||||
$id_map->lookupDestinationIds(['nid' => 1, 'aaa' => '2']);
|
||||
$this->fail('Unknown source ID key should throw');
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertEquals("Extra unknown items in source IDs: array (\n 'aaa' => '2',\n)", $e->getMessage());
|
||||
$this->assertEquals("Extra unknown items for map migrate_map_sql_idmap_test in source IDs: array (\n 'aaa' => '2',\n)", $e->getMessage());
|
||||
}
|
||||
|
||||
// Verify that we are looking up by source_id_hash when all source IDs are
|
||||
|
|
|
@ -175,6 +175,42 @@ class FormatDateTest extends MigrateProcessTestCase {
|
|||
// converted from Australia/Sydney to America/Managua timezone.
|
||||
'expected' => '2004-12-18 17:19:42 America/Managua',
|
||||
],
|
||||
'integer_0' => [
|
||||
'configuration' => [
|
||||
'from_format' => 'U',
|
||||
'to_format' => 'Y-m-d',
|
||||
],
|
||||
'value' => 0,
|
||||
'expected' => '1970-01-01',
|
||||
],
|
||||
'string_0' => [
|
||||
'configuration' => [
|
||||
'from_format' => 'U',
|
||||
'to_format' => 'Y-m-d',
|
||||
],
|
||||
'value' => '0',
|
||||
'expected' => '1970-01-01',
|
||||
],
|
||||
'zeros' => [
|
||||
'configuration' => [
|
||||
'from_format' => 'Y-m-d H:i:s',
|
||||
'to_format' => 'Y-m-d H:i:s e',
|
||||
'settings' => ['validate_format' => FALSE],
|
||||
],
|
||||
'value' => '0000-00-00 00:00:00',
|
||||
'expected' => '-0001-11-30 00:00:00 Australia/Sydney',
|
||||
],
|
||||
'zeros_same_timezone' => [
|
||||
'configuration' => [
|
||||
'from_format' => 'Y-m-d H:i:s',
|
||||
'to_format' => 'Y-m-d H:i:s',
|
||||
'settings' => ['validate_format' => FALSE],
|
||||
'from_timezone' => 'UTC',
|
||||
'to_timezone' => 'UTC',
|
||||
],
|
||||
'value' => '0000-00-00 00:00:00',
|
||||
'expected' => '-0001-11-30 00:00:00',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ abstract class DrupalSqlBase extends SqlBase implements ContainerFactoryPluginIn
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
parent::checkRequirements();
|
||||
if ($this->pluginDefinition['requirements_met'] === TRUE) {
|
||||
if (isset($this->pluginDefinition['source_module'])) {
|
||||
if ($this->moduleExists($this->pluginDefinition['source_module'])) {
|
||||
|
@ -114,7 +115,6 @@ abstract class DrupalSqlBase extends SqlBase implements ContainerFactoryPluginIn
|
|||
}
|
||||
}
|
||||
}
|
||||
parent::checkRequirements();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -292,7 +292,6 @@ $connection->insert('actions')
|
|||
'description' => 'Block current user',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('actions_aid', array(
|
||||
'fields' => array(
|
||||
'aid' => array(
|
||||
|
@ -331,7 +330,6 @@ $connection->insert('actions_aid')
|
|||
'aid' => '6',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('aggregator_category', array(
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
|
@ -463,7 +461,6 @@ $connection->insert('aggregator_feed')
|
|||
'block' => '7',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('aggregator_item', array(
|
||||
'fields' => array(
|
||||
'iid' => array(
|
||||
|
@ -539,7 +536,6 @@ $connection->insert('aggregator_item')
|
|||
'guid' => '395218 at https://groups.drupal.org',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('authmap', array(
|
||||
'fields' => array(
|
||||
'aid' => array(
|
||||
|
@ -617,7 +613,6 @@ $connection->insert('batch')
|
|||
'batch' => NULL,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('blocks', array(
|
||||
'fields' => array(
|
||||
'bid' => array(
|
||||
|
@ -1080,7 +1075,6 @@ $connection->insert('blocks')
|
|||
'cache' => '-1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('blocks_roles', array(
|
||||
'fields' => array(
|
||||
'module' => array(
|
||||
|
@ -1125,7 +1119,6 @@ $connection->insert('blocks_roles')
|
|||
'rid' => '3',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('book', array(
|
||||
'fields' => array(
|
||||
'mlid' => array(
|
||||
|
@ -1188,7 +1181,6 @@ $connection->insert('book')
|
|||
'bid' => '8',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('boxes', array(
|
||||
'fields' => array(
|
||||
'bid' => array(
|
||||
|
@ -1241,7 +1233,6 @@ $connection->insert('boxes')
|
|||
'format' => '2',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('cache', array(
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
|
@ -2023,7 +2014,6 @@ $connection->insert('comments')
|
|||
'homepage' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('config', array(
|
||||
'fields' => array(
|
||||
'collection' => array(
|
||||
|
@ -2063,7 +2053,6 @@ $connection->insert('config')
|
|||
'data' => 'a:1:{s:4:"path";a:1:{s:9:"temporary";s:4:"/tmp";}}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('contact', array(
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
|
@ -2141,7 +2130,6 @@ $connection->insert('contact')
|
|||
'selected' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_company', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2219,7 +2207,6 @@ $connection->insert('content_field_company')
|
|||
'field_company_nid' => '16',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_image', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2288,7 +2275,6 @@ $connection->insert('content_field_image')
|
|||
'field_image_data' => 'a:2:{s:3:"alt";s:0:"";s:5:"title";s:0:"";}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_multivalue', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2351,7 +2337,6 @@ $connection->insert('content_field_multivalue')
|
|||
'delta' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_test', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2429,7 +2414,6 @@ $connection->insert('content_field_test')
|
|||
'field_test_format' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_test_text_single_checkbox', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2495,7 +2479,6 @@ $connection->insert('content_field_test_text_single_checkbox')
|
|||
'field_test_text_single_checkbox_value' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_field_test_two', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -2581,7 +2564,6 @@ $connection->insert('content_field_test_two')
|
|||
'field_test_two_value' => '20',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_group', array(
|
||||
'fields' => array(
|
||||
'group_type' => array(
|
||||
|
@ -3073,7 +3055,6 @@ $connection->insert('content_node_field')
|
|||
'locked' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_node_field_instance', array(
|
||||
'fields' => array(
|
||||
'field_name' => array(
|
||||
|
@ -3515,7 +3496,6 @@ $connection->insert('content_node_field_instance')
|
|||
'widget_active' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_type_employee', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -3601,7 +3581,6 @@ $connection->insert('content_type_employee')
|
|||
'field_company_3_nid' => NULL,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_type_page', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -3687,7 +3666,6 @@ $connection->insert('content_type_page')
|
|||
'field_reference_2_nid' => '11',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_type_story', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -4057,7 +4035,6 @@ $connection->insert('content_type_story')
|
|||
'field_test_string_selectlist_value' => NULL,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('content_type_test_page', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -4148,7 +4125,6 @@ $connection->insert('content_type_test_planet')
|
|||
'vid' => '11',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('date_format_locale', array(
|
||||
'fields' => array(
|
||||
'format' => array(
|
||||
|
@ -4221,7 +4197,6 @@ $connection->insert('date_format_types')
|
|||
'locked' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('date_formats', array(
|
||||
'fields' => array(
|
||||
'dfid' => array(
|
||||
|
@ -4471,7 +4446,6 @@ $connection->insert('date_formats')
|
|||
'locked' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('event', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -8271,7 +8245,6 @@ $connection->insert('event_timezones')
|
|||
'is_dst' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('files', array(
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
|
@ -8394,7 +8367,6 @@ $connection->insert('files')
|
|||
'timestamp' => '1420858106',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('filter_formats', array(
|
||||
'fields' => array(
|
||||
'format' => array(
|
||||
|
@ -8459,7 +8431,6 @@ $connection->insert('filter_formats')
|
|||
'cache' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('filters', array(
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
|
@ -8591,7 +8562,6 @@ $connection->insert('filters')
|
|||
'weight' => '10',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('flood', array(
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
|
@ -8674,7 +8644,6 @@ $connection->insert('forum')
|
|||
'tid' => '8',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('history', array(
|
||||
'fields' => array(
|
||||
'uid' => array(
|
||||
|
@ -8770,7 +8739,6 @@ $connection->insert('history')
|
|||
'timestamp' => '1534014763',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('i18n_blocks', array(
|
||||
'fields' => array(
|
||||
'ibid' => array(
|
||||
|
@ -8832,7 +8800,6 @@ $connection->insert('i18n_blocks')
|
|||
'language' => 'zu',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('i18n_strings', array(
|
||||
'fields' => array(
|
||||
'lid' => array(
|
||||
|
@ -10071,8 +10038,31 @@ $connection->insert('i18n_strings')
|
|||
'objectindex' => '0',
|
||||
'format' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1692',
|
||||
'objectid' => '14',
|
||||
'type' => 'term',
|
||||
'property' => 'name',
|
||||
'objectindex' => '14',
|
||||
'format' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1693',
|
||||
'objectid' => '15',
|
||||
'type' => 'term',
|
||||
'property' => 'name',
|
||||
'objectindex' => '15',
|
||||
'format' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1694',
|
||||
'objectid' => '14',
|
||||
'type' => 'term',
|
||||
'property' => 'description',
|
||||
'objectindex' => '14',
|
||||
'format' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('i18n_variable', array(
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
|
@ -10512,7 +10502,6 @@ $connection->insert('i18n_variable')
|
|||
'value' => 's:1:"1";',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('imagecache_action', array(
|
||||
'fields' => array(
|
||||
'actionid' => array(
|
||||
|
@ -10598,7 +10587,6 @@ $connection->insert('imagecache_action')
|
|||
'data' => 'a:3:{s:7:"degrees";s:2:"55";s:6:"random";i:0;s:7:"bgcolor";s:0:"";}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('imagecache_preset', array(
|
||||
'fields' => array(
|
||||
'presetid' => array(
|
||||
|
@ -10633,7 +10621,6 @@ $connection->insert('imagecache_preset')
|
|||
'presetname' => 'big_blue_cheese',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('languages', array(
|
||||
'fields' => array(
|
||||
'language' => array(
|
||||
|
@ -10769,7 +10756,6 @@ $connection->insert('languages')
|
|||
'javascript' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('locales_source', array(
|
||||
'fields' => array(
|
||||
'lid' => array(
|
||||
|
@ -22642,8 +22628,28 @@ $connection->insert('locales_source')
|
|||
'source' => 'White',
|
||||
'version' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1692',
|
||||
'location' => 'term:14:name',
|
||||
'textgroup' => 'taxonomy',
|
||||
'source' => 'Talos IV',
|
||||
'version' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1693',
|
||||
'location' => 'term:15:name',
|
||||
'textgroup' => 'taxonomy',
|
||||
'source' => 'Vulcan',
|
||||
'version' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1694',
|
||||
'location' => 'term:14:description',
|
||||
'textgroup' => 'taxonomy',
|
||||
'source' => 'The home of Captain Christopher Pike.',
|
||||
'version' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('locales_target', array(
|
||||
'fields' => array(
|
||||
'lid' => array(
|
||||
|
@ -27702,6 +27708,14 @@ $connection->insert('locales_target')
|
|||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1672',
|
||||
'translation' => 'fr - Type',
|
||||
'language' => 'fr',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1678',
|
||||
'translation' => 'fr - I really, really, really love migrating ',
|
||||
|
@ -27718,6 +27732,22 @@ $connection->insert('locales_target')
|
|||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1692',
|
||||
'translation' => 'fr - Talos IV',
|
||||
'language' => 'fr',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1694',
|
||||
'translation' => 'fr - The home of Captain Christopher Pike.',
|
||||
'language' => 'fr',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '66',
|
||||
'translation' => 'zu - CCK - Aucune Intégration aux Vues',
|
||||
|
@ -27830,6 +27860,14 @@ $connection->insert('locales_target')
|
|||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1672',
|
||||
'translation' => 'zu - Type',
|
||||
'language' => 'zu',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1690',
|
||||
'translation' => 'Okumnyama',
|
||||
|
@ -27846,8 +27884,23 @@ $connection->insert('locales_target')
|
|||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1693',
|
||||
'translation' => 'zu - Vulcan',
|
||||
'language' => 'zu',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'lid' => '1694',
|
||||
'translation' => 'zu - The home of Captain Christopher Pike.',
|
||||
'language' => 'zu',
|
||||
'plid' => '0',
|
||||
'plural' => '0',
|
||||
'i18n_status' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('menu_custom', array(
|
||||
'fields' => array(
|
||||
'menu_name' => array(
|
||||
|
@ -27896,7 +27949,6 @@ $connection->insert('menu_custom')
|
|||
'description' => 'Secondary links are often used for pages like legal notices, contact details, and other secondary navigation items that play a lesser role than primary links',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('menu_links', array(
|
||||
'fields' => array(
|
||||
'menu_name' => array(
|
||||
|
@ -34385,7 +34437,6 @@ $connection->insert('menu_links')
|
|||
'updated' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('menu_router', array(
|
||||
'fields' => array(
|
||||
'path' => array(
|
||||
|
@ -40934,6 +40985,28 @@ $connection->insert('menu_router')
|
|||
'weight' => '0',
|
||||
'file' => 'sites/all/modules/i18n/i18n.admin.inc',
|
||||
))
|
||||
->values(array(
|
||||
'path' => 'admin/settings/language/i18n/variables',
|
||||
'load_functions' => '',
|
||||
'to_arg_functions' => '',
|
||||
'access_callback' => 'user_access',
|
||||
'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
|
||||
'page_callback' => 'drupal_get_form',
|
||||
'page_arguments' => 'a:1:{i:0;s:25:"i18n_admin_variables_form";}',
|
||||
'fit' => '31',
|
||||
'number_parts' => '5',
|
||||
'tab_parent' => 'admin/settings/language/i18n',
|
||||
'tab_root' => 'admin/settings/language',
|
||||
'title' => 'Variables',
|
||||
'title_callback' => 't',
|
||||
'title_arguments' => '',
|
||||
'type' => '128',
|
||||
'block_callback' => '',
|
||||
'description' => 'Multilingual variables.',
|
||||
'position' => '',
|
||||
'weight' => '0',
|
||||
'file' => 'sites/all/modules/i18n/i18n.admin.inc',
|
||||
))
|
||||
->values(array(
|
||||
'path' => 'admin/settings/language/overview',
|
||||
'load_functions' => '',
|
||||
|
@ -43575,7 +43648,6 @@ $connection->insert('menu_router')
|
|||
'file' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -44070,7 +44142,6 @@ $connection->insert('node')
|
|||
'translate' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_access', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -44141,7 +44212,6 @@ $connection->insert('node_access')
|
|||
'grant_delete' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_comment_statistics', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -44290,7 +44360,6 @@ $connection->insert('node_comment_statistics')
|
|||
'comment_count' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_counter', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -44437,7 +44506,6 @@ $connection->insert('node_counter')
|
|||
'timestamp' => '1534014763',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_revisions', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -44787,7 +44855,6 @@ $connection->insert('node_revisions')
|
|||
'format' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('node_type', array(
|
||||
'fields' => array(
|
||||
'type' => array(
|
||||
|
@ -45087,7 +45154,6 @@ $connection->insert('node_type')
|
|||
'orig_type' => 'test_story',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('permission', array(
|
||||
'fields' => array(
|
||||
'pid' => array(
|
||||
|
@ -45153,7 +45219,6 @@ $connection->insert('permission')
|
|||
'tid' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('profile_fields', array(
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
|
@ -45386,7 +45451,6 @@ $connection->insert('profile_fields')
|
|||
'options' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('profile_values', array(
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
|
@ -45603,7 +45667,6 @@ $connection->insert('profile_values')
|
|||
'value' => '1',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('role', array(
|
||||
'fields' => array(
|
||||
'rid' => array(
|
||||
|
@ -45651,7 +45714,6 @@ $connection->insert('role')
|
|||
'name' => 'migrate test role 3 that is longer than thirty two characters',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('semaphore', array(
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
|
@ -46957,7 +47019,6 @@ $connection->insert('system')
|
|||
'info' => 'a:13:{s:4:"name";s:10:"Pushbutton";s:11:"description";s:52:"Tabled, multi-column theme in blue and orange tones.";s:7:"version";s:4:"6.38";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1456343372";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/pushbutton/script.js";}s:10:"screenshot";s:32:"themes/pushbutton/screenshot.png";s:3:"php";s:5:"4.3.5";}',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('term_data', array(
|
||||
'fields' => array(
|
||||
'tid' => array(
|
||||
|
@ -47091,8 +47152,70 @@ $connection->insert('term_data')
|
|||
'language' => '',
|
||||
'trid' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '9',
|
||||
'vid' => '3',
|
||||
'name' => 'fr - term 4 of vocabulary 3',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => 'fr',
|
||||
'trid' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '10',
|
||||
'vid' => '3',
|
||||
'name' => 'zu - term 4 of vocabulary 3',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => 'zu',
|
||||
'trid' => '1',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '11',
|
||||
'vid' => '3',
|
||||
'name' => 'term 7 of vocabulary 3',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => 'en',
|
||||
'trid' => '2',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '12',
|
||||
'vid' => '3',
|
||||
'name' => 'fr - term 7 of vocabulary 3',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => 'fr',
|
||||
'trid' => '2',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '13',
|
||||
'vid' => '3',
|
||||
'name' => 'zu - term 7 of vocabulary 3',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => 'zu',
|
||||
'trid' => '2',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '14',
|
||||
'vid' => '5',
|
||||
'name' => 'Talos IV',
|
||||
'description' => 'The home of Captain Christopher Pike.',
|
||||
'weight' => '0',
|
||||
'language' => '',
|
||||
'trid' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '15',
|
||||
'vid' => '5',
|
||||
'name' => 'Vulcan',
|
||||
'description' => '',
|
||||
'weight' => '0',
|
||||
'language' => '',
|
||||
'trid' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('term_hierarchy', array(
|
||||
'fields' => array(
|
||||
'tid' => array(
|
||||
|
@ -47142,6 +47265,34 @@ $connection->insert('term_hierarchy')
|
|||
'tid' => '8',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '9',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '10',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '11',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '12',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '13',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '14',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '15',
|
||||
'parent' => '0',
|
||||
))
|
||||
->values(array(
|
||||
'tid' => '3',
|
||||
'parent' => '2',
|
||||
|
@ -47159,7 +47310,6 @@ $connection->insert('term_hierarchy')
|
|||
'parent' => '5',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('term_node', array(
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
|
@ -47233,7 +47383,6 @@ $connection->insert('term_node')
|
|||
'tid' => '8',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('term_relation', array(
|
||||
'fields' => array(
|
||||
'trid' => array(
|
||||
|
@ -47397,7 +47546,6 @@ $connection->insert('upload')
|
|||
'weight' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('url_alias', array(
|
||||
'fields' => array(
|
||||
'pid' => array(
|
||||
|
@ -47487,7 +47635,6 @@ $connection->insert('url_alias')
|
|||
'language' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('users', array(
|
||||
'fields' => array(
|
||||
'uid' => array(
|
||||
|
@ -47809,7 +47956,6 @@ $connection->insert('users')
|
|||
'timezone_id' => '0',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('users_roles', array(
|
||||
'fields' => array(
|
||||
'uid' => array(
|
||||
|
@ -47876,7 +48022,6 @@ $connection->insert('users_roles')
|
|||
'rid' => '5',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('variable', array(
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
|
@ -48740,7 +48885,7 @@ $connection->insert('variable')
|
|||
))
|
||||
->values(array(
|
||||
'name' => 'i18ntaxonomy_vocabulary',
|
||||
'value' => 'a:3:{i:1;s:1:"3";i:2;s:1:"2";i:3;s:1:"1";}',
|
||||
'value' => 'a:4:{i:1;s:1:"3";i:2;s:1:"2";i:3;s:1:"3";i:5;s:1:"1";}',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'i18n_lock_node_article',
|
||||
|
@ -49143,7 +49288,6 @@ $connection->insert('variable')
|
|||
'value' => 's:1:"1";',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('vocabulary', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -49343,7 +49487,6 @@ $connection->insert('vocabulary')
|
|||
'language' => '',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('vocabulary_node_types', array(
|
||||
'fields' => array(
|
||||
'vid' => array(
|
||||
|
@ -49401,7 +49544,6 @@ $connection->insert('vocabulary_node_types')
|
|||
'type' => 'story',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$connection->schema()->createTable('watchdog', array(
|
||||
'fields' => array(
|
||||
'wid' => array(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue