Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto heading id filter to the wysiwyg format on install. #143

Merged
merged 7 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
}
],
"require": {
"drupal/auto_heading_ids": "^2.0@beta",
"localgovdrupal/localgov_core": ">=2.1.10",
"localgovdrupal/localgov_paragraphs": ">=2.4.0"
},
Expand Down
1 change: 0 additions & 1 deletion localgov_publications.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package: LocalGov Drupal
core_version_requirement: ^9 || ^10

dependencies:
- auto_heading_ids:auto_heading_ids
- drupal:book
- drupal:menu_ui
- drupal:views
Expand Down
23 changes: 23 additions & 0 deletions localgov_publications.install
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,40 @@
* Install, update and uninstall functions for the LocalGov Publications module.
*/

use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterFormatInterface;

/**
* Implements hook_install().
*/
function localgov_publications_install() {

// Add our localgov_publication_page content type to
// book.settings.allowed_types. This lets editors create publication pages.
$config = \Drupal::configFactory()->getEditable('book.settings');
$allowed_types = $config->get('allowed_types');
$allowed_types[] = 'localgov_publication_page';
$config->set('allowed_types', $allowed_types);
$config->save();

// Add our localgov-publication-cover-page-alias to
// pathauto.settings.safe_tokens. This prevents double escaping in the
// resulting URL.
$path_auto_config = \Drupal::configFactory()->getEditable('pathauto.settings');
$safe_tokens = $path_auto_config->get('safe_tokens');
$safe_tokens[] = 'localgov-publication-cover-page-alias';
$path_auto_config->set('safe_tokens', $safe_tokens)->save();

// Add heading_id_filter from the auto_heading_ids module to the wysiwyg
// filter format. This is required for the ToC block to work consistently.
$wysiwygFormat = FilterFormat::load('wysiwyg');
if ($wysiwygFormat instanceof FilterFormatInterface) {
$wysiwygFormat->setFilterConfig('localgov_publications_heading_ids', [
'status' => TRUE,
'settings' => [
'keep_existing_ids' => TRUE,
],
]);
$wysiwygFormat->save();
}
}
158 changes: 158 additions & 0 deletions src/Plugin/Filter/HeadingIdFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

namespace Drupal\localgov_publications\Plugin\Filter;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Transliteration\PhpTransliteration;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Apply identifiers (anchors) to headings in content.
*
* This filter is copied from the auto_heading_ids module.
*
* @Filter(
* id = "localgov_publications_heading_ids",
* title = @Translation("Automatically apply identifiers (anchors) to headings in content"),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
* weight = 10
* )
*/
class HeadingIdFilter extends FilterBase implements ContainerFactoryPluginInterface {

/**
* Word separator.
*/
const SEPARATOR = '-';

/**
* Transliteration service.
*
* @var \Drupal\Core\Transliteration\PhpTransliteration
*/
protected $transliteration;

/**
* Constructs a \Drupal\editor\Plugin\Filter\EditorFileReference object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Transliteration\PhpTransliteration $transliteration
* The transliteration service instance.
*
* @internal param \Drupal\Core\Entity\EntityManagerInterface $entity_manager An entity manager object.* An entity manager object.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PhpTransliteration $transliteration) {
$this->transliteration = $transliteration;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('transliteration')
);
}

/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
return new FilterProcessResult($this->filterAttributes($text));
}

/**
* Applies ids to headings.
*
* @param string $text
* The HTML text string to be filtered.
*
* @return string
* Filtered HTML.
*/
public function filterAttributes($text) {
$output = $text;
$html_dom = Html::load($text);
$xpath = new \DOMXPath($html_dom);
$heading_tags = '//h2|//h3|//h4|//h5|//h6';
$keep_existing_ids = $this->settings['keep_existing_ids'] ?? FALSE;

// Apply attribute restrictions to headings.
$headings_found = FALSE;
foreach ($xpath->query($heading_tags) as $heading_tag) {

if ($keep_existing_ids && $heading_tag->hasAttribute('id')) {
continue;
}

$id = $this->transformHeadingToId($heading_tag->nodeValue);
$heading_tag->setAttribute('id', Html::getUniqueId($id));
$headings_found = TRUE;
}

if ($headings_found) {
// Only bother serializing if something changed.
$output = Html::serialize($html_dom);
$output = trim($output);
}

return $output;
}

/**
* Creates a machine name based on the heading.
*
* Inspired by Drupal\migrate\Plugin\migrate\process\MachineName::transform.
* Improved by Drupal\pathauto\AliasCleaner.
*
* @param string $heading
* String to convert to id.
*
* @return mixed
* A dash separated id.
*/
public function transformHeadingToId($heading) {
// Reduce to ascii.
$new_value = $this->transliteration->transliterate($heading);
// Reduce to letters and numbers.
$new_value = preg_replace('/[^a-zA-Z0-9\/]+/', static::SEPARATOR, $new_value);
// Remove consecutive separators.
$new_value = preg_replace('/' . static::SEPARATOR . '+/', static::SEPARATOR, $new_value);
// Remove leading and trailing separators.
$new_value = trim($new_value, static::SEPARATOR);
// Convert to lowercase.
$new_value = mb_strtolower($new_value);
// Truncate to 128 chars.
return Unicode::truncate($new_value, 128, TRUE);
}

/**
* {@inheritDoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {

$form['keep_existing_ids'] = [
'#title' => 'Keep existing IDs',
'#type' => 'checkbox',
'#default_value' => $this->settings['keep_existing_ids'] ?? FALSE,
'#description' => $this->t('When this is selected, existing IDs in content will not be changed.'),
];

return $form;
}

}
Loading