Quantcast
Channel: Issues for Drupal core
Viewing all 297668 articles
Browse latest View live

Pass attributes to create_attribute in top-bar template

$
0
0

Problem/Motivation

When I try to preprocess an attribute into the wrapper in top-bar.html.twig it doesn't show up.

Steps to reproduce

function navigation_preprocess_top_bar(&$variables) {
  $variables['attributes']['class'][] = 'my-class';
}

Proposed resolution

Pass attributes into create_attribute.


Nightwatch tests from submodules do not run in Gitlab CI because of missing option to follow symlinks

$
0
0

Problem/Motivation

I created a Nightwatch test in the submodule (the test path is like my_module/modules/my_submodule/tests/src/Nightwatch/Tests/myTest.js) and the Drupal CI can't find it with an error:

Error
No tests defined! using source folder: ../core/modules/ckeditor5/tests/src/Nightwatch/Tests,../core/modules/toolbar/tests/src/Nightwatch/Tests,../core/tests/Drupal/Nightwatch/Tests

Here is an example of the failed pipeline: https://git.drupalcode.org/project/test_helpers/-/jobs/2514818

But If I move the test to the root module (my_module/tests/src/Nightwatch/Tests/myTest.js) - it starts to work well - here is the successful pipeline: https://git.drupalcode.org/project/test_helpers/-/jobs/2514887

Steps to reproduce

1. Create a submodule in a module.
2. Create a Nightwatch test in the submodule, put it into the path like modules/my_submodule/tests/src/Nightwatch/Tests/myTest.js.
3. See that the Drupal CI pipeline fails with the error.

Proposed resolution

The root cause of the issue is the created symlinks for each module subdirectory in this step of the pipeline:
https://git.drupalcode.org/project/gitlab_templates/-/blob/1.5.5/include...

Here is the part of the PHP file that makes the symlinks for each file and subdirectory of the module:
https://git.drupalcode.org/project/gitlab_templates/-/blob/1.5.5/scripts...

Then, the Nightwatch scans the module directory with symlinks, using this glob pattern:
https://git.drupalcode.org/project/drupal/-/blob/11.0.1/core/tests/Drupa...

globSync('**/tests/**/Nightwatch/**/*.js', {
  cwd: path.resolve(process.cwd(), `../${searchDirectory}`),
  ignore: process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES
    ? process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES.split(',').concat(
        defaultIgnore,
      )
    : defaultIgnore,
})

But! This pattern doesn't follow the created symlinks, because of the missing parameter follow: true, here is the documentation: https://github.com/isaacs/node-glob

** If a "globstar" is alone in a path portion, then it matches zero or more directories and subdirectories searching for matches. It does not crawl symlinked directories, unless {follow:true} is passed in the options object. A pattern like a/b/** will only match a/b if it is a directory. Follows 1 symbolic link if not the first item in the pattern, or 0 if it is the first item, unless follow:true is set, in which case it follows all symbolic links.

So, to resolve this issue, we should simply add the follow: true argument to the glob function, like this

globSync('**/tests/**/Nightwatch/**/*.js', {
  cwd: path.resolve(process.cwd(), `../${searchDirectory}`),
  follow: true,
  ignore: process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES
    ? process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES.split(',').concat(
        defaultIgnore,
      )
    : defaultIgnore,
})

And here is a workaround for module developers, until the issue is fixed: override in the module's .gitlab-ci.yml file the included .nightwatch-basebefore_script with adding a step that adds this line, like this:

.nightwatch-base:
  before_script:
    - apt-get update && apt-get install -y wget unzip
    - wget https://chromedriver.storage.googleapis.com/91.0.4472.101/chromedriver_linux64.zip
    - unzip chromedriver_linux64.zip
    - mv chromedriver /usr/local/bin/
    - chmod +x /usr/local/bin/chromedriver
    - npm install -g nightwatch
    - 'sed -i "/  ignore: process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES/i\  follow: true," web/core/tests/Drupal/Nightwatch/nightwatch.conf.js'
    - cat web/core/tests/Drupal/Nightwatch/nightwatch.conf.js

