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\FilterCode
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('"', """, $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;
}