Previously created media URLs should update if the matcher is changed from canonical to direct URL

Created on 17 March 2023, almost 2 years ago
Updated 18 September 2024, 3 months ago
This issue was created to address continuing discussion in πŸ’¬ Direct Link to File in Media Entity Document Fixed . The maintainers are currently debating whether the change proposed below is even desirable. There is no guarantee the current behavior will change.

Linkit provides a media "Matcher" configuration option to set whether links to media entities should display at the canonical URL (e.g., /media/1) or as the direct link to the file (e.g., /sites/default/files/myfile.png). For background, see #2866525: Direct link to file of linked media entity β†’ and #2786049: Make entity URL substitutions pluggable to support a wider variety of use cases. β†’

However, this setting does not work retroactively for links inserted prior to configuring the Linkit matcher.

Similarly, if you change the setting after configuring it initially, the links do not take on the new configuration behavior.

This is because the markup stored includes either data-entity-substitution="media" or data-entity-substitution="canonical" and that value is not reevaluated against the current configuration setting in Linkit when the text filter runs.

Steps to reproduce

1. (Drupal core): Enable direct media links at /admin/config/media/media-settings
2. Add the "Linkit URL Converter" text filter to a text format.
3. Add a media item. From Step 1, its path should be /media/1
4. Create some content with you text format referencing a link to /media/1
5. When viewing the content, you should see a link to the canonical entity URL (e.g., /media/1)
6. Navigate to /content/linkit/manage/default/matchers/add
7. Add a new matcher for "Media"
8. Set the "Substitution type" to "Direct URL to media file entity"
9. Clear the cache and reload your content. You might expect to see the link to sites/default/files/myfile.png, but it remains with the original rendering of /media/1.

Next steps

Decide whether or not a change is even desirable. Specifically, should URLs be rendered in whatever form they were initially entered (data-entity-substitution "media" or "canonical"), or should Linkit override the data-entity-substitution value with whatever the current matcher configuration is set to?

Workarounds if the current behavior remains

From https://www.drupal.org/project/linkit/issues/2926655#comment-14971258 πŸ’¬ Direct Link to File in Media Entity Document Fixed

If the value is 'canonical', it points to the Media entity. If you replace it with 'media', it points to to the file.

You could do a bulk Find and Replace (carefully) to save the pain of finding and replacing all your media links.

✨ Feature request
Status

Active

Version

7.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States mark_fullmer Tucson

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

Comments & Activities

  • Issue created by @mark_fullmer
  • πŸ‡ΊπŸ‡ΈUnited States mark_fullmer Tucson
  • πŸ‡ΊπŸ‡ΈUnited States lhridley

    No only is this desirable, it is critical, backwards compatibility issue.

    I have a client that has a site built with Layout Builder and embedded (inline) blocks that have multiple embedded links to PDFs. Over 3,000 blocks, some with dozens of links, all of which are now breaking. They are using Linkit version 6.1.4.

    Finding the pages with the inline blocks for over 3,000 affected pieces of content will take more resources than this client has available.

    Using "find and replace" to query the database and hope you get them all is not an option.

  • πŸ‡©πŸ‡ͺGermany mrshowerman Munich

    +1 for implementing this change and dropping (or at least ignoring) the data-entity-substitution attribute.

    We have a global setting that controls the substitution type, and I would expect all (existing and newly created) links to update their generated URLs when I change that setting. There is no point in storing the substitution type in every single link element if editors can't change it.

    I came across this issue today when I tried to create a custom substitution type that extends the media type, and does not get applied to existing links because they all decide to stick with the old one. The only chance I have is to use the same plugin ID (feels like a bad idea).

  • πŸ‡ΊπŸ‡ΈUnited States lhridley

    For those of you that need to update Linkit links in inline blocks placed with Layout Builder (which was the issue that our client was having to deal with), here is the code we used to update the embedded links in the inline block entities.

    In our case, the blocks were placed with Layout Builder, and the content of the blocks was created with CKEditor. The embedded links were in the "body" field of the inline block.

    The client was dealing with over 3,000 links to PDFs that were embedded in the wysiwyg field of the inline blocks.

    This code was deployed as a hook_update.

    Context:

    Code for the hook_update:

      // Selects all blocks of type utc_text_block containing
      // 'data-entity-substitution="canonical"'.
      $query = \Drupal::entityQuery('block_content')
        ->condition('type', 'utc_text_block')
        ->condition('body', '%data-entity-substitution="canonical"%', 'LIKE')
        ->accessCheck(FALSE);
      $results = $query->execute();
      // Sets a counter to 0.
      $blocks_updated = 0;
      // Loops through blocks selected in query above.
      foreach ($results as $block) {
        $replaced = FALSE;
        $block_content = BlockContent::load($block);
        $body_field = $block_content->body->getValue();
        $body_text = $block_content->body->getValue()[0]['value'];
        $body_format = $block_content->body->getValue()[0]['format'];
        $body_summary = $block_content->body->getValue()[0]['summary'];
        // Setting up new DOMDocument for extracting tags to replace.
        $doc = new DOMDocument();
        libxml_use_internal_errors(true); // Ignore parsing errors due to malformed HTML
        // Loads the body text into the DOMDocument without the <html>, <body> and doc def tags.
        $doc->loadHTML($body_text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // Clear any libXML internal errors that were suppressed.
        libxml_clear_errors();
        // Gets iterable DOMNodeList object made up of matches.
        $links = $doc->getElementsByTagName('a');
        // Loop through the number of elements that match the tag.
        foreach ($links as $link) {
          // If link contains attributes data-entity-substitution="canonical" and
          // data-entity-type="media", change the value for data-entity-substitution
          // to "media" also.
          if($link->getAttribute('data-entity-substitution') == 'canonical'
            && $link->getAttribute('data-entity-type') == 'media') {
            // Clone the link node.
            $newLink = $link->cloneNode(true);
            // Set a new attributes value.
            $newLink->setAttribute('data-entity-substitution', 'media');
            // Replace the original link node with the new link node.
            $link->replaceWith($newLink);
            // Set $replaced flag to true.
            $replaced = TRUE;
          }
        }
        // New body text from DOMdocument.
        $new_body_text = $doc->saveHTML();
        // Set values for summary, format, and new body text.
        $new_body_data = [
          'value' => $new_body_text,
          'summary' => $body_summary,
          'format' => $body_format,
        ];
        // Save the revised block if $replaced = TRUE.
        if ($replaced) {
          $block_content->set('body', $new_body_data);
          // We do not want to create a new revision because the nodes with the embedded blocks have stored
          // the revision ID.
          // Commented out this line:  $block_content->setNewRevision();
          $block_content->save();
          $blocks_updated++;
        }
      }
      // When processing is complete, return the number of updated blocks.
      return t("Updated @blockcount blocks", ["@blockcount" => $blocks_updated]);
    

    The same concept could be applied to nodes with links in body fields as well (or any other entity for that matter), you'd just need to change the entityQuery type from `block_content` to `node`. Unlike the blocks, however, where we had to preserve the revision ID so that the nodes created with Layout Builder would reflect the changes, you could create a new node revision.

    Hopefully this will save someone some time.

  • πŸ‡ΊπŸ‡ΈUnited States lhridley

    A followup to the above hook_update: Don't forget to also change the configuration for the LinkIt module configuration to change the matcher substitution from `canonical` to `media` (direct link to media file). This makes future Linkit-inserted media document links go directly to the PDF file address instead of to the Media entity display.

Production build 0.71.5 2024