Skip to content

Commit 0bc36b5

Browse files
authored
Update AJAX method and enhance CacheTag revalidator (#887)
1 parent c7074dd commit 0bc36b5

2 files changed

Lines changed: 167 additions & 15 deletions

File tree

modules/next/src/Form/NextEntityTypeConfigForm.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function form(array $form, FormStateInterface $form_state) {
103103
'#ajax' => [
104104
'callback' => '::ajaxReplaceSettingsForm',
105105
'wrapper' => 'settings-container',
106-
'method' => 'replace',
106+
'method' => 'replaceWith',
107107
],
108108
];
109109

@@ -120,7 +120,7 @@ public function form(array $form, FormStateInterface $form_state) {
120120
'#ajax' => [
121121
'callback' => '::ajaxReplaceSiteResolverSettingsForm',
122122
'wrapper' => 'site-resolver-settings',
123-
'method' => 'replace',
123+
'method' => 'replaceWith',
124124
],
125125
];
126126

@@ -167,7 +167,7 @@ public function form(array $form, FormStateInterface $form_state) {
167167
'#ajax' => [
168168
'callback' => '::ajaxReplaceSettingsForm',
169169
'wrapper' => 'settings-container',
170-
'method' => 'replace',
170+
'method' => 'replaceWith',
171171
],
172172
];
173173

@@ -191,7 +191,7 @@ public function form(array $form, FormStateInterface $form_state) {
191191
'#ajax' => [
192192
'callback' => '::ajaxReplaceRevalidatorSettingsForm',
193193
'wrapper' => 'revalidator-settings',
194-
'method' => 'replace',
194+
'method' => 'replaceWith',
195195
],
196196
];
197197

modules/next/src/Plugin/Next/Revalidator/CacheTag.php

Lines changed: 163 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,134 @@ class CacheTag extends ConfigurableRevalidatorBase implements RevalidatorInterfa
2525
* {@inheritdoc}
2626
*/
2727
public function defaultConfiguration() {
28-
return [];
28+
return [
29+
'entity_tag' => TRUE,
30+
'entity_list_tag' => TRUE,
31+
'additional_tags' => NULL,
32+
];
2933
}
3034

3135
/**
3236
* {@inheritdoc}
3337
*/
3438
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
39+
// Get entity type and bundle from form callback object.
40+
$entity_type_id = NULL;
41+
$bundle = NULL;
42+
43+
try {
44+
$entity_bundle_string = $form_state->getBuildInfo()['callback_object']->getEntity()->id();
45+
// Split "node.quote" into entity type and bundle.
46+
if (strpos($entity_bundle_string, '.') !== FALSE) {
47+
[$entity_type_id, $bundle] = explode('.', $entity_bundle_string, 2);
48+
}
49+
}
50+
catch (\Exception $exception) {
51+
// Fallback if we can't get the entity info.
52+
Error::logException($this->logger, $exception);
53+
$entity_type_id = NULL;
54+
$bundle = NULL;
55+
}
56+
57+
$form['entity_tag'] = [
58+
'#title' => $this->t('Revalidate entity cache tag'),
59+
'#description' => $this->t('Revalidate pages with the individual entity cache tag (e.g., @entity_type:123).', [
60+
'@entity_type' => $entity_type_id ?: 'node',
61+
]),
62+
'#type' => 'checkbox',
63+
'#default_value' => $this->configuration['entity_tag'] ?? TRUE,
64+
];
65+
66+
// Generate specific label and description based on detected entity type.
67+
if ($entity_type_id && $bundle) {
68+
if ($entity_type_id === 'node') {
69+
$list_tag_example = 'node_list:' . $bundle;
70+
$next_js_example = 'tags: ["node_list:' . $bundle . '"]';
71+
}
72+
elseif ($entity_type_id === 'taxonomy_term') {
73+
$list_tag_example = 'taxonomy_term_list:' . $bundle;
74+
$next_js_example = 'tags: ["taxonomy_term_list:' . $bundle . '"]';
75+
}
76+
else {
77+
$list_tag_example = $entity_type_id . '_list:' . $bundle;
78+
$next_js_example = 'tags: ["' . $entity_type_id . '_list:' . $bundle . '"]';
79+
}
80+
81+
$title = $this->t('Revalidate @tag cache tags', ['@tag' => $list_tag_example]);
82+
$description = $this->t('Revalidates pages tagged with @tag when @entity_type entities of type @bundle change.<br><br>In Next.js use: <code>@example</code>', [
83+
'@tag' => $list_tag_example,
84+
'@entity_type' => $entity_type_id,
85+
'@bundle' => $bundle,
86+
'@example' => $next_js_example,
87+
]);
88+
}
89+
else {
90+
$title = $this->t('Revalidate [entity_type]_list:[bundle] cache tags');
91+
$description = $this->t('Revalidates pages tagged with entity type and bundle list cache tags when entities change.<br><strong>Node entities:</strong> generates node_list:[bundle] (e.g., node_list:article, node_list:person)<br><strong>Taxonomy terms:</strong> generates taxonomy_term_list:[vocabulary] (e.g., taxonomy_term_list:tags)<br><strong>Other entities:</strong> generates [entity_type]_list:[bundle]<br><br>In Next.js use: <code>tags: ["node_list:article"]</code> or <code>tags: ["taxonomy_term_list:tags"]</code>');
92+
}
93+
94+
$form['entity_list_tag'] = [
95+
'#title' => $title,
96+
'#description' => $description,
97+
'#type' => 'checkbox',
98+
'#default_value' => $this->configuration['entity_list_tag'] ?? TRUE,
99+
];
100+
101+
$form['additional_tags'] = [
102+
'#type' => 'textarea',
103+
'#title' => $this->t('Additional cache tags to revalidate'),
104+
'#default_value' => $this->configuration['additional_tags'] ?? '',
105+
'#description' => $this->t('Additional cache tags to revalidate when this entity type changes. Enter one tag per line. Examples:<br>node_list:all<br>search_results<br>homepage'),
106+
];
107+
35108
return $form;
36109
}
37110

