You are here

class ConvertUrlToEmbedFilter in URL Embed 8

Provides a filter to display embedded entities based on data attributes.

Plugin annotation


@Filter(
  id = "url_embed_convert_links",
  title = @Translation("Convert URLs to URL embeds"),
  description = @Translation("Convert plain URLs to embed elements that can be rendered with the <em>Display embedded URLs</em> filter."),
  type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
  settings = {
    "url_prefix" = "",
  },
)

Hierarchy

Expanded class hierarchy of ConvertUrlToEmbedFilter

File

src/Plugin/Filter/ConvertUrlToEmbedFilter.php, line 23

Namespace

Drupal\url_embed\Plugin\Filter
View source
class ConvertUrlToEmbedFilter extends FilterBase {

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $form['url_prefix'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('URL prefix'),
      '#default_value' => $this->settings['url_prefix'],
      '#description' => $this
        ->t('Optional prefix that will be used to indicate which URLs that apply. All URLs that are supported will be converted if empty. Example: EMBED-https://twitter.com/drupal/status/735873777683320832'),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode) {
    return new FilterProcessResult(static::convertUrls($text, $this->settings['url_prefix']));
  }

  /**
   * Replaces appearances of supported URLs with <drupal-url> embed elements.
   *
   * Logic of this function is copied from _filter_url() and slightly adopted
   * for our use case. _filter_url() is unfortunately not general enough to
   * re-use it.
   *
   * @param string $text
   *   Text to be processed.
   * @param string $url_prefix
   *   (Optional) Prefix that should be used to manually choose which URLs
   *   should be converted.
   *
   * @return string
   *   Processed text.
   */
  public static function convertUrls($text, $url_prefix = '') {

    // Tags to skip and not recurse into.
    $ignore_tags = 'a|script|style|code|pre';

    // Create an array which contains the regexps for each type of link.
    // The key to the regexp is the name of a function that is used as
    // callback function to process matches of the regexp. The callback function
    // is to return the replacement for the match. The array is used and
    // matching/replacement done below inside some loops.
    $tasks = [];

    // Prepare protocols pattern for absolute URLs.
    // \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() will replace
    // any bad protocols with HTTP, so we need to support the identical list.
    // While '//' is technically optional for MAILTO only, we cannot cleanly
    // differ between protocols here without hard-coding MAILTO, so '//' is
    // optional for all protocols.
    // @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
    $protocols = \Drupal::getContainer()
      ->getParameter('filter_protocols');
    $protocols = implode(':(?://)?|', $protocols) . ':(?://)?';
    $valid_url_path_characters = "[\\p{L}\\p{M}\\p{N}!\\*\\';:=\\+,\\.\$\\/%#\\[\\]\\-_~@&]";

    // Allow URL paths to contain balanced parens
    // 1. Used in Wikipedia URLs like /Primer_(film)
    // 2. Used in IIS sessions like /S(dfd346)/
    $valid_url_balanced_parens = '\\(' . $valid_url_path_characters . '+\\)';

    // Valid end-of-path characters (so /foo. does not gobble the period).
    // 1. Allow =&# for empty URL parameters and other URL-join artifacts
    $valid_url_ending_characters = '[\\p{L}\\p{M}\\p{N}:_+~#=/]|(?:' . $valid_url_balanced_parens . ')';
    $valid_url_query_chars = '[a-zA-Z0-9!?\\*\'@\\(\\);:&=\\+\\$\\/%#\\[\\]\\-_\\.,~|]';
    $valid_url_query_ending_chars = '[a-zA-Z0-9_&=#\\/]';

    //full path

    //and allow @ in a url, but only in the middle. Catch things like http://example.com/@user/
    $valid_url_path = '(?:(?:' . $valid_url_path_characters . '*(?:' . $valid_url_balanced_parens . $valid_url_path_characters . '*)*' . $valid_url_ending_characters . ')|(?:@' . $valid_url_path_characters . '+\\/))';

    // Prepare domain name pattern.
    // The ICANN seems to be on track towards accepting more diverse top level
    // domains, so this pattern has been "future-proofed" to allow for TLDs
    // of length 2-64.
    $domain = '(?:[\\p{L}\\p{M}\\p{N}._+-]+\\.)?[\\p{L}\\p{M}]{2,64}\\b';
    $ip = '(?:[0-9]{1,3}\\.){3}[0-9]{1,3}';
    $auth = '[\\p{L}\\p{M}\\p{N}:%_+*~#?&=.,/;-]+@';
    $trail = '(' . $valid_url_path . '*)?(\\?' . $valid_url_query_chars . '*' . $valid_url_query_ending_chars . ')?';

    // Match absolute URLs.
    $url_pattern = "(?:{$auth})?(?:{$domain}|{$ip})/?(?:{$trail})?";
    $pattern = "`{$url_prefix}((?:{$protocols})(?:{$url_pattern}))`u";
    $tasks['replaceFullLinks'] = $pattern;

    // HTML comments need to be handled separately, as they may contain HTML
    // markup, especially a '>'. Therefore, remove all comment contents and add
    // them back later.
    _filter_url_escape_comments('', TRUE);
    $text = preg_replace_callback('`<!--(.*?)-->`s', '_filter_url_escape_comments', $text);

    // Split at all tags; ensures that no tags or attributes are processed.
    $chunks = preg_split('/(<.+?>)/is', $text, -1, PREG_SPLIT_DELIM_CAPTURE);

    // PHP ensures that the array consists of alternating delimiters and
    // literals, and begins and ends with a literal (inserting NULL as
    // required). Therefore, the first chunk is always text:
    $chunk_type = 'text';

    // If a tag of $ignore_tags is found, it is stored in $open_tag and only
    // removed when the closing tag is found. Until the closing tag is found,
    // no replacements are made.
    $open_tag = '';
    for ($i = 0; $i < count($chunks); $i++) {
      if ($chunk_type == 'text') {

        // Only process this text if there are no unclosed $ignore_tags.
        if ($open_tag == '') {

          // If there is a match, inject a link into this chunk via the callback
          // function contained in $task.
          $chunks[$i] = preg_replace_callback($pattern, function ($match) {
            if (\Drupal::service('url_embed')
              ->getEmbed(Html::decodeEntities($match[1]))) {
              return '<drupal-url data-embed-url="' . $match[1] . '"></drupal-url>';
            }
            else {
              return $match[1];
            }
          }, $chunks[$i]);
        }

        // Text chunk is done, so next chunk must be a tag.
        $chunk_type = 'tag';
      }
      else {

        // Only process this tag if there are no unclosed $ignore_tags.
        if ($open_tag == '') {

          // Check whether this tag is contained in $ignore_tags.
          if (preg_match("`<({$ignore_tags})(?:\\s|>)`i", $chunks[$i], $matches)) {
            $open_tag = $matches[1];
          }
        }
        else {
          if (preg_match("`<\\/{$open_tag}>`i", $chunks[$i], $matches)) {
            $open_tag = '';
          }
        }

        // Tag chunk is done, so next chunk must be text.
        $chunk_type = 'text';
      }
    }
    $text = implode($chunks);

    // Revert to the original comment contents
    _filter_url_escape_comments('', FALSE);
    return preg_replace_callback('`<!--(.*?)-->`', '_filter_url_escape_comments', $text);
  }

