You are here

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

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\Filter
View 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 &nbsp; 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

Namesort descending Modifiers Type Description Overrides
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.
TocFilter::parseOptions public static function Parse options from an attributes string.
TocFilter::process public function Performs the filter processing. Overrides FilterInterface::process
TocFilter::setOption protected static function Set nested option name and value.
TocFilter::settingsForm public function Generates a filter's settings form. Overrides FilterBase::settingsForm
TocFilter::tips public function Generates a filter's tip. Overrides FilterBase::tips