111+
/**
112+
* {@inheritdoc}
113+
*/
114+
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
115+
$additional_tags = $form_state->getValue('additional_tags');
116+
117+
if (!empty($additional_tags)) {
118+
$tags = array_map('trim', explode("\n", $additional_tags));
119+
$tags = array_filter($tags);
120+
121+
foreach ($tags as $tag) {
122+
// Validate that each tag is a string and doesn't contain invalid
123+
// characters.
124+
if (!is_string($tag) || empty($tag)) {
125+
$form_state->setErrorByName('additional_tags', $this->t('Each cache tag must be a non-empty string.'));
126+
break;
127+
}
128+
129+
// Check for invalid characters (spaces, special characters that could
130+
// break cache tags).
131+
if (preg_match('/[^\w\-:._]/', $tag)) {
132+
$form_state->setErrorByName('additional_tags', $this->t('Cache tags can only contain letters, numbers, hyphens, colons, periods, and underscores. Invalid tag: @tag', [
133+
'@tag' => $tag,
134+
]));
135+
break;
136+
}
137+
138+
// Check for reasonable length limit.
139+
if (strlen($tag) > 255) {
140+
$form_state->setErrorByName('additional_tags', $this->t('Cache tags must be 255 characters or less. Invalid tag: @tag', [
141+
'@tag' => $tag,
142+
]));
143+
break;
144+
}
145+
}
146+
}
147+
}
148+
38149
/**
39150
* {@inheritdoc}
40151
*/
41152
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
42-
// No configuration form.
153+
$this->configuration['entity_tag'] = $form_state->getValue('entity_tag');
154+
$this->configuration['entity_list_tag'] = $form_state->getValue('entity_list_tag');
155+
$this->configuration['additional_tags'] = $form_state->getValue('additional_tags');
43156
}
44157