I will prepare a MR to fix this in the Drupal CI.

Remaining tasks

User interface changes

API changes

Data model changes

Deprecate $variables['view'] for node.html.twig

$
0
0

Problem/Motivation

Views module adds $variables['view'] to node templates.

This is impossible to provide cacheable metadata for, because we don't have the render context of the view. I've also never seen it used.

We should deprecate it in 11.1.0 for removal in 12.0.0

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Improve description for parameters in hook_menu_links_discovered_alter

$
0
0

Problem/Motivation

API page: function hook_menu_links_discovered_alter

hook_menu_links_discovered_alter(array &$links)

The documentation about this function describes the structure of the returned value. But, as all alter hooks, the returned value is not used by the caller and this documentation should describe the expected structure of the $links parameter after alteration.

Parameters

array $links: The link definitions to be altered.

Proposed resolution

array $links: The discovered menu links to be altered.

Remaining tasks

Patch
Review
Commit

User interface changes

N/A

API changes

N/A

Data model changes

N/A

Release notes snippet

N/A

Separate MIME type mapping from ExtensionMimeTypeGuesser

$
0
0

Problem/Motivation

With #1921558: Convert file_get_mimetype() to use Symfony MimeTypeGuessers, the function file_mimetype_mapping() was removed. The mapping was placed as a protected property of the ExtensionMimeTypeGuesser class, as its main purpose was for the guesser to use it. But there are cases when the mapping, or just the list of MIME types, should be used in itself, outside the context of guessing. For instance, a form in the File entity module should present known MIME types for the user.

Proposed resolution

Move the default mapping to a new service and class, Drupal\Core\File\MimeType\MimeTypeMapper, with getter/setter methods.

Introduce an alterMapping() method which invokes the mimetype alter hook upon service instantiation to allow modules to play with MIME type<->extension mapping.

Change the ExtensionMimeTypeGuesser class to use the new mapper; deprecate its ::setMapping method.

Deprecate the file_mimetype_mapping alter hook.

Add tests.

Add tests.

Remaining tasks

Commit

User interface changes

None

API changes

A new service file.mime_type.mapper.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue categoryBug because functionality in previous version is now missing
Issue priorityMajor because it is a small regression. In D7 one could access the mapping out of the context of guessing, and could eg get a list of all known mimetypes . After #1921558: Convert file_get_mimetype() to use Symfony MimeTypeGuessers its impossible without extending the class, because the mapping is a protected property
Disruption None, a BC layer covers the case of custom/contrib classes extending from ExtensionMimeTypeGuesser.

Support #[AsEventListener] attribute on classes and methods

$
0
0

Problem/Motivation

Event subscribers in Drupal are quite verbose.

symfony provides an easier way to register event listeners: The #[AsEventListener] attribute, which can be placed on service classes and methods.
https://symfony.com/doc/current/event_dispatcher.html#defining-event-lis...

Steps to reproduce

Proposed resolution

Let's support this in Drupal.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Fix case in comment (OPcache)

$
0
0

Problem/Motivation

The term OPcache should consistently follow its correct capitalization throughout the codebase and documentation. Variations such as "opcache" and "OPCache" are being used, which is inconsistent and can lead to confusion (and may look unprofessional)

Expected behavior

The term should always be written as "OPcache" to maintain proper case usage.

This will improve clarity and ensure consistency in the project's naming conventions.

Source: https://www.php.net/manual/en/book.opcache.php

NULL contextual argument should be skipped during views URL generation

$
0
0

Problem/Motivation

I used the NULL contextual argument to skip the default URL argument value for rewriting it later with the custom argument_default plugin but I faced an issue with URL generation. My view except contextual filters also have exposed filters, and the problem is that exposed form action was wrong.

