Unverified Commit 045fcb5d authored by alexpott's avatar alexpott

Issue #2953656 by tedbow, tim.plunkett, diqidoq, phenaproxima, sarahjean,...

Issue #2953656 by tedbow, tim.plunkett, diqidoq, phenaproxima, sarahjean, mglaman, xjm: No ability to control "extra fields" with Layout Builder
parent 2f08f6de
......@@ -12,6 +12,10 @@
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
/**
* Implements hook_help().
......@@ -81,3 +85,52 @@ function layout_builder_field_config_delete(FieldConfigInterface $field_config)
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
/**
* Implements hook_entity_view_alter().
*
* ExtraFieldBlock block plugins add placeholders for each extra field which is
* configured to be displayed. Those placeholders are replaced by this hook.
* Modules that implement hook_entity_extra_field_info() use their
* implementations of hook_entity_view_alter() to add the rendered output of
* the extra fields they provide, so we cannot get the rendered output of extra
* fields before this point in the view process.
* layout_builder_module_implements_alter() moves this implementation of
* hook_entity_view_alter() to the end of the list.
*
* @see \Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build()
* @see layout_builder_module_implements_alter()
*/
function layout_builder_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if ($display instanceof LayoutEntityDisplayInterface) {
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
$field_manager = \Drupal::service('entity_field.manager');
$extra_fields = $field_manager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
if (!empty($extra_fields['display'])) {
foreach ($extra_fields['display'] as $field_name => $extra_field) {
// If the extra field is not set replace with an empty array to avoid
// the placeholder text from being rendered.
$replacement = isset($build[$field_name]) ? $build[$field_name] : [];
ExtraFieldBlock::replaceFieldPlaceholder($build, $replacement, $field_name);
// After the rendered field in $build has been copied over to the
// ExtraFieldBlock block we must remove it from its original location or
// else it will be rendered twice.
unset($build[$field_name]);
}
}
}
}
/**
* Implements hook_builder_module_implements_alter().
*/
function layout_builder_module_implements_alter(&$implementations, $hook) {
if ($hook === 'entity_view_alter') {
// Ensure that this module's implementation of hook_entity_view_alter() runs
// last so that other modules that use this hook to render extra fields will
// run before it.
$group = $implementations['layout_builder'];
unset($implementations['layout_builder']);
$implementations['layout_builder'] = $group;
}
}
......@@ -5,6 +5,9 @@
* Post update functions for Layout Builder.
*/
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
/**
* Rebuild plugin dependencies for all entity view displays.
*/
......@@ -24,3 +27,30 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL
$sandbox['#finished'] = empty($sandbox['ids']) ? 1 : ($sandbox['count'] - count($sandbox['ids'])) / $sandbox['count'];
}
/**
* Ensure all extra fields are properly stored on entity view displays.
*
* Previously
* \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::setComponent()
* was not correctly setting the configuration for extra fields. This function
* calls setComponent() for all extra field components to ensure the updated
* logic is invoked on all extra fields to correct the settings.
*/
function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) {
$entity_field_manager = \Drupal::service('entity_field.manager');
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (EntityViewDisplayInterface $display) use ($entity_field_manager) {
$extra_fields = $entity_field_manager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle());
$components = $display->getComponents();
// Sort the components to avoid them being reordered by setComponent().
uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
$result = FALSE;
foreach ($components as $name => $component) {
if (isset($extra_fields['display'][$name])) {
$display->setComponent($name, $component);
$result = TRUE;
}
}
return $result;
});
}
......@@ -25,6 +25,24 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
use SectionStorageTrait;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type) {
// Set $entityFieldManager before calling the parent constructor because the
// constructor will call init() which then calls setComponent() which needs
// $entityFieldManager.
$this->entityFieldManager = \Drupal::service('entity_field.manager');
parent::__construct($values, $entity_type);
}
/**
* {@inheritdoc}
*/
......@@ -258,13 +276,21 @@ public function setComponent($name, array $options = []) {
$options = $this->content[$name];
// Provide backwards compatibility by converting to a section component.
$field_definition = $this->getFieldDefinition($name);
if ($field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type'])) {
$configuration = [];
$configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
$configuration['label_display'] = FALSE;
$keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
$configuration['formatter'] = array_intersect_key($options, $keys);
$configuration['context_mapping']['entity'] = 'layout_builder.entity';
$extra_fields = $this->entityFieldManager->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle());
$is_view_configurable_non_extra_field = $field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type']);
if ($is_view_configurable_non_extra_field || isset($extra_fields['display'][$name])) {
$configuration = [
'label_display' => '0',
'context_mapping' => ['entity' => 'layout_builder.entity'],
];
if ($is_view_configurable_non_extra_field) {
$configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
$keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
$configuration['formatter'] = array_intersect_key($options, $keys);
}
else {
$configuration['id'] = 'extra_field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
}
$section = $this->getDefaultSection();
$region = isset($options['region']) ? $options['region'] : $section->getDefaultRegion();
......
<?php
namespace Drupal\layout_builder\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block that renders an extra field from an entity.
*
* This block handles fields that are provided by implementations of
* hook_entity_extra_field_info().
*
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
* This block plugin handles all other field entities not provided by
* hook_entity_extra_field_info().
*
* @Block(
* id = "extra_field_block",
* deriver = "\Drupal\layout_builder\Plugin\Derivative\ExtraFieldBlockDeriver",
* )
*
* @internal
* Plugin classes are internal.
*/
class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The field name.
*
* @var string
*/
protected $fieldName;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new ExtraFieldBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
// Get field name from the plugin ID.
list (, , , $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
assert(!empty($field_name));
$this->fieldName = $field_name;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'label_display' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('entity_field.manager')
);
}
/**
* Gets the entity that has the field.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity.
*/
protected function getEntity() {
return $this->getContextValue('entity');
}
/**
* {@inheritdoc}
*/
public function build() {
$entity = $this->getEntity();
// Add a placeholder to replace after the entity view is built.
// @see layout_builder_entity_view_alter().
$extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
if (!isset($extra_fields['display'][$this->fieldName])) {
$build = [];
}
else {
$build = [
'#extra_field_placeholder_field_name' => $this->fieldName,
// Always provide a placeholder. The Layout Builder will NOT invoke
// hook_entity_view_alter() so extra fields will not be added to the
// render array. If the hook is invoked the placeholder will be
// replaced.
// @see ::replaceFieldPlaceholder()
'#markup' => new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]),
];
}
CacheableMetadata::createFromObject($this)->applyTo($build);
return $build;
}
/**
* Replaces all placeholders for a given field.
*
* @param array $build
* The built render array for the elements.
* @param array $built_field
* The render array to replace the placeholder.
* @param string $field_name
* The field name.
*
* @see ::build()
*/
public static function replaceFieldPlaceholder(array &$build, array $built_field, $field_name) {
foreach (Element::children($build) as $child) {
if (isset($build[$child]['#extra_field_placeholder_field_name']) && $build[$child]['#extra_field_placeholder_field_name'] === $field_name) {
$placeholder_cache = CacheableMetadata::createFromRenderArray($build[$child]);
$built_cache = CacheableMetadata::createFromRenderArray($built_field);
$merged_cache = $placeholder_cache->merge($built_cache);
$build[$child] = $built_field;
$merged_cache->applyTo($build);
}
else {
static::replaceFieldPlaceholder($build[$child], $built_field, $field_name);
}
}
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
return $this->getEntity()->access('view', $account, TRUE);
}
}
<?php
namespace Drupal\layout_builder\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Plugin\Context\EntityContextDefinition;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides entity field block definitions for every field.
*
* @internal
* Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*/
class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* Constructs new FieldBlockDeriver.
*
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
*/
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
$this->entityFieldManager = $entity_field_manager;
$this->entityTypeManager = $entity_type_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_field.manager'),
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
// Only process fieldable entity types.
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
continue;
}
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
foreach ($bundles as $bundle_id => $bundle) {
$extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle_id);
// Skip bundles without any extra fields.
if (empty($extra_fields['display'])) {
continue;
}
foreach ($extra_fields['display'] as $extra_field_id => $extra_field) {
$derivative = $base_plugin_definition;
$derivative['category'] = $entity_type->getLabel();
$derivative['admin_label'] = $extra_field['label'];
$context_definition = EntityContextDefinition::fromEntityType($entity_type)
->addConstraint('Bundle', [$bundle_id]);
$derivative['context'] = [
'entity' => $context_definition,
];
$derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle_id . PluginBase::DERIVATIVE_SEPARATOR . $extra_field_id;
$this->derivatives[$derivative_id] = $derivative;
}
}
}
return $this->derivatives;
}
}
<?php
/**
* @file
* Test fixture.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Update core.extension.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['layout_builder'] = 0;
$extensions['module']['layout_discovery'] = 0;
$extensions['module']['layout_test'] = 0;
$connection->update('config')
->fields([
'data' => serialize($extensions),
'collection' => '',
'name' => 'core.extension',
])
->condition('collection', '')
->condition('name', 'core.extension')
->execute();
......@@ -9,27 +9,6 @@
$connection = Database::getConnection();
// Update core.extension.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['layout_builder'] = 0;
$extensions['module']['layout_discovery'] = 0;
$extensions['module']['layout_test'] = 0;
$connection->update('config')
->fields([
'data' => serialize($extensions),
'collection' => '',
'name' => 'core.extension',
])
->condition('collection', '')
->condition('name', 'core.extension')
->execute();
// Add a layout plugin with a dependency to an existing entity view display.
$display = $connection->select('config')
->fields('config', ['data'])
......
......@@ -5,6 +5,9 @@
* Provides hook implementations for Layout Builder tests.
*/
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
*/
......@@ -32,3 +35,26 @@ function layout_builder_test_plugin_filter_block__layout_builder_alter(array &$d
}
}
}
/**
* Implements hook_entity_extra_field_info().
*/
function layout_builder_test_entity_extra_field_info() {
$extra['node']['bundle_with_section_field']['display']['layout_builder_test'] = [
'label' => t('Extra label'),
'description' => t('Extra description'),
'weight' => 0,
];
return $extra;
}
/**
* Implements hook_entity_node_view().
*/
function layout_builder_test_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
if ($display->getComponent('layout_builder_test')) {
$build['layout_builder_test'] = [
'#markup' => 'Extra, Extra read all about it.',
];
}
}
......@@ -83,8 +83,25 @@ public function testLayoutBuilderUi() {
$assert_session->linkExists('Manage layout');
$this->clickLink('Manage layout');
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
// The body field is present.
$assert_session->elementExists('css', '.field--name-body');
// The body field is only present once.
$assert_session->elementsCount('css', '.field--name-body', 1);
// The extra field is only present once.
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
// Save the defaults.
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$assert_session->addressEquals("$field_ui_prefix/display/default");
// Load the default layouts again after saving to confirm fields are only
// added on new layouts.
$this->drupalGet("$field_ui_prefix/display/default");
$assert_session->linkExists('Manage layout');
$this->clickLink('Manage layout');
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
// The body field is only present once.
$assert_session->elementsCount('css', '.field--name-body', 1);
// The extra field is only present once.
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
// Add a new block.
$assert_session->linkExists('Add Block');
......@@ -107,6 +124,8 @@ public function testLayoutBuilderUi() {
$this->drupalGet('node/1');
$assert_session->pageTextContains('The first node body');
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->pageTextContains('Extra, Extra read all about it.');
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
$assert_session->linkNotExists('Layout');
// Enable overrides.
......@@ -116,6 +135,7 @@ public function testLayoutBuilderUi() {
// Remove the section from the defaults.
$assert_session->linkExists('Layout');
$this->clickLink('Layout');
$assert_session->pageTextContains('Placeholder for the "Extra label" field');
$assert_session->linkExists('Remove section');
$this->clickLink('Remove section');
$page->pressButton('Remove');
......@@ -128,6 +148,8 @@ public function testLayoutBuilderUi() {
$this->clickLink('Save Layout');
$assert_session->pageTextNotContains('The first node body');
$assert_session->pageTextNotContains('Powered by Drupal');
$assert_session->pageTextNotContains('Extra, Extra read all about it.');
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
// Assert that overrides cannot be turned off while overrides exist.
$this->drupalGet("$field_ui_prefix/display/default");
......@@ -149,12 +171,16 @@ public function testLayoutBuilderUi() {
$assert_session->pageTextContains('The second node title');
$assert_session->pageTextContains('The second node body');
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->pageTextContains('Extra, Extra read all about it.');
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
// The overridden node does not pick up the changes to defaults.
$this->drupalGet('node/1');
$assert_session->elementNotExists('css', '.field--name-title');
$assert_session->pageTextNotContains('The first node body');
$assert_session->pageTextNotContains('Powered by Drupal');
$assert_session->pageTextNotContains('Extra, Extra read all about it.');
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
$assert_session->linkExists('Layout');
// Reverting the override returns it to the defaults.
......@@ -166,6 +192,7 @@ public function testLayoutBuilderUi() {
$assert_session->elementExists('css', '.field--name-title');
$assert_session->pageTextContains('The first node body');
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->pageTextContains('Placeholder for the "Extra label" field');
// Assert that overrides can be turned off now that all overrides are gone.
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => FALSE], 'Save');
......@@ -407,4 +434,14 @@ public function testDeletedView() {
$assert_session->pageTextNotContains('Test Block View');
}
/**
* Asserts that a text string only appears once on the page.
*
* @param string $needle
* The string to look for.
*/
protected function assertTextAppearsOnce($needle) {
$this->assertEquals(1, substr_count($this->getSession()->getPage()->getContent(), $needle), "'$needle' only appears once on the page.");
}
}
<?php
namespace Drupal\Tests\layout_builder\Functional\Update;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for Layout Builder extra fields.
*
* @group layout_builder
* @group legacy
*/
class ExtraFieldUpdatePathTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/layout-builder.php',
];
}
/**
* Tests the upgrade path for Layout Builder extra fields.
*/
public function testRunUpdates() {
$data = EntityViewDisplay::load('node.article.teaser')->toArray();
$this->assertArrayNotHasKey('third_party_settings', $data);
$this->runUpdates();
$data = EntityViewDisplay::load('node.article.teaser')->toArray();
$components = $data['third_party_settings']['layout_builder']['sections'][0]->getComponents();
$component = reset($components);
$this->assertSame('extra_field_block:node:article:links', $component->getPluginId());
}
}
......@@ -19,6 +19,7 @@ class SectionDependenciesUpdatePathTest extends UpdatePathTestBase {
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/layout-builder.php',
__DIR__ . '/../../../fixtures/update/section-dependencies.php',
];
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment