0

I want to remove all libraries of modules & themes, except those from modules/themes that are defined by me. This should only be done for anonymous users. All libraries should be served to authenticated users.

I'm adding the user.roles cache context in the html preprocess hook and remove all library definitions that are not desired:

/**
 * Implements hook_preprocess_html().
 */
function mytheme_preprocess_html(array &$variables) {
  $variables['page']['#cache']['contexts'][] = 'user.roles';
}

/**
 * Implements hook_library_info_alter().
 */
function mytheme_library_info_alter(array &$libraries, $module) {
  /* Remove all libraries that are not needed.
  Only remove them for anonymous users, since a lot of libraries are required for the admin interface to work. */
  $allowed_modules_with_libraries = [
    'mytheme',
    'google_tag',
    'my_custom_module',
  ];

  if (!in_array($module, $allowed_modules_with_libraries) && !Drupal::currentUser()->isAuthenticated()) {
    $libraries = [];
  }
}

My problem is, that the attached libraries are not completely separated by user role cache context:

My test workflow:

  • Clear all caches
  • Requesting page as authenticated user
  • Contrib assets in browser sources panel:

authenticated assets

This is as expected.

  • Requesting same page as anonymous user
  • Contrib assets in browser sources panel:

anonymous user

This is not as expected, nothing should be present here.

The other way around:

  • Clear all caches
    • Requesting page as anonymous user
    • No contrib assets are displayed browser sources panel.

This is as expected.

  • Requesting same page as authenticated user
  • Assets in browser sources panel:

enter image description here

This is not as expected, paragraphs and webform assets are missing.

The x-drupal-cache-contexts HTTP response header content: languages:language_content languages:language_interface languages:language_url route session theme timezone url.path url.query_args user

The user cache context is there, shouldn't the response be cached by user (including the role) then?

I tried doing this by using hook_page_attachments_alter, but not all libraries that are attached to the response are listed there. I'm not sure if changing the library definitions with hook_library_info_alter is the right approach. Since I don't want to change the libraries. I just want to detach them from the response.

The page_cache and dynamic_page_cache modules are enabled.

  • hook_library_info_alter is not the right approach, you get the role of the user who installs a module or clears the cache, not of the user who requests the page. Can you provide more details why hook_page_attachments_alter is not listing all libraries? – 4k4 Feb 21 at 11:08
  • Indeed hook_page_attachments_alter lists only libraries added by other hook_page_attachments(_alter) hooks. So we can rule out both hooks you've mentioned and hopefully someone knows a different approach. – 4k4 Feb 21 at 13:02
  • Yes I would need something that lists all attached libraries. From all the rendered child components. Blocks, forms, etc... I also had a look at drupal.stackexchange.com/a/233928/82156 and added the user role to the cache id for the PageCache. But I realized that the page cache is passed on authenticated requests. – Orlando Feb 21 at 13:10
  • Yes, code depending on user roles normally doesn't cause caching issues. PageCache caches only anonymous and the dynamic page cache uses a cache context for the user permissions by default. I've tried to provide an answer what you can do else instead of the two hooks. – 4k4 Feb 21 at 13:39
1

hook_library_info_alter() is not the right approach. You get the role of the user who installs a module or clears the cache, not of the user who requests the page. Info alter hooks in general are about changing information provided by other extensions and usually are only recalculated if an extension is added or removed and for a better dev experience on a cache clear.

And indeed, hook_page_attachments_alter() lists only libraries added by other hook_page_attachments(_alter) hooks. See invokePageAttachmentHooks(), which starts with an empty attachments array:

  public function invokePageAttachmentHooks(array &$page) {
    // Modules can add attachments.
    $attachments = [];
    ...

So we can rule out both hooks you've mentioned. Attached libraries are collected from many different places (YAML, render arrays, hooks) and they are merged (bubbled up) through the entire page building and rendering process. So it might be a good idea to attach the override in form of a custom library the same way and let Drupal calculate the assets when rendering the page. See https://www.drupal.org/node/2216195#override-extend

  • Thanks for your help. But overriding the libraries removes them for all users. I only want them to be removed for anonymous users. – Orlando Feb 21 at 16:25
  • But you can re-attach them dynamically. – 4k4 Feb 21 at 16:34
  • Yes that could be a way. But one problem I have is, I want to remove all libraries of all modules. It's not really practical, for development and maintenance, to override every single library. Especially when it comes to updates, every time updating a module, I would have to check if a library was added or removed. For every module. There is not a way to override all libraries of a module with a single statement inside the libraries.yml right? – Orlando Feb 25 at 9:21
  • To avoid the warning "libraries don't exist" you could keep the libraries and remove only the assets like in drupal.stackexchange.com/questions/276767/… and then attach your custom libraries containing the assets you want depending on user roles. – 4k4 Feb 25 at 13:05
  • Yes this could work. Thanks :) Will try this out. – Orlando Feb 25 at 15:42
0

I created a decorator for the LibraryDiscoveryCollector. I think this is not ideal, since it does not remove the attachments, and basically says "these libraries don't exist", if I understand it correctly. But it works. Didn't find a way to remove attached libraries for everything inside preprocessing.

LibraryDiscoveryCollector decorator:

namespace Drupal\mymodule_assets\Asset;

use Drupal\Core\Asset\LibraryDiscoveryCollector;

/**
 * Decorates the LibraryDiscoveryCollector service.
 *
 * @package Drupal\mymodule_assets\Asset
 */
class MyLibraryDiscoveryCollector extends LibraryDiscoveryCollector {

  /**
   * Adds the user authenticated part to the cache id.
   *
   * @return string
   *   Cache id.
   */
  protected function getCid() {
    if (!isset($this->cid)) {
      $originalCacheId = 'library_info:' . $this->themeManager->getActiveTheme()->getName();

      if (\Drupal::currentUser()->isAuthenticated()) {
        $authenticatedCidString = 'authenticated';
      }
      else {
        $authenticatedCidString = 'anonymous';
      }

      $cacheIdPartsToAdd = [
        $authenticatedCidString,
      ];

      if (!empty($cacheIdPartsToAdd)) {
        $cacheId = $originalCacheId . ':' . implode(':', $cacheIdPartsToAdd);
      }
      else {
        $cacheId = $originalCacheId;
      }

      $this->cid = $cacheId;
    }

    return $this->cid;
  }

}

Services YML:

services:
  mymodule_assets.library.discovery.collector:
    class: Drupal\mymodule_assets\Asset\MyLibraryDiscoveryCollector
    decorates: library.discovery.collector
    decoration_priority: 0 # Execute last
    arguments: ['@cache.discovery', '@lock', '@library.discovery.parser', '@theme.manager']
    tags:
    - { name: needs_destruction }

This allows me to cache the library definitions by the user role.

Removal of libraries:

/**
 * Implements hook_library_info_alter().
 */
function mymodule_library_info_alter(array &$libraries, $module) {
  /* Remove all libraries that are not from this theme.
  Only remove them for anonymous users, since a lot of libraries are required for the admin interface to work. */
  $allowed_modules = [
    'mymodule',
    'google_tag',
  ];

  if (!in_array($module, $allowed_modules) && !Drupal::currentUser()->isAuthenticated()) {
    $libraries = [];
  }
}

Help is appreciated if somebody finds a better solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.