Commit 127c62eb authored by Paul McKibben's avatar Paul McKibben
Browse files

Issue #3263032: HTML links no longer work in custom captions

parent b9b5db1c
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@
 * Install, update and uninstall functions for the colorbox module.
 */

use Drupal\Core\Url;

/**
 * Implements hook_install().
 */
@@ -21,9 +23,14 @@ function colorbox_requirements($phase) {
  }

  $library = \Drupal::service('library.discovery')->getLibraryByName('colorbox', 'colorbox');
  $library_exists = file_exists(DRUPAL_ROOT . '/' . $library['js'][0]['data']);
  $library_exists = !empty($library['js'][0]['data']) &&
    file_exists(DRUPAL_ROOT . '/' . $library['js'][0]['data']);

  $dompurify = \Drupal::service('library.discovery')->getLibraryByName('colorbox', 'dompurify');
  $dompurify_exists = !empty($dompurify['js'][0]['data']) &&
    file_exists(DRUPAL_ROOT . '/' . $dompurify['js'][0]['data']);

  return [
  $library_requirements = [
    'colorbox_library_downloaded' => [
      'title' => t('Colorbox library'),
      'value' => $library_exists ? t('Installed') : t('Not installed'),
@@ -31,4 +38,29 @@ function colorbox_requirements($phase) {
      'severity' => $library_exists ? REQUIREMENT_OK : REQUIREMENT_ERROR,
    ],
  ];

  $suppress_warning = \Drupal::config('colorbox.settings')->get('dompurify_hide_warning');
  if (!$suppress_warning) {
    $library_requirements['colorbox_dompurify_downloaded'] = [
      'title' => t('DOMPurify library'),
      'value' => $dompurify_exists ? t('Installed') : t('Not installed'),
      'description' => $dompurify_exists ? '' :
        t('The DOMPurify library is not installed. ' .
          'The Colorbox module uses this library to sanitize HTML captions. ' .
          'Without this library, all captions will be treated as plain text. ' .
          'If you intend to have HTML captions in Colorbox content, ' .
          'the DOMPurify library needs to be <a href="@url">downloaded</a> ' .
          'and extracted into the /libraries/DOMPurify folder in your Drupal ' .
          'installation directory. <br /><br />' .
          'If you do not intend to use HTML captions, you can suppress this warning on the ' .
          '<a href="@config">Colorbox configuration page.</a>',
          [
            '@url' => 'https://github.com/cure53/DOMPurify/archive/main.zip',
            '@config' => Url::fromRoute('colorbox.admin_settings')->toString(),
          ]),
      'severity' => $dompurify_exists ? REQUIREMENT_OK : REQUIREMENT_WARNING,
    ];
  }

  return $library_requirements;
}
+11 −0
Original line number Diff line number Diff line
@@ -7,6 +7,8 @@ colorbox:
  js:
    /libraries/colorbox/jquery.colorbox-min.js: { minified: true }
  dependencies:
    - core/drupal
    - core/drupalSettings
    - core/jquery
    - core/jquery.once

@@ -22,6 +24,15 @@ colorbox-dev:
    - core/jquery
    - core/jquery.once

dompurify:
  remote: https://github.com/cure53/DOMPurify
  version: VERSION
  license:
    name: MPL
    gpl-compatible: false
  js:
    /libraries/DOMPurify/dist/purify.min.js: { }

init:
  version: VERSION
  js:
+48 −3
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
 * Colorbox JS.
 */

(function ($, Drupal) {
(function ($, Drupal, drupalSettings) {

  'use strict';

@@ -36,7 +36,7 @@
        // If a title attribute is supplied, sanitize it.
        var title = $(this).attr('title');
        if (title) {
          extendParams.title = Drupal.checkPlain(title);
          extendParams.title = Drupal.colorbox.sanitizeMarkup(title);
        }
        $(this).colorbox($.extend({}, settings.colorbox, extendParams));
      });
@@ -64,4 +64,49 @@
    }
  };

})(jQuery, Drupal);
  // Create colorbox namespace if it doesn't exist.
  if (!Drupal.hasOwnProperty('colorbox')) {
    Drupal.colorbox = {};
  }

  /**
   * Global function to allow sanitizing captions and control strings.
   *
   * @param markup
   *   String containing potential markup.
   * @return @string
   *  Sanitized string with potentially dangerous markup removed.
   */
  Drupal.colorbox.sanitizeMarkup = function(markup) {
    // If DOMPurify installed, allow some HTML. Otherwise, treat as plain text.
    if (typeof DOMPurify !== 'undefined') {
      var purifyConfig = {
        ALLOWED_TAGS: [
          'a',
          'b',
          'strong',
          'i',
          'em',
          'u',
          'cite',
          'code',
          'br'
        ],
        ALLOWED_ATTR: [
          'href',
          'hreflang',
          'title',
          'target'
        ]
      }
      if (drupalSettings.hasOwnProperty('dompurify_custom_config')) {
        purifyConfig = drupalSettings.dompurify_custom_config;
      }
      return DOMPurify.sanitize(markup, purifyConfig);
    }
    else {
      return Drupal.checkPlain(markup);
    }
  }

})(jQuery, Drupal, drupalSettings);
+81 −0
Original line number Diff line number Diff line
@@ -115,4 +115,85 @@ class ColorboxCommands extends DrushCommands {
    }
  }

  /**
   * Download and install the DOMPurify plugin.
   *
   * @param mixed $path
   *   Optional. A path where to install the DOMPurify plugin.
   *   If omitted Drush will use the default location.
   *
   * @command colorbox:dompurify
   * @aliases colorboxdompurify,colorbox-dompurify
   */
  public function domPurify($path = '') {

    $fs = new Filesystem();

    if (empty($path)) {
      $path = DRUPAL_ROOT . '/libraries/DOMPurify';
    }

    // Create path if it doesn't exist
    // Exit with a message otherwise.
    if (!$fs->exists($path)) {
      $fs->mkdir($path);
    }
    else {
      $this->logger()->notice(dt('DOMPurify is already present at @path. No download required.', ['@path' => $path]));
      return;
    }

    // Load the DOMPurify defined library.
    if ($dompurify_library = $this->libraryDiscovery->getLibraryByName('colorbox', 'dompurify')) {
      // Download the file.
      $client = new Client();
      $destination = tempnam(sys_get_temp_dir(), 'DOMPurify-tmp');
      try {
        $client->get($dompurify_library['remote'] . '/archive/main.zip', ['save_to' => $destination]);
      }
      catch (RequestException $e) {
        // Remove the directory.
        $fs->remove($path);
        $this->logger()->error(dt('Drush was unable to download the DOMPurify library from @remote. @exception', [
          '@remote' => $dompurify_library['remote'] . '/archive/main.zip',
          '@exception' => $e->getMessage(),
        ], 'error'));
        return;
      }

      // Move downloaded file.
      $fs->rename($destination, $path . '/DOMPurify.zip');

      // Unzip the file.
      $zip = new \ZipArchive();
      $res = $zip->open($path . '/DOMPurify.zip');
      if ($res === TRUE) {
        $zip->extractTo($path);
        $zip->close();
      }
      else {
        // Remove the directory if unzip fails and exit.
        $fs->remove($path);
        $this->logger()->error(dt('Error: unable to unzip DOMPurify file.', [], 'error'));
        return;
      }

      // Remove the downloaded zip file.
      $fs->remove($path . '/DOMPurify.zip');

      // Move the dist directory.
      $fs->mirror($path . '/DOMPurify-main/dist', $path . '/dist', NULL, ['override' => TRUE]);
      $fs->remove($path . '/DOMPurify-main');

      // Success.
      $this->logger()->notice(dt('The DOMPurify library has been successfully downloaded to @path.', [
        '@path' => $path,
      ], 'success'));
    }
    else {
      $this->logger()->error(dt('Drush was unable to load the DOMPurify library'));
    }
  }


}
+56 −2
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\colorbox\Form;

