Drupal core update
This commit is contained in:
parent
d1f95f27f0
commit
7d59824d2d
@ -534,6 +534,10 @@ Decoupled Menus Initiative
|
|||||||
Media Initiative
|
Media Initiative
|
||||||
- Janez Urevc 'slashrsm' https://www.drupal.org/u/slashrsm
|
- Janez Urevc 'slashrsm' https://www.drupal.org/u/slashrsm
|
||||||
|
|
||||||
|
Project Browser Initiative
|
||||||
|
- Leslie Glynn 'leslieg' https://www.drupal.org/u/leslieg
|
||||||
|
- Chris Wells 'chrisfromredfin' https://www.drupal.org/u/chrisfromredfin
|
||||||
|
|
||||||
|
|
||||||
Core mentoring coordinators
|
Core mentoring coordinators
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -558,7 +558,9 @@ function drupal_flush_all_caches($kernel = NULL) {
|
|||||||
// Wipe the Twig PHP Storage cache.
|
// Wipe the Twig PHP Storage cache.
|
||||||
\Drupal::service('twig')->invalidate();
|
\Drupal::service('twig')->invalidate();
|
||||||
|
|
||||||
// Rebuild theme data that is stored in state.
|
// Rebuild profile, profile, theme_engine and theme data.
|
||||||
|
\Drupal::service('extension.list.profile')->reset();
|
||||||
|
\Drupal::service('extension.list.theme_engine')->reset();
|
||||||
\Drupal::service('theme_handler')->refreshInfo();
|
\Drupal::service('theme_handler')->refreshInfo();
|
||||||
// In case the active theme gets requested later in the same request we need
|
// In case the active theme gets requested later in the same request we need
|
||||||
// to reset the theme manager.
|
// to reset the theme manager.
|
||||||
|
@ -75,7 +75,7 @@ class Drupal {
|
|||||||
/**
|
/**
|
||||||
* The current system version.
|
* The current system version.
|
||||||
*/
|
*/
|
||||||
const VERSION = '9.4.1';
|
const VERSION = '9.4.2';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core API compatibility.
|
* Core API compatibility.
|
||||||
|
@ -130,13 +130,6 @@ class Composer {
|
|||||||
$vendor_dir . '/symfony/http-kernel/TerminableInterface.php',
|
$vendor_dir . '/symfony/http-kernel/TerminableInterface.php',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($repository->findPackage('symfony/http-kernel', $constraint)) {
|
|
||||||
$autoload['classmap'] = array_merge($autoload['classmap'], [
|
|
||||||
$vendor_dir . '/symfony/http-kernel/HttpKernel.php',
|
|
||||||
$vendor_dir . '/symfony/http-kernel/HttpKernelInterface.php',
|
|
||||||
$vendor_dir . '/symfony/http-kernel/TerminableInterface.php',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($repository->findPackage('symfony/dependency-injection', $constraint)) {
|
if ($repository->findPackage('symfony/dependency-injection', $constraint)) {
|
||||||
$autoload['classmap'] = array_merge($autoload['classmap'], [
|
$autoload['classmap'] = array_merge($autoload['classmap'], [
|
||||||
$vendor_dir . '/symfony/dependency-injection/ContainerAwareInterface.php',
|
$vendor_dir . '/symfony/dependency-injection/ContainerAwareInterface.php',
|
||||||
|
@ -16,12 +16,34 @@ class DependencySerializationTraitPass implements CompilerPassInterface {
|
|||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function process(ContainerBuilder $container) {
|
public function process(ContainerBuilder $container) {
|
||||||
|
$decorations = new \SplPriorityQueue();
|
||||||
|
$order = PHP_INT_MAX;
|
||||||
|
|
||||||
foreach ($container->getDefinitions() as $service_id => $definition) {
|
foreach ($container->getDefinitions() as $service_id => $definition) {
|
||||||
// Only add the property to services that are public (as private services
|
// Only add the property to services that are public (as private services
|
||||||
// can not be reloaded through Container::get()) and are objects.
|
// can not be reloaded through Container::get()) and are objects.
|
||||||
if (!$definition->hasTag('parameter_service') && $definition->isPublic()) {
|
if (!$definition->hasTag('parameter_service') && $definition->isPublic()) {
|
||||||
$definition->setProperty('_serviceId', $service_id);
|
$definition->setProperty('_serviceId', $service_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($decorated = $definition->getDecoratedService()) {
|
||||||
|
$decorations->insert([$service_id, $definition], [$decorated[2], --$order]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($decorations as list($service_id, $definition)) {
|
||||||
|
list($inner, $renamedId) = $definition->getDecoratedService();
|
||||||
|
if (!$renamedId) {
|
||||||
|
$renamedId = $service_id . '.inner';
|
||||||
|
}
|
||||||
|
|
||||||
|
$original = $container->getDefinition($inner);
|
||||||
|
if ($original->isPublic()) {
|
||||||
|
// The old service is renamed.
|
||||||
|
$original->setProperty('_serviceId', $renamedId);
|
||||||
|
// The decorating service takes over the old ID.
|
||||||
|
$definition->setProperty('_serviceId', $inner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ final class Settings {
|
|||||||
// the database. Therefore, allow the connection info to specify an
|
// the database. Therefore, allow the connection info to specify an
|
||||||
// autoload directory for the driver.
|
// autoload directory for the driver.
|
||||||
if (isset($info['autoload'])) {
|
if (isset($info['autoload'])) {
|
||||||
$class_loader->addPsr4($info['namespace'] . '\\', $info['autoload']);
|
$class_loader->addPsr4($info['namespace'] . '\\', $app_root . '/' . $info['autoload']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ ckeditor5.plugin.ckeditor5_list:
|
|||||||
# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
|
# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
|
||||||
ckeditor5.plugin.media_media:
|
ckeditor5.plugin.media_media:
|
||||||
type: mapping
|
type: mapping
|
||||||
label: List
|
label: Media
|
||||||
mapping:
|
mapping:
|
||||||
allow_view_mode_override:
|
allow_view_mode_override:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
@ -136,7 +136,7 @@ class ConfigSingleExportForm extends FormBase {
|
|||||||
*/
|
*/
|
||||||
public function updateConfigurationType($form, FormStateInterface $form_state) {
|
public function updateConfigurationType($form, FormStateInterface $form_state) {
|
||||||
$form['config_name']['#options'] = $this->findConfiguration($form_state->getValue('config_type'));
|
$form['config_name']['#options'] = $this->findConfiguration($form_state->getValue('config_type'));
|
||||||
unset($form['export']['#value']);
|
$form['export']['#value'] = NULL;
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace Drupal\config_override_test;
|
|||||||
|
|
||||||
use Drupal\Core\Cache\CacheableMetadata;
|
use Drupal\Core\Cache\CacheableMetadata;
|
||||||
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
|
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
|
||||||
|
use Drupal\Core\Config\StorageInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests module overrides for configuration.
|
* Tests module overrides for configuration.
|
||||||
|
@ -49,7 +49,7 @@ class FieldInstanceSettings extends ProcessPluginBase {
|
|||||||
case 'imagefield_widget':
|
case 'imagefield_widget':
|
||||||
$settings['file_extensions'] = $widget_settings['file_extensions'];
|
$settings['file_extensions'] = $widget_settings['file_extensions'];
|
||||||
$settings['file_directory'] = $widget_settings['file_path'];
|
$settings['file_directory'] = $widget_settings['file_path'];
|
||||||
$settings['max_filesize'] = $this->convertSizeUnit($widget_settings['max_filesize_per_file']);
|
$settings['max_filesize'] = $this->convertSizeUnit($widget_settings['max_filesize_per_file'] ?? '');
|
||||||
$settings['alt_field'] = $widget_settings['alt'];
|
$settings['alt_field'] = $widget_settings['alt'];
|
||||||
$settings['alt_field_required'] = $widget_settings['custom_alt'];
|
$settings['alt_field_required'] = $widget_settings['custom_alt'];
|
||||||
$settings['title_field'] = $widget_settings['title'];
|
$settings['title_field'] = $widget_settings['title'];
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\field\Unit\Plugin\migrate\process\d6;
|
||||||
|
|
||||||
|
use Drupal\field\Plugin\migrate\process\d6\FieldInstanceSettings;
|
||||||
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\Tests\UnitTestCase;
|
||||||
|
|
||||||
|
// cspell:ignore imagefield
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass \Drupal\field\Plugin\migrate\process\d6\FieldInstanceSettings
|
||||||
|
* @group field
|
||||||
|
*/
|
||||||
|
class FieldInstanceSettingsTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::getSettings
|
||||||
|
*
|
||||||
|
* @dataProvider getSettingsProvider
|
||||||
|
*/
|
||||||
|
public function testGetSettings($field_type, $instance_settings, $expected) {
|
||||||
|
$instance_settings = unserialize($instance_settings);
|
||||||
|
$migration = $this->createMock(MigrationInterface::class);
|
||||||
|
$plugin = new FieldInstanceSettings([], 'd6_field_field_settings', [], $migration);
|
||||||
|
|
||||||
|
$executable = $this->createMock(MigrateExecutableInterface::class);
|
||||||
|
$row = $this->getMockBuilder(Row::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$result = $plugin->transform([
|
||||||
|
$field_type,
|
||||||
|
$instance_settings,
|
||||||
|
NULL,
|
||||||
|
], $executable, $row, 'foo');
|
||||||
|
$this->assertSame($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides field settings for testGetSettings().
|
||||||
|
*/
|
||||||
|
public function getSettingsProvider() {
|
||||||
|
return [
|
||||||
|
'imagefield size set' => [
|
||||||
|
'imagefield_widget',
|
||||||
|
'a:14:{s:15:"file_extensions";s:11:"gif jpg png";s:9:"file_path";N;s:18:"progress_indicator";N;s:21:"max_filesize_per_file";s:3:"10M";s:21:"max_filesize_per_node";N;s:14:"max_resolution";N;s:14:"min_resolution";N;s:3:"alt";N;s:10:"custom_alt";i:1;s:5:"title";N;s:12:"custom_title";i:1;s:10:"title_type";N;s:13:"default_image";N;s:17:"use_default_image";N;}',
|
||||||
|
[
|
||||||
|
'file_extensions' => 'gif jpg png',
|
||||||
|
'file_directory' => NULL,
|
||||||
|
'max_filesize' => '10MB',
|
||||||
|
'alt_field' => NULL,
|
||||||
|
'alt_field_required' => 1,
|
||||||
|
'title_field' => NULL,
|
||||||
|
'title_field_required' => 1,
|
||||||
|
'max_resolution' => '',
|
||||||
|
'min_resolution' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'imagefield size NULL' => [
|
||||||
|
'imagefield_widget',
|
||||||
|
'a:14:{s:15:"file_extensions";s:11:"gif jpg png";s:9:"file_path";N;s:18:"progress_indicator";N;s:21:"max_filesize_per_file";N;s:21:"max_filesize_per_node";N;s:14:"max_resolution";N;s:14:"min_resolution";N;s:3:"alt";N;s:10:"custom_alt";i:1;s:5:"title";N;s:12:"custom_title";i:1;s:10:"title_type";N;s:13:"default_image";N;s:17:"use_default_image";N;}',
|
||||||
|
[
|
||||||
|
'file_extensions' => 'gif jpg png',
|
||||||
|
'file_directory' => NULL,
|
||||||
|
'max_filesize' => '',
|
||||||
|
'alt_field' => NULL,
|
||||||
|
'alt_field_required' => 1,
|
||||||
|
'title_field' => NULL,
|
||||||
|
'title_field_required' => 1,
|
||||||
|
'max_resolution' => '',
|
||||||
|
'min_resolution' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\hal\Functional\quickedit;
|
||||||
|
|
||||||
|
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonAnonTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group hal
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayHalJsonAnonTest extends LayoutBuilderEntityViewDisplayHalJsonAnonTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\hal\Functional\quickedit;
|
||||||
|
|
||||||
|
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonBasicAuthTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group hal
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayHalJsonBasicAuthTest extends LayoutBuilderEntityViewDisplayHalJsonBasicAuthTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\hal\Functional\quickedit;
|
||||||
|
|
||||||
|
use Drupal\Tests\hal\Functional\layout_builder\LayoutBuilderEntityViewDisplayHalJsonCookieTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group hal
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayHalJsonCookieTest extends LayoutBuilderEntityViewDisplayHalJsonCookieTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -192,7 +192,7 @@ class ResourceObjectNormalizer extends NormalizerBase {
|
|||||||
// @todo Replace this workaround after https://www.drupal.org/node/3043245
|
// @todo Replace this workaround after https://www.drupal.org/node/3043245
|
||||||
// or remove the need for this in https://www.drupal.org/node/2942975.
|
// or remove the need for this in https://www.drupal.org/node/2942975.
|
||||||
// See \Drupal\layout_builder\Normalizer\LayoutEntityDisplayNormalizer.
|
// See \Drupal\layout_builder\Normalizer\LayoutEntityDisplayNormalizer.
|
||||||
if ($context['resource_object']->getResourceType()->getDeserializationTargetClass() === 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay' && $context['resource_object']->getField('third_party_settings') === $field) {
|
if (is_a($context['resource_object']->getResourceType()->getDeserializationTargetClass(), 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay', TRUE) && $context['resource_object']->getField('third_party_settings') === $field) {
|
||||||
unset($field['layout_builder']['sections']);
|
unset($field['layout_builder']['sections']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ use Drupal\layout_builder\InlineBlockEntityOperations;
|
|||||||
use Drupal\Core\Session\AccountInterface;
|
use Drupal\Core\Session\AccountInterface;
|
||||||
use Drupal\Core\Access\AccessResult;
|
use Drupal\Core\Access\AccessResult;
|
||||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||||
use Drupal\layout_builder\QuickEditIntegration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_help().
|
* Implements hook_help().
|
||||||
@ -162,12 +161,6 @@ function layout_builder_entity_view_alter(array &$build, EntityInterface $entity
|
|||||||
if ($display instanceof LayoutBuilderEntityViewDisplay && strpos($route_name, 'layout_builder.') === 0) {
|
if ($display instanceof LayoutBuilderEntityViewDisplay && strpos($route_name, 'layout_builder.') === 0) {
|
||||||
unset($build['#contextual_links']);
|
unset($build['#contextual_links']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Drupal::moduleHandler()->moduleExists('quickedit')) {
|
|
||||||
/** @var \Drupal\layout_builder\QuickEditIntegration $quick_edit_integration */
|
|
||||||
$quick_edit_integration = \Drupal::classResolver(QuickEditIntegration::class);
|
|
||||||
$quick_edit_integration->entityViewAlter($build, $entity, $display);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -351,15 +344,6 @@ function layout_builder_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_quickedit_render_field().
|
|
||||||
*/
|
|
||||||
function layout_builder_quickedit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
|
|
||||||
/** @var \Drupal\layout_builder\QuickEditIntegration $quick_edit_integration */
|
|
||||||
$quick_edit_integration = \Drupal::classResolver(QuickEditIntegration::class);
|
|
||||||
return $quick_edit_integration->quickEditRenderField($entity, $field_name, $view_mode_id, $langcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_entity_translation_create().
|
* Implements hook_entity_translation_create().
|
||||||
*/
|
*/
|
||||||
|
@ -18,7 +18,6 @@ use Drupal\field\Entity\FieldConfig;
|
|||||||
use Drupal\field\Entity\FieldStorageConfig;
|
use Drupal\field\Entity\FieldStorageConfig;
|
||||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
||||||
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
||||||
use Drupal\layout_builder\QuickEditIntegration;
|
|
||||||
use Drupal\layout_builder\Section;
|
use Drupal\layout_builder\Section;
|
||||||
use Drupal\layout_builder\SectionComponent;
|
use Drupal\layout_builder\SectionComponent;
|
||||||
use Drupal\layout_builder\SectionListTrait;
|
use Drupal\layout_builder\SectionListTrait;
|
||||||
@ -473,7 +472,7 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
|||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getComponent($name) {
|
public function getComponent($name) {
|
||||||
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent() ?: $this->getSectionComponentForFieldName($name)) {
|
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getSectionComponentForFieldName($name)) {
|
||||||
$plugin = $section_component->getPlugin();
|
$plugin = $section_component->getPlugin();
|
||||||
if ($plugin instanceof ConfigurableInterface) {
|
if ($plugin instanceof ConfigurableInterface) {
|
||||||
$configuration = $plugin->getConfiguration();
|
$configuration = $plugin->getConfiguration();
|
||||||
@ -485,43 +484,6 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
|||||||
return parent::getComponent($name);
|
return parent::getComponent($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Quick Edit formatter settings.
|
|
||||||
*
|
|
||||||
* @return \Drupal\layout_builder\SectionComponent|null
|
|
||||||
* The section component if it is available.
|
|
||||||
*
|
|
||||||
* @see \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
|
|
||||||
* @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata()
|
|
||||||
*/
|
|
||||||
private function getQuickEditSectionComponent() {
|
|
||||||
// To determine the Quick Edit view_mode ID we need an originalMode set.
|
|
||||||
if ($original_mode = $this->getOriginalMode()) {
|
|
||||||
$parts = explode('-', $original_mode);
|
|
||||||
// The Quick Edit view mode ID is created by
|
|
||||||
// \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
|
|
||||||
// concatenating together the information we need to retrieve the Layout
|
|
||||||
// Builder component. It follows the structure prescribed by the
|
|
||||||
// documentation of hook_quickedit_render_field().
|
|
||||||
if (count($parts) === 6 && $parts[0] === 'layout_builder') {
|
|
||||||
[, $delta, $component_uuid, $entity_id] = QuickEditIntegration::deconstructViewModeId($original_mode);
|
|
||||||
$entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id);
|
|
||||||
$sections = $this->getEntitySections($entity);
|
|
||||||
if (isset($sections[$delta])) {
|
|
||||||
$component = $sections[$delta]->getComponent($component_uuid);
|
|
||||||
$plugin = $component->getPlugin();
|
|
||||||
// We only care about FieldBlock because these are only components
|
|
||||||
// that provide Quick Edit integration: Quick Edit enables in-place
|
|
||||||
// editing of fields of entities, not of anything else.
|
|
||||||
if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
|
|
||||||
return $component;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the component for a given field name if any.
|
* Gets the component for a given field name if any.
|
||||||
*
|
*
|
||||||
|
@ -44,18 +44,12 @@ class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
|||||||
/**
|
/**
|
||||||
* Constructs a new EntityOperations object.
|
* Constructs a new EntityOperations object.
|
||||||
*
|
*
|
||||||
* @todo This constructor has one optional parameter, $section_storage_manager
|
|
||||||
* and one totally unused $database parameter. Deprecate the current
|
|
||||||
* constructor signature in https://www.drupal.org/node/3031492 after the
|
|
||||||
* general policy for constructor backwards compatibility is determined in
|
|
||||||
* https://www.drupal.org/node/3030640.
|
|
||||||
*
|
|
||||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||||
* The entity type manager service.
|
* The entity type manager service.
|
||||||
* @param \Drupal\layout_builder\InlineBlockUsageInterface $usage
|
* @param \Drupal\layout_builder\InlineBlockUsageInterface $usage
|
||||||
* Inline block usage tracking service.
|
* Inline block usage tracking service.
|
||||||
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
||||||
* (optional) The section storage manager.
|
* The section storage manager.
|
||||||
*/
|
*/
|
||||||
public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsageInterface $usage, SectionStorageManagerInterface $section_storage_manager) {
|
public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsageInterface $usage, SectionStorageManagerInterface $section_storage_manager) {
|
||||||
$this->entityTypeManager = $entityTypeManager;
|
$this->entityTypeManager = $entityTypeManager;
|
||||||
|
@ -2,21 +2,9 @@
|
|||||||
|
|
||||||
namespace Drupal\layout_builder;
|
namespace Drupal\layout_builder;
|
||||||
|
|
||||||
use Drupal\Component\Utility\NestedArray;
|
@trigger_error(__NAMESPACE__ . '\QuickEditIntegration is deprecated in drupal:9.4.2 and is removed from drupal:10.0.0. Instead, use \Drupal\quickedit\LayoutBuilderIntegration. See https://www.drupal.org/node/3265518', E_USER_DEPRECATED);
|
||||||
use Drupal\Core\Cache\CacheableMetadata;
|
|
||||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
use Drupal\quickedit\LayoutBuilderIntegration;
|
||||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
|
||||||
use Drupal\Core\Entity\EntityInterface;
|
|
||||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
||||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
|
||||||
use Drupal\Core\Logger\LoggerChannelTrait;
|
|
||||||
use Drupal\Core\Plugin\Context\Context;
|
|
||||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
|
||||||
use Drupal\Core\Plugin\Context\EntityContext;
|
|
||||||
use Drupal\Core\Render\Element;
|
|
||||||
use Drupal\Core\Session\AccountInterface;
|
|
||||||
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper methods for Quick Edit module integration.
|
* Helper methods for Quick Edit module integration.
|
||||||
@ -24,298 +12,4 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||||||
* @internal
|
* @internal
|
||||||
* This is an internal utility class wrapping hook implementations.
|
* This is an internal utility class wrapping hook implementations.
|
||||||
*/
|
*/
|
||||||
class QuickEditIntegration implements ContainerInjectionInterface {
|
class QuickEditIntegration extends LayoutBuilderIntegration {}
|
||||||
|
|
||||||
use LoggerChannelTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The section storage manager.
|
|
||||||
*
|
|
||||||
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
|
||||||
*/
|
|
||||||
protected $sectionStorageManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current user.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Session\AccountInterface
|
|
||||||
*/
|
|
||||||
protected $currentUser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entity type manager.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
|
||||||
*/
|
|
||||||
protected $entityTypeManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new QuickEditIntegration object.
|
|
||||||
*
|
|
||||||
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
|
||||||
* The section storage manager.
|
|
||||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
|
||||||
* The current user.
|
|
||||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
|
||||||
* The entity type manager.
|
|
||||||
*/
|
|
||||||
public function __construct(SectionStorageManagerInterface $section_storage_manager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
|
|
||||||
$this->sectionStorageManager = $section_storage_manager;
|
|
||||||
$this->currentUser = $current_user;
|
|
||||||
$this->entityTypeManager = $entity_type_manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public static function create(ContainerInterface $container) {
|
|
||||||
return new static(
|
|
||||||
$container->get('plugin.manager.layout_builder.section_storage'),
|
|
||||||
$container->get('current_user'),
|
|
||||||
$container->get('entity_type.manager')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alters the entity view build for Quick Edit compatibility.
|
|
||||||
*
|
|
||||||
* When rendering fields outside of normal view modes, Quick Edit requires
|
|
||||||
* that modules identify themselves with a view mode ID in the format
|
|
||||||
* [module_name]-[information the module needs to rerender], as prescribed by
|
|
||||||
* hook_quickedit_render_field().
|
|
||||||
*
|
|
||||||
* @param array $build
|
|
||||||
* The built entity render array.
|
|
||||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
||||||
* The entity.
|
|
||||||
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
|
||||||
* The entity view display.
|
|
||||||
*
|
|
||||||
* @see hook_quickedit_render_field()
|
|
||||||
* @see layout_builder_quickedit_render_field()
|
|
||||||
*/
|
|
||||||
public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
|
|
||||||
if (!$entity instanceof FieldableEntityInterface || !isset($build['_layout_builder'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$build['#cache']['contexts'][] = 'user.permissions';
|
|
||||||
if (!$this->currentUser->hasPermission('access in-place editing')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
|
|
||||||
$section_list = $this->sectionStorageManager->findByContext(
|
|
||||||
[
|
|
||||||
'display' => EntityContext::fromEntity($display),
|
|
||||||
'entity' => EntityContext::fromEntity($entity),
|
|
||||||
'view_mode' => new Context(new ContextDefinition('string'), $display->getMode()),
|
|
||||||
],
|
|
||||||
$cacheable_metadata
|
|
||||||
);
|
|
||||||
$cacheable_metadata->applyTo($build);
|
|
||||||
|
|
||||||
if (empty($section_list)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a hash of the sections and use it in the unique Quick Edit view
|
|
||||||
// mode ID. Any changes to the sections will result in a different hash,
|
|
||||||
// forcing Quick Edit's JavaScript to recognize any changes and retrieve
|
|
||||||
// up-to-date metadata.
|
|
||||||
$sections_hash = hash('sha256', serialize($section_list->getSections()));
|
|
||||||
|
|
||||||
// Track each component by their plugin ID, delta, region, and UUID.
|
|
||||||
$plugin_ids_to_update = [];
|
|
||||||
foreach (Element::children($build['_layout_builder']) as $delta) {
|
|
||||||
$section = $build['_layout_builder'][$delta];
|
|
||||||
|
|
||||||
if (!Element::isEmpty($section)) {
|
|
||||||
/** @var \Drupal\Core\Layout\LayoutDefinition $layout */
|
|
||||||
$layout = $section['#layout'];
|
|
||||||
$regions = $layout->getRegionNames();
|
|
||||||
|
|
||||||
foreach ($regions as $region) {
|
|
||||||
if (isset($section[$region])) {
|
|
||||||
foreach ($section[$region] as $uuid => $component) {
|
|
||||||
if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) {
|
|
||||||
$plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo Remove when https://www.drupal.org/node/3041850 is resolved.
|
|
||||||
$plugin_ids_to_update = array_filter($plugin_ids_to_update, function ($info) {
|
|
||||||
// Delta, region, and UUID each count as one.
|
|
||||||
return count($info, COUNT_RECURSIVE) === 3;
|
|
||||||
});
|
|
||||||
|
|
||||||
$plugin_ids_to_update = NestedArray::mergeDeepArray($plugin_ids_to_update, TRUE);
|
|
||||||
foreach ($plugin_ids_to_update as $delta => $regions) {
|
|
||||||
foreach ($regions as $region => $uuids) {
|
|
||||||
foreach ($uuids as $uuid => $component) {
|
|
||||||
$build['_layout_builder'][$delta][$region][$uuid]['content']['#view_mode'] = static::getViewModeId($entity, $display, $delta, $uuid, $sections_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Alter the Quick Edit view mode ID of all fields outside of the Layout
|
|
||||||
// Builder sections to force Quick Edit to request to the field metadata.
|
|
||||||
// @todo Remove this logic in https://www.drupal.org/project/node/2966136.
|
|
||||||
foreach (Element::children($build) as $field_name) {
|
|
||||||
if ($field_name !== '_layout_builder') {
|
|
||||||
$field_build = &$build[$field_name];
|
|
||||||
if (isset($field_build['#view_mode'])) {
|
|
||||||
$field_build['#view_mode'] = "layout_builder-{$display->getMode()}-non_component-$sections_hash";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Quick Edit view mode ID.
|
|
||||||
*
|
|
||||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
||||||
* The entity.
|
|
||||||
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
|
||||||
* The entity view display.
|
|
||||||
* @param int $delta
|
|
||||||
* The delta.
|
|
||||||
* @param string $component_uuid
|
|
||||||
* The component UUID.
|
|
||||||
* @param string $sections_hash
|
|
||||||
* The hash of the sections; must change whenever the sections change.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* The Quick Edit view mode ID.
|
|
||||||
*
|
|
||||||
* @see \Drupal\layout_builder\QuickEditIntegration::deconstructViewModeId()
|
|
||||||
*/
|
|
||||||
private static function getViewModeId(EntityInterface $entity, EntityViewDisplayInterface $display, $delta, $component_uuid, $sections_hash) {
|
|
||||||
return implode('-', [
|
|
||||||
'layout_builder',
|
|
||||||
$display->getMode(),
|
|
||||||
$delta,
|
|
||||||
// Replace the dashes in the component UUID because we need to
|
|
||||||
// use dashes to join the parts.
|
|
||||||
str_replace('-', '_', $component_uuid),
|
|
||||||
$entity->id(),
|
|
||||||
$sections_hash,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deconstructs the Quick Edit view mode ID into its constituent parts.
|
|
||||||
*
|
|
||||||
* @param string $quick_edit_view_mode_id
|
|
||||||
* The Quick Edit view mode ID.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
* An array containing the entity view mode ID, the delta, the component
|
|
||||||
* UUID, and the entity ID.
|
|
||||||
*
|
|
||||||
* @see \Drupal\layout_builder\QuickEditIntegration::getViewModeId()
|
|
||||||
*/
|
|
||||||
public static function deconstructViewModeId($quick_edit_view_mode_id) {
|
|
||||||
[, $entity_view_mode_id, $delta, $component_uuid, $entity_id] = explode('-', $quick_edit_view_mode_id, 7);
|
|
||||||
return [
|
|
||||||
$entity_view_mode_id,
|
|
||||||
// @todo Explicitly cast delta to an integer, remove this in
|
|
||||||
// https://www.drupal.org/project/drupal/issues/2984509.
|
|
||||||
(int) $delta,
|
|
||||||
// Replace the underscores with dash to get back the component UUID.
|
|
||||||
str_replace('_', '-', $component_uuid),
|
|
||||||
$entity_id,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-renders a field rendered by Layout Builder, edited with Quick Edit.
|
|
||||||
*
|
|
||||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
|
||||||
* The entity.
|
|
||||||
* @param string $field_name
|
|
||||||
* The field name.
|
|
||||||
* @param string $quick_edit_view_mode_id
|
|
||||||
* The Quick Edit view mode ID.
|
|
||||||
* @param string $langcode
|
|
||||||
* The language code.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
* The re-rendered field.
|
|
||||||
*/
|
|
||||||
public function quickEditRenderField(FieldableEntityInterface $entity, $field_name, $quick_edit_view_mode_id, $langcode) {
|
|
||||||
[$entity_view_mode, $delta, $component_uuid] = static::deconstructViewModeId($quick_edit_view_mode_id);
|
|
||||||
|
|
||||||
$entity_build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $entity_view_mode, $langcode);
|
|
||||||
$this->buildEntityView($entity_build);
|
|
||||||
|
|
||||||
if (isset($entity_build['_layout_builder'][$delta])) {
|
|
||||||
foreach (Element::children($entity_build['_layout_builder'][$delta]) as $region) {
|
|
||||||
if (isset($entity_build['_layout_builder'][$delta][$region][$component_uuid])) {
|
|
||||||
return $entity_build['_layout_builder'][$delta][$region][$component_uuid]['content'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getLogger('layout_builder')->warning('The field "%field" failed to render.', ['%field' => $field_name]);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*
|
|
||||||
* @todo Replace this hardcoded processing when
|
|
||||||
* https://www.drupal.org/project/drupal/issues/3041635 is resolved.
|
|
||||||
*
|
|
||||||
* @see \Drupal\Tests\EntityViewTrait::buildEntityView()
|
|
||||||
*/
|
|
||||||
private function buildEntityView(array &$elements) {
|
|
||||||
// If the default values for this element have not been loaded yet,
|
|
||||||
// populate them.
|
|
||||||
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
|
|
||||||
$elements += \Drupal::service('element_info')->getInfo($elements['#type']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make any final changes to the element before it is rendered. This means
|
|
||||||
// that the $element or the children can be altered or corrected before
|
|
||||||
// the element is rendered into the final text.
|
|
||||||
if (isset($elements['#pre_render'])) {
|
|
||||||
foreach ($elements['#pre_render'] as $callable) {
|
|
||||||
$elements = call_user_func($callable, $elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// And recurse.
|
|
||||||
$children = Element::children($elements, TRUE);
|
|
||||||
foreach ($children as $key) {
|
|
||||||
$this->buildEntityView($elements[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether a component has Quick Edit support.
|
|
||||||
*
|
|
||||||
* Only field_block components for display configurable fields should be
|
|
||||||
* supported.
|
|
||||||
*
|
|
||||||
* @param array $component
|
|
||||||
* The component render array.
|
|
||||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
|
||||||
* The entity being displayed.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* Whether Quick Edit is supported on the component.
|
|
||||||
*
|
|
||||||
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
|
|
||||||
*/
|
|
||||||
private function supportQuickEditOnComponent(array $component, FieldableEntityInterface $entity) {
|
|
||||||
if (isset($component['content']['#field_name'], $component['#base_plugin_id']) && $component['#base_plugin_id'] === 'field_block' && $entity->hasField($component['content']['#field_name'])) {
|
|
||||||
return $entity->getFieldDefinition($component['content']['#field_name'])->isDisplayConfigurable('view');
|
|
||||||
}
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -76,8 +76,11 @@ class DownloadFunctionalTest extends BrowserTestBase {
|
|||||||
$this->assertCount(1, $messages);
|
$this->assertCount(1, $messages);
|
||||||
$message = reset($messages);
|
$message = reset($messages);
|
||||||
|
|
||||||
|
// Assert critical parts of the error message, but not the exact message,
|
||||||
|
// since it depends on Guzzle's internal implementation of PSR-7.
|
||||||
$id = $migration->getPluginId();
|
$id = $migration->getPluginId();
|
||||||
$this->assertEquals("$id:uri:download: Client error: `GET $invalid_url` resulted in a `404 Not Found` response ($invalid_url)", $message->message);
|
$this->assertStringContainsString("$id:uri:download:", $message->message);
|
||||||
|
$this->assertStringContainsString($invalid_url, $message->message);
|
||||||
$this->assertEquals(MigrationInterface::MESSAGE_ERROR, $message->level);
|
$this->assertEquals(MigrationInterface::MESSAGE_ERROR, $message->level);
|
||||||
|
|
||||||
// Check that the second row was migrated successfully.
|
// Check that the second row was migrated successfully.
|
||||||
|
@ -75,6 +75,7 @@ class Vid extends NumericArgument {
|
|||||||
->accessCheck(FALSE)
|
->accessCheck(FALSE)
|
||||||
->allRevisions()
|
->allRevisions()
|
||||||
->groupBy('title')
|
->groupBy('title')
|
||||||
|
->condition('vid', $this->value, 'IN')
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
foreach ($results as $result) {
|
foreach ($results as $result) {
|
||||||
|
@ -13,45 +13,12 @@ base_table: node_field_data
|
|||||||
base_field: nid
|
base_field: nid
|
||||||
display:
|
display:
|
||||||
default:
|
default:
|
||||||
display_plugin: default
|
|
||||||
id: default
|
id: default
|
||||||
display_title: Default
|
display_title: Default
|
||||||
|
display_plugin: default
|
||||||
position: 0
|
position: 0
|
||||||
display_options:
|
display_options:
|
||||||
access:
|
title: test_node_revision_id_argument
|
||||||
type: perm
|
|
||||||
options:
|
|
||||||
perm: 'access content'
|
|
||||||
cache:
|
|
||||||
type: tag
|
|
||||||
options: { }
|
|
||||||
query:
|
|
||||||
type: views_query
|
|
||||||
options:
|
|
||||||
disable_sql_rewrite: false
|
|
||||||
distinct: false
|
|
||||||
replica: false
|
|
||||||
query_comment: ''
|
|
||||||
query_tags: { }
|
|
||||||
exposed_form:
|
|
||||||
type: basic
|
|
||||||
options:
|
|
||||||
submit_button: Apply
|
|
||||||
reset_button: false
|
|
||||||
reset_button_label: Reset
|
|
||||||
exposed_sorts_label: 'Sort by'
|
|
||||||
expose_sort_order: true
|
|
||||||
sort_asc_label: Asc
|
|
||||||
sort_desc_label: Desc
|
|
||||||
pager:
|
|
||||||
type: none
|
|
||||||
options:
|
|
||||||
items_per_page: null
|
|
||||||
offset: 0
|
|
||||||
style:
|
|
||||||
type: default
|
|
||||||
row:
|
|
||||||
type: fields
|
|
||||||
fields:
|
fields:
|
||||||
title:
|
title:
|
||||||
id: title
|
id: title
|
||||||
@ -60,6 +27,9 @@ display:
|
|||||||
relationship: none
|
relationship: none
|
||||||
group_type: group
|
group_type: group
|
||||||
admin_label: ''
|
admin_label: ''
|
||||||
|
entity_type: node
|
||||||
|
entity_field: title
|
||||||
|
plugin_id: field
|
||||||
label: ''
|
label: ''
|
||||||
exclude: false
|
exclude: false
|
||||||
alter:
|
alter:
|
||||||
@ -115,16 +85,30 @@ display:
|
|||||||
multi_type: separator
|
multi_type: separator
|
||||||
separator: ', '
|
separator: ', '
|
||||||
field_api_classes: false
|
field_api_classes: false
|
||||||
entity_type: node
|
pager:
|
||||||
entity_field: title
|
type: none
|
||||||
plugin_id: field
|
options:
|
||||||
filters: { }
|
offset: 0
|
||||||
sorts: { }
|
items_per_page: null
|
||||||
title: test_node_revision_id_argument
|
exposed_form:
|
||||||
header: { }
|
type: basic
|
||||||
footer: { }
|
options:
|
||||||
|
submit_button: Apply
|
||||||
|
reset_button: false
|
||||||
|
reset_button_label: Reset
|
||||||
|
exposed_sorts_label: 'Sort by'
|
||||||
|
expose_sort_order: true
|
||||||
|
sort_asc_label: Asc
|
||||||
|
sort_desc_label: Desc
|
||||||
|
access:
|
||||||
|
type: perm
|
||||||
|
options:
|
||||||
|
perm: 'access content'
|
||||||
|
cache:
|
||||||
|
type: tag
|
||||||
|
options: { }
|
||||||
empty: { }
|
empty: { }
|
||||||
relationships: { }
|
sorts: { }
|
||||||
arguments:
|
arguments:
|
||||||
vid:
|
vid:
|
||||||
id: vid
|
id: vid
|
||||||
@ -133,13 +117,16 @@ display:
|
|||||||
relationship: none
|
relationship: none
|
||||||
group_type: group
|
group_type: group
|
||||||
admin_label: ''
|
admin_label: ''
|
||||||
|
entity_type: node
|
||||||
|
entity_field: vid
|
||||||
|
plugin_id: node_vid
|
||||||
default_action: ignore
|
default_action: ignore
|
||||||
exception:
|
exception:
|
||||||
value: all
|
value: all
|
||||||
title_enable: false
|
title_enable: false
|
||||||
title: All
|
title: All
|
||||||
title_enable: false
|
title_enable: true
|
||||||
title: ''
|
title: '{{ arguments.vid }}'
|
||||||
default_argument_type: fixed
|
default_argument_type: fixed
|
||||||
default_argument_options:
|
default_argument_options:
|
||||||
argument: ''
|
argument: ''
|
||||||
@ -147,8 +134,8 @@ display:
|
|||||||
summary_options:
|
summary_options:
|
||||||
base_path: ''
|
base_path: ''
|
||||||
count: true
|
count: true
|
||||||
items_per_page: 25
|
|
||||||
override: false
|
override: false
|
||||||
|
items_per_page: 25
|
||||||
summary:
|
summary:
|
||||||
sort_order: asc
|
sort_order: asc
|
||||||
number_of_records: 0
|
number_of_records: 0
|
||||||
@ -160,38 +147,51 @@ display:
|
|||||||
validate_options: { }
|
validate_options: { }
|
||||||
break_phrase: false
|
break_phrase: false
|
||||||
not: false
|
not: false
|
||||||
entity_type: node
|
filters: { }
|
||||||
entity_field: vid
|
|
||||||
plugin_id: node_vid
|
|
||||||
display_extenders: { }
|
|
||||||
filter_groups:
|
filter_groups:
|
||||||
operator: AND
|
operator: AND
|
||||||
groups: { }
|
groups: { }
|
||||||
|
style:
|
||||||
|
type: default
|
||||||
|
row:
|
||||||
|
type: fields
|
||||||
|
query:
|
||||||
|
type: views_query
|
||||||
|
options:
|
||||||
|
query_comment: ''
|
||||||
|
disable_sql_rewrite: false
|
||||||
|
distinct: false
|
||||||
|
replica: false
|
||||||
|
query_tags: { }
|
||||||
|
relationships: { }
|
||||||
|
header: { }
|
||||||
|
footer: { }
|
||||||
|
display_extenders: { }
|
||||||
cache_metadata:
|
cache_metadata:
|
||||||
|
max-age: -1
|
||||||
contexts:
|
contexts:
|
||||||
- 'languages:language_content'
|
- 'languages:language_content'
|
||||||
- 'languages:language_interface'
|
- 'languages:language_interface'
|
||||||
- url
|
- url
|
||||||
- 'user.node_grants:view'
|
- 'user.node_grants:view'
|
||||||
- user.permissions
|
- user.permissions
|
||||||
cacheable: false
|
|
||||||
max-age: -1
|
|
||||||
tags: { }
|
tags: { }
|
||||||
|
cacheable: false
|
||||||
page_1:
|
page_1:
|
||||||
display_plugin: page
|
|
||||||
id: page_1
|
id: page_1
|
||||||
display_title: Page
|
display_title: Page
|
||||||
|
display_plugin: page
|
||||||
position: 1
|
position: 1
|
||||||
display_options:
|
display_options:
|
||||||
display_extenders: { }
|
display_extenders: { }
|
||||||
path: test-revision-vid-argument
|
path: test-revision-vid-argument
|
||||||
cache_metadata:
|
cache_metadata:
|
||||||
|
max-age: -1
|
||||||
contexts:
|
contexts:
|
||||||
- 'languages:language_content'
|
- 'languages:language_content'
|
||||||
- 'languages:language_interface'
|
- 'languages:language_interface'
|
||||||
- url
|
- url
|
||||||
- 'user.node_grants:view'
|
- 'user.node_grants:view'
|
||||||
- user.permissions
|
- user.permissions
|
||||||
cacheable: false
|
|
||||||
max-age: -1
|
|
||||||
tags: { }
|
tags: { }
|
||||||
|
cacheable: false
|
||||||
|
@ -46,13 +46,16 @@ class ArgumentNodeRevisionIdTest extends ViewsKernelTestBase {
|
|||||||
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
|
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
|
||||||
$node = Node::create(['type' => 'page', 'title' => 'test1', 'uid' => 1]);
|
$node = Node::create(['type' => 'page', 'title' => 'test1', 'uid' => 1]);
|
||||||
$node->save();
|
$node->save();
|
||||||
|
$first_revision_id = $node->getRevisionId();
|
||||||
$node->setNewRevision();
|
$node->setNewRevision();
|
||||||
$node->setTitle('test2');
|
$node->setTitle('test2');
|
||||||
$node->save();
|
$node->save();
|
||||||
|
$second_revision_id = $node->getRevisionId();
|
||||||
|
|
||||||
$view_nid = Views::getView('test_node_revision_id_argument');
|
$view_nid = Views::getView('test_node_revision_id_argument');
|
||||||
$this->executeView($view_nid, [$node->getRevisionId()]);
|
$this->executeView($view_nid, [$second_revision_id]);
|
||||||
$this->assertIdenticalResultset($view_nid, [['title' => 'test2']]);
|
$this->assertIdenticalResultset($view_nid, [['title' => 'test2']]);
|
||||||
|
$this->assertSame('test2', $view_nid->getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,8 @@ use Drupal\Core\Entity\EntityInterface;
|
|||||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||||
use Drupal\Core\Entity\RevisionableInterface;
|
use Drupal\Core\Entity\RevisionableInterface;
|
||||||
use Drupal\Core\Routing\RouteMatchInterface;
|
use Drupal\Core\Routing\RouteMatchInterface;
|
||||||
|
use Drupal\quickedit\Entity\QuickEditLayoutBuilderEntityViewDisplay;
|
||||||
|
use Drupal\quickedit\LayoutBuilderIntegration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_help().
|
* Implements hook_help().
|
||||||
@ -174,10 +176,28 @@ function quickedit_preprocess_field(&$variables) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_entity_type_alter().
|
||||||
|
*/
|
||||||
|
function quickedit_entity_type_alter(array &$entity_types) {
|
||||||
|
if (\Drupal::moduleHandler()->moduleExists('layout_builder')) {
|
||||||
|
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
|
||||||
|
if ($entity_types['entity_view_display']->getClass() === 'Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay\LayoutBuilderEntityViewDisplay') {
|
||||||
|
$entity_types['entity_view_display']->setClass(QuickEditLayoutBuilderEntityViewDisplay::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_entity_view_alter().
|
* Implements hook_entity_view_alter().
|
||||||
*/
|
*/
|
||||||
function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
|
function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||||
|
if (\Drupal::moduleHandler()->moduleExists('layout_builder')) {
|
||||||
|
/** @var \Drupal\quickedit\LayoutBuilderIntegration $layout_builder_integration */
|
||||||
|
$layout_builder_integration = \Drupal::classResolver(LayoutBuilderIntegration::class);
|
||||||
|
$layout_builder_integration->entityViewAlter($build, $entity, $display);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($build['#embed'])) {
|
if (isset($build['#embed'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -189,3 +209,12 @@ function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityVie
|
|||||||
|
|
||||||
$build['#attributes']['data-quickedit-entity-id'] = $entity->getEntityTypeId() . '/' . $entity->id();
|
$build['#attributes']['data-quickedit-entity-id'] = $entity->getEntityTypeId() . '/' . $entity->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_quickedit_render_field().
|
||||||
|
*/
|
||||||
|
function layout_builder_quickedit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
|
||||||
|
/** @var \Drupal\quickedit\LayoutBuilderIntegration $layout_builder_integration */
|
||||||
|
$layout_builder_integration = \Drupal::classResolver(LayoutBuilderIntegration::class);
|
||||||
|
return $layout_builder_integration->quickEditRenderField($entity, $field_name, $view_mode_id, $langcode);
|
||||||
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\quickedit\Entity;
|
||||||
|
|
||||||
|
use Drupal\Component\Plugin\ConfigurableInterface;
|
||||||
|
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||||
|
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||||
|
use Drupal\quickedit\LayoutBuilderIntegration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an entity view display entity that has a layout with quickedit.
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplay extends LayoutBuilderEntityViewDisplay {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getComponent($name) {
|
||||||
|
if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent()) {
|
||||||
|
$plugin = $section_component->getPlugin();
|
||||||
|
if ($plugin instanceof ConfigurableInterface) {
|
||||||
|
$configuration = $plugin->getConfiguration();
|
||||||
|
if (isset($configuration['formatter'])) {
|
||||||
|
return $configuration['formatter'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parent::getComponent($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Quick Edit formatter settings.
|
||||||
|
*
|
||||||
|
* @return \Drupal\layout_builder\SectionComponent|null
|
||||||
|
* The section component if it is available.
|
||||||
|
*
|
||||||
|
* @see \Drupal\quickedit\LayoutBuilderIntegration::entityViewAlter()
|
||||||
|
* @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata()
|
||||||
|
*/
|
||||||
|
private function getQuickEditSectionComponent() {
|
||||||
|
// To determine the Quick Edit view_mode ID we need an originalMode set.
|
||||||
|
if ($original_mode = $this->getOriginalMode()) {
|
||||||
|
$parts = explode('-', $original_mode);
|
||||||
|
// The Quick Edit view mode ID is created by
|
||||||
|
// \Drupal\quickedit\LayoutBuilderIntegration::entityViewAlter()
|
||||||
|
// concatenating together the information we need to retrieve the Layout
|
||||||
|
// Builder component. It follows the structure prescribed by the
|
||||||
|
// documentation of hook_quickedit_render_field().
|
||||||
|
if (count($parts) === 6 && $parts[0] === 'layout_builder') {
|
||||||
|
[, $delta, $component_uuid, $entity_id] = LayoutBuilderIntegration::deconstructViewModeId($original_mode);
|
||||||
|
$entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id);
|
||||||
|
$sections = $this->getEntitySections($entity);
|
||||||
|
if (isset($sections[$delta])) {
|
||||||
|
$component = $sections[$delta]->getComponent($component_uuid);
|
||||||
|
$plugin = $component->getPlugin();
|
||||||
|
// We only care about FieldBlock because these are only components
|
||||||
|
// that provide Quick Edit integration: Quick Edit enables in-place
|
||||||
|
// editing of fields of entities, not of anything else.
|
||||||
|
if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
|
||||||
|
return $component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,321 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\quickedit;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\NestedArray;
|
||||||
|
use Drupal\Core\Cache\CacheableMetadata;
|
||||||
|
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||||
|
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||||
|
use Drupal\Core\Logger\LoggerChannelTrait;
|
||||||
|
use Drupal\Core\Plugin\Context\Context;
|
||||||
|
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||||
|
use Drupal\Core\Plugin\Context\EntityContext;
|
||||||
|
use Drupal\Core\Render\Element;
|
||||||
|
use Drupal\Core\Session\AccountInterface;
|
||||||
|
use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper methods for Layout Builder module integration.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* This is an internal utility class wrapping hook implementations.
|
||||||
|
*/
|
||||||
|
class LayoutBuilderIntegration implements ContainerInjectionInterface {
|
||||||
|
|
||||||
|
use LoggerChannelTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The section storage manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
||||||
|
*/
|
||||||
|
protected $sectionStorageManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current user.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Session\AccountInterface
|
||||||
|
*/
|
||||||
|
protected $currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity type manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||||
|
*/
|
||||||
|
protected $entityTypeManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new LayoutBuilderIntegration object.
|
||||||
|
*
|
||||||
|
* @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
|
||||||
|
* The section storage manager.
|
||||||
|
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||||
|
* The current user.
|
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||||
|
* The entity type manager.
|
||||||
|
*/
|
||||||
|
public function __construct(SectionStorageManagerInterface $section_storage_manager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
|
||||||
|
$this->sectionStorageManager = $section_storage_manager;
|
||||||
|
$this->currentUser = $current_user;
|
||||||
|
$this->entityTypeManager = $entity_type_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container) {
|
||||||
|
return new static(
|
||||||
|
$container->get('plugin.manager.layout_builder.section_storage'),
|
||||||
|
$container->get('current_user'),
|
||||||
|
$container->get('entity_type.manager')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alters the entity view build for Layout Builder compatibility.
|
||||||
|
*
|
||||||
|
* When rendering fields outside of normal view modes, Quick Edit requires
|
||||||
|
* that modules identify themselves with a view mode ID in the format
|
||||||
|
* [module_name]-[information the module needs to rerender], as prescribed by
|
||||||
|
* hook_quickedit_render_field().
|
||||||
|
*
|
||||||
|
* @param array $build
|
||||||
|
* The built entity render array.
|
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||||
|
* The entity.
|
||||||
|
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
||||||
|
* The entity view display.
|
||||||
|
*
|
||||||
|
* @see hook_quickedit_render_field()
|
||||||
|
* @see layout_builder_quickedit_render_field()
|
||||||
|
*/
|
||||||
|
public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||||
|
if (!$entity instanceof FieldableEntityInterface || !isset($build['_layout_builder'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$build['#cache']['contexts'][] = 'user.permissions';
|
||||||
|
if (!$this->currentUser->hasPermission('access in-place editing')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
|
||||||
|
$section_list = $this->sectionStorageManager->findByContext(
|
||||||
|
[
|
||||||
|
'display' => EntityContext::fromEntity($display),
|
||||||
|
'entity' => EntityContext::fromEntity($entity),
|
||||||
|
'view_mode' => new Context(new ContextDefinition('string'), $display->getMode()),
|
||||||
|
],
|
||||||
|
$cacheable_metadata
|
||||||
|
);
|
||||||
|
$cacheable_metadata->applyTo($build);
|
||||||
|
|
||||||
|
if (empty($section_list)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hash of the sections and use it in the unique Quick Edit view
|
||||||
|
// mode ID. Any changes to the sections will result in a different hash,
|
||||||
|
// forcing Quick Edit's JavaScript to recognize any changes and retrieve
|
||||||
|
// up-to-date metadata.
|
||||||
|
$sections_hash = hash('sha256', serialize($section_list->getSections()));
|
||||||
|
|
||||||
|
// Track each component by their plugin ID, delta, region, and UUID.
|
||||||
|
$plugin_ids_to_update = [];
|
||||||
|
foreach (Element::children($build['_layout_builder']) as $delta) {
|
||||||
|
$section = $build['_layout_builder'][$delta];
|
||||||
|
|
||||||
|
if (!Element::isEmpty($section)) {
|
||||||
|
/** @var \Drupal\Core\Layout\LayoutDefinition $layout */
|
||||||
|
$layout = $section['#layout'];
|
||||||
|
$regions = $layout->getRegionNames();
|
||||||
|
|
||||||
|
foreach ($regions as $region) {
|
||||||
|
if (isset($section[$region])) {
|
||||||
|
foreach ($section[$region] as $uuid => $component) {
|
||||||
|
if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) {
|
||||||
|
$plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo Remove when https://www.drupal.org/node/3041850 is resolved.
|
||||||
|
$plugin_ids_to_update = array_filter($plugin_ids_to_update, function ($info) {
|
||||||
|
// Delta, region, and UUID each count as one.
|
||||||
|
return count($info, COUNT_RECURSIVE) === 3;
|
||||||
|
});
|
||||||
|
|
||||||
|
$plugin_ids_to_update = NestedArray::mergeDeepArray($plugin_ids_to_update, TRUE);
|
||||||
|
foreach ($plugin_ids_to_update as $delta => $regions) {
|
||||||
|
foreach ($regions as $region => $uuids) {
|
||||||
|
foreach ($uuids as $uuid => $component) {
|
||||||
|
$build['_layout_builder'][$delta][$region][$uuid]['content']['#view_mode'] = static::getViewModeId($entity, $display, $delta, $uuid, $sections_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Alter the Quick Edit view mode ID of all fields outside of the Layout
|
||||||
|
// Builder sections to force Quick Edit to request to the field metadata.
|
||||||
|
// @todo Remove this logic in https://www.drupal.org/project/node/2966136.
|
||||||
|
foreach (Element::children($build) as $field_name) {
|
||||||
|
if ($field_name !== '_layout_builder') {
|
||||||
|
$field_build = &$build[$field_name];
|
||||||
|
if (isset($field_build['#view_mode'])) {
|
||||||
|
$field_build['#view_mode'] = "layout_builder-{$display->getMode()}-non_component-$sections_hash";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Quick Edit view mode ID.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||||
|
* The entity.
|
||||||
|
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
||||||
|
* The entity view display.
|
||||||
|
* @param int $delta
|
||||||
|
* The delta.
|
||||||
|
* @param string $component_uuid
|
||||||
|
* The component UUID.
|
||||||
|
* @param string $sections_hash
|
||||||
|
* The hash of the sections; must change whenever the sections change.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The Quick Edit view mode ID.
|
||||||
|
*
|
||||||
|
* @see \Drupal\quickedit\LayoutBuilderIntegration::deconstructViewModeId()
|
||||||
|
*/
|
||||||
|
private static function getViewModeId(EntityInterface $entity, EntityViewDisplayInterface $display, $delta, $component_uuid, $sections_hash) {
|
||||||
|
return implode('-', [
|
||||||
|
'layout_builder',
|
||||||
|
$display->getMode(),
|
||||||
|
$delta,
|
||||||
|
// Replace the dashes in the component UUID because we need to
|
||||||
|
// use dashes to join the parts.
|
||||||
|
str_replace('-', '_', $component_uuid),
|
||||||
|
$entity->id(),
|
||||||
|
$sections_hash,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstructs the Quick Edit view mode ID into its constituent parts.
|
||||||
|
*
|
||||||
|
* @param string $quick_edit_view_mode_id
|
||||||
|
* The Quick Edit view mode ID.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An array containing the entity view mode ID, the delta, the component
|
||||||
|
* UUID, and the entity ID.
|
||||||
|
*
|
||||||
|
* @see \Drupal\quickedit\LayoutBuilderIntegration::getViewModeId()
|
||||||
|
*/
|
||||||
|
public static function deconstructViewModeId($quick_edit_view_mode_id) {
|
||||||
|
[, $entity_view_mode_id, $delta, $component_uuid, $entity_id] = explode('-', $quick_edit_view_mode_id, 7);
|
||||||
|
return [
|
||||||
|
$entity_view_mode_id,
|
||||||
|
// @todo Explicitly cast delta to an integer, remove this in
|
||||||
|
// https://www.drupal.org/project/drupal/issues/2984509.
|
||||||
|
(int) $delta,
|
||||||
|
// Replace the underscores with dash to get back the component UUID.
|
||||||
|
str_replace('_', '-', $component_uuid),
|
||||||
|
$entity_id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-renders a field rendered by Layout Builder, edited with Quick Edit.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||||
|
* The entity.
|
||||||
|
* @param string $field_name
|
||||||
|
* The field name.
|
||||||
|
* @param string $quick_edit_view_mode_id
|
||||||
|
* The Quick Edit view mode ID.
|
||||||
|
* @param string $langcode
|
||||||
|
* The language code.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The re-rendered field.
|
||||||
|
*/
|
||||||
|
public function quickEditRenderField(FieldableEntityInterface $entity, $field_name, $quick_edit_view_mode_id, $langcode) {
|
||||||
|
[$entity_view_mode, $delta, $component_uuid] = static::deconstructViewModeId($quick_edit_view_mode_id);
|
||||||
|
|
||||||
|
$entity_build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $entity_view_mode, $langcode);
|
||||||
|
$this->buildEntityView($entity_build);
|
||||||
|
|
||||||
|
if (isset($entity_build['_layout_builder'][$delta])) {
|
||||||
|
foreach (Element::children($entity_build['_layout_builder'][$delta]) as $region) {
|
||||||
|
if (isset($entity_build['_layout_builder'][$delta][$region][$component_uuid])) {
|
||||||
|
return $entity_build['_layout_builder'][$delta][$region][$component_uuid]['content'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getLogger('layout_builder')->warning('The field "%field" failed to render.', ['%field' => $field_name]);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @todo Replace this hardcoded processing when
|
||||||
|
* https://www.drupal.org/project/drupal/issues/3041635 is resolved.
|
||||||
|
*
|
||||||
|
* @see \Drupal\Tests\EntityViewTrait::buildEntityView()
|
||||||
|
*/
|
||||||
|
private function buildEntityView(array &$elements) {
|
||||||
|
// If the default values for this element have not been loaded yet,
|
||||||
|
// populate them.
|
||||||
|
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
|
||||||
|
$elements += \Drupal::service('element_info')->getInfo($elements['#type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make any final changes to the element before it is rendered. This means
|
||||||
|
// that the $element or the children can be altered or corrected before
|
||||||
|
// the element is rendered into the final text.
|
||||||
|
if (isset($elements['#pre_render'])) {
|
||||||
|
foreach ($elements['#pre_render'] as $callable) {
|
||||||
|
$elements = call_user_func($callable, $elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And recurse.
|
||||||
|
$children = Element::children($elements, TRUE);
|
||||||
|
foreach ($children as $key) {
|
||||||
|
$this->buildEntityView($elements[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a component has Quick Edit support.
|
||||||
|
*
|
||||||
|
* Only field_block components for display configurable fields should be
|
||||||
|
* supported.
|
||||||
|
*
|
||||||
|
* @param array $component
|
||||||
|
* The component render array.
|
||||||
|
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||||
|
* The entity being displayed.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* Whether Quick Edit is supported on the component.
|
||||||
|
*
|
||||||
|
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
|
||||||
|
*/
|
||||||
|
private function supportQuickEditOnComponent(array $component, FieldableEntityInterface $entity) {
|
||||||
|
if (isset($component['content']['#field_name'], $component['#base_plugin_id']) && $component['#base_plugin_id'] === 'field_block' && $entity->hasField($component['content']['#field_name'])) {
|
||||||
|
return $entity->getFieldDefinition($component['content']['#field_name'])->isDisplayConfigurable('view');
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Jsonapi;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Jsonapi\LayoutBuilderEntityViewDisplayTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON:API integration test for the "EntityViewDisplay" config entity type.
|
||||||
|
*
|
||||||
|
* @group jsonapi
|
||||||
|
* @group layout_builder
|
||||||
|
* @group quickedit
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayTest extends LayoutBuilderEntityViewDisplayTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonAnonTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayJsonAnonTest extends LayoutBuilderEntityViewDisplayJsonAnonTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonBasicAuthTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayJsonBasicAuthTest extends LayoutBuilderEntityViewDisplayJsonBasicAuthTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayJsonCookieTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayJsonCookieTest extends LayoutBuilderEntityViewDisplayJsonCookieTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlAnonTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayXmlAnonTest extends LayoutBuilderEntityViewDisplayXmlAnonTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlBasicAuthTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayXmlBasicAuthTest extends LayoutBuilderEntityViewDisplayXmlBasicAuthTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\quickedit\Functional\Rest;
|
||||||
|
|
||||||
|
use Drupal\Tests\layout_builder\Functional\Rest\LayoutBuilderEntityViewDisplayXmlCookieTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group quickedit
|
||||||
|
* @group layout_builder
|
||||||
|
* @group rest
|
||||||
|
*/
|
||||||
|
class QuickEditLayoutBuilderEntityViewDisplayXmlCookieTest extends LayoutBuilderEntityViewDisplayXmlCookieTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['quickedit'];
|
||||||
|
|
||||||
|
}
|
@ -14,7 +14,7 @@ use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
|||||||
/**
|
/**
|
||||||
* @group quickedit
|
* @group quickedit
|
||||||
*/
|
*/
|
||||||
class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
class LayoutBuilderIntegrationTest extends QuickEditJavascriptTestBase {
|
||||||
|
|
||||||
use EntityReferenceTestTrait;
|
use EntityReferenceTestTrait;
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
name: 'Decorated Service Test'
|
||||||
|
type: module
|
||||||
|
description: 'Support module for decorated service test.'
|
||||||
|
package: Testing
|
||||||
|
version: VERSION
|
@ -0,0 +1,17 @@
|
|||||||
|
services:
|
||||||
|
test_service:
|
||||||
|
class: 'Drupal\decorated_service_test\TestService'
|
||||||
|
test_service_decorator:
|
||||||
|
class: 'Drupal\decorated_service_test\TestServiceDecorator'
|
||||||
|
public: false
|
||||||
|
decorates: test_service
|
||||||
|
test_service2:
|
||||||
|
class: 'Drupal\decorated_service_test\TestService'
|
||||||
|
test_service2_decorator:
|
||||||
|
class: 'Drupal\decorated_service_test\TestServiceDecorator'
|
||||||
|
public: false
|
||||||
|
decorates: test_service2
|
||||||
|
test_service2_decorator2:
|
||||||
|
class: 'Drupal\decorated_service_test\TestServiceDecorator'
|
||||||
|
public: false
|
||||||
|
decorates: test_service2
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\decorated_service_test;
|
||||||
|
|
||||||
|
class TestService {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\decorated_service_test;
|
||||||
|
|
||||||
|
class TestServiceDecorator extends TestService {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\system\Kernel;
|
||||||
|
|
||||||
|
use Drupal\decorated_service_test\TestServiceDecorator;
|
||||||
|
use Drupal\KernelTests\KernelTestBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test handling of decorated services in DependencySerializationTraitPass.
|
||||||
|
*
|
||||||
|
* @group system
|
||||||
|
*/
|
||||||
|
class DecoratedServiceTest extends KernelTestBase {
|
||||||
|
|
||||||
|
protected static $modules = [
|
||||||
|
'decorated_service_test',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that decorated services keep their original service ID.
|
||||||
|
*/
|
||||||
|
public function testDecoratedServiceId() {
|
||||||
|
// Service decorated once.
|
||||||
|
$test_service = $this->container->get('test_service');
|
||||||
|
$this->assertEquals('test_service', $test_service->_serviceId);
|
||||||
|
$this->assertInstanceOf(TestServiceDecorator::class, $test_service);
|
||||||
|
|
||||||
|
// Service decorated twice.
|
||||||
|
$test_service2 = $this->container->get('test_service2');
|
||||||
|
$this->assertEquals('test_service2', $test_service2->_serviceId);
|
||||||
|
$this->assertInstanceOf(TestServiceDecorator::class, $test_service2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2492,8 +2492,6 @@ class ViewExecutable {
|
|||||||
// state during unserialization.
|
// state during unserialization.
|
||||||
$this->serializationData = [
|
$this->serializationData = [
|
||||||
'storage' => $this->storage->id(),
|
'storage' => $this->storage->id(),
|
||||||
'views_data' => $this->viewsData->_serviceId,
|
|
||||||
'route_provider' => $this->routeProvider->_serviceId,
|
|
||||||
'current_display' => $this->current_display,
|
'current_display' => $this->current_display,
|
||||||
'args' => $this->args,
|
'args' => $this->args,
|
||||||
'current_page' => $this->current_page,
|
'current_page' => $this->current_page,
|
||||||
@ -2520,8 +2518,8 @@ class ViewExecutable {
|
|||||||
|
|
||||||
// Attach all necessary services.
|
// Attach all necessary services.
|
||||||
$this->user = \Drupal::currentUser();
|
$this->user = \Drupal::currentUser();
|
||||||
$this->viewsData = \Drupal::service($this->serializationData['views_data']);
|
$this->viewsData = \Drupal::service('views.views_data');
|
||||||
$this->routeProvider = \Drupal::service($this->serializationData['route_provider']);
|
$this->routeProvider = \Drupal::service('router.route_provider');
|
||||||
|
|
||||||
// Restore the state of this executable.
|
// Restore the state of this executable.
|
||||||
if ($request = \Drupal::request()) {
|
if ($request = \Drupal::request()) {
|
||||||
|
@ -155,9 +155,6 @@ class BasicTest extends WizardTestBase {
|
|||||||
$this->assertSession()->pageTextContains($node1->label());
|
$this->assertSession()->pageTextContains($node1->label());
|
||||||
$this->assertSession()->pageTextNotContains($node2->label());
|
$this->assertSession()->pageTextNotContains($node2->label());
|
||||||
|
|
||||||
// Make sure the listing page doesn't show disabled default views.
|
|
||||||
$this->assertSession()->pageTextNotContains('tracker');
|
|
||||||
|
|
||||||
// Create a view with only a REST export.
|
// Create a view with only a REST export.
|
||||||
$view4 = [];
|
$view4 = [];
|
||||||
$view4['label'] = $this->randomMachineName(16);
|
$view4['label'] = $this->randomMachineName(16);
|
||||||
|
@ -77,7 +77,7 @@ const assetsFolder = `${coreFolder}/assets/vendor`;
|
|||||||
{
|
{
|
||||||
pack: 'backbone',
|
pack: 'backbone',
|
||||||
library: 'internal.backbone',
|
library: 'internal.backbone',
|
||||||
files: ['backbone.js', 'backbone-min.js', 'backbone-min.map'],
|
files: ['backbone.js', 'backbone-min.js', 'backbone-min.js.map'],
|
||||||
},
|
},
|
||||||
// Only used to update the version number of the deprecated library.
|
// Only used to update the version number of the deprecated library.
|
||||||
{
|
{
|
||||||
|
@ -244,6 +244,14 @@ class ComposerProjectTemplatesTest extends BuildTestBase {
|
|||||||
|
|
||||||
$this->executeCommand("COMPOSER_HOME=$composer_home COMPOSER_ROOT_VERSION=$simulated_core_version composer create-project --no-ansi $project testproject $simulated_core_version -vvv --repository $repository_path");
|
$this->executeCommand("COMPOSER_HOME=$composer_home COMPOSER_ROOT_VERSION=$simulated_core_version composer create-project --no-ansi $project testproject $simulated_core_version -vvv --repository $repository_path");
|
||||||
$this->assertCommandSuccessful();
|
$this->assertCommandSuccessful();
|
||||||
|
// Check the output of the project creation for the absence of warnings
|
||||||
|
// about any non-allowed composer plugins.
|
||||||
|
// Note: There are different warnings for unallowed composer plugins
|
||||||
|
// depending on running in non-interactive mode or not. It seems the Drupal
|
||||||
|
// CI environment always forces composer commands to run in the
|
||||||
|
// non-interactive mode. The only thing these messages have in common is the
|
||||||
|
// following string.
|
||||||
|
$this->assertErrorOutputNotContains('See https://getcomposer.org/allow-plugins');
|
||||||
|
|
||||||
// Ensure we used the project from our codebase.
|
// Ensure we used the project from our codebase.
|
||||||
$this->assertErrorOutputContains("Installing $project ($simulated_core_version): Symlinking from $package_dir");
|
$this->assertErrorOutputContains("Installing $project ($simulated_core_version): Symlinking from $package_dir");
|
||||||
@ -386,6 +394,16 @@ JSON;
|
|||||||
"version" => $version,
|
"version" => $version,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
// Ensure composer plugins are registered correctly.
|
||||||
|
$package_json = json_decode(file_get_contents($full_path . '/composer.json'), TRUE);
|
||||||
|
if (isset($package_json['type']) && $package_json['type'] === 'composer-plugin') {
|
||||||
|
$packages['packages'][$name][$version]['type'] = $package_json['type'];
|
||||||
|
$packages['packages'][$name][$version]['require'] = $package_json['require'];
|
||||||
|
$packages['packages'][$name][$version]['extra'] = $package_json['extra'];
|
||||||
|
if (isset($package_json['autoload'])) {
|
||||||
|
$packages['packages'][$name][$version]['autoload'] = $package_json['autoload'];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +265,16 @@ abstract class BuildTestBase extends TestCase {
|
|||||||
$this->assertStringContainsString($expected, $this->commandProcess->getErrorOutput());
|
$this->assertStringContainsString($expected, $this->commandProcess->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert text is not present in the error output of the most recent command.
|
||||||
|
*
|
||||||
|
* @param string $expected
|
||||||
|
* Text we expect not to find in the error output of the command.
|
||||||
|
*/
|
||||||
|
public function assertErrorOutputNotContains($expected) {
|
||||||
|
$this->assertStringNotContainsString($expected, $this->commandProcess->getErrorOutput());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that text is present in the output of the most recent command.
|
* Assert that text is present in the output of the most recent command.
|
||||||
*
|
*
|
||||||
|
@ -760,6 +760,9 @@ class BrowserTestBaseTest extends BrowserTestBase {
|
|||||||
public function testInstall() {
|
public function testInstall() {
|
||||||
$htaccess_filename = $this->tempFilesDirectory . '/.htaccess';
|
$htaccess_filename = $this->tempFilesDirectory . '/.htaccess';
|
||||||
$this->assertFileExists($htaccess_filename);
|
$this->assertFileExists($htaccess_filename);
|
||||||
|
|
||||||
|
// Ensure the update module is not installed.
|
||||||
|
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('update'), 'The Update module is not installed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,7 +105,8 @@ abstract class InstallerExistingConfigTestBase extends InstallerTestBase {
|
|||||||
// existing configuration.
|
// existing configuration.
|
||||||
unset($parameters['forms']['install_configure_form']['site_name']);
|
unset($parameters['forms']['install_configure_form']['site_name']);
|
||||||
unset($parameters['forms']['install_configure_form']['site_mail']);
|
unset($parameters['forms']['install_configure_form']['site_mail']);
|
||||||
unset($parameters['forms']['install_configure_form']['update_status_module']);
|
unset($parameters['forms']['install_configure_form']['enable_update_status_module']);
|
||||||
|
unset($parameters['forms']['install_configure_form']['enable_update_status_emails']);
|
||||||
|
|
||||||
return $parameters;
|
return $parameters;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,9 @@ class InstallerTest extends InstallerTestBase {
|
|||||||
$module = $database->getProvider();
|
$module = $database->getProvider();
|
||||||
$module_handler = \Drupal::service('module_handler');
|
$module_handler = \Drupal::service('module_handler');
|
||||||
|
|
||||||
|
// Ensure the update module is not installed.
|
||||||
|
$this->assertFalse($module_handler->moduleExists('update'), 'The Update module is not installed.');
|
||||||
|
|
||||||
// Assert that the module that is providing the database driver has been
|
// Assert that the module that is providing the database driver has been
|
||||||
// installed.
|
// installed.
|
||||||
$this->assertTrue($module_handler->moduleExists($module));
|
$this->assertTrue($module_handler->moduleExists($module));
|
||||||
|
@ -73,6 +73,19 @@ abstract class InstallerTestBase extends BrowserTestBase {
|
|||||||
*/
|
*/
|
||||||
protected $isInstalled = FALSE;
|
protected $isInstalled = FALSE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function installParameters() {
|
||||||
|
$params = parent::installParameters();
|
||||||
|
// Set the checkbox values to FALSE so that
|
||||||
|
// \Drupal\Tests\BrowserTestBase::translatePostValues() does not remove
|
||||||
|
// them.
|
||||||
|
$params['forms']['install_configure_form']['enable_update_status_module'] = FALSE;
|
||||||
|
$params['forms']['install_configure_form']['enable_update_status_emails'] = FALSE;
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -4,19 +4,30 @@ namespace Drupal\KernelTests\Core\Image;
|
|||||||
|
|
||||||
use Drupal\Core\File\FileSystemInterface;
|
use Drupal\Core\File\FileSystemInterface;
|
||||||
use Drupal\Core\Image\ImageInterface;
|
use Drupal\Core\Image\ImageInterface;
|
||||||
use Drupal\Component\Render\FormattableMarkup;
|
|
||||||
use Drupal\Core\Site\Settings;
|
|
||||||
use Drupal\KernelTests\KernelTestBase;
|
use Drupal\KernelTests\KernelTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests that core image manipulations work properly: scale, resize, rotate,
|
* Tests for the GD image toolkit.
|
||||||
* crop, scale and crop, and desaturate.
|
|
||||||
*
|
*
|
||||||
|
* @coversDefaultClass \Drupal\system\Plugin\ImageToolkit\GDToolkit
|
||||||
* @group Image
|
* @group Image
|
||||||
* @requires extension gd
|
* @requires extension gd
|
||||||
*/
|
*/
|
||||||
class ToolkitGdTest extends KernelTestBase {
|
class ToolkitGdTest extends KernelTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Colors that are used in testing.
|
||||||
|
*/
|
||||||
|
protected const BLACK = [0, 0, 0, 0];
|
||||||
|
protected const RED = [255, 0, 0, 0];
|
||||||
|
protected const GREEN = [0, 255, 0, 0];
|
||||||
|
protected const BLUE = [0, 0, 255, 0];
|
||||||
|
protected const YELLOW = [255, 255, 0, 0];
|
||||||
|
protected const WHITE = [255, 255, 255, 0];
|
||||||
|
protected const TRANSPARENT = [0, 0, 0, 127];
|
||||||
|
protected const FUCHSIA = [255, 0, 255, 0];
|
||||||
|
protected const ROTATE_TRANSPARENT = [255, 255, 255, 127];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The image factory service.
|
* The image factory service.
|
||||||
*
|
*
|
||||||
@ -25,33 +36,14 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
protected $imageFactory;
|
protected $imageFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Colors that are used in testing.
|
* A directory where test image files can be saved to.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $black = [0, 0, 0, 0];
|
protected $directory;
|
||||||
protected $red = [255, 0, 0, 0];
|
|
||||||
protected $green = [0, 255, 0, 0];
|
|
||||||
protected $blue = [0, 0, 255, 0];
|
|
||||||
protected $yellow = [255, 255, 0, 0];
|
|
||||||
protected $white = [255, 255, 255, 0];
|
|
||||||
protected $transparent = [0, 0, 0, 127];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used as rotate background colors.
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $fuchsia = [255, 0, 255, 0];
|
|
||||||
protected $rotateTransparent = [255, 255, 255, 127];
|
|
||||||
|
|
||||||
protected $width = 40;
|
|
||||||
protected $height = 20;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modules to enable.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
*/
|
||||||
protected static $modules = ['system'];
|
protected static $modules = ['system'];
|
||||||
|
|
||||||
@ -62,32 +54,41 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->installConfig(['system']);
|
$this->installConfig(['system']);
|
||||||
|
|
||||||
// Set the image factory service.
|
// Set the image factory service.
|
||||||
$this->imageFactory = $this->container->get('image.factory');
|
$this->imageFactory = $this->container->get('image.factory');
|
||||||
|
$this->assertEquals('gd', $this->imageFactory->getToolkitId(), 'The image factory is set to use the \'gd\' image toolkit.');
|
||||||
|
|
||||||
|
// Prepare a directory for test file results.
|
||||||
|
$this->directory = 'public://imagetest';
|
||||||
|
\Drupal::service('file_system')->prepareDirectory($this->directory, FileSystemInterface::CREATE_DIRECTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to compare two colors by RGBa.
|
* Assert two colors are equal by RGBA, net of full transparency.
|
||||||
|
*
|
||||||
|
* @param int[] $expected
|
||||||
|
* The expected RGBA array.
|
||||||
|
* @param int[] $actual
|
||||||
|
* The actual RGBA array.
|
||||||
|
* @param int $tolerance
|
||||||
|
* The acceptable difference between the colors.
|
||||||
|
* @param string $message
|
||||||
|
* The assertion message.
|
||||||
*/
|
*/
|
||||||
public function colorsAreEqual($color_a, $color_b) {
|
protected function assertColorsAreEqual(array $expected, array $actual, int $tolerance, string $message = ''): void {
|
||||||
// Fully transparent pixels are equal, regardless of RGB.
|
// Fully transparent colors are equal, regardless of RGB.
|
||||||
if ($color_a[3] == 127 && $color_b[3] == 127) {
|
if ($actual[3] == 127 && $expected[3] == 127) {
|
||||||
return TRUE;
|
return;
|
||||||
}
|
}
|
||||||
|
$distance = pow(($actual[0] - $expected[0]), 2) + pow(($actual[1] - $expected[1]), 2) + pow(($actual[2] - $expected[2]), 2) + pow(($actual[3] - $expected[3]), 2);
|
||||||
foreach ($color_a as $key => $value) {
|
$this->assertLessThanOrEqual($tolerance, $distance, $message . " - Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance);
|
||||||
if ($color_b[$key] != $value) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function for finding a pixel's RGBa values.
|
* Function for finding a pixel's RGBa values.
|
||||||
*/
|
*/
|
||||||
public function getPixelColor(ImageInterface $image, $x, $y) {
|
public function getPixelColor(ImageInterface $image, int $x, int $y): array {
|
||||||
$toolkit = $image->getToolkit();
|
$toolkit = $image->getToolkit();
|
||||||
$color_index = imagecolorat($toolkit->getResource(), $x, $y);
|
$color_index = imagecolorat($toolkit->getResource(), $x, $y);
|
||||||
|
|
||||||
@ -100,123 +101,87 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since PHP can't visually check that our images have been manipulated
|
* Data provider for ::testManipulations().
|
||||||
* properly, build a list of expected color values for each of the corners and
|
|
||||||
* the expected height and widths for the final images.
|
|
||||||
*/
|
*/
|
||||||
public function testManipulations() {
|
public function providerTestImageFiles(): array {
|
||||||
|
|
||||||
// Test that the image factory is set to use the GD toolkit.
|
|
||||||
$this->assertEquals('gd', $this->imageFactory->getToolkitId(), 'The image factory is set to use the \'gd\' image toolkit.');
|
|
||||||
|
|
||||||
// Test the list of supported extensions.
|
|
||||||
$expected_extensions = ['png', 'gif', 'jpeg', 'jpg', 'jpe', 'webp'];
|
|
||||||
$supported_extensions = $this->imageFactory->getSupportedExtensions();
|
|
||||||
$this->assertEquals($expected_extensions, array_intersect($expected_extensions, $supported_extensions));
|
|
||||||
|
|
||||||
// Test that the supported extensions map to correct internal GD image
|
|
||||||
// types.
|
|
||||||
$expected_image_types = [
|
|
||||||
'png' => IMAGETYPE_PNG,
|
|
||||||
'gif' => IMAGETYPE_GIF,
|
|
||||||
'jpeg' => IMAGETYPE_JPEG,
|
|
||||||
'jpg' => IMAGETYPE_JPEG,
|
|
||||||
'jpe' => IMAGETYPE_JPEG,
|
|
||||||
'webp' => IMAGETYPE_WEBP,
|
|
||||||
];
|
|
||||||
$image = $this->imageFactory->get();
|
|
||||||
foreach ($expected_image_types as $extension => $expected_image_type) {
|
|
||||||
$image_type = $image->getToolkit()->extensionToImageType($extension);
|
|
||||||
$this->assertSame($expected_image_type, $image_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typically the corner colors will be unchanged. These colors are in the
|
// Typically the corner colors will be unchanged. These colors are in the
|
||||||
// order of top-left, top-right, bottom-right, bottom-left.
|
// order of top-left, top-right, bottom-right, bottom-left.
|
||||||
$default_corners = [$this->red, $this->green, $this->blue, $this->transparent];
|
$default_corners = [static::RED, static::GREEN, static::BLUE, static::TRANSPARENT];
|
||||||
|
|
||||||
// A list of files that will be tested.
|
|
||||||
$files = [
|
|
||||||
'image-test.png',
|
|
||||||
'image-test.gif',
|
|
||||||
'image-test-no-transparency.gif',
|
|
||||||
'image-test.jpg',
|
|
||||||
'img-test.webp',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Setup a list of tests to perform on each type.
|
// Setup a list of tests to perform on each type.
|
||||||
$operations = [
|
$test_cases = [
|
||||||
'resize' => [
|
'resize' => [
|
||||||
'function' => 'resize',
|
'operation' => 'resize',
|
||||||
'arguments' => ['width' => 20, 'height' => 10],
|
'arguments' => ['width' => 20, 'height' => 10],
|
||||||
'width' => 20,
|
'width' => 20,
|
||||||
'height' => 10,
|
'height' => 10,
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'scale_x' => [
|
'scale_x' => [
|
||||||
'function' => 'scale',
|
'operation' => 'scale',
|
||||||
'arguments' => ['width' => 20],
|
'arguments' => ['width' => 20],
|
||||||
'width' => 20,
|
'width' => 20,
|
||||||
'height' => 10,
|
'height' => 10,
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'scale_y' => [
|
'scale_y' => [
|
||||||
'function' => 'scale',
|
'operation' => 'scale',
|
||||||
'arguments' => ['height' => 10],
|
'arguments' => ['height' => 10],
|
||||||
'width' => 20,
|
'width' => 20,
|
||||||
'height' => 10,
|
'height' => 10,
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'upscale_x' => [
|
'upscale_x' => [
|
||||||
'function' => 'scale',
|
'operation' => 'scale',
|
||||||
'arguments' => ['width' => 80, 'upscale' => TRUE],
|
'arguments' => ['width' => 80, 'upscale' => TRUE],
|
||||||
'width' => 80,
|
'width' => 80,
|
||||||
'height' => 40,
|
'height' => 40,
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'upscale_y' => [
|
'upscale_y' => [
|
||||||
'function' => 'scale',
|
'operation' => 'scale',
|
||||||
'arguments' => ['height' => 40, 'upscale' => TRUE],
|
'arguments' => ['height' => 40, 'upscale' => TRUE],
|
||||||
'width' => 80,
|
'width' => 80,
|
||||||
'height' => 40,
|
'height' => 40,
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'crop' => [
|
'crop' => [
|
||||||
'function' => 'crop',
|
'operation' => 'crop',
|
||||||
'arguments' => ['x' => 12, 'y' => 4, 'width' => 16, 'height' => 12],
|
'arguments' => ['x' => 12, 'y' => 4, 'width' => 16, 'height' => 12],
|
||||||
'width' => 16,
|
'width' => 16,
|
||||||
'height' => 12,
|
'height' => 12,
|
||||||
'corners' => array_fill(0, 4, $this->white),
|
'corners' => array_fill(0, 4, static::WHITE),
|
||||||
],
|
],
|
||||||
'scale_and_crop' => [
|
'scale_and_crop' => [
|
||||||
'function' => 'scale_and_crop',
|
'operation' => 'scale_and_crop',
|
||||||
'arguments' => ['width' => 10, 'height' => 8],
|
'arguments' => ['width' => 10, 'height' => 8],
|
||||||
'width' => 10,
|
'width' => 10,
|
||||||
'height' => 8,
|
'height' => 8,
|
||||||
'corners' => array_fill(0, 4, $this->black),
|
'corners' => array_fill(0, 4, static::BLACK),
|
||||||
],
|
],
|
||||||
'convert_jpg' => [
|
'convert_jpg' => [
|
||||||
'function' => 'convert',
|
'operation' => 'convert',
|
||||||
'width' => 40,
|
'width' => 40,
|
||||||
'height' => 20,
|
'height' => 20,
|
||||||
'arguments' => ['extension' => 'jpeg'],
|
'arguments' => ['extension' => 'jpeg'],
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'convert_gif' => [
|
'convert_gif' => [
|
||||||
'function' => 'convert',
|
'operation' => 'convert',
|
||||||
'width' => 40,
|
'width' => 40,
|
||||||
'height' => 20,
|
'height' => 20,
|
||||||
'arguments' => ['extension' => 'gif'],
|
'arguments' => ['extension' => 'gif'],
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'convert_png' => [
|
'convert_png' => [
|
||||||
'function' => 'convert',
|
'operation' => 'convert',
|
||||||
'width' => 40,
|
'width' => 40,
|
||||||
'height' => 20,
|
'height' => 20,
|
||||||
'arguments' => ['extension' => 'png'],
|
'arguments' => ['extension' => 'png'],
|
||||||
'corners' => $default_corners,
|
'corners' => $default_corners,
|
||||||
],
|
],
|
||||||
'convert_webp' => [
|
'convert_webp' => [
|
||||||
'function' => 'convert',
|
'operation' => 'convert',
|
||||||
'width' => 40,
|
'width' => 40,
|
||||||
'height' => 20,
|
'height' => 20,
|
||||||
'arguments' => ['extension' => 'webp'],
|
'arguments' => ['extension' => 'webp'],
|
||||||
@ -224,49 +189,51 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Systems using non-bundled GD2 don't have imagerotate. Test if available.
|
// Systems using non-bundled GD2 may miss imagerotate(). Test if available.
|
||||||
// @todo Remove the version check once
|
if (function_exists('imagerotate')) {
|
||||||
// https://www.drupal.org/project/drupal/issues/2670966 is resolved.
|
$test_cases += [
|
||||||
if (function_exists('imagerotate') && (version_compare(phpversion(), '7.0.26') < 0)) {
|
|
||||||
$operations += [
|
|
||||||
'rotate_5' => [
|
'rotate_5' => [
|
||||||
'function' => 'rotate',
|
'operation' => 'rotate',
|
||||||
// Fuchsia background.
|
// Fuchsia background.
|
||||||
'arguments' => ['degrees' => 5, 'background' => '#FF00FF'],
|
'arguments' => ['degrees' => 5, 'background' => '#FF00FF'],
|
||||||
'width' => 41,
|
// @todo Re-enable dimensions' check once
|
||||||
'height' => 23,
|
// https://www.drupal.org/project/drupal/issues/2921123 is resolved.
|
||||||
'corners' => array_fill(0, 4, $this->fuchsia),
|
// 'width' => 41,
|
||||||
|
// 'height' => 23,
|
||||||
|
'corners' => array_fill(0, 4, static::FUCHSIA),
|
||||||
|
],
|
||||||
|
'rotate_transparent_5' => [
|
||||||
|
'operation' => 'rotate',
|
||||||
|
'arguments' => ['degrees' => 5],
|
||||||
|
// @todo Re-enable dimensions' check once
|
||||||
|
// https://www.drupal.org/project/drupal/issues/2921123 is resolved.
|
||||||
|
// 'width' => 41,
|
||||||
|
// 'height' => 23,
|
||||||
|
'corners' => array_fill(0, 4, static::ROTATE_TRANSPARENT),
|
||||||
],
|
],
|
||||||
'rotate_90' => [
|
'rotate_90' => [
|
||||||
'function' => 'rotate',
|
'operation' => 'rotate',
|
||||||
// Fuchsia background.
|
// Fuchsia background.
|
||||||
'arguments' => ['degrees' => 90, 'background' => '#FF00FF'],
|
'arguments' => ['degrees' => 90, 'background' => '#FF00FF'],
|
||||||
'width' => 20,
|
'width' => 20,
|
||||||
'height' => 40,
|
'height' => 40,
|
||||||
'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
|
'corners' => [static::TRANSPARENT, static::RED, static::GREEN, static::BLUE],
|
||||||
],
|
|
||||||
'rotate_transparent_5' => [
|
|
||||||
'function' => 'rotate',
|
|
||||||
'arguments' => ['degrees' => 5],
|
|
||||||
'width' => 41,
|
|
||||||
'height' => 23,
|
|
||||||
'corners' => array_fill(0, 4, $this->rotateTransparent),
|
|
||||||
],
|
],
|
||||||
'rotate_transparent_90' => [
|
'rotate_transparent_90' => [
|
||||||
'function' => 'rotate',
|
'operation' => 'rotate',
|
||||||
'arguments' => ['degrees' => 90],
|
'arguments' => ['degrees' => 90],
|
||||||
'width' => 20,
|
'width' => 20,
|
||||||
'height' => 40,
|
'height' => 40,
|
||||||
'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
|
'corners' => [static::TRANSPARENT, static::RED, static::GREEN, static::BLUE],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Systems using non-bundled GD2 don't have imagefilter. Test if available.
|
// Systems using non-bundled GD2 may miss imagefilter(). Test if available.
|
||||||
if (function_exists('imagefilter')) {
|
if (function_exists('imagefilter')) {
|
||||||
$operations += [
|
$test_cases += [
|
||||||
'desaturate' => [
|
'desaturate' => [
|
||||||
'function' => 'desaturate',
|
'operation' => 'desaturate',
|
||||||
'arguments' => [],
|
'arguments' => [],
|
||||||
'height' => 20,
|
'height' => 20,
|
||||||
'width' => 40,
|
'width' => 40,
|
||||||
@ -283,148 +250,202 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare a directory for test file results.
|
$ret = [];
|
||||||
$directory = Settings::get('file_public_path') . '/imagetest';
|
foreach ([
|
||||||
\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
|
'image-test.png',
|
||||||
|
'image-test.gif',
|
||||||
foreach ($files as $file) {
|
'image-test-no-transparency.gif',
|
||||||
foreach ($operations as $op => $values) {
|
'image-test.jpg',
|
||||||
// Load up a fresh image.
|
'img-test.webp',
|
||||||
$image = $this->imageFactory->get('core/tests/fixtures/files/' . $file);
|
] as $file_name) {
|
||||||
$toolkit = $image->getToolkit();
|
foreach ($test_cases as $test_case => $values) {
|
||||||
if (!$image->isValid()) {
|
$operation = $values['operation'];
|
||||||
$this->fail(new FormattableMarkup('Could not load image %file.', ['%file' => $file]));
|
$arguments = $values['arguments'];
|
||||||
continue 2;
|
unset($values['operation'], $values['arguments']);
|
||||||
}
|
$ret[] = [$file_name, $test_case, $operation, $arguments, $values];
|
||||||
$image_original_type = $image->getToolkit()->getType();
|
|
||||||
|
|
||||||
// All images should be converted to truecolor when loaded.
|
|
||||||
$image_truecolor = imageistruecolor($toolkit->getResource());
|
|
||||||
$this->assertTrue($image_truecolor, new FormattableMarkup('Image %file after load is a truecolor image.', ['%file' => $file]));
|
|
||||||
|
|
||||||
// Store the original GD resource.
|
|
||||||
$old_res = $toolkit->getResource();
|
|
||||||
|
|
||||||
// Perform our operation.
|
|
||||||
$image->apply($values['function'], $values['arguments']);
|
|
||||||
|
|
||||||
// If the operation replaced the resource, check that the old one has
|
|
||||||
// been destroyed.
|
|
||||||
$new_res = $toolkit->getResource();
|
|
||||||
if ($new_res !== $old_res) {
|
|
||||||
// @todo In https://www.drupal.org/node/3133236 convert this to
|
|
||||||
// $this->assertIsNotResource($old_res).
|
|
||||||
$this->assertFalse(is_resource($old_res), new FormattableMarkup("'%operation' destroyed the original resource.", ['%operation' => $values['function']]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// To keep from flooding the test with assert values, make a general
|
|
||||||
// value for whether each group of values fail.
|
|
||||||
$correct_dimensions_real = TRUE;
|
|
||||||
$correct_dimensions_object = TRUE;
|
|
||||||
|
|
||||||
if (imagesy($toolkit->getResource()) != $values['height'] || imagesx($toolkit->getResource()) != $values['width']) {
|
|
||||||
$correct_dimensions_real = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the image object has an accurate record of the dimensions.
|
|
||||||
if ($image->getWidth() != $values['width'] || $image->getHeight() != $values['height']) {
|
|
||||||
$correct_dimensions_object = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$file_path = $directory . '/' . $op . image_type_to_extension($image->getToolkit()->getType());
|
|
||||||
$image->save($file_path);
|
|
||||||
|
|
||||||
$this->assertTrue($correct_dimensions_real, new FormattableMarkup('Image %file after %action action has proper dimensions.', ['%file' => $file, '%action' => $op]));
|
|
||||||
$this->assertTrue($correct_dimensions_object, new FormattableMarkup('Image %file object after %action action is reporting the proper height and width values.', ['%file' => $file, '%action' => $op]));
|
|
||||||
|
|
||||||
// JPEG colors will always be messed up due to compression. So we skip
|
|
||||||
// these tests if the original or the result is in jpeg format.
|
|
||||||
if ($image->getToolkit()->getType() != IMAGETYPE_JPEG && $image_original_type != IMAGETYPE_JPEG) {
|
|
||||||
// Now check each of the corners to ensure color correctness.
|
|
||||||
foreach ($values['corners'] as $key => $corner) {
|
|
||||||
// The test gif that does not have transparency color set is a
|
|
||||||
// special case.
|
|
||||||
if ($file === 'image-test-no-transparency.gif') {
|
|
||||||
if ($op == 'desaturate') {
|
|
||||||
// For desaturating, keep the expected color from the test
|
|
||||||
// data, but set alpha channel to fully opaque.
|
|
||||||
$corner[3] = 0;
|
|
||||||
}
|
|
||||||
elseif ($corner === $this->transparent) {
|
|
||||||
// Set expected pixel to yellow where the others have
|
|
||||||
// transparent.
|
|
||||||
$corner = $this->yellow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the location of the corner.
|
|
||||||
switch ($key) {
|
|
||||||
case 0:
|
|
||||||
$x = 0;
|
|
||||||
$y = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
$x = $image->getWidth() - 1;
|
|
||||||
$y = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
$x = $image->getWidth() - 1;
|
|
||||||
$y = $image->getHeight() - 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
$x = 0;
|
|
||||||
$y = $image->getHeight() - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$color = $this->getPixelColor($image, $x, $y);
|
|
||||||
// We also skip the color test for transparency for gif <-> png
|
|
||||||
// conversion. The convert operation cannot handle that correctly.
|
|
||||||
if ($image->getToolkit()->getType() == $image_original_type || $corner != $this->transparent) {
|
|
||||||
$correct_colors = $this->colorsAreEqual($color, $corner);
|
|
||||||
$this->assertTrue($correct_colors, new FormattableMarkup('Image %file object after %action action has the correct color placement at corner %corner.',
|
|
||||||
['%file' => $file, '%action' => $op, '%corner' => $key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that saved image reloads without raising PHP errors.
|
|
||||||
$image_reloaded = $this->imageFactory->get($file_path);
|
|
||||||
$resource = $image_reloaded->getToolkit()->getResource();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test creation of image from scratch, and saving to storage.
|
return $ret;
|
||||||
foreach ([IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_WEBP] as $type) {
|
}
|
||||||
$image = $this->imageFactory->get();
|
|
||||||
$image->createNew(50, 20, image_type_to_extension($type, FALSE), '#ffff00');
|
|
||||||
$file = 'from_null' . image_type_to_extension($type);
|
|
||||||
$file_path = $directory . '/' . $file;
|
|
||||||
$this->assertEquals(50, $image->getWidth(), new FormattableMarkup('Image file %file has the correct width.', ['%file' => $file]));
|
|
||||||
$this->assertEquals(20, $image->getHeight(), new FormattableMarkup('Image file %file has the correct height.', ['%file' => $file]));
|
|
||||||
$this->assertEquals(image_type_to_mime_type($type), $image->getMimeType(), new FormattableMarkup('Image file %file has the correct MIME type.', ['%file' => $file]));
|
|
||||||
$this->assertTrue($image->save($file_path), new FormattableMarkup('Image %file created anew from a null image was saved.', ['%file' => $file]));
|
|
||||||
|
|
||||||
// Reload saved image.
|
/**
|
||||||
$image_reloaded = $this->imageFactory->get($file_path);
|
* Since PHP can't visually check that our images have been manipulated
|
||||||
if (!$image_reloaded->isValid()) {
|
* properly, build a list of expected color values for each of the corners and
|
||||||
$this->fail(new FormattableMarkup('Could not load image %file.', ['%file' => $file]));
|
* the expected height and widths for the final images.
|
||||||
|
*
|
||||||
|
* @dataProvider providerTestImageFiles
|
||||||
|
*/
|
||||||
|
public function testManipulations(string $file_name, string $test_case, string $operation, array $arguments, array $expected): void {
|
||||||
|
// Load up a fresh image.
|
||||||
|
$image = $this->imageFactory->get('core/tests/fixtures/files/' . $file_name);
|
||||||
|
$toolkit = $image->getToolkit();
|
||||||
|
$this->assertTrue($image->isValid());
|
||||||
|
$image_original_type = $image->getToolkit()->getType();
|
||||||
|
|
||||||
|
$this->assertTrue(imageistruecolor($toolkit->getResource()), "Image '$file_name' after load should be a truecolor image, but it is not.");
|
||||||
|
|
||||||
|
// Perform our operation.
|
||||||
|
$image->apply($operation, $arguments);
|
||||||
|
|
||||||
|
// Flush Image object to disk storage.
|
||||||
|
$file_path = $this->directory . '/' . $test_case . image_type_to_extension($image->getToolkit()->getType());
|
||||||
|
$image->save($file_path);
|
||||||
|
|
||||||
|
// Check that the both the GD object and the Image object have an accurate
|
||||||
|
// record of the dimensions.
|
||||||
|
if (isset($expected['height']) && isset($expected['width'])) {
|
||||||
|
$this->assertSame($expected['height'], imagesy($toolkit->getResource()), "Image '$file_name' after '$test_case' should have a proper height.");
|
||||||
|
$this->assertSame($expected['width'], imagesx($toolkit->getResource()), "Image '$file_name' after '$test_case' should have a proper width.");
|
||||||
|
$this->assertSame($expected['height'], $image->getHeight(), "Image '$file_name' after '$test_case' should have a proper height.");
|
||||||
|
$this->assertSame($expected['width'], $image->getWidth(), "Image '$file_name' after '$test_case' should have a proper width.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check each of the corners to ensure color correctness.
|
||||||
|
foreach ($expected['corners'] as $key => $expected_color) {
|
||||||
|
// The test gif that does not have transparency color set is a
|
||||||
|
// special case.
|
||||||
|
if ($file_name === 'image-test-no-transparency.gif') {
|
||||||
|
if ($test_case == 'desaturate') {
|
||||||
|
// For desaturating, keep the expected color from the test
|
||||||
|
// data, but set alpha channel to fully opaque.
|
||||||
|
$expected_color[3] = 0;
|
||||||
|
}
|
||||||
|
elseif ($expected_color === static::TRANSPARENT) {
|
||||||
|
// Set expected pixel to yellow where the others have
|
||||||
|
// transparent.
|
||||||
|
$expected_color = static::YELLOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the location of the corner.
|
||||||
|
switch ($key) {
|
||||||
|
case 0:
|
||||||
|
$x = 0;
|
||||||
|
$y = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
$x = $image->getWidth() - 1;
|
||||||
|
$y = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
$x = $image->getWidth() - 1;
|
||||||
|
$y = $image->getHeight() - 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
$x = 0;
|
||||||
|
$y = $image->getHeight() - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$actual_color = $this->getPixelColor($image, $x, $y);
|
||||||
|
|
||||||
|
// If image cannot handle transparent colors, skip the pixel color test.
|
||||||
|
if ($actual_color[3] === 0 && $expected_color[3] === 127) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->assertEquals(50, $image_reloaded->getWidth(), new FormattableMarkup('Image file %file has the correct width.', ['%file' => $file]));
|
|
||||||
$this->assertEquals(20, $image_reloaded->getHeight(), new FormattableMarkup('Image file %file has the correct height.', ['%file' => $file]));
|
// JPEG has small differences in color after processing.
|
||||||
$this->assertEquals(image_type_to_mime_type($type), $image_reloaded->getMimeType(), new FormattableMarkup('Image file %file has the correct MIME type.', ['%file' => $file]));
|
$tolerance = $image_original_type === IMAGETYPE_JPEG ? 3 : 0;
|
||||||
if ($image_reloaded->getToolkit()->getType() == IMAGETYPE_GIF) {
|
|
||||||
$this->assertEquals('#ffff00', $image_reloaded->getToolkit()->getTransparentColor(), new FormattableMarkup('Image file %file has the correct transparent color channel set.', ['%file' => $file]));
|
$this->assertColorsAreEqual($expected_color, $actual_color, $tolerance, "Image '$file_name' object after '$test_case' action has the correct color placement at corner '$key'");
|
||||||
}
|
|
||||||
else {
|
|
||||||
$this->assertNull($image_reloaded->getToolkit()->getTransparentColor(), new FormattableMarkup('Image file %file has no color channel set.', ['%file' => $file]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test failures of the 'create_new' operation.
|
// Check that saved image reloads without raising PHP errors.
|
||||||
|
$image_reloaded = $this->imageFactory->get($file_path);
|
||||||
|
if (PHP_VERSION_ID >= 80000) {
|
||||||
|
$this->assertInstanceOf(\GDImage::class, $image_reloaded->getToolkit()->getResource());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->assertIsResource($image_reloaded->getToolkit()->getResource());
|
||||||
|
$this->assertSame(get_resource_type($image_reloaded->getToolkit()->getResource()), 'gd');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::getSupportedExtensions
|
||||||
|
* @covers ::extensionToImageType
|
||||||
|
*/
|
||||||
|
public function testSupportedExtensions(): void {
|
||||||
|
// Test the list of supported extensions.
|
||||||
|
$expected_extensions = ['png', 'gif', 'jpeg', 'jpg', 'jpe', 'webp'];
|
||||||
|
$this->assertEqualsCanonicalizing($expected_extensions, $this->imageFactory->getSupportedExtensions());
|
||||||
|
|
||||||
|
// Test that the supported extensions map to correct internal GD image
|
||||||
|
// types.
|
||||||
|
$expected_image_types = [
|
||||||
|
'png' => IMAGETYPE_PNG,
|
||||||
|
'gif' => IMAGETYPE_GIF,
|
||||||
|
'jpeg' => IMAGETYPE_JPEG,
|
||||||
|
'jpg' => IMAGETYPE_JPEG,
|
||||||
|
'jpe' => IMAGETYPE_JPEG,
|
||||||
|
'webp' => IMAGETYPE_WEBP,
|
||||||
|
];
|
||||||
|
$image = $this->imageFactory->get();
|
||||||
|
foreach ($expected_image_types as $extension => $expected_image_type) {
|
||||||
|
$this->assertSame($expected_image_type, $image->getToolkit()->extensionToImageType($extension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider for ::testCreateImageFromScratch().
|
||||||
|
*/
|
||||||
|
public function providerSupportedImageTypes(): array {
|
||||||
|
return [
|
||||||
|
[IMAGETYPE_PNG],
|
||||||
|
[IMAGETYPE_GIF],
|
||||||
|
[IMAGETYPE_JPEG],
|
||||||
|
[IMAGETYPE_WEBP],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that GD functions for the image type are available.
|
||||||
|
*
|
||||||
|
* @dataProvider providerSupportedImageTypes
|
||||||
|
*/
|
||||||
|
public function testGdFunctionsExist(int $type): void {
|
||||||
|
$extension = image_type_to_extension($type, FALSE);
|
||||||
|
$this->assertTrue(function_exists("imagecreatefrom$extension"), "imagecreatefrom$extension should exist.");
|
||||||
|
$this->assertTrue(function_exists("image$extension"), "image$extension should exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests creation of image from scratch, and saving to storage.
|
||||||
|
*
|
||||||
|
* @dataProvider providerSupportedImageTypes
|
||||||
|
*/
|
||||||
|
public function testCreateImageFromScratch(int $type): void {
|
||||||
|
// Build an image from scratch.
|
||||||
|
$image = $this->imageFactory->get();
|
||||||
|
$image->createNew(50, 20, image_type_to_extension($type, FALSE), '#ffff00');
|
||||||
|
$file = 'from_null' . image_type_to_extension($type);
|
||||||
|
$file_path = $this->directory . '/' . $file;
|
||||||
|
$this->assertSame(50, $image->getWidth());
|
||||||
|
$this->assertSame(20, $image->getHeight());
|
||||||
|
$this->assertSame(image_type_to_mime_type($type), $image->getMimeType());
|
||||||
|
$this->assertTrue($image->save($file_path), "Image '$file' should have been saved successfully, but it has not.");
|
||||||
|
|
||||||
|
// Reload and check saved image.
|
||||||
|
$image_reloaded = $this->imageFactory->get($file_path);
|
||||||
|
$this->assertTrue($image_reloaded->isValid());
|
||||||
|
$this->assertSame(50, $image_reloaded->getWidth());
|
||||||
|
$this->assertSame(20, $image_reloaded->getHeight());
|
||||||
|
$this->assertSame(image_type_to_mime_type($type), $image_reloaded->getMimeType());
|
||||||
|
if ($image_reloaded->getToolkit()->getType() == IMAGETYPE_GIF) {
|
||||||
|
$this->assertSame('#ffff00', $image_reloaded->getToolkit()->getTransparentColor(), "Image '$file' after reload should have color channel set to #ffff00, but it has not.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->assertNull($image_reloaded->getToolkit()->getTransparentColor(), "Image '$file' after reload should have no color channel set, but it has.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests failures of the 'create_new' operation.
|
||||||
|
*/
|
||||||
|
public function testCreateNewFailures(): void {
|
||||||
$image = $this->imageFactory->get();
|
$image = $this->imageFactory->get();
|
||||||
$image->createNew(-50, 20);
|
$image->createNew(-50, 20);
|
||||||
$this->assertFalse($image->isValid(), 'CreateNew with negative width fails.');
|
$this->assertFalse($image->isValid(), 'CreateNew with negative width fails.');
|
||||||
@ -472,11 +493,7 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
/**
|
/**
|
||||||
* Tests for GIF images with transparency.
|
* Tests for GIF images with transparency.
|
||||||
*/
|
*/
|
||||||
public function testGifTransparentImages() {
|
public function testGifTransparentImages(): void {
|
||||||
// Prepare a directory for test file results.
|
|
||||||
$directory = Settings::get('file_public_path') . '/imagetest';
|
|
||||||
\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
|
|
||||||
|
|
||||||
// Test loading an indexed GIF image with transparent color set.
|
// Test loading an indexed GIF image with transparent color set.
|
||||||
// Color at top-right pixel should be fully transparent.
|
// Color at top-right pixel should be fully transparent.
|
||||||
$file = 'image-test-transparent-indexed.gif';
|
$file = 'image-test-transparent-indexed.gif';
|
||||||
@ -484,20 +501,20 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
$resource = $image->getToolkit()->getResource();
|
$resource = $image->getToolkit()->getResource();
|
||||||
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
|
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
|
||||||
$color = array_values(imagecolorsforindex($resource, $color_index));
|
$color = array_values(imagecolorsforindex($resource, $color_index));
|
||||||
$this->assertEquals($this->rotateTransparent, $color, "Image {$file} after load has full transparent color at corner 1.");
|
$this->assertEquals(static::ROTATE_TRANSPARENT, $color, "Image {$file} after load has full transparent color at corner 1.");
|
||||||
|
|
||||||
// Test deliberately creating a GIF image with no transparent color set.
|
// Test deliberately creating a GIF image with no transparent color set.
|
||||||
// Color at top-right pixel should be fully transparent while in memory,
|
// Color at top-right pixel should be fully transparent while in memory,
|
||||||
// fully opaque after flushing image to file.
|
// fully opaque after flushing image to file.
|
||||||
$file = 'image-test-no-transparent-color-set.gif';
|
$file = 'image-test-no-transparent-color-set.gif';
|
||||||
$file_path = $directory . '/' . $file;
|
$file_path = $this->directory . '/' . $file;
|
||||||
// Create image.
|
// Create image.
|
||||||
$image = $this->imageFactory->get();
|
$image = $this->imageFactory->get();
|
||||||
$image->createNew(50, 20, 'gif', NULL);
|
$image->createNew(50, 20, 'gif', NULL);
|
||||||
$resource = $image->getToolkit()->getResource();
|
$resource = $image->getToolkit()->getResource();
|
||||||
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
|
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
|
||||||
$color = array_values(imagecolorsforindex($resource, $color_index));
|
$color = array_values(imagecolorsforindex($resource, $color_index));
|
||||||
$this->assertEquals($this->rotateTransparent, $color, "New GIF image with no transparent color set after creation has full transparent color at corner 1.");
|
$this->assertEquals(static::ROTATE_TRANSPARENT, $color, "New GIF image with no transparent color set after creation has full transparent color at corner 1.");
|
||||||
// Save image.
|
// Save image.
|
||||||
$this->assertTrue($image->save($file_path), "New GIF image {$file} was saved.");
|
$this->assertTrue($image->save($file_path), "New GIF image {$file} was saved.");
|
||||||
// Reload image.
|
// Reload image.
|
||||||
@ -522,37 +539,20 @@ class ToolkitGdTest extends KernelTestBase {
|
|||||||
// can be loaded correctly.
|
// can be loaded correctly.
|
||||||
$file = 'image-test-transparent-out-of-range.gif';
|
$file = 'image-test-transparent-out-of-range.gif';
|
||||||
$image = $this->imageFactory->get('core/tests/fixtures/files/' . $file);
|
$image = $this->imageFactory->get('core/tests/fixtures/files/' . $file);
|
||||||
$toolkit = $image->getToolkit();
|
$this->assertTrue($image->isValid(), "Image '$file' after load should be valid, but it is not.");
|
||||||
|
$this->assertTrue(imageistruecolor($image->getToolkit()->getResource()), "Image '$file' after load should be a truecolor image, but it is not.");
|
||||||
if (!$image->isValid()) {
|
|
||||||
$this->fail(new FormattableMarkup('Could not load image %file.', ['%file' => $file]));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// All images should be converted to truecolor when loaded.
|
|
||||||
$image_truecolor = imageistruecolor($toolkit->getResource());
|
|
||||||
$this->assertTrue($image_truecolor, new FormattableMarkup('Image %file after load is a truecolor image.', ['%file' => $file]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests calling a missing image operation plugin.
|
* Tests calling a missing image operation plugin.
|
||||||
*/
|
*/
|
||||||
public function testMissingOperation() {
|
public function testMissingOperation(): void {
|
||||||
|
|
||||||
// Test that the image factory is set to use the GD toolkit.
|
|
||||||
$this->assertEquals('gd', $this->imageFactory->getToolkitId(), 'The image factory is set to use the \'gd\' image toolkit.');
|
|
||||||
|
|
||||||
// An image file that will be tested.
|
|
||||||
$file = 'image-test.png';
|
|
||||||
|
|
||||||
// Load up a fresh image.
|
// Load up a fresh image.
|
||||||
$image = $this->imageFactory->get('core/tests/fixtures/files/' . $file);
|
$image = $this->imageFactory->get('core/tests/fixtures/files/image-test.png');
|
||||||
if (!$image->isValid()) {
|
$this->assertTrue($image->isValid(), "Image 'image-test.png' after load should be valid, but it is not.");
|
||||||
$this->fail(new FormattableMarkup('Could not load image %file.', ['%file' => $file]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try perform a missing toolkit operation.
|
// Try perform a missing toolkit operation.
|
||||||
$this->assertFalse($image->apply('missing_op', []), 'Calling a missing image toolkit operation plugin fails.');
|
$this->assertFalse($image->apply('missing_op', []), 'Calling a missing image toolkit operation plugin should fail, but it did not.');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -179,11 +179,14 @@ trait BrowserHtmlDebugTrait {
|
|||||||
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
|
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
|
||||||
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
|
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
|
||||||
|
|
||||||
// Get the response body as a string. Any errors are silenced as
|
/** @var \Psr\Http\Message\StreamInterface $stream */
|
||||||
// tests should not fail if there is a problem. On PHP 7.4
|
$stream = $response->getBody();
|
||||||
// \Drupal\Tests\migrate\Functional\process\DownloadFunctionalTest
|
|
||||||
// fails without the usage of a silence operator.
|
// Get the response body as a string.
|
||||||
$body = @(string) $response->getBody();
|
$body = $stream->isReadable()
|
||||||
|
? (string) $stream
|
||||||
|
: 'Response is not readable.';
|
||||||
|
|
||||||
// On redirect responses (status code starting with '3') we need
|
// On redirect responses (status code starting with '3') we need
|
||||||
// to remove the meta tag that would do a browser refresh. We
|
// to remove the meta tag that would do a browser refresh. We
|
||||||
// don't want to redirect developers away when they look at the
|
// don't want to redirect developers away when they look at the
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
namespace Drupal\Tests\Composer\Plugin\Scaffold\Functional;
|
namespace Drupal\Tests\Composer\Plugin\Scaffold\Functional;
|
||||||
|
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Drupal\BuildTests\Framework\BuildTestBase;
|
||||||
use Drupal\Tests\Composer\Plugin\Scaffold\AssertUtilsTrait;
|
use Drupal\Tests\Composer\Plugin\Scaffold\AssertUtilsTrait;
|
||||||
use Drupal\Tests\Composer\Plugin\Scaffold\ExecTrait;
|
use Drupal\Tests\Composer\Plugin\Scaffold\ExecTrait;
|
||||||
use Drupal\Tests\Composer\Plugin\Scaffold\Fixtures;
|
use Drupal\Tests\Composer\Plugin\Scaffold\Fixtures;
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests Composer Hooks that run scaffold operations.
|
* Tests Composer Hooks that run scaffold operations.
|
||||||
@ -22,7 +22,8 @@ use PHPUnit\Framework\TestCase;
|
|||||||
*
|
*
|
||||||
* @group Scaffold
|
* @group Scaffold
|
||||||
*/
|
*/
|
||||||
class ComposerHookTest extends TestCase {
|
class ComposerHookTest extends BuildTestBase {
|
||||||
|
|
||||||
use ExecTrait;
|
use ExecTrait;
|
||||||
use AssertUtilsTrait;
|
use AssertUtilsTrait;
|
||||||
|
|
||||||
@ -120,9 +121,10 @@ class ComposerHookTest extends TestCase {
|
|||||||
$this->mustExec("composer install --no-ansi", $sut);
|
$this->mustExec("composer install --no-ansi", $sut);
|
||||||
// Require a project that is not allowed to scaffold and confirm that we
|
// Require a project that is not allowed to scaffold and confirm that we
|
||||||
// get a warning, and it does not scaffold.
|
// get a warning, and it does not scaffold.
|
||||||
$stdout = $this->mustExec("composer require --no-ansi --no-interaction fixtures/drupal-assets-fixture:dev-main fixtures/scaffold-override-fixture:dev-main", $sut);
|
$this->executeCommand("composer require --no-ansi --no-interaction fixtures/drupal-assets-fixture:dev-main fixtures/scaffold-override-fixture:dev-main", $sut);
|
||||||
|
$this->assertCommandSuccessful();
|
||||||
$this->assertFileDoesNotExist($sut . '/sites/default/default.settings.php');
|
$this->assertFileDoesNotExist($sut . '/sites/default/default.settings.php');
|
||||||
$this->assertStringContainsString("Not scaffolding files for fixtures/scaffold-override-fixture, because it is not listed in the element 'extra.drupal-scaffold.allowed-packages' in the root-level composer.json file.", $stdout);
|
$this->assertErrorOutputContains('See https://getcomposer.org/allow-plugins');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,5 +64,10 @@
|
|||||||
"libraries/{$name}": ["type:drupal-library"],
|
"libraries/{$name}": ["type:drupal-library"],
|
||||||
"drush/Commands/contrib/{$name}": ["type:drupal-drush"]
|
"drush/Commands/contrib/{$name}": ["type:drupal-drush"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"drupal/core-composer-scaffold": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,5 +67,10 @@
|
|||||||
"libraries/{$name}": ["type:drupal-library"],
|
"libraries/{$name}": ["type:drupal-library"],
|
||||||
"drush/Commands/contrib/{$name}": ["type:drupal-drush"]
|
"drush/Commands/contrib/{$name}": ["type:drupal-drush"]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"drupal/core-composer-scaffold": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@ class SettingsTest extends UnitTestCase {
|
|||||||
if (!empty($expected_autoload)) {
|
if (!empty($expected_autoload)) {
|
||||||
$class_loader->expects($this->once())
|
$class_loader->expects($this->once())
|
||||||
->method('addPsr4')
|
->method('addPsr4')
|
||||||
->with($expected_namespace . '\\', $expected_autoload);
|
->with($expected_namespace . '\\', vfsStream::url('root') . '/' . $expected_autoload);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$class_loader->expects($this->never())
|
$class_loader->expects($this->never())
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
{% set drupal_community = 'https://www.drupal.org/community' %}
|
{% set drupal_community = 'https://www.drupal.org/community' %}
|
||||||
{% set drupal_values = 'https://www.drupal.org/about/values-and-principles' %}
|
{% set drupal_values = 'https://www.drupal.org/about/values-and-principles' %}
|
||||||
{% set drupal_user_guide = 'https://www.drupal.org/docs/user_guide/en/index.html' %}
|
{% set drupal_user_guide = 'https://www.drupal.org/docs/user_guide/en/index.html' %}
|
||||||
{% set create_content = '/node/add' %}
|
{% set create_content = path('node.add_page') %}
|
||||||
{% set drupal_extend = 'https://www.drupal.org/docs/extending-drupal' %}
|
{% set drupal_extend = 'https://www.drupal.org/docs/extending-drupal' %}
|
||||||
{% set drupal_global_training_days = 'https://groups.drupal.org/global-training-days' %}
|
{% set drupal_global_training_days = 'https://groups.drupal.org/global-training-days' %}
|
||||||
{% set drupal_events = 'https://www.drupal.org/community/events' %}
|
{% set drupal_events = 'https://www.drupal.org/community/events' %}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
{% apply spaceless %}
|
{% apply spaceless %}
|
||||||
<li class="pager__item pager__item--control pager__item--next">
|
<li class="pager__item pager__item--control pager__item--next">
|
||||||
<a href="{{ items.next.href }}" class="pager__link" title="{{ 'Go to next page'|t }}" rel="next"{{ items.next.attributes|without('href', 'title', 'rel') }}>
|
<a href="{{ items.next.href }}" class="pager__link" title="{{ 'Go to next page'|t }}" rel="next"{{ items.next.attributes|without('href', 'title', 'rel') }}>
|
||||||
<span class="visually-hidden">{{ 'Previous page'|t }}</span>
|
<span class="visually-hidden">{{ 'Next page'|t }}</span>
|
||||||
{% include "@olivero/../images/pager-previous.svg" %}
|
{% include "@olivero/../images/pager-previous.svg" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user