I investigated this issue deeper and found that this problem comes from \Drupal\views\Plugin\views\display\PathPluginBase::getRoute. When we generate routes for views we include all contextual arguments into the view display URL path.

Steps to reproduce

I have created dummy module that provides node type and view with exposed and contextual filters for reproducing this issue. So, steps to reproduce:

  1. Install the module (this module will generate on install 20 nodes needed for reproducing the problem)
  2. Go to the /views-null-argument-issue/{NodeID} (use any Node ID of views_null_argument_issue node type for NodeID) and check that
    • the view displays 10 items (result summary above of the form)
    • all items have the same type in the table
  3. Submit the exposed form with any filter value
  4. Check the following
    • the URL was changed to like this /views-null-argument-issue/{NodeID}/exception-value?...
    • the view displays 20 items (result summary above of the form)
    • there are type 1 and type 2 items

Proposed resolution

I think the NULL argument should be excluded from route params in the \Drupal\views\Plugin\views\display\PathPluginBase::getRoute to fix this issue.

We can exclude it by creating a new SkipFromRouteParamsInterface interface, and use this interface for filtering all arguments in the \Drupal\views\Plugin\views\display\PathPluginBase::getRoute to skip arguments that implement it.

Another way of fixing this is to exclude the NULL argument from the route params by configurations. We can add a new checkbox to the NULL argument configuration form that will be responsible for skipping it from route parameters.

In the end, both of these ideas should filter $argument_ids to exclude the NULL argument (and other arguments that implement either interface or configuration) in the \Drupal\views\Plugin\views\display\PathPluginBase::getRoute

Remaining tasks

Change record
Update issue summary
Review

User interface changes

N/A

API changes

N/A

Data model changes

N/A

Release notes snippet

N/A


datetime date views filter: DateTime object not set

$
0
0

Problem/Motivation

Using datetime views filter and filtering with an invalid date, there is no validation and fatal error occured.