  /**
   * {@inheritdoc}
   */
  public function tips($long = FALSE) {
    if ($long) {
      return $this
        ->t('<p>You can convert plain URLs to &lt;drupal-url&gt; HTML elements. Those elements are later converted to embeds using "Display embedded URLs" text filter.</p>');
    }
    else {
      return $this
        ->t('You can convert plain URLs to embed elements.');
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ConvertUrlToEmbedFilter::convertUrls public static function Replaces appearances of supported URLs with <drupal-url> embed elements.
ConvertUrlToEmbedFilter::process public function Performs the filter processing. Overrides FilterInterface::process
ConvertUrlToEmbedFilter::settingsForm public function Generates a filter's settings form. Overrides FilterBase::settingsForm
ConvertUrlToEmbedFilter::tips public function Generates a filter's tip. Overrides FilterBase::tips
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FilterBase::$provider public property The name of the provider that owns this filter.
FilterBase::$settings public property An associative array containing the configured settings of this filter.
FilterBase::$status public property A Boolean indicating whether this filter is enabled.
FilterBase::$weight public property The weight of this filter compared to others in a filter collection.
FilterBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 1
FilterBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration
FilterBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
FilterBase::getDescription public function Returns the administrative description for this filter plugin. Overrides FilterInterface::getDescription
FilterBase::getHTMLRestrictions public function Returns HTML allowed by this filter's configuration. Overrides FilterInterface::getHTMLRestrictions 4
FilterBase::getLabel public function Returns the administrative label for this filter plugin. Overrides FilterInterface::getLabel
FilterBase::getType public function Returns the processing type of this filter plugin. Overrides FilterInterface::getType
FilterBase::prepare public function Prepares the text for processing. Overrides FilterInterface::prepare
FilterBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration 1
FilterBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. Overrides PluginBase::__construct 4
FilterInterface::TYPE_HTML_RESTRICTOR constant HTML tag and attribute restricting filters to prevent XSS attacks.
FilterInterface::TYPE_MARKUP_LANGUAGE constant Non-HTML markup language filters that generate HTML.
FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE constant Irreversible transformation filters.
FilterInterface::TYPE_TRANSFORM_REVERSIBLE constant Reversible transformation filters.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.