Skip to content

Commit 48043d0

Browse files
authored
Merge pull request #150 from GSA/feature/DIGITAL-89-linkit
DIGITAL-89 Add linkit module
2 parents 7dedc1b + 83d25c9 commit 48043d0

File tree

8 files changed

+198
-21
lines changed

8 files changed

+198
-21
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"drupal/google_tag": "^2.0",
4141
"drupal/inline_entity_form": "^3.0@RC",
4242
"drupal/link_class": "^2.1",
43-
"drupal/linkit": "^6.1",
43+
"drupal/linkit": "^7.0",
4444
"drupal/log_stdout": "^1.5",
4545
"drupal/maillog": "dev-1.x",
4646
"drupal/mailsystem": "^4.4",

composer.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composer.log

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ b2b17cbb32ac57e73928af9f52acb537|Oscar Merida|feature/DIGITAL-96-ckeditor-emoji|
7070
c73ec25d5b68a3e0e1bdfc83c24312ba|Matt Poole|feature/DIGITAL-396-update-deps-8-0|Wed Feb 5 15:32:16 EST 2025|./composer.sh update
7171
d0ca998c567e8cc218ee57bb5c9b6679|cathybaptista|feature/DIGITAL-119-configure-images|Thu Feb 6 11:06:05 EST 2025|./composer.sh require drupal/media_library_edit
7272
6b5963bf3c6740285c129be6d6273aed|cathybaptista|feature/DIGITAL-119-configure-images|Thu Feb 6 11:06:14 EST 2025|./composer.sh require drupal/edit_media_modal
73+
caeb4cc7f30e1f80c7425f9c3fcf8a98|Oscar Merida|feature/DIGITAL-89-linkit|Wed Feb 5 16:40:40 EST 2025|./composer.sh require drupal/linkit:^7.0
74+
c063010712247ba9dfd709c97fccb648|Oscar Merida|feature/DIGITAL-89-linkit|Thu Feb 6 16:27:25 EST 2025|./composer.sh require drupal/linkit:^7.0

config/sync/linkit.linkit_profile.default.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,19 @@ matchers:
1515
uuid: 556010a3-e317-48b3-b4ed-854c10f4b950
1616
settings:
1717
metadata: '[node:content-type:name] #[node:nid] | [node:created:medium] by [node:author]'
18-
bundles: { }
18+
bundles:
19+
authors: authors
20+
basic_page: basic_page
21+
community: community
22+
event: event
23+
guides: guides
24+
guide_landing: guide_landing
25+
landing_page: landing_page
26+
news: news
27+
resources: resources
28+
short_post: short_post
29+
source: source
30+
topics: topics
1931
group_by_bundle: false
2032
substitution_type: canonical
2133
limit: 100

web/modules/custom/convert_text/src/ConvertText.php

Lines changed: 148 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Drupal\convert_text;
66

7+
use Drupal\Core\Url;
8+
use Drupal\node\Entity\Node;
9+
use Drupal\taxonomy\Entity\Term;
710
use League\CommonMark\CommonMarkConverter;
811
use LitEmoji\LitEmoji;
912

