Skip to content

Commit

Permalink
Allow configuration of rel attributes in ExternalLink extension
Browse files Browse the repository at this point in the history
Implements #476
  • Loading branch information
colinodell committed May 16, 2020
1 parent fe22268 commit cac1649
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .phpstorm.meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
expectedArguments(\League\CommonMark\Inline\Element\Newline::__construct(), 0, argumentsSet('league_commonmark_newline_types'));
expectedReturnValues(\League\CommonMark\Inline\Element\Newline::getType(), argumentsSet('league_commonmark_newline_types'));

registerArgumentsSet('league_commonmark_options', 'renderer', 'enable_em', 'enable_strong', 'use_asterisk', 'use_underscore', 'unordered_list_markers', 'html_input', 'allow_unsafe_links', 'max_nesting_level', 'heading_permalink', 'heading_permalink/html_class', 'heading_permalink/id_prefix', 'heading_permalink/inner_contents', 'heading_permalink/insert', 'heading_permalink/title', 'table_of_contents', 'table_of_contents/style', 'table_of_contents/normalize', 'table_of_contents/position', 'table_of_contents/html_class', 'table_of_contents/min_heading_level', 'table_of_contents/max_heading_level', 'table_of_contents/placeholder');
registerArgumentsSet('league_commonmark_options', 'renderer', 'enable_em', 'enable_strong', 'use_asterisk', 'use_underscore', 'unordered_list_markers', 'html_input', 'allow_unsafe_links', 'max_nesting_level', 'external_link', 'external_link/nofollow', 'external_link/noopener', 'external_link/noreferrer', 'heading_permalink', 'heading_permalink/html_class', 'heading_permalink/id_prefix', 'heading_permalink/inner_contents', 'heading_permalink/insert', 'heading_permalink/title', 'table_of_contents', 'table_of_contents/style', 'table_of_contents/normalize', 'table_of_contents/position', 'table_of_contents/html_class', 'table_of_contents/min_heading_level', 'table_of_contents/max_heading_level', 'table_of_contents/placeholder');
expectedArguments(\League\CommonMark\EnvironmentInterface::getConfig(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\CommonMark\Util\ConfigurationInterface::get(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\CommonMark\Util\ConfigurationInterface::set(), 0, argumentsSet('league_commonmark_options'));
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi

- Added the ability to render `TableOfContents` nodes anywhere in a document (given by a placeholder)
- Added the ability to properly clone `Node` objects
- Added options to customize the value of `rel` attributes set via the `ExternalLink` extension (#476)
- Added new classes:
- `TableOfContentsGenerator`
- `TableOfContentsGeneratorInterface`
Expand All @@ -18,6 +19,7 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi
### Changed

- "Moved" the `TableOfContents` class into a new `Node` sub-namespace (with backward-compatibility)
- External links detected by the `ExternalLink` extension will now include `nofollow` in the renderer `rel` attribute (#476)

### Deprecated

Expand Down
17 changes: 15 additions & 2 deletions docs/1.5/extensions/external-links.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ redirect_from: /extensions/external-links/

This extension can detect links to external sites and adjust the markup accordingly:

- Adds a `rel="noopener noreferrer"` attribute
- Make the links open in new tabs/windows
- Adds a `rel` attribute to the resulting `<a>` tag with values like `"nofollow noopener noreferrer"`
- Optionally adds any custom HTML classes

## Usage
Expand All @@ -30,9 +31,12 @@ $environment->addExtension(new ExternalLinkExtension());
// Set your configuration
$config = [
'external_link' => [
'internal_hosts' => 'www.example.com', // Don't forget to set this!
'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
'open_in_new_window' => true,
'html_class' => 'external-link',
'nofollow' => 'external',
'noopener' => 'all',
'noreferrer' => 'external',
],
];

Expand Down Expand Up @@ -73,6 +77,15 @@ This option (which defaults to `false`) determines whether any external links sh

This option allows you to provide a `string` containing one or more HTML classes that should be added to the external link `<a>` tags: No classes are added by default.

### `nofollow`, `noopener`, and `noreferrer`

These options allow you to configure whether a `rel` attribute should be applied to links. Each of these options can be set to one of the following `string` values:

- `'external'` - **Apply to external links only (default)**
- `'internal'` - Apply to internal links only
- `'all'` - Apply to all links (both internal and external)
- `''` (empty string) - Don't apply to any links

## Advanced Rendering

When an external link is detected, the `ExternalLinkProcessor` will set the `external` data option on the `Link` node to either `true` or `false`. You can therefore create a [custom link renderer](/1.5/customization/inline-rendering/) which checks this value and behaves accordingly:
Expand Down
29 changes: 28 additions & 1 deletion src/Extension/ExternalLink/ExternalLinkProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

final class ExternalLinkProcessor
{
public const APPLY_NONE = '';
public const APPLY_ALL = 'all';
public const APPLY_EXTERNAL = 'external';
public const APPLY_INTERNAL = 'internal';

/** @var EnvironmentInterface */
private $environment;

Expand Down Expand Up @@ -50,6 +55,7 @@ public function __invoke(DocumentParsedEvent $e)

if (self::hostMatches($host, $internalHosts)) {
$link->data['external'] = false;
$this->applyRelAttribute($link, false);
continue;
}

Expand All @@ -63,7 +69,7 @@ private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $c
{
$link->data['external'] = true;
$link->data['attributes'] = $link->getData('attributes', []);
$link->data['attributes']['rel'] = 'noopener noreferrer';
$this->applyRelAttribute($link, true);

if ($openInNewWindow) {
$link->data['attributes']['target'] = '_blank';
Expand All @@ -74,6 +80,27 @@ private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $c
}
}

private function applyRelAttribute(Link $link, bool $isExternal): void
{
$rel = [];

foreach (['nofollow', 'noopener', 'noreferrer'] as $type) {
$option = $this->environment->getConfig('external_link/' . $type, self::APPLY_EXTERNAL);
switch (true) {
case $option === self::APPLY_ALL:
case $isExternal && $option === self::APPLY_EXTERNAL:
case !$isExternal && $option === self::APPLY_INTERNAL:
$rel[] = $type;
}
}

if ($rel === []) {
return;
}

$link->data['attributes']['rel'] = \implode(' ', $rel);
}

/**
* @param string $host
* @param mixed $compareTo
Expand Down
Loading

0 comments on commit cac1649

Please sign in to comment.