Error with layout builder

Created on 3 July 2023, about 1 year ago
Updated 10 December 2023, 10 months ago

Problem/Motivation

My drupal Version is:
Drupal 9.5.9
PHP 8.1
MySQL 5.7.41

If I enable the layout builder module and use juice box from there I get the following error:

Deprecated function: preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in Drupal\juicebox\JuiceboxFormatter->newGallery() (line 163 of modules/contrib/juicebox/src/JuiceboxFormatter.php).
Drupal\juicebox\JuiceboxFormatter->newGallery(Array) (Line: 261)
Drupal\juicebox\Plugin\Field\FieldFormatter\JuiceboxFieldFormatter->viewElements(Object, 'en') (Line: 89)
Drupal\Core\Field\FormatterBase->view(Object, 'en') (Line: 265)
Drupal\Core\Entity\Entity\EntityViewDisplay->buildMultiple(Array) (Line: 266)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildMultiple(Array) (Line: 223)
Drupal\Core\Entity\Entity\EntityViewDisplay->build(Object) (Line: 461)
Drupal\Core\Entity\EntityViewBuilder->viewField(Object, Array) (Line: 243)
Drupal\Core\Field\FieldItemList->view(Array) (Line: 162)
Drupal\layout_builder\Plugin\Block\FieldBlock->build() (Line: 106)
Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray->onBuildRender(Object, 'section_component.build.render_array', Object)
call_user_func(Array, Object, 'section_component.build.render_array', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'section_component.build.render_array') (Line: 90)
Drupal\layout_builder\SectionComponent->toRenderArray(Array, 1) (Line: 88)
Drupal\layout_builder\Section->toRenderArray(Array, 1) (Line: 240)
Drupal\layout_builder\Element\LayoutBuilder->buildAdministrativeSection(Object, 0) (Line: 124)
Drupal\layout_builder\Element\LayoutBuilder->layout(Object) (Line: 98)
Drupal\layout_builder\Element\LayoutBuilder->preRender(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725 ', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 243)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 718)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Deprecated function: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 201 of core/lib/Drupal/Core/Routing/UrlGenerator.php).
Drupal\Core\Routing\UrlGenerator->doGenerate(Array, Array, Array, Array, Array, 'juicebox.xml_field') (Line: 250)
Drupal\Core\Routing\UrlGenerator->getInternalPathFromRoute('juicebox.xml_field', Object, Array, Array) (Line: 291)
Drupal\Core\Routing\UrlGenerator->generateFromRoute('juicebox.xml_field', Array, Array, 1) (Line: 105)
Drupal\Core\Render\MetadataBubblingUrlGenerator->generateFromRoute('juicebox.xml_field', Array, Array) (Line: 284)
Drupal\juicebox\JuiceboxFormatter->buildEmbed(Object, Array, Array, 1, 1, Array) (Line: 267)
Drupal\juicebox\Plugin\Field\FieldFormatter\JuiceboxFieldFormatter->viewElements(Object, 'en') (Line: 89)
Drupal\Core\Field\FormatterBase->view(Object, 'en') (Line: 265)
Drupal\Core\Entity\Entity\EntityViewDisplay->buildMultiple(Array) (Line: 266)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildMultiple(Array) (Line: 223)
Drupal\Core\Entity\Entity\EntityViewDisplay->build(Object) (Line: 461)
Drupal\Core\Entity\EntityViewBuilder->viewField(Object, Array) (Line: 243)
Drupal\Core\Field\FieldItemList->view(Array) (Line: 162)
Drupal\layout_builder\Plugin\Block\FieldBlock->build() (Line: 106)
Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray->onBuildRender(Object, 'section_component.build.render_array', Object)
call_user_func(Array, Object, 'section_component.build.render_array', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'section_component.build.render_array') (Line: 90)
Drupal\layout_builder\SectionComponent->toRenderArray(Array, 1) (Line: 88)
Drupal\layout_builder\Section->toRenderArray(Array, 1) (Line: 240)
Drupal\layout_builder\Element\LayoutBuilder->buildAdministrativeSection(Object, 0) (Line: 124)
Drupal\layout_builder\Element\LayoutBuilder->layout(Object) (Line: 98)
Drupal\layout_builder\Element\LayoutBuilder->preRender(Array)
call_user_func_array(Array, Array) (Line: 101)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. See https://www.drupal.org/node/2966725 ', 'exception', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 788)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 374)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 446)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 204)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 242)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 243)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 132)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.view') (Line: 174)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 718)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Steps to reproduce

  1. Add a media reference field to a node.
  2. Enable layout builder on the node type.
  3. Add the media reference field to the node, configuring it to use the Juicebox formatter.
  4. Observe deprecation notice.
🐛 Bug report
Status

Fixed

Version

4.0

Component

Code

Created by

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @Nelo_Drup
  • 🇺🇸United States fkelly

    The function that is causing the error is "supposed" to have a correction. It should read:

    foreach ($id_args as $arg) {
          // Drop special characters in individual args and delimit by double-dash.
          if (is_string($arg)) {
            $arg = preg_replace('/[^0-9a-zA-Z-]/', '-', $arg);
            $id .= $arg . '--';
          }

    we
    However, looking at the Juicebox repository for 4.0.x and 4.0.0-alsph the test for is_string is not there, leading to the deprecation warning. We have a pending patch for JuiceboxFormatter.php but that doesn't have the (is_string) test in it either. Strangely enough my local copy has that test in it.

    I can't be certain that having that patch in will eliminate all layout builder related problems. That path through the code has probably not been extensively tested in real life. But we'll try to get it into the next set of patches.

    Very strange: in my local history on PHPstorm that is_string test goes back at least to May 3, 2023 but somehow it never got incorporated into the official code on Drupal dot org. It's been a transitional time for the project and I guess it somehow slipped through the cracks. Time to patch them.

  • 🇺🇸United States luke.leber Pennsylvania

    Hey @Nelo_Drup,

    Can you give a bit more detail about your blocks and content type configurations? I set up a custom block type with a media reference field, configured the juicebox formatter on the block type, and added an inline block to a piece of content and did not see this happen. I probably missed a step in getting my test site set up like yours though, so we'll need your help.

    In order to be sure that we're getting this resolved properly, it would be best for us to write a failing test that proves there's an issue, and then we can write the fix to make the test pass.

    It might seem a bit pedantic, but once we have a test with coverage, we can be a lot more sure that this will never break again in the future.

    Thanks!

  • #4 Hello, just create a field type image and then use layout builder change the formatter of the image to juicebox you save and when you go back to edit the layout it will show you that error

  • 🇺🇸United States valderson

    Drupal 9.5.10
    PHP 8.2.8
    juicebox 4.0.0-alpha1

    I am getting a similar error, everything was working fine until I used media manager and deleted some images that were referenced in a juicebox slider multi image field. After that I get the following error and must disable the module to access layout builder on that content type.

    Error: Call to a member function isDisplayed() on null in web/modules/contrib/juicebox/src/Plugin/Field/FieldFormatter/JuiceboxFieldFormatter.php on line 292 #0

  • 🇺🇸United States fkelly

    Thanks for the report #valderson. We will need to check that the fix we mentioned in #2 is active in the code you are using. Looking at your description of the problem I'm not even sure that we fix in #2 will totally eliminate the problem ... it might just be a band-aid type solution. But it will take some testing to determine that. Media management came along quite some time after Juicebox was developed and I don't think the interactions between the two have been extensively tested. IF needed, we'll try to get a fix in to the next release.

  • 🇺🇸United States luke.leber Pennsylvania

    Ah! So the juicebox galleries aren't added to layouts here as block_content blocks, but fields! That should be something we can replicate, fix, and make a test for!

  • 🇺🇸United States fkelly

    The patch I mentioned in #2 is NOT in 4.0.x-dev or 4.0.x itself.

    Being unfamiliar with Layout Builder or the media module I can't contribute much here.

    However, it appears to me that we may have similar problems with: JuiceBoxFieldFormatter in the Plugin\Field\FieldFormatter directory as we had with JuiceBoxFormatter.php just recently. We may need similar expertise to solve the issues. I'll post a separate issue if I can confirm this.

  • 🇺🇸United States NicholasS

    Also have this same issue when trying to use the Juicebox in Layout Builder.

    Here is my Juicebox Config for the field

    field_images:
        type: juicebox_formatter
        label: hidden
        settings:
          image_style: large_image
          thumb_style: juicebox_square_thumb
          caption_source: ''
          title_source: alt
          jlib_galleryWidth: 100%
          jlib_galleryHeight: 600px
          jlib_backgroundColor: '#e6e6e6'
          jlib_textColor: 'rgba(255,255,255,1)'
          jlib_thumbFrameColor: 'rgba(255,255,255,.5)'
          jlib_showOpenButton: true
          jlib_showExpandButton: true
          jlib_showThumbsButton: true
          jlib_useThumbDots: false
          jlib_useFullscreenExpand: '0'
          manual_config: 'imageScaleMode="SCALE"'
          custom_parent_classes: ''
          apply_markup_filter: true
          linkurl_source: ''
          linkurl_target: _blank
          incompatible_file_action: show_icon_and_link
        third_party_settings: {  }
        weight: 1
        region: content
    

    When I tried the current dev branch I get a WSOD (Upgrading drupal/juicebox (4.0.0-alpha1 => dev-4.0.x 99b564e))

    TypeError: Drupal\juicebox\JuiceboxFormatter::__construct(): Argument #9 ($file_url_generator) must be of type Drupal\Core\File\FileUrlGeneratorInterface, Drupal\Core\Entity\EntityDisplayRepository given, called in /var/www/html/docroot/core/lib/Drupal/Component/DependencyInjection/Container.php on line 259 in Drupal\juicebox\JuiceboxFormatter->__construct() (line 115 of modules/contrib/juicebox/src/JuiceboxFormatter.php).
    Drupal\Component\DependencyInjection\Container->createService(Array, 'juicebox.formatter') (Line: 177)
    Drupal\Component\DependencyInjection\Container->get('juicebox.formatter') (Line: 197)
    Drupal::service('juicebox.formatter') (Line: 107)
    Drupal\juicebox\Plugin\Field\FieldFormatter\JuiceboxFieldFormatter::defaultSettings() (Line: 205)
    Drupal\Core\Field\FormatterPluginManager->getDefaultSettings('juicebox_formatter') (Line: 153)
    Drupal\Core\Field\FormatterPluginManager->prepareConfiguration('image', Array) (Line: 357)
    Drupal\Core\Entity\EntityDisplayBase->setComponent('field_images', Array) (Line: 412)
    Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->setComponent('field_images', Array) (Line: 529)
    Drupal\Core\Entity\EntityViewBuilder->getSingleFieldDisplay(Object, 'field_images', Array) (Line: 457)
    Drupal\Core\Entity\EntityViewBuilder->viewField(Object, Array) (Line: 243)
    Drupal\Core\Field\FieldItemList->view(Array) (Line: 163)
    Drupal\layout_builder\Plugin\Block\FieldBlock->build() (Line: 106)
  • 🇺🇸United States fkelly

    We are working towards a new releases and I've posted a diff of changes I've been making (a work in progress admittedly) for Luke to consider when he has time. I don't want to be posting new releases willy-nilly. In the code section below there is code for the latest juiceboxformatter.php. If you have a true dev version and want to try this as a replacement for the 4.0.dev version of juiceboxformatter, have at it and let me know. There are a few "open issues" I am working on, particularly with building embedded galleries and with replacing deprecated watchdog functions that won't be supported in Drupal 10 going forwards. Backup your current juiceboxformatter.php code first and let me know.

    code for JuiceboxFormatter.php below.

    <?php
    
    namespace Drupal\juicebox;
    
    use Drupal\Core\Messenger\MessengerInterface;
    use Exception;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\RequestStack;
    use Drupal\Core\Path\CurrentPathStack;
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\Core\Routing\UrlGeneratorInterface;
    use Drupal\Core\Extension\ModuleHandlerInterface;
    use Drupal\Component\Render\FormattableMarkup;
    use Drupal\Component\Utility\Html;
    use Drupal\file\FileInterface;
    use Drupal\Core\StringTranslation\TranslationInterface;
    use Drupal\Core\StringTranslation\StringTranslationTrait;
    use Drupal\Core\Entity\EntityTypeManagerInterface;
    use Drupal\Core\Security\TrustedCallbackInterface;
    use Drupal\Core\Render\Element;
    use Drupal\Core\File\FileUrlGeneratorInterface;
    
    /**
     * Class to define a Drupal service with common formatter methods.
     *
     * @internal
     */
    class JuiceboxFormatter implements JuiceboxFormatterInterface, TrustedCallbackInterface {
    
      use StringTranslationTrait;
    
      /**
       * A Drupal configuration factory service.
       *
       * @var \Drupal\Core\Config\ConfigFactoryInterface
       */
      protected ConfigFactoryInterface $configFactory;
    
      /**
       * A Drupal URL generator service.
       *
       * @var \Drupal\Core\Routing\UrlGeneratorInterface
       */
      protected UrlGeneratorInterface $urlGenerator;
    
      /**
       * A Drupal module manager service.
       *
       * @var \Drupal\Core\Extension\ModuleHandlerInterface
       */
      protected ModuleHandlerInterface $moduleManager;
    
      /**
       * A Drupal current path service.
       *
       * @var \Drupal\Core\Path\CurrentPathStack
       */
      protected CurrentPathStack $currentPathStack;
    
      /**
       * A Symfony request object for the current request.
       *
       * @var \Symfony\Component\HttpFoundation\Request
       */
      protected Request $request;
    
      /**
       * The messenger service.
       *
       * @var \Drupal\Core\Messenger\MessengerInterface
       */
      protected MessengerInterface $messenger;
    
      /**
       * A Drupal entity type manager service.
       *
       * @var \Drupal\Core\Entity\EntityTypeManagerInterface
       */
      protected EntityTypeManagerInterface $entityTypeManager;
    
      /**
       * The file URL generator.
       *
       * @var \Drupal\Core\File\FileUrlGeneratorInterface
       */
      protected FileUrlGeneratorInterface $fileUrlGenerator;
    
      /**
       * Storage of library details as defined by Libraries API.
       *
       * @var array
       */
      static protected array $library = [];
    
      /**
       * Constructor.
       *
       * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
       *   The Drupal config factory that can be used to derive global Juicebox
       *   settings.
       * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
       *   A string translation service.
       * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
       *   A URL generator service.
       * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_manager
       *   A module manager service.
       * @param \Drupal\Core\Path\CurrentPathStack $currentPathStack
       *   A current path service.
       * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
       *   The Symfony request stack from which to extract the current request.
       * @param \Drupal\Core\Messenger\MessengerInterface $messenger_interface
       *   The messenger interface.
       * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
       *   The entity type manager service.
       * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
       *   The file URL generator.
       */
      public function __construct(ConfigFactoryInterface $config_factory,
            TranslationInterface $string_translation,
            UrlGeneratorInterface $url_generator,
            ModuleHandlerInterface $module_manager,
            CurrentPathStack $currentPathStack,
            RequestStack $request_stack,
            MessengerInterface $messenger_interface,
            EntityTypeManagerInterface $entity_type_manager,
            FileUrlGeneratorInterface $file_url_generator
        ) {
        $this->configFactory = $config_factory;
        $this->stringTranslation = $string_translation;
        $this->urlGenerator = $url_generator;
        $this->moduleManager = $module_manager;
        $this->currentPathStack = $currentPathStack;
        $this->request = $request_stack->getCurrentRequest();
        $this->messenger = $messenger_interface;
        $this->entityTypeManager = $entity_type_manager;
        $this->fileUrlGenerator = $file_url_generator;
      }
    
      /**
       * {@inheritdoc}
       */
      public static function trustedCallbacks(): array {
        return [
          'preRenderFieldsets',
        ];
      }
      /**
       * Form pre-render callback.
       *
       * Visually render fieldsets without affecting tree-based variable storage.
       *
       * This technique/code is taken almost directly from the D7 Views module in
       * views_ui_pre_render_add_fieldset_markup()
       */
      public static function preRenderFieldsets($form): array {
        foreach (Element::children($form) as $key) {
          $element = $form[$key];
          // In our form builder functions, we added an arbitrary #jb_fieldset
          // If this form element has that property, move it into its fieldset.
          if (isset($element['#jb_fieldset']) && isset($form[$element['#jb_fieldset']])) {
            $form[$element['#jb_fieldset']][$key] = $element;
            // Remove the original element this duplicates.
            unset($form[$key]);
          }
        }
        return $form;
      }
    
      /**
       * {@inheritdoc}
       */
      public function newGallery(array $id_args): JuiceboxGallery|JuiceboxGalleryInterface {
        // Calculate the gallery ID.
        $id = '';
        foreach ($id_args as $arg) {
          if (is_string($arg)) {
            // Drop special characters in individual args and delimit by double-dash.
            $arg = preg_replace('/[^0-9a-zA-Z-]/', '-', $arg);
            $id .= $arg . '--';
          }
        }
        $id = trim($id, '- ');
        // Get the library data. We do this early (before instantiating) as the lib
        // details should be allowed to impact which classes are used.
        $library = $this->getLibrary();
        // Calculate the class that needs to be instantiated allowing modules to
        // alter the result.
        $class = 'Drupal\juicebox\JuiceboxGallery';
        $this->moduleManager->alter('juicebox_gallery_class', $class, $library);
        // Instantiate the Juicebox gallery objects.
        $object_settings = [
          'filter_markup' => $this->configFactory->get('juicebox.settings')->get('apply_markup_filter'),
          'process_attributes' => FALSE,
        ];
    
        $gallery = new $class($id, $object_settings);
        if ($gallery instanceof JuiceboxGalleryInterface) {
          return $gallery;
        }
        throw new Exception('Could not instantiate Juicebox gallery.');
      }
    
      /**
       * {@inheritdoc}
       */
      public function getGlobalSettings(): array {
        return $this->configFactory->get('juicebox.settings')->get();
      }
    
      /**
       * {@inheritdoc}
       */
      public function getLibrary(bool $force_local = FALSE, bool $reset = FALSE): array {
        $library = [];
        // We "default" to sites/all/libraries and that will override
        // anything in /libraries. Rationale is that sites/all/libraries
        // was the original location for these files theoretically,
        // we could check the versions of both and pick the one
        // with the highest version not sure the juice(box) is worth the squeeze.
        if (file_exists(DRUPAL_ROOT . '/' . 'sites/all/libraries/juicebox/juicebox.js')) {
          $librarypath = '/sites/all/libraries/juicebox/juicebox.js';
        }
        elseif (file_exists(DRUPAL_ROOT . '/' . 'libraries/juicebox/juicebox.js')) {
          $librarypath = '/libraries/juicebox/juicebox.js';
        }
        if (isset($librarypath)) {
          juicebox_build_library_array($librarypath, $library);
        }
        else {
          $notification_top = $this->t('The Juicebox Javascript library does not appear to be installed. Please download and install the most recent version of the Juicebox library.');
          $this->messenger->addError($notification_top);
        }
        return $library;
      }
    
      /**
       * {@inheritdoc}
       */
      public function runCommonBuild(JuiceboxGalleryInterface $gallery, array $settings, mixed $data = NULL): void {
        $global_settings = $this->getGlobalSettings();
        // Add all gallery options directly from the settings.
        $this->setGalleryOptions($gallery, $settings);
        // Also attempt to translate interface strings.
        if ($global_settings['translate_interface']) {
          $base_string = $global_settings['base_languagelist'];
          if (!empty($base_string)) {
            $base_string = Html::escape($base_string);
            $gallery->addOption('languagelist', $base_string, FALSE);
          }
        }
        // Allow other modules to alter the built gallery data before it's
        // rendered. After this point the gallery should no longer change.
        $this->moduleManager->alter('juicebox_gallery', $gallery, $data);
      }
    
      /**
       * {@inheritdoc}
       */
      public function buildEmbed(JuiceboxGalleryInterface $gallery, array $settings, array $xml_route_info, bool $add_js = TRUE, bool $add_xml = FALSE, array $contextual = []): array {
        // Merge all settings.
        $settings = $settings + $this->getGlobalSettings();
        // Set some defaults for the route info.
        $xml_route_info += [
          'route_name' => '',
          'route_parameters' => [],
          'options' => [],
        ];
        // Prep the ids that may be used.
        $embed_id = $gallery->getId();
        $embed_xml_id = 'xml--' . $embed_id;
        // Construct the base render array for the gallery.
        $output = [
          '#gallery' => $gallery,
          '#theme' => 'juicebox_embed_markup',
          '#settings' => $settings,
          '#attached' => [],
          '#contextual_links' => $contextual + [
            'juicebox_conf_global' => [
              'route_parameters' => [],
            ],
          ],
          '#cache' => [
            'tags' => ['juicebox_gallery'],
          ],
          '#suffix' => '',
        ];
        // Process JS additions.
        if ($add_js) {
          // If we are also embedding the XML we want to set some query string
          // values on the XML URL that will allow the XML build methods to fetch
          // it later.
          $embed_query_additions = [];
          if ($add_xml) {
            $embed_query_additions['xml-source-path'] = trim($this->currentPathStack->getPath(), '/');
            $embed_query_additions['xml-source-id'] = $embed_xml_id;
          }
          // Add some query params that apply to all types of Juicebox galleries and
          // generate the final XML URL.
          $xml_query_additions = array_merge(['checksum' => $gallery->getChecksum()], $embed_query_additions);
          $xml_options = array_merge_recursive(['query' => $xml_query_additions], $xml_route_info['options']);
          $xml_url = $this->urlGenerator->generateFromRoute($xml_route_info['route_name'], $xml_route_info['route_parameters'], $xml_options);
          // Add the main library.
          if (file_exists(DRUPAL_ROOT . '/' . 'sites/all/libraries/juicebox/juicebox.js')) {
            $output['#attached']['library'][] = 'juicebox/juicebox.sites';
          }
          elseif (file_exists(DRUPAL_ROOT . '/' . 'libraries/juicebox/juicebox.js')) {
            $output['#attached']['library'][] = 'juicebox/juicebox';
          }
          else {
            $notification_top = $this->t('The Juicebox Javascript library does not appear to be installed. Please download and install the most recent version of the Juicebox library.');
            $this->messenger->addError($notification_top);
          }
          // Add the JS gallery details as Drupal.settings.
          $output['#attached']['drupalSettings']['juicebox'] = [$embed_id => $gallery->getJavascriptVars($xml_url)];
          // Add some local JS (implementing Drupal.behaviors) that will process
          // the Drupal.settings above into a new client-side juicebox object.
          $output['#attached']['library'][] = 'juicebox/juicebox.local';
        }
        if ($add_xml) {
          $output['#suffix'] .= $gallery->renderXml($embed_xml_id);
        }
        // Ensure that our suffix is not further sanitized.
        $output['#suffix'] = new FormattableMarkup($output['#suffix'], []);
        return $output;
      }
    
      /**
       * {@inheritdoc}
       */
      public function styleImageSrcData(FileInterface $image_file, string $image_style, FileInterface $thumb_file, string $thumb_style, array $settings): array {
        $check_incompatible = (!empty($settings['incompatible_file_action']));
         // Style the main item.
        $src_data = $this->styleImage($image_file, $image_style, $check_incompatible);
        // Set thumb data and add it to the source info.
        if (!$src_data['juicebox_compatible'] && $image_file->id() == $thumb_file->id()) {
          $src_data['thumbURL'] = $src_data['imageURL'];
        }
        else {
          $thumb_image_data = $this->styleImage($thumb_file, $thumb_style, $check_incompatible);
          $src_data['thumbURL'] = $thumb_image_data['imageURL'];
        }
        // Check if the linkURL should be customized based on settings.
        $src_data['linkURL'] = $src_data['unstyled_src'];
        if ($src_data['juicebox_compatible'] && !empty($settings['linkurl_source']) && $settings['linkurl_source'] == 'image_styled') {
          $src_data['linkURL'] = $src_data['imageURL'];
        }
        // Set the link target directly from the gallery settings.
        $src_data['linkTarget'] = !empty($settings['linkurl_target']) ? $settings['linkurl_target'] : '_blank';
        return $src_data;
      }
    
      /**
       * Utility to style an individual file entity for use in a Juicebox gallery.
       *
       * This method can detect if the passed file is incompatible with Juicebox.
       * If so it styles the output as a mimetype image icon representing the file
       * type. Otherwise, the item is styled normally with the passed image style.
       *
       * @param \Drupal\file\FileInterface $file
       *   A file entity containing the image data to append Juicebox styled image
       *   data to.
       * @param string $style
       *   The Drupal image style to apply to the item.
       * @param bool $check_compatible
       *   Whether-or-not to detect if the item is compatible with Juicebox, and if
       *   so, substitute a mimetype icon for its output.
       *
       * @return array
       *   The styled image data.
       */
      protected function styleImage(FileInterface $file, string $style, bool $check_compatible = TRUE): array {
        $global_settings = $this->getGlobalSettings();
        $library = $this->getLibrary();
        $mimetype = $file->getMimeType();
        $image_data = [];
        $image_data['juicebox_compatible'] = TRUE;
        // Set the normal, unstyled, url for reference.
        $image_data['unstyled_src'] = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
        // Check compatibility if configured and if the library info contains
        // mimetype compatibility information.
        if ($check_compatible && !empty($library['compatible_mimetypes']) && !in_array($mimetype, $library['compatible_mimetypes'])) {
          // If the item is not compatible, find the substitute mimetype icon.
          $image_data['juicebox_compatible'] = FALSE;
          $icon_dir = $this->moduleManager->getModule('juicebox')->getPath() . '/images/mimetypes';
          // We only have icons for each major type, so simplify accordingly.
          // file_icon_class() could also be useful here but would require
          // supporting icons for more package types.
          $type_parts = explode('/', $mimetype);
          $icon_path = $icon_dir . '/' . reset($type_parts) . '.png';
          if (file_exists($icon_path)) {
            $image_data['imageURL'] = $this->fileUrlGenerator->generateAbsoluteString($icon_path);
          }
          else {
            $image_data['imageURL'] = $this->fileUrlGenerator->generateAbsoluteString($icon_dir . '/general.png');
          }
        }
        // If the item is compatible, style it.
        else {
          $sizes = ['imageURL' => $style];
          // The "juicebox_multisize" style is special, and actually consists of 3
          // individual styles configured globally.
          if ($style == 'juicebox_multisize') {
            $sizes = [
              'smallImageURL' => $global_settings['juicebox_multisize_small'],
              'imageURL' => $global_settings['juicebox_multisize_medium'],
              'largeImageURL' => $global_settings['juicebox_multisize_large'],
            ];
          }
          foreach ($sizes as $size => $style_each) {
            if (!empty($style_each)) {
              $style_obj = $this->entityTypeManager->getStorage('image_style')->load($style_each);
              if ($style_obj) {
                $image_data[$size] = $style_obj->buildUrl($file->getFileUri());
              }
            }
            else {
              $image_data[$size] = $image_data['unstyled_src'];
            }
          }
        }
        return $image_data;
      }
    
      /**
       * Utility to extract Juicebox options from common Drupal display settings.
       *
       * And add them to the gallery.
       *
       * Some common Juicebox configuration options are set via a GUI and others
       * are set as manual strings. This method fetches all of these values from
       * drupal settings data and merges them into the gallery. Note that this only
       * accounts for common settings.
       *
       * @param \Drupal\juicebox\JuiceboxGalleryInterface $gallery
       *   An initialized Juicebox gallery object.
       * @param array $settings
       *   An associative array of gallery-specific settings.
       */
      protected function setGalleryOptions(JuiceboxGalleryInterface $gallery, array $settings): void {
        // Get the string options set via the GUI.
        foreach (['jlib_galleryWidth', 'jlib_galleryHeight',
          'jlib_backgroundColor', 'jlib_textColor',
          'jlib_thumbFrameColor',
        ] as $name) {
          if (isset($settings[$name])) {
            $name_real = str_replace('jlib_', '', $name);
            $gallery->addOption(mb_strtolower($name_real), trim(Html::escape($settings[$name])));
          }
        }
        // Get the bool options set via the GUI.
        foreach (['jlib_showOpenButton', 'jlib_showExpandButton',
          'jlib_showThumbsButton', 'jlib_useThumbDots', 'jlib_useFullscreenExpand',
        ] as $name) {
          if (isset($settings[$name])) {
            $name_real = str_replace('jlib_', '', $name);
            $gallery->addOption(mb_strtolower($name_real), (!empty($settings[$name]) ? 'TRUE' : 'FALSE'));
          }
        }
        // Merge-in the manually assigned options making sure they take priority
        // over any conflicting GUI options.
        if (!empty($settings['manual_config'])) {
          $manual_options = explode("\n", $settings['manual_config']);
          foreach ($manual_options as $option) {
            $option = trim($option);
            if (!empty($option)) {
              // Each manual option has only been validated (on input) to be in the
              // form optionName="optionValue". Now we need split and sanitize the
              // values.
              $matches = [];
              preg_match('/^([A-Za-z0-9]+?)="([^"]+?)"$/u', $option, $matches);
              [, $name, $value] = $matches;
              $gallery->addOption(mb_strtolower($name), Html::escape($value));
            }
          }
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function confBaseOptions(): array {
        return [
          'jlib_galleryWidth' => '100%',
          'jlib_galleryHeight' => '100%',
          'jlib_backgroundColor' => '#222222',
          'jlib_textColor' => 'rgba(255,255,255,1)',
          'jlib_thumbFrameColor' => 'rgba(255,255,255,.5)',
          'jlib_showOpenButton' => 1,
          'jlib_showExpandButton' => 1,
          'jlib_showThumbsButton' => 1,
          'jlib_useThumbDots' => 0,
          'jlib_useFullscreenExpand' => 0,
          'manual_config' => '',
          'custom_parent_classes' => '',
          'apply_markup_filter' => 1,
          'linkurl_source' => '',
          'linkurl_target' => '_blank',
          'incompatible_file_action' => 'show_icon_and_link',
        ];
      }
    
      /**
       * {@inheritdoc}
       */
      public function confBaseForm(array $form, array $settings): array {
        // Get locally installed library details.
        $library = $this->getLibrary();
        $disallowed_conf = [];
        if (!empty($library) && empty($library['error'])) {
          // If we don't have a known version of the Juicebox library, just show a
          // generic warning.
          if (empty($library['version'])) {
            $notification_top = $this->t('<strong>Notice:</strong> Your Juicebox Library version could not be detected. Some options below may not function correctly.');
          }
          // If this version does not support some LITE options, show a message.
          elseif (!empty($library['disallowed_conf'])) {
            $disallowed_conf = $library['disallowed_conf'];
            $notification_top = $this->t('<strong>Notice:</strong> You are currently using Juicebox library version <strong>@version</strong> which is not compatible with some of the options listed below. These options will appear disabled until you upgrade to the most recent Juicebox library version.', ['@version' => $library['version']]);
            $notification_label = $this->t('&nbsp;(not available in @version)', ['@version' => $library['version']]);
          }
        }
        // If the library itself is not installed, display formal error message.
        else {
          $notification_top = $this->t('The Juicebox Javascript library does not appear to be installed. Please download and install the most recent version of the Juicebox library.');
          $this->messenger->addError($notification_top);
          $form['#pre_render'] = [static::class . '::preRenderFieldsets'];
          return $form;
        }
        $form['juicebox_config'] = [
          '#type' => 'details',
          '#title' => $this->t('Juicebox Library - Lite Config'),
          '#open' => FALSE,
          '#description' => !empty($notification_top) ? '<p>' . $notification_top . '</p>' : '',
          '#weight' => 10,
        ];
        $form['jlib_galleryWidth'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'textfield',
          '#title' => $this->t('Gallery Width'),
          '#description' => $this->t('Set the gallery width in a standard numeric format (such as 100% or 300px).'),
          '#element_validate' => ['juicebox_element_validate_dimension'],
        ];
        $form['jlib_galleryHeight'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'textfield',
          '#title' => $this->t('Gallery Height'),
          '#description' => $this->t('Set the gallery height in a standard numeric format (such as 100% or 300px).'),
          '#element_validate' => ['juicebox_element_validate_dimension'],
        ];
        $form['jlib_backgroundColor'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'textfield',
          '#title' => $this->t('Background Color'),
          '#description' => $this->t('Set the gallery background color as a CSS3 color value (such as rgba(10,50,100,0.7) or #FF00FF).'),
        ];
        $form['jlib_textColor'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'textfield',
          '#title' => $this->t('Text Color'),
          '#description' => $this->t('Set the color of all gallery text as a CSS3 color value (such as rgba(255,255,255,1) or #FF00FF).'),
        ];
        $form['jlib_thumbFrameColor'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'textfield',
          '#title' => $this->t('Thumbnail Frame Color'),
          '#description' => $this->t('Set the color of the thumbnail frame as a CSS3 color value (such as rgba(255,255,255,.5) or #FF00FF).'),
        ];
        $form['jlib_showOpenButton'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'checkbox',
          '#title' => $this->t('Show Open Image Button'),
          '#description' => $this->t('Whether to show the "Open Image" button. This will link to the full size version of the image within a new tab to facilitate downloading.'),
        ];
        $form['jlib_showExpandButton'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'checkbox',
          '#title' => $this->t('Show Expand Button'),
          '#description' => $this->t('Whether to show the "Expand" button. Clicking this button expands the gallery to fill the browser window.'),
        ];
        $form['jlib_useFullscreenExpand'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'checkbox',
          '#title' => $this->t('Use Fullscreen Expand'),
          '#description' => $this->t('Whether to trigger fullscreen mode when clicking the expand button (for supported browsers).'),
        ];
        $form['jlib_showThumbsButton'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'checkbox',
          '#title' => $this->t('Show Thumbs Button'),
          '#description' => $this->t('Whether to show the "Toggle Thumbnails" button.'),
        ];
        $form['jlib_useThumbDots'] = [
          '#jb_fieldset' => 'juicebox_config',
          '#type' => 'checkbox',
          '#title' => $this->t('Show Thumbs Dots'),
          '#description' => $this->t('Whether to replace the thumbnail images with small dots.'),
        ];
        $form['juicebox_manual_config'] = [
          '#type' => 'details',
          '#title' => $this->t('Juicebox Library - Pro / Manual Config'),
          '#open' => FALSE,
          '#description' => $this->t('Specify any additional Juicebox library configuration options (such as "Pro" options) here.<br/>Options set here always take precedence over those set in the "Lite" options above if there is a conflict.'),
          '#weight' => 20,
        ];
        $form['manual_config'] = [
          '#jb_fieldset' => 'juicebox_manual_config',
          '#type' => 'textarea',
          '#title' => $this->t('Pro / Manual Configuration Options'),
          '#description' => $this->t('Add one option per line in the format <strong>optionName="optionValue"</strong><br/>See also: https://www.juicebox.net/support/config_options'),
          '#element_validate' => ['juicebox_element_validate_config'],
        ];
        $form['advanced'] = [
          '#type' => 'details',
          '#title' => $this->t('Juicebox - Advanced Options'),
          '#open' => FALSE,
          '#weight' => 30,
        ];
        $form['incompatible_file_action'] = [
          '#jb_fieldset' => 'advanced',
          '#type' => 'select',
          '#title' => $this->t('Incompatible File Type Handling'),
          '#options' => [
            'skip' => $this->t('Bypass incompatible files'),
            'show_icon' => $this->t('Show mimetype icon placeholder'),
            'show_icon_and_link' => $this->t('Show mimetype icon placeholder and link to file'),
          ],
          '#empty_option' => $this->t('Do nothing'),
          '#description' => $this->t('Specify any special handling that should be applied to files that Juicebox cannot display (non-images).'),
        ];
        $form['linkurl_source'] = [
          '#jb_fieldset' => 'advanced',
          '#type' => 'select',
          '#title' => $this->t("LinkURL Source"),
          '#description' => $this->t('The linkURL is an image-specific path for accessing each image outside the gallery. This is used by features such as the "Open Image Button".'),
          '#options' => ['image_styled' => 'Main Image - Styled (use this gallery\'s main image style setting)'],
          '#empty_option' => $this->t('Main Image - Unstyled (original image)'),
        ];
        $form['linkurl_target'] = [
          '#jb_fieldset' => 'advanced',
          '#type' => 'select',
          '#title' => $this->t('LinkURL Target'),
          '#options' => [
            '_blank' => $this->t('_blank'),
            '_self' => $this->t('_self'),
            '_parent' => $this->t('_parent'),
            '_top' => $this->t('_top'),
          ],
          '#description' => $this->t('Specify a target for any links that make user of the image linkURL.'),
        ];
        $form['custom_parent_classes'] = [
          '#jb_fieldset' => 'advanced',
          '#type' => 'textfield',
          '#title' => $this->t('Custom Classes for Parent Container'),
          '#description' => $this->t('Define any custom classes that should be added to the parent container within the Juicebox embed markup.<br/>This can be handy if you want to apply more advanced styling or dimensioning rules to this gallery via CSS. Enter as space-separated values.'),
        ];
        // Set values that are directly related to each key.
        foreach ($form as $conf_key => &$conf_value) {
          if (!empty($conf_value['#type']) && $conf_value['#type'] != 'details') {
            $conf_value['#default_value'] = $settings[$conf_key];
            if (in_array($conf_key, $disallowed_conf)) {
              if (isset($notification_label)) {
                $conf_value['#title'] .= $notification_label;
              }
              $conf_value['#disabled'] = TRUE;
            }
          }
        }
        // Add a pre render callback that will ensure that the items are nested
        // correctly into fieldsets just before display.
        $form['#pre_render'] = [static::class . '::preRenderFieldsets'];
        return $form;
      }
    
      /**
       * {@inheritdoc}
       */
      public function confBaseStylePresets(bool $allow_multisize = TRUE): array {
        $library = $this->getLibrary();
        // Get available image style presets.
        $presets = image_style_options(FALSE);
        // If multisize is allowed, include it with the normal styles.
        if ($allow_multisize && (!isset($library['disallowed_cont']) || !in_array('juicebox_multisize_image_style', $library['disallowed_conf']))) {
          $presets = ['juicebox_multisize' => $this->t('Juicebox PRO multi-size (adaptive)')] + $presets;
        }
        return $presets;
      }
    
    }
    
  • 🇺🇸United States fkelly

    Stepping back: the initial post in this thread (from 5+ months ago, July 2023) started:

    If I enable the layout builder module and use juice box from there I get the following error:

    "Deprecated function: preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in Drupal\juicebox\JuiceboxFormatter->newGallery() (line 163 of modules/contrib/juicebox/src/JuiceboxFormatter.php)."

    This error can be obviated by putting a test " if (is_string($arg)) {" into the newgallery function in juiceboxFormatter and that test is in the latest releases. That initial error report also contains the:

    "Deprecated function: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 201 of core/lib/Drupal/Core/Routing/UrlGenerator.php)."

    error message which we have discussed in subsequent posts in the Juicebox issue queue:

    https://www.drupal.org/project/juicebox/issues/3400618 🐛 Problems Building Gallery XML Active

    In issue 3400618 I determined that we can work around the "problem" be turning off the preview function for Juicebox content types. It appears that a similar problem may exist with layout builder. We can avert the JuiceboxFormatter side of the problem by getting the latest version of JuiceboxFormatter that has the "is_string" test. I don't know if this averts the layout builder side of the problem or if instead there is a deeper problem inside the Juicebox code and setup that's driving this. More work needed.

    If @Nicholas could give the latest version of JuiceboxFormatter a try and report back it would be helpful in determining the scope of the problem: is it just layout builder or something more systematic?

  • 🇺🇸United States fkelly

    Following up on #12 ...

    as mentioned I use a content type named "Quick Juicebox" for embedding gallery images directly into Drupal. All this content type has in it is a body field and an image field named "jimage". When you go into the structure / manage display screen you have the option to use the layout builder. If you turn that option on, when you try to view an instance of that content type (i.e. a Juicebox Gallery with a few images embedded, you get

    "Deprecated function: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 201 of core/lib/Drupal/Core/Routing/UrlGenerator.php).

    if you go back into Structure for that content type and and turn use layout builder off in the manage display screen, then go back and view that instance of the content type, it works just fine. (This is also with preview turned off as I've mentioned in previous posts).

    Bottom line, there is something in common wrong when using preview or layout builder. The error shows up in a Drupal/Core/Routing program so there is no easy fix. Most likely the problem lies where Juicebox communicates with core. I haven't been able to isolate that problem yet.

    For now, don't use layout builder for Juicebox content types and don't turn on preview.

  • Status changed to Needs work 10 months ago
  • 🇺🇸United States luke.leber Pennsylvania

    re: #10 - it is expected that cache flushes are required if you're upgrading to the 4.0.x-dev release.
    We shipped an empty post-update hook in order to facilitate such a cache flush. After upgrading, until a manual cache flush happens (or users run updates), sites will encounter WSOD conditions due to constructor argument changes.

    The steps to reproduce are pretty straight-forward sounding. Can the OP please confirm that this is a deprecation notice and not an error?

    As for this issue, the first step here is to produce a new test that fails on Drupal CI (thus proving there's an issue -- which we can write code to fix -- and prove that it indeed fixes it. This doubles to add test coverage for a layout builder integration. :-) ).

    As it turns out, I have Wed/Thurs/Fri off work, so I may be able to get around to crafting a test case either early Wednesday, or at some point on Friday.

  • 🇺🇸United States luke.leber Pennsylvania
  • Status changed to Needs review 10 months ago
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update 10 months ago
    11 pass
  • 🇺🇸United States luke.leber Pennsylvania

    It looks like this issue stems from the fact that when generating previews in Layout Builder, there is not always an entity ID available (due to the fact that some layouts use default layout storage.

    While we can fix this by simply null-coalescing the argument like so...

          $arg = preg_replace('/[^0-9a-zA-Z-]/', '-', $arg ?? '');
    

    That doesn't solve the underlying UX issue in that Juicebox fails to provide a useful preview in contexts like Layout Builder. I've opened https://www.drupal.org/project/juicebox/issues/3403266 Provide a useful "preview" UX for layout builder Active as a follow-up, since resolving this issue won't really make the UX any better for production users.

    I've attached a patch that should maintain perfect backwards compatibility with the ID generator while also preventing the notice.

  • 🇺🇸United States fkelly

    This may lead to a solution at some point: https://www.drupal.org/project/juicebox/issues/3403266 Provide a useful "preview" UX for layout builder Active

    but for the time being, it does not appear that the patch 3372033-16.patch resolves the problem. I tested on my local system without the patch to confirm that the problem was still there, then applied the patch, then tested again and the problem is still there;

    The full code that I'm using is:

        foreach ($id_args as $arg) {
          if (is_string($arg)) {
            // Drop special characters in individual args and delimit by double-dash.
            $arg = preg_replace('/[^0-9a-zA-Z-]/', '-', $arg ?? '');
            $id .= $arg . '--';
          }
        }

    The is_string test was put in at some point to deal with another bug but I've tested the code with and without it.

    The error (deprecation?) that I see is:

    Deprecated function: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 205 of D:\webpage\compg_2\web\core\lib\Drupal\Core\Routing\UrlGenerator.php). =>

    Looking at the detail that follows the deprecation message, I see
    "37: preg_match()" => array:1 [▼
    "args" => array:2 [▼
    0 => "#^[^/]++$#"
    1 => null
    ]
    ]

    where I think that the second element in the array being set to null is what is causing the deprecation.

    I will work at this a bit more before I have to shut things down for a couple of days.

    Working further but running out of time for the week. The patch, or the way I have it applied is not resolving or even affecting the problem.

    Steps to reproduce: 1. set up a content type for a Juicebox Gallery. 2. Use the simplest set of fields (a title, a body, an image). 3. Create an instance of that content type. Preview it. You should see the errors I posted. The null coalescing operator, at least as it's applied here, does not fix it. Whatever is getting passed in to the variable $token[2] when called from a Juicebox preview, is null. Preg_match doesn't like that and stops you in your tracks.

    You can do the same thing for another content type, e.g., article, and the preview function works fine. I've been chasing this for almost two weeks.

  • 🇺🇸United States luke.leber Pennsylvania

    Ah, there were two separate stack traces in the OP. (One regarding preg_replace and one regarding preg_match in different parts of the codebase!). I completely missed the preg_match notice.

    That likely means that there are two different problems described in this issue.

  • 🇺🇸United States fkelly

    yes. sorry if I "conflated" the issue statement. The preg_match issue is the one that's stopped me in my tracks.

    So the patch I manually applied was not really relevant to the problem I have been focused on. Oh well.

    I will work more on this over the weekend.

    The full set of patches will take some work for me to apply, unless we are going to do a full release. Let me know what I can usefully do to test all this.

  • Hi :)

    Yes problem with juicebox 4.0.0-alpha1 and Layout Builder with Drupal 10.1.5 for me. When I go layout builder I have this error:

    Deprecated function: preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in Drupal\juicebox\JuiceboxFormatter->newGallery() (line 163 of modules/contrib/juicebox/src/JuiceboxFormatter.php).

    Deprecated function: preg_match(): Passing null to parameter #2 ($subject) of type string is deprecated in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 207 of core/lib/Drupal/Core/Routing/UrlGenerator.php).

  • 🇺🇸United States fkelly

    @Amavi ... thanks for the report. The deprecated function with preg_match error is not limited to layout builder. We are working on the problem.

    For the time being, just don't use the preview function or try to format a Juicebox gallery using layout builder. We will have a solution as part of the next release of Juicebox for Drupal 10.

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update 10 months ago
    12 pass
  • 🇺🇸United States luke.leber Pennsylvania

    Added test coverage and a more holistic solution to resolve layout builder issues.

    Let's see if the test bot agrees.

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update 10 months ago
    12 pass
  • 🇺🇸United States luke.leber Pennsylvania

    Uniquify the juicebox preview ID (needed if there is more than one gallery rendering in preview mode on any given pageview).

    • Luke.Leber authored e859e5ef on 4.0.x
      Issue #3372033 by Luke.Leber, fkelly12054@gmail.com, Nelo_Drup,...
  • Status changed to Fixed 10 months ago
  • 🇺🇸United States luke.leber Pennsylvania

    Committed and pushed to 4.0.x - thanks all.

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024