class TocFilter in TOC filter 8.2
Provides a filter to display a table of contents.
IMPORTANT: TOC options is an associative array which is not easily supported via filter settings because every single options must be defined with default values. This limitation would require all TOC options to duplicated in the below settings annotation.
So the eases solution was to serialize the options before they are stored as configuration.
@see: \Drupal\toc_filter\Plugin\Filter\TocFilter::settingsForm @see: toc_filter_form_filter_format_edit_form_submit
Plugin annotation
@Filter(
id = "toc_filter",
module = "toc_filter",
title = @Translation("Display a table of contents"),
type = Drupal\filter\Plugin\FilterInterface::TYPE_MARKUP_LANGUAGE,
settings = {
"type" = "default",
"auto" = FALSE,
"block" = FALSE,
},
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\filter\Plugin\FilterBase implements FilterInterface
- class \Drupal\toc_filter\Plugin\Filter\TocFilter
- class \Drupal\filter\Plugin\FilterBase implements FilterInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of TocFilter
1 file declares its use of TocFilter
- TocFilterOptionsTest.php in tests/
src/ Unit/ TocFilterOptionsTest.php - Contains \Drupal\Tests\toc_filter\Unit\TocFilterOptionsTest.
File
- src/
Plugin/ Filter/ TocFilter.php, line 45 - Contains \Drupal\toc_filter\Plugin\Filter\TocFilter.
Namespace
Drupal\toc_filter\Plugin\FilterView source
class TocFilter extends FilterBase {
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$toc_types = TocType::loadMultiple();
foreach ($toc_types as $toc_type) {
$types[$toc_type
->id()] = $toc_type
->label();
}
$form['type'] = [
'#title' => $this
->t('Type'),
'#type' => 'select',
'#options' => $types,
'#default_value' => $this->settings['type'] ?: 'default',
];
$form['auto'] = [
'#title' => $this
->t('Automatically include table of contents'),
'#description' => $this
->t('If set, a table of contents will be added when a <code>[toc]</code> token is not present in the content.'),
'#type' => 'select',
'#options' => [
'' => '',
'top' => $this
->t('At the top of the page'),
'bottom' => $this
->t('At the bottom of the page'),
],
'#default_value' => $this->settings['auto'],
];
$form['block'] = [
'#title' => $this
->t('Display table of contents in a block.'),
'#description' => $this
->t('Please make sure to place the <a href=":href">TOC filter</a> block on your site.', [
':href' => URL::fromRoute('block.admin_display')
->toString(),
]),
'#type' => 'checkbox',
'#default_value' => $this->settings['block'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
$result = new FilterProcessResult($text);
// If no [toc] is found, see if [toc] can be automatically added, else
// return.
if (stripos($text, '[toc') === FALSE) {
switch ($this->settings['auto']) {
case 'top':
$text = '[toc]' . $text;
break;
case 'bottom':
$text .= '[toc]';
break;
case '':
default:
return $result;
}
}
// Remove block tags around token.
$text = preg_replace('#<(p|div|h\\d|blockquote)[^>]*>\\s*(\\[toc[^]]*\\])\\s*</\\1>#', '\\2', $text);
// Get custom options.
if ($this->settings['type'] && ($toc_type = TocType::load($this->settings['type']))) {
$toc_type_options = $toc_type
->getOptions() ?: [];
$result
->addCacheableDependency($toc_type);
}
else {
$toc_type_options = [];
}
// Replace first [toc] token and update the content.
if (!preg_match('#\\[toc([^]]*)?\\]#is', $text, $match)) {
return $result;
}
// Remove the [toc] token for the processed text.
// This makes it easier to just return the original text.
$result
->setProcessedText(str_replace($match[0], '', $text));
// Parse inline options (aka attributes).
$inline_options = self::parseOptions($match[1]);
// Add custom setting to inline options.
$inline_options += [
'block' => $this->settings['block'],
];
// Merge with default, global, filter, and inline options.
$options = NestedArray::mergeDeepArray([
$toc_type_options,
$inline_options,
]);
// Allow TOC filter options to be altered and optionally set to FALSE,
// which will block a table of contents from being added.
\Drupal::moduleHandler()
->alter('toc_filter', $text, $options);
// If $option is FALSE, then just return the unprocessed result w/o
// the [toc] token.
if ($options === FALSE) {
return $result;
}
/** @var \Drupal\toc_api\TocManagerInterface $toc_manager */
$toc_manager = \Drupal::service('toc_api.manager');
/** @var \Drupal\toc_api\TocBuilderInterface $toc_builder */
$toc_builder = \Drupal::service('toc_api.builder');
/** @var \Drupal\toc_api\TocInterface $toc */
$toc = $toc_manager
->create('toc_filter', $text, $options);
// If table of content is not visible, return the unprocessed result w/o
// the [toc] token.
if (!$toc
->isVisible()) {
return $result;
}
// Replace the text with the render the content.
$text = '<div class="toc-filter">' . $toc_builder
->renderContent($toc) . '</div>';
// If block remove [toc] token, else replace it with the rendered TOC.
if ($toc
->isBlock()) {
$text = str_replace($match[0], '', $text);
}
else {
$text = str_replace($match[0], $toc_builder
->renderToc($toc), $text);
}
return $result
->setProcessedText($text);
}
/**
* {@inheritdoc}
*/
public function tips($long = FALSE) {
return $this
->t("Converts header tags into a hierarchical table of contents. (i.e [toc type=(tree|menu|responsive) title='Table of Contents']");
}
/**
* Parse options from an attributes string.
*
* @param string $text
* A string of options.
*
* @return array
* An associative array of parsed name/value pairs.
*/
public static function parseOptions($text) {
// Decode special characters.
$text = html_entity_decode($text);
// Convert decode to expected ASCII code 32 character.
// See: http://stackoverflow.com/questions/6275380
$text = str_replace("", ' ', $text);
// Create a DomElement so that we can parse its attributes as options.
$html = Html::load('<div ' . $text . ' />');
$dom_node = $html
->getElementsByTagName('div')
->item(0);
$options = [];
foreach ($dom_node->attributes as $name => $node) {
// Empty attribute values (ie name="") and attributes with no defined
// value (ie just name) both return empty strings.
// See: http://stackoverflow.com/questions/6232412
// So we are going to work-around this limitation and look at the
// actually attribute to see if is assigned a value, if not then set it to
// 'true'.
$value = $node->nodeValue ?: (preg_match('/' . preg_quote($name) . '\\s*=/i', $text) ? '' : 'true');
switch (strtolower($value)) {
case 'true':
case 'false':
$value = strtolower($value) === 'true';
break;
default:
if ($value !== '' && is_numeric($value)) {
$value = floatval($value);
}
break;
}
// Prefix h1-6 tags with 'headers.' to map it to the correction
// $option['headers'] array.
if (preg_match('/h[1-6]/', $name)) {
$name = 'headers.' . $name;
}
self::setOption($options, $name, $value);
}
return $options;
}
/**
* Set nested option name and value.
*
* From: http://stackoverflow.com/questions/9635968
*
* @param array $options
* An associative array of options.
* @param string $name
* Option name with path delimited by period.
* @param mixed $value
* Option value.
*/
protected static function setOption(array &$options, $name, $value) {
$keys = explode('.', $name);
while ($key = array_shift($keys)) {
$options =& $options[$key];
}
$options = $value;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
FilterBase:: |
public | property | The name of the provider that owns this filter. | |
FilterBase:: |
public | property | An associative array containing the configured settings of this filter. | |
FilterBase:: |
public | property | A Boolean indicating whether this filter is enabled. | |
FilterBase:: |
public | property | The weight of this filter compared to others in a filter collection. | |
FilterBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
1 |
FilterBase:: |
public | function |
Gets default configuration for this plugin. Overrides ConfigurableInterface:: |
|
FilterBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
FilterBase:: |
public | function |
Returns the administrative description for this filter plugin. Overrides FilterInterface:: |
|
FilterBase:: |
public | function |
Returns HTML allowed by this filter's configuration. Overrides FilterInterface:: |
4 |
FilterBase:: |
public | function |
Returns the administrative label for this filter plugin. Overrides FilterInterface:: |
|
FilterBase:: |
public | function |
Returns the processing type of this filter plugin. Overrides FilterInterface:: |
|
FilterBase:: |
public | function |
Prepares the text for processing. Overrides FilterInterface:: |
|
FilterBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
1 |
FilterBase:: |
public | function |
Constructs a \Drupal\Component\Plugin\PluginBase object. Overrides PluginBase:: |
4 |
FilterInterface:: |
constant | HTML tag and attribute restricting filters to prevent XSS attacks. | ||
FilterInterface:: |
constant | Non-HTML markup language filters that generate HTML. | ||
FilterInterface:: |
constant | Irreversible transformation filters. | ||
FilterInterface:: |
constant | Reversible transformation filters. | ||
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
TocFilter:: |
public static | function | Parse options from an attributes string. | |
TocFilter:: |
public | function |
Performs the filter processing. Overrides FilterInterface:: |
|
TocFilter:: |
protected static | function | Set nested option name and value. | |
TocFilter:: |
public | function |
Generates a filter's settings form. Overrides FilterBase:: |
|
TocFilter:: |
public | function |
Generates a filter's tip. Overrides FilterBase:: |