45158
/**
@@ -57,26 +170,55 @@ public function revalidate(EntityActionEvent $event): bool {
57170
return FALSE;
58171
}
59172

60-
// Get all available cache tags (including list tags).
61-
$list_tags = $entity->getEntityType()->getListCacheTags();
62-
if ($entity->getEntityType()->hasKey('bundle')) {
63-
$list_tags[] = $entity->getEntityTypeId() . '_list:' . $entity->bundle();
173+
$cache_tags = [];
174+
175+
// Add individual entity cache tags if enabled.
176+
if (!empty($this->configuration['entity_tag'])) {
177+
$cache_tags = array_merge($cache_tags, $entity->getCacheTags());
178+
}
179+
180+
// Add entity list cache tags if enabled.
181+
if (!empty($this->configuration['entity_list_tag'])) {
182+
$list_tags = $entity->getEntityType()->getListCacheTags();
183+
if ($entity->getEntityType()->hasKey('bundle')) {
184+
$list_tags[] = $entity->getEntityTypeId() . '_list:' . $entity->bundle();
185+
}
186+
$cache_tags = array_merge($cache_tags, $list_tags);
187+
}
188+
189+
// Add additional cache tags.
190+
if (!empty($this->configuration['additional_tags'])) {
191+
$additional_tags = array_map('trim', explode("\n", $this->configuration['additional_tags']));
192+
$additional_tags = array_filter($additional_tags);
193+
$cache_tags = array_merge($cache_tags, $additional_tags);
194+
}
195+
196+
if (!count($cache_tags)) {
197+
if ($this->nextSettingsManager->isDebug()) {
198+
$this->logger->debug('No cache tags found for revalidation. Entity: @entity_type:@entity_id', [
199+
'@entity_type' => $entity->getEntityTypeId(),
200+
'@entity_id' => $entity->id(),
201+
]);
202+
}
203+
return FALSE;
64204
}
65-
$combined_tags = array_merge($entity->getCacheTags(), $list_tags);
66-
$cache_tags = implode(',', $combined_tags);
205+
206+
// Remove duplicates and convert to comma-separated string.
207+
$cache_tags = array_unique($cache_tags);
208+
$cache_tags_string = implode(',', $cache_tags);
67209

68210
/** @var \Drupal\next\Entity\NextSite $site */
69211
foreach ($sites as $site) {
70212
try {
71-
$revalidate_url = $site->buildRevalidateUrl(['tags' => $cache_tags]);
213+
$revalidate_url = $site->buildRevalidateUrl(['tags' => $cache_tags_string]);
72214
if (!$revalidate_url) {
73215
throw new \Exception('No revalidate url set.');
74216
}
75217

76218
if ($this->nextSettingsManager->isDebug()) {
77219
$this->logger->notice('(@action): Revalidating tags %list for the site %site. URL: %url', [
78220
'@action' => $event->getAction(),
79-
'%list' => $cache_tags,
221+
'%list' => $cache_tags_string,
80222
'%site' => $site->label(),
81223
'%url' => $revalidate_url->toString(),
82224
]);
@@ -87,14 +229,24 @@ public function revalidate(EntityActionEvent $event): bool {
87229
if ($this->nextSettingsManager->isDebug()) {
88230
$this->logger->notice('(@action): Successfully revalidated tags %list for the site %site. URL: %url', [
89231
'@action' => $event->getAction(),
90-
'%list' => $cache_tags,
232+
'%list' => $cache_tags_string,
91233
'%site' => $site->label(),
92234
'%url' => $revalidate_url->toString(),
93235
]);
94236
}
95237

96238
$revalidated = TRUE;
97239
}
240+
else {
241+
$status_code = $response ? $response->getStatusCode() : 'unknown';
242+
$this->logger->warning('(@action): Failed to revalidate tags %list for the site %site. HTTP status: %status. URL: %url', [
243+
'@action' => $event->getAction(),
244+
'%list' => $cache_tags_string,
245+
'%site' => $site->label(),
246+
'%status' => $status_code,
247+
'%url' => $revalidate_url->toString(),
248+
]);
249+
}
98250
}
99251
catch (\Exception $exception) {
100252
Error::logException($this->logger, $exception);

0 commit comments

Comments
 (0)