@@ -15,9 +18,9 @@ class ConvertText {
1518
/**
1619
* Converts text for the given $field_type.
1720
*
18-
* @var string $source_text
21+
* @param string $source_text
1922
* The original source value.
20-
* @var string $field_type
23+
* @param string $field_type
2124
* Either plain or html.
2225
*
2326
* @return string
@@ -37,18 +40,158 @@ protected static function convert(string $source_text, string $field_type): stri
3740

3841
$converter = new CommonMarkConverter();
3942
$html = $converter->convert($source_text)->getContent();
40-
return LitEmoji::encodeUnicode($html);
43+
$html = LitEmoji::encodeUnicode($html);
44+
return self::addLinkItMarkup($html);
4145

4246
default:
4347
throw new \Exception("Invalid \$field_type of $field_type given");
48+
}
49+
}
50+
51+
/**
52+
* Update local link tags with linkit data attributes.
53+
*/
54+
protected static function addLinkItMarkup(string $source_text): string {
55+
56+
// Consider these domains local.
57+
$base_domains = [\Drupal::request()->getHost(), 'digital.gov', 'www.digital.gov'];
58+
59+
$dom = new \DOMDocument();
60+
$dom->loadHTML($source_text);
61+
62+
foreach ($dom->getElementsByTagName('a') as $link) {
63+
$href = $link->getAttribute('href');
64+
if (!$href || str_starts_with($href, 'mailto:') || str_starts_with($href, '#')) {
65+
continue;
66+
}
67+
68+
$anchor = '';
69+
if (preg_match('/\#(.*)$/', $href, $matches)) {
70+
$anchor = $matches[0];
71+
}
72+
73+
// Add a trailing slash for links with just the domain w/o trailing slash.
74+
if (preg_match('/^https?:\/\/(' . implode('|', $base_domains) . ')$/', $href, $matches)) {
75+
$href = $matches[0] . '/';
76+
}
77+
// Now, strip the local domains from links that include them.
78+
$href = preg_replace(
79+
'/^https?:\/\/(' . implode('|', $base_domains) . ')/',
80+
'',
81+
$href, count: $replaced);
82+
if ($replaced > 0) {
83+
// HREF here includes any anchor.
84+
$link->setAttribute('href', $href);
85+
}
86+
87+
$host = parse_url($href, PHP_URL_HOST) ?? '';
88+
if ($host === '') {
89+
$alias = parse_url($href, PHP_URL_PATH);
4490

91+
$sysPath = \Drupal::service('path_alias.manager')->getPathByAlias($alias);
92+
93+
// If a link already has all the linkit attributes, leave it be.
94+
if (
95+
$link->hasAttribute('data-entity-type')
96+
&& $link->hasAttribute('data-entity-uuid')
97+
&& $link->hasAttribute('data-entity-substitution')
98+
) {
99+
continue;
100+
}
101+
102+
$url = Url::fromUserInput($sysPath);
103+
104+
if (!$url->isRouted()) {
105+
// What do we have here?
106+
$client = \Drupal::httpClient();
107+
108+
$host = \Drupal::request()->getSchemeAndHttpHost();
109+
$response = $client->get($host . $sysPath, [
110+
// Don't throw exceptions on error codes.
111+
'http_errors' => FALSE,
112+
'allow_redirects' => [
113+
'max' => 10,
114+
'track_redirects' => TRUE,
115+
],
116+
]);
117+
118+
if ($response->getStatusCode() > 400) {
119+
continue;
120+
}
121+
if ($response->getStatusCode() === 200) {
122+
if ($history = $response->getHeader('X-Guzzle-Redirect-History')) {
123+
$finalURI = $history[array_key_last($history)];
124+
$redirHost = parse_url($finalURI, PHP_URL_HOST);
125+
if (!str_ends_with($host, $redirHost)) {
126+
// We were redirected off site. Let's fix the link and move on.
127+
$link->setAttribute('href', $finalURI . $anchor);
128+
continue;
129+
}
130+
$redirPath = parse_url($finalURI, PHP_URL_PATH) ?? '/';
131+
$url = Url::fromUserInput($redirPath);
132+
if (!$url->isRouted()) {
133+
// Redirected to an unrouted path, not much we can do here.
134+
continue;
135+
}
136+
// Replace the alias in the href with the internal path.
137+
$sysPath = \Drupal::request()->getBasePath() . '/' . $url->getInternalPath();
138+
}
139+
}
140+
else {
141+
// Leave 40x links alone.
142+
continue;
143+
}
144+
}
145+
146+
switch ($url->getRouteName()) {
147+
case 'entity.node.canonical':
148+
$params = $url->getRouteParameters();
149+
$node = Node::load($params['node']);
150+
if (!$node) {
151+
continue 2;
152+
}
153+
$entityType = 'node';
154+
$uuid = $node->uuid();
155+
break;
156+
157+
case 'entity.taxonomy_term.canonical':
158+
$entityType = 'term';
159+
$params = $url->getRouteParameters();
160+
$term = Term::load($params['taxonomy_term']);
161+
if (!$term) {
162+
continue 2;
163+
}
164+
$uuid = $term->uuid();
165+
break;
166+
167+
case '<front>':
168+
// Ensure the link to the homepage doesn't have a domain name.
169+
// Don't need to add any other attributes or anchor.
170+
$link->setAttribute('href', $href);
171+
continue 2;
172+
173+
default:
174+
continue 2;
175+
}
176+
177+
// Linkit saves the internal path and renders the alias.
178+
$link->setAttribute('href', $sysPath . $anchor);
179+
$link->setAttribute('data-entity-type', $entityType);
180+
$link->setAttribute('data-entity-uuid', $uuid);
181+
$link->setAttribute('data-entity-substitution', 'canonical');
182+
}
45183
}
184+
185+
$body = $dom->getElementsByTagName('body')->item(0);
186+
$html = $dom->saveHTML($body);
187+
// No good way to keep white space AND omit the body tag automatically.
188+
return preg_replace(['/^<body>/', '/<\/body>$/'], '', $html);
46189
}
47190

48191
/**
49192
* Gets text ready to be stored in plain text fields.
50193
*
51-
* @var string $source_text
194+
* @param string $source_text
52195
* The original source value.
53196
*
54197
* @return string
@@ -61,7 +204,7 @@ public static function plainText(string $source_text): string {
61204
/**
62205
* Gets text ready to be stored in html text fields.
63206
*
64-
* @var string $source_text
207+
* @param string $source_text
65208
* The original source value.
66209
*
67210
* @return string

web/modules/custom/convert_text/src/Form/ConvertTextForm.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,26 @@ public function buildForm(array $form, FormStateInterface $form_state): array {
3232
'#default_value' => $converted_text,
3333
'#disabled' => TRUE,
3434
];
35+
36+
switch ($form_state->getValue('dest')) {
37+
case 'html':
38+
$form['html_filter'] = [
39+
'#markup' => '<h5>HTML Filter</h5>' . check_markup($converted_text, 'html'),
40+
];
41+
$form['multiline_filter'] = [
42+
'#markup' => '<h5>Multiline HTML Filter</h5>' . check_markup($converted_text, 'multiline_inline_html'),
43+
];
44+
$form['single_inline_filter'] = [
45+
'#markup' => '<h5>Single Inline Filter</h5>' . check_markup($converted_text, 'single_inline_html'),
46+
];
47+
break;
48+
49+
case 'plain_text':
50+
$form['Plain text'] = [
51+
'#markup' => '<h5>Plain text</h5>' . check_markup($converted_text, 'plain_text'),
52+
];
53+
break;
54+
}
3555
}
3656

3757
$form['source_text'] = [

web/modules/custom/default_content_config/content/node/ad7aa7a5-33cf-400e-93cf-11820e467111.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ default:
3939
pathauto: 0
4040
body:
4141
-
42-
value: '<p><a href="/node/19" data-entity-type="node" data-entity-uuid="7086f5d5-7c6f-41ef-8a60-70cd267a9860" data-entity-substitution="canonical">RESOURCES</a>&nbsp;</p>'
42+
value: '<p><a href="/node/14" data-entity-type="node" data-entity-uuid="fefddcf3-e162-4622-9176-a3f4c2dadd8e" data-entity-substitution="canonical">RESOURCES</a>&nbsp;</p>'
4343
format: html
4444
summary: ''
4545
field_deck:

web/modules/custom/default_content_config/content/node/e8c684ae-bff8-4e8f-8388-6005a617fc20.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ default:
3636
pathauto: 0
3737
body:
3838
-
39-
value: '<p><strong>The page you''re looking for cannot be found.</strong> Here are a few things that you can try:</p><ul><li>Go back to the <a href="/">homepage</a>.</li><li><a href="http://find.digitalgov.gov/search?affiliate=digitalgov">Search</a> for the page.</li><li>View our <a href="/news">latest news</a>.</li><li>Explore topics in our <a href="/resources">guides and resources</a>.</li><li>Send us an email and <a href="mailto:[email protected]">let us know this page is missing</a>.</li></ul>'
39+
value: '<p><strong>The page you''re looking for cannot be found.</strong> Here are a few things that you can try:</p><ul><li>Go back to the <a href="/">homepage</a>.</li><li><a href="http://find.digitalgov.gov/search?affiliate=digitalgov">Search</a> for the page.</li><li>View our <a href="/node/21" data-entity-type="node" data-entity-uuid="986de625-5015-47c0-9ccc-273f985b69b8" data-entity-substitution="canonical">latest news</a>.</li><li>Explore topics in our <a href="/node/14" data-entity-type="node" data-entity-uuid="fefddcf3-e162-4622-9176-a3f4c2dadd8e" data-entity-substitution="canonical">guides and resources</a>.</li><li>Send us an email and <a href="mailto:[email protected]">let us know this page is missing</a>.</li></ul>'
4040
format: html
4141
summary: ''
4242
field_deck:

0 commit comments

Comments
 (0)