To reproduce:

  • Add a datetime field in an entity bundle.
  • Create a view to list this entities and add a filter on this datetime field
  • Search for a date with wrong format or for example day in french: lun 2018-04-27. The following error occur:
    <em class="placeholder">Exception</em>: DateTime object not set. in <em class="placeholder">Drupal\Component\Datetime\DateTimePlus-&gt;__call()</em> (line <em class="placeholder">355</em> of <em class="placeholder">core/lib/Drupal/Component/Datetime/DateTimePlus.php</em>). <pre class="backtrace">Drupal\datetime\Plugin\views\filter\Date-&gt;opSimple(&#039;sesame_quota_option__field_sesame_opt_date.field_sesame_opt_date_value&#039;) (Line: 314)
    Drupal\views\Plugin\views\filter\NumericFilter-&gt;query() (Line: 1370)
    Drupal\views\ViewExecutable-&gt;_build(&#039;filter&#039;) (Line: 1259)
    Drupal\views\ViewExecutable-&gt;build() (Line: 390)
    Drupal\views\Plugin\views\display\PathPluginBase-&gt;execute() (Line: 180)
    Drupal\views\Plugin\views\display\Page-&gt;execute() (Line: 1627)
    Drupal\views\ViewExecutable-&gt;executeDisplay(&#039;page_1&#039;, Array) (Line: 77)
    Drupal\views\Element\View::preRenderViewElement(Array)
    call_user_func(Array, Array) (Line: 378)
    Drupal\Core\Render\Renderer-&gt;doRender(Array, ) (Line: 195)
    Drupal\Core\Render\Renderer-&gt;render(Array, ) (Line: 226)
    Drupal\Core\Render\MainContent\HtmlRenderer-&gt;Drupal\Core\Render\MainContent\{closure}() (Line: 582)
    Drupal\Core\Render\Renderer-&gt;executeInRenderContext(Object, Object) (Line: 227)
    Drupal\Core\Render\MainContent\HtmlRenderer-&gt;prepare(Array, Object, Object) (Line: 117)
    Drupal\Core\Render\MainContent\HtmlRenderer-&gt;renderResponse(Array, Object, Object) (Line: 90)
    Drupal\Core\EventSubscriber\MainContentViewSubscriber-&gt;onViewRenderArray(Object, &#039;kernel.view&#039;, Object) (Line: 76)
    Drupal\webprofiler\EventDispatcher\TraceableEventDispatcher-&gt;dispatch(&#039;kernel.view&#039;, Object) (Line: 156)
    Symfony\Component\HttpKernel\HttpKernel-&gt;handleRaw(Object, 1) (Line: 68)
    Symfony\Component\HttpKernel\HttpKernel-&gt;handle(Object, 1, 1) (Line: 57)
    Drupal\Core\StackMiddleware\Session-&gt;handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\KernelPreHandle-&gt;handle(Object, 1, 1) (Line: 99)
    Drupal\page_cache\StackMiddleware\PageCache-&gt;pass(Object, 1, 1) (Line: 78)
    Drupal\page_cache\StackMiddleware\PageCache-&gt;handle(Object, 1, 1) (Line: 47)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware-&gt;handle(Object, 1, 1) (Line: 38)
    Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware-&gt;handle(Object, 1, 1) (Line: 50)
    Drupal\Core\StackMiddleware\NegotiationMiddleware-&gt;handle(Object, 1, 1) (Line: 23)
    Stack\StackedHttpKernel-&gt;handle(Object, 1, 1) (Line: 664)
    Drupal\Core\DrupalKernel-&gt;handle(Object) (Line: 19)
  • Search for the same date with day in english works: mon 2018-04-27. Same for date only: 2018-04-27

Proposed resolution

Mimic the date filter validation from views module.

Remaining tasks

  1. Add tests
  2. Review

CKEditor 5 has its own border color and is not using Claro's colors

$
0
0

Problem/Motivation

CKeditor 5 fields have a different border color:

ckeditor border

Steps to reproduce

- Go to /node/add/page or /node/add/article
- Compare CKEditor 5 text field with the others

User interface changes

Before

ckeditor border

After (example with different fields, but with the same type)

ckeditor border, after

InvalidArgumentException when creating a content with Media Embed in CKeditor

$
0
0

If we install the media library, then configure a content format with Ckeditor and the Media Embed Button and we go to edit or create any node that can use that Content Format I get a WSOD with this error:

InvalidArgumentException: The allowed types parameter is required and must be an array of strings. en Drupal\media_library\MediaLibraryState->validateRequiredParameters() (linea 146 de C:\wamp64\www\ayudea\core\modules\media_library\src\MediaLibraryState.php).

I can fix it creating a new media type in: admin/structure/media/add

But the error should be more user friendly that sending me a WSOD.

I think this is related to: https://www.drupal.org/project/drupal/issues/3033653 and this one: https://www.drupal.org/project/drupal/issues/3033652

Views viewsScrollTop (ScrollTopCommand AJAX command) doesn't work inside Drupal Off canvas dialogs

$
0
0

Problem/Motivation

When we use ScrollTopCommand AJAX command for elements inside Drupal Off canvas dialogs - the function Drupal.AjaxCommands.prototype.viewsScrollTop doesn't work well when the main page is scrolled down, because it fails to calculate the right scroll position inside fixed elements.

Steps to reproduce

1. Open some Views in Drupal Off canvas area, that has enable AJAX and Pagination.
This Views should have long content to make a vertical scroll in the area.

2. Try to use pagination.

As result you will always be scrolled to the top of the page, instead to the right position, like in full page.

Proposed resolution

The problem is because Drupal Off canvas area has fixed position, so we should get the actual coordinates of it and current scroll position, to calculate the needed scroll position in the right way.

So now we have:

      $(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);

but it should be something like this:

      const scrollOffset =
        offset.top
        - scrollTarget.offset().top
        + $(scrollTarget).scrollTop();
      $(scrollTarget).animate({ scrollTop: scrollOffset - 10 }, 500);

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

AJAX MessageCommand markup and styling differs from Theme default

$
0
0

Problem/Motivation

MessageCommand was introduced in #3086096: Add a generic Ajax Message command. It is helpful to show status messages in AJAX contexts. Sadly it uses a totally different "render pipeline" than the active theme does. In fact it uses none, but builds the message area in JavaScript, without using the Theme's twig templates. That's how it's implemented.

From a Themers and Theme Prespective and from "Component" thinking, this is wrong. AJAX messages should use the same templates, as regular messages do.
That's why I'd rate this as bug from outer perspective. Still I think it was a (discussable) design decision.

This results in issues like this: #3165452: Override Drupal.theme.message to to make sure JS messages get rendered correctly

Of course, I know talk is cheap and this is hard to resolve, because the messages are added dynamically and you can't tell if the message area already exists. But these problems can be solved.

Implementation:
https://git.drupalcode.org/project/drupal/-/blob/11.x/core/misc/message....
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Ajax%21Me...

Steps to reproduce

Show a regular status message using PHP in a custom theme, where the message area was modified in twig
Compare the result to a status message added by JavaScript. They differ a lot, if the JS-built dom structure isn't the same as the one in Twig.

Proposed resolution

Return the theme rendered message wrapper and the messages in the AJAX result. Only add the wrapper on the page, if none is present.
After adding or if the message wrapper is already existing, add the rendered messages.

This way the theme handles the message output, like it would do on a regular call, but AJAX builds the message from the theme results without a difference.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Allow an install hook in profiles to install from configuration

$
0
0

Problem/Motivation

In #2788777: Allow a site-specific profile to be installed from existing config we excluded profiles from being installed that contain hook_install implementations.

The reasons for this was to exclude complexity, limit the scope of the issue and getting it done.
The complexity can be summarized as:
Install hooks in profiles were so far only called when a site was installed and after all the listed modules were installed and all the configuration was imported. Thus hook_install implementations were able to make a lot of assumptions about the state of the site when the code was executed.
When installing from configuration the configuration is synchronized and the install hooks are called as the extensions are installed and then the configuration is imported. In addition a profile can list modules to install which are not dependencies and, therefore, these modules can be uninstalled. The install hook can therefore also not rely on all the modules being installed.
The canonical example being standard expecting the contact module to be installed.

Right now if a profile wants to be able to be installed from config and still do something extra it has to not implement hook_install and instead define a custom install step.

Proposed resolution

to be defined

Temporary Workaround

See @slucero's comment in #25. There are differences with hook_install, see hook_install_tasks for details. This is an example of how it can be used, replacing "PROFILE" with your profile's machine name:

/**
 * Implements hook_install_tasks().
 */
function PROFILE_install_tasks(&$install_state) {
  $tasks = [
    'PROFILE_install_content' => [
      'display_name' => t('Install default content'),
      'type' => 'normal',
    ]
  ];

  return $tasks;
}


/**
 * Callback function to install default profile content.
 *
 * @see PROFILE_install_tasks()
 */
function PROFILE_install_content() {
  ...
}

Remaining tasks

find solution
implement it
test it
commit it

User interface changes

none probably.

API changes

to be seen.

Data model changes

none.

TypeError: Illegal offset type in Drupal\taxonomy\Form\OverviewTerms->submitForm()

$
0
0

Problem/Motivation

Trying to reorder on `admin/structure/taxonomy/manage/{VOCAB-ID}/overview
TypeError: Illegal offset type in Drupal\taxonomy\Form\OverviewTerms->submitForm() (line 630 of /mnt/www/html/njcourts/docroot/core/modules/taxonomy/src/Form/OverviewTerms.php).

Proposed resolution

Add int check around values before trying to set.


Olivero theme have an error for Drupal 10.3|navigation-utils.js?v=10.3.5:147:16 Uncaught TypeError: Cannot read properties of null (reading 'classList')

$
0
0

Problem/Motivation

I upgraded Drupal Core from 10.1.8 to 10.3.2( and also 10.3.5), after that, I have an issue of console error.
Browser console says:
navigation-utils.js?v=10.3.5:147:16 Uncaught TypeError: Cannot read properties of null (reading 'classList')
I have this error only when Aggregate JS option is activated. And I debug this issue and found that below code is tried to load before DOM is created, so the body is null.

    function getRootMargin() {
      let rootMarginTop = 72;
      const { body } = document;

      if (body.classList.contains('toolbar-fixed')) {
        rootMarginTop -= 39;
      }

      if (
        body.classList.contains('toolbar-horizontal') &&
        body.classList.contains('toolbar-tray-open')
      ) {
        rootMarginTop -= 40;
      }

      return `${rootMarginTop}px 0px 0px 0px`;
    }

Steps to reproduce

Install Drupal 10.3.2 or 10.3.5 and choose Olivero, then activate JS aggregation.

Proposed resolution

Below code solved the issue. I hope JS aggregation could be put at the bottom of Body tag, though some of js code like gtag are needed in head tag.

    function getRootMargin() {
      document.addEventListener('DOMContentLoaded', function () {
      let rootMarginTop = 72;
      const { body } = document;

      if (body.classList.contains('toolbar-fixed')) {
        rootMarginTop -= 39;
      }

      if (
        body.classList.contains('toolbar-horizontal') &&
        body.classList.contains('toolbar-tray-open')
      ) {
        rootMarginTop -= 40;
      }

      return `${rootMarginTop}px 0px 0px 0px`;
      });
    }

Unable to add content to a content type named 'type'

$
0
0

Problem/Motivation

The form hooks for entity type and bundles can cause a naming collision if the bundle is named 'type'.

Example:

After installing a new Drupal 8.8.x, I created a content type with the machine name type
When I tried to add content to the above content type I received WSOD.

In the error log, this is the message

Error: Call to undefined method Drupal\node\Entity\Node::getThirdPartySetting() in menu_ui_form_node_type_form_alter() (line 376 of /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/modules/menu_ui/menu_ui.module) #0 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/Extension/ModuleHandler.php(539): menu_ui_form_node_type_form_alter(Array, Object(Drupal\Core\Form\FormState), 'node_type_form') #1 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/Form/FormBuilder.php(835): Drupal\Core\Extension\ModuleHandler->alter('form', Array, Object(Drupal\Core\Form\FormState), 'node_type_form') #2 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/modules/contrib/devel/webprofiler/src/Form/FormBuilderWrapper.php(29): Drupal\Core\Form\FormBuilder->prepareForm('node_type_form', Array, Object(Drupal\Core\Form\FormState)) #3 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/Form/FormBuilder.php(277): Drupal\webprofiler\Form\FormBuilderWrapper->prepareForm('node_type_form', Array, Object(Drupal\Core\Form\FormState)) #4 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/Entity/EntityFormBuilder.php(61): Drupal\Core\Form\FormBuilder->buildForm('node_type_form', Object(Drupal\Core\Form\FormState)) #5 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/modules/node/src/Controller/NodeController.php(129): Drupal\Core\Entity\EntityFormBuilder->getForm(Object(Drupal\node\Entity\Node)) #6 [internal function]: Drupal\node\Controller\NodeController->add(Object(Drupal\node\Entity\NodeType)) #7 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array) #8 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/Render/Renderer.php(582): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() #9 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\Core\Render\Renderer->executeInRenderContext(Object(Drupal\Core\Render\RenderContext), Object(Closure)) #10 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) #11 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() #12 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1) #13 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #14 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #15 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #16 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true) #17 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #18 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/modules/contrib/devel/webprofiler/src/StackMiddleware/WebprofilerMiddleware.php(38): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #19 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #20 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #21 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/core/lib/Drupal/Core/DrupalKernel.php(693): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #22 /srv/bindings/3bbcd18b06c44304aea5aca03069c666/code/web/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #23 {main}.

Proposed resolution

Steps to replicate

  1. Install a new Drupal 8.8.x
  2. Create content type with machine name type
  3. Create new content of the content type type

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

[policy] Subsystem maintainer permissions on GitLab

$
0
0

Problem/Motivation

With the adoption of gitlab there a few challenges and oppertunities to have subsystem maintainers help out core committers with certian tasks.

Things like retargeting or closing merge requests. This is only possible for accounts with permissions on the drupal project.

This requires is being maintainer or developer. The issue with that is that this would give commit permissions. But we can fix that with branch permissions

https://docs.gitlab.com/ee/user/project/protected_branches.html

By protecting the release branches we can give more permissions to subsystem maintainers but also protect branches from commits by us.

Slack discussion: https://drupal.slack.com/archives/CGKLP028K/p1701285278934459

Steps to reproduce

Proposed resolution

  1. Protected Branches and Tags will only allow GitLab Maintainers (and above) to push or GitLab Developers granted explicit permission to push.
  2. Core Committers will be GitLab Mainatiners
  3. Subsystem maintainers will be GitLab Developers.
  4. Provisional committers will be GitLab Developers with an explicit access permission to push (or if the Core team wants a more lax security policy they can skip this and just make them regular maintainers)

Setup up as documented in #26

Remaining tasks

  1. Move the documentation from this pdf to d.o. Where?
  2. Collect subsystem maintainer usernames
  3. Setup permissions as documented in #26
  4. Test with single user to validate the policy above has been correctly installed on the core repo
  5. Add all subsystem maintainers

User interface changes

API changes

Data model changes

Release notes snippet

OOP hooks using attributes and event dispatcher

$
0
0

Problem/Motivation

For a very long time we wanted to introduce hooks in some OOP manner. The following goals altogether however have proven elusive

  1. No magic naming in the implementations.
  2. A hook implementation should be simple and have minimal boilerplate.
  3. Calling any hook without defining anything should be possible as it is now.
  4. Reordering hooks should be doable.
  5. Relative reordering should be easier. ckeditor5_module_implements_alter is ouch.
  6. Minimal added code to core.

Proposed resolution

Big kudos to EclipseGc for realizing the Symfony EventDispatcherInterface has a getListeners method which allows us to call listeners any way we want. Unlike dispatch this allows us to call listeners with any arguments we want without defining an event object. dpi pioneered attributes for hooks on Hux. I also advocate for attributes but simpler. In short:

namespace Drupal\node\Hook;
class NodeHooks {
  #[Hook('user_cancel')]
  public function userCancel($edit, UserInterface $account, $method) {

In detail:

  1. Hook implementations go into Drupal\modulename\Hook namespace (subdirectories work too). Familiar pattern from plugins. These are automatically registered as autowired services with the class name as the service id. This makes for minimal boilerplate. If autowire doesn't suffice -- should be very rare -- they can be registered manually as well, the service id is the class name.
  2. Hook implementations needs to be marked with a Hook attribute. This is new. Either on a method #[Hook('user_cancel')] or on the class #[Hook('user_cancel', method: 'UserCancel')]. If the class Hook doesn't specify a method, __invoke is the default method.
  3. The attribute supports a priority as well: #[Hook('user_cancel', priority: -20)]. The priority defaults to such values as to keep module order.
  4. The edge case of "implementing a hook on behalf of another module" is also supported by simply specifying the module in the attribute. It defaults to the defining module.
  5. This attribute is patterned on the Symfony AsEventListener attribute which is shipped with the event dispatcher but it is only used in the full Symfony framework.
  6. Multiple implementations are totally allowed on multiple axis: one method can implement multiple hooks by adding a Hook attribute for each. One module can implement a hook as many times as it wants in as many classes as it wants. This allows, for example, adding form_alter implementations firing on other conditions than form id without touching any existing implementation. For now I exempted the hooks fired via ModuleHandler::invoke from this, documentation would be needed for those, luckily very few such are used runtime: library_build_info, mail and help. For example, help expects a string return, it's not even clear what multiple implementations could mean in this case. Also, ModuleHandler::invoke is used as a replacement for a failure-tolerant $function() call, once again it's not at all clear what multiple replacements could mean.
  7. Reordering hook implementation is done by manipulating the kernel listeners in service alter providers. A helper providing setBefore/setAfter/setFirst/setLast/setPriority functionality is included.
  8. Old style procedural calls are integrated into the new system. Separate hook caching is removed, everything is now stored in the container. For a contrib which works in Drupal 10 and 11, you will need to 1) manually register the autowired service 2) have something like \Drupal::service(NodeHook::class)->userCancel($user); as the procedural implementation 3) mark the procedural hook implementation with the [@LegacyHook] attribute. The new system will recognize LegacyHook and just not call it instead calls the new implementation directly. The rector rule for this is here it handles the service registration, moves the function body into a method and adds attributes as needed.
  9. Not much code is needed and a lot of is BC: due to the vast simplification of ModuleHandler, core/lib/Drupal/Core only becomes 72 lines longer. This is enough for the new attribute, gathering all the implementation data, registering them as event subscribers and firing them as needed. Once the BC layers are removed, it'll be significantly negative. Luckily we didn't need to provide any new facility for hook_module_implements_alter() as service provider alter can do it nor any sorting because event dispatcher does that.
  10. If loading all hook classes at build time becomes a problem we already have an issue for that: #3395260: Investigate possibilities to parse attributes without reflection .
  11. For API documentation purposes, it is possible to create a separate attribute class for each and move the doxygen to the constructor. An example of such an attribute is provided in HookFormAlter (without moving the doxygen for now).
  12. Install hooks remain procedural. The jury is still out on hook_requirements.
  13. Theme hooks remain procedural.

Remaining tasks

  1. A framework manager needs to review per 61.
  2. Determine if more tests are needed. There are explicit tests for new functionality and a significant number of implicit tests across core.

User interface changes

API changes

Oh my.

Data model changes

Release notes snippet

Add a filecache-backed attribute parser

$
0
0

Problem/Motivation

With #3442009: OOP hooks using attributes and event dispatcher and #3396165: [meta] Convert all core plugin types to attribute discovery (and potentially others in the future like autowiring) we're going to be doing a lot of scanning for attributes in core.

In general, each thing-that-looks-for-attributes implements iterating over a load of files, getting the attributes via reflection, then pulling out whatever information it needs, either into the symfony container or into a cache entry.

We won't have reliable profiling numbers on this until lots of conversions are done and possibly not until someone profiles a 150+ module site in the wild, but it's likely to be expensive.

Steps to reproduce

Proposed resolution

I think we can possibly use filecache to mitigate most or all performance overhead from scanning attributes.

We can implement something that:
- given a file
- collects all the attributes from that file at once
- puts them into a structure that we can store in filecache (which is based on the mtime of the file so persists across cache clears)
- allows a single attribute value to be returned from a method, possibly all of them.

If we use this in all/most of the places we are parsing attributes, then files that potentially use more than one type of attribute (like a service that provides a route using route attributes, whenever that lands, as well as a hook implementation), would only be parsed once instead of for each service that needs to check.

This should be easy when Drupal is doing the attribute parsing, it may be more complicated for the container itself, but maybe we can swap that out too.

I don't yet know what the API or data structure might look like, only have the very rough outline above so far.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Viewing all 297668 articles
Browse latest View live