use Drupal\Core\Asset\LibraryDiscoveryInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
@@ -21,6 +22,13 @@ class ColorboxSettingsForm extends ConfigFormBase {
   */
  protected $extensionListModule;

  /**
   * Library discovery service.
   *
   * @var LibraryDiscoveryInterface
   */
  protected $libraryDiscovery;

  /**
   * A state that represents the custom settings being enabled.
   */
@@ -47,11 +55,17 @@ class ColorboxSettingsForm extends ConfigFormBase {
   *   The module handler service.
   * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
   *   The list of available modules.
   * @param \Drupal\Core\Asset\LibraryDiscoveryInterface $libraryDiscovery
   *   The library discovery service.
   */
  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $moduleHandler, ModuleExtensionList $extension_list_module) {
  public function __construct(ConfigFactoryInterface $config_factory,
                              ModuleHandlerInterface $moduleHandler,
                              ModuleExtensionList $extension_list_module,
                              LibraryDiscoveryInterface $libraryDiscovery) {
    parent::__construct($config_factory);
    $this->moduleHandler = $moduleHandler;
    $this->extensionListModule = $extension_list_module;
    $this->libraryDiscovery = $libraryDiscovery;
  }

  /**
@@ -61,7 +75,8 @@ class ColorboxSettingsForm extends ConfigFormBase {
    return new static(
      $container->get('config.factory'),
      $container->get('module_handler'),
      $container->get('extension.list.module')
      $container->get('extension.list.module'),
      $container->get('library.discovery')
    );
  }

@@ -89,6 +104,44 @@ class ColorboxSettingsForm extends ConfigFormBase {

    $config = $this->configFactory->get('colorbox.settings');

    $dompurify = $this->libraryDiscovery->getLibraryByName('colorbox', 'dompurify');
    $dompurify_file = !empty($dompurify['js'][0]['data']) ?
      DRUPAL_ROOT . '/' . $dompurify['js'][0]['data'] : NULL;
    $dompurify_exists = !empty($dompurify) && !empty($dompurify_file) &&
      file_exists($dompurify_file);

    $form['colorbox_dompurify'] = [
      '#type' => 'details',
      '#title' => $this->t('DOMPurify Library'),
      '#open' => TRUE,
    ];
    $dompurify_message = $dompurify_exists ?
      $this->t('The DOMPurify library is installed. ' .
        'This library will sanitize HTML in Colorbox captions.') :
      $this->t('The <a href="@dompurify_link">DOMPurify</a> ' .
        'library is not installed. ' .
        'This library is necessary if you want to use HTML in Colorbox captions. ' .
        'Without it, all captions will be treated as plain text.',
        [
          '@dompurify_link' => 'https://github.com/cure53/DOMPurify/archive/main.zip'
        ]);
    $form['colorbox_dompurify']['dompurify_message'] = array(
      '#type' => 'markup',
      '#prefix' => '<p>',
      '#suffix' => '</p>',
      '#markup' => $dompurify_message,
    );
    if (!$dompurify_exists) {
      $form['colorbox_dompurify']['dompurify_hide_warning'] = array(
        '#type' => 'checkbox',
        '#title' => t('Don\'t show warning on status report'),
        '#default_value' => $config->get('dompurify_hide_warning'),
        '#description' => t('By default, a warning appears on Drupal\'s ' .
          'status report if this library is missing. Check this box to ' .
          'suppress the warning.'),
      );
    }

    $form['colorbox_custom_settings'] = [
      '#type' => 'details',
      '#title' => $this->t('Styles and options'),
@@ -370,6 +423,7 @@ class ColorboxSettingsForm extends ConfigFormBase {
    $config = $this->configFactory->getEditable('colorbox.settings');

    $config
      ->set('dompurify_hide_warning', $form_state->getValue('dompurify_hide_warning'))
      ->set('custom.style', $form_state->getValue('colorbox_style'))
      ->set('custom.activate', $form_state->getValue('colorbox_custom_settings_activate'))
      ->set('custom.transition_type', $form_state->getValue('colorbox_transition_type'))
Loading