You are here

protected function FootnotesFilter::replaceCallback in Footnotes 8.2

Helper function called from preg_replace_callback() above.

Uses static vars to temporarily store footnotes found. This is not threadsafe, but PHP isn't.

Parameters

mixed $matches: Elements from array:

  • 0: complete matched string.
  • 1: tag name.
  • 2: tag attributes.
  • 3: tag content.

string $op: Operation.

Return value

string Return the string processed by geshi library.

1 call to FootnotesFilter::replaceCallback()
FootnotesFilter::process in src/Plugin/Filter/FootnotesFilter.php
Performs the filter processing.

File

src/Plugin/Filter/FootnotesFilter.php, line 156

Class

FootnotesFilter
Provides a base filter for Footnotes filter.

Namespace

Drupal\footnotes\Plugin\Filter

Code

protected function replaceCallback($matches, $op = '') {
  static $opt_collapse = 0;
  static $opt_html = 0;
  static $n = 0;
  static $store_matches = [];
  static $used_values = [];
  $str = '';
  if ($op == 'prepare') {

    // In the 'prepare' case, the first argument contains the options to use.
    // The name 'matches' is incorrect, we just use the variable anyway.
    $opt_collapse = $matches['footnotes_collapse'];
    $opt_html = $matches['footnotes_html'];
    return 0;
  }
  if ($op == 'output footer') {
    if (count($store_matches) > 0) {

      // Only if there are stored fn matches, pass the array of fns to be
      // themed as a list Drupal 7 requires we use "render element" which
      // just introduces a wrapper around the old array.
      // @FIXME
      // theme() has been renamed to _theme() and should NEVER be called
      // directly. Calling _theme() directly can alter the expected output and
      // potentially introduce  security issues
      // (see https://www.drupal.org/node/2195739). You should use renderable
      // arrays instead. @see https://www.drupal.org/node/2195739
      $markup = [
        '#theme' => 'footnote_list',
        '#footnotes' => $store_matches,
      ];
      $str = $this->renderer
        ->render($markup);
    }

    // Reset the static variables so they can be used again next time.
    $n = 0;
    $store_matches = [];
    $used_values = [];
    return $str;
  }

  // Default op: act as called by preg_replace_callback()
  // Random string used to ensure footnote id's are unique, even
  // when contents of multiple nodes reside on same page.
  // (fixes http://drupal.org/node/194558).
  $randstr = $this
    ->randstr();
  $value = '';

  // Did the pattern match anything in the <fn> tag?
  if ($matches[1]) {

    // See if value attribute can parsed, either well-formed in quotes eg
    // <fn value="3">.
    if (preg_match('|value=["\'](.*?)["\']|', $matches[1], $value_match)) {
      $value = $value_match[1];

      // Or without quotes eg <fn value=8>.
    }
    elseif (preg_match('|value=(\\S*)|', $matches[1], $value_match)) {
      $value = $value_match[1];
    }
  }
  if ($value) {

    // A value label was found. If it is numeric, record it in $n so further
    // notes can increment from there.
    // After adding support for multiple references to same footnote in the
    // body (http://drupal.org/node/636808) also must check that $n is
    // monotonously increasing.
    if (is_numeric($value) && $n < $value) {
      $n = $value;
    }
  }
  elseif ($opt_collapse and $value_existing = $this
    ->findFootnote($matches[2], $store_matches)) {

    // An identical footnote already exists. Set value to the previously
    // existing value.
    $value = $value_existing;
  }
  else {

    // No value label, either a plain <fn> or unparsable attributes. Increment
    // the footnote counter, set label equal to it.
    $n++;
    $value = $n;
  }

  // Remove illegal characters from $value so it can be used as an HTML id
  // attribute.
  $value_id = preg_replace('|[^\\w\\-]|', '', $value);

  // Create a sanitized version of $text that is suitable for using as HTML
  // attribute value. (In particular, as the title attribute to the footnote
  // link).
  $allowed_tags = [];
  $text_clean = Xss::filter($matches['2'], $allowed_tags);

  // HTML attribute cannot contain quotes.
  $text_clean = str_replace('"', "&quot;", $text_clean);

  // Remove newlines. Browsers don't support them anyway and they'll confuse
  // line break converter in filter.module.
  $text_clean = str_replace("\n", " ", $text_clean);
  $text_clean = str_replace("\r", "", $text_clean);

  // Create a footnote item as an array.
  $fn = [
    'value' => $value,
    'text' => $opt_html ? html_entity_decode($matches[2]) : $matches[2],
    'text_clean' => $text_clean,
    'fn_id' => 'footnote' . $value_id . '_' . $randstr,
    'ref_id' => 'footnoteref' . $value_id . '_' . $randstr,
  ];

  // We now allow to repeat the footnote value label, in which case the link
  // to the previously existing footnote is returned. Content of the current
  // footnote is ignored. See http://drupal.org/node/636808 .
  if (!in_array($value, $used_values)) {

    // This is the normal case, add the footnote to $store_matches.
    // Store the footnote item.
    array_push($store_matches, $fn);
    array_push($used_values, $value);
  }
  else {

    // A footnote with the same label already exists.
    // Use the text and id from the first footnote with this value.
    // Any text in this footnote is discarded.
    $i = array_search($value, $used_values);
    $fn['text'] = $store_matches[$i]['text'];
    $fn['text_clean'] = $store_matches[$i]['text_clean'];
    $fn['fn_id'] = $store_matches[$i]['fn_id'];

    // Push the new ref_id into the first occurrence of this footnote label
    // The stored footnote thus holds a list of ref_id's rather than just one
    // id.
    $ref_array = is_array($store_matches[$i]['ref_id']) ? $store_matches[$i]['ref_id'] : [
      $store_matches[$i]['ref_id'],
    ];
    array_push($ref_array, $fn['ref_id']);
    $store_matches[$i]['ref_id'] = $ref_array;
  }

  // Return the item themed into a footnote link.
  // Drupal 7 requires we use "render element" which just introduces a wrapper
  // around the old array.
  $fn = [
    '#theme' => 'footnote_link',
    '#fn' => $fn,
  ];
  $result = $this->renderer
    ->render($fn);
  return $result;
}