-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlocalgov_publications.module
More file actions
464 lines (408 loc) · 17.1 KB
/
localgov_publications.module
File metadata and controls
464 lines (408 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
<?php
/**
* @file
* Module file for the LocalGov Publications module.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\block\Entity\Block;
use Drupal\localgov_roles\RolesHelper;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
/**
* Implements hook_theme().
*/
function localgov_publications_theme($existing, $type, $theme, $path): array {
return [
'book_navigation__publication' => [
'template' => 'book-navigation--publication',
'base hook' => 'book_navigation__publication',
],
'localgov_publication_page_header_block' => [
'variables' => [
'title' => '',
'node_title' => '',
'published_date' => NULL,
'last_updated_date' => NULL,
],
],
'paragraph__localgov_publications_banner' => [
'template' => 'paragraph--localgov-publications-banner',
'base hook' => 'paragraph',
],
'media__document__publication' => [
'template' => 'media--document--publication',
'base hook' => 'media',
],
'field__localgov_publication' => [
'template' => 'publication-html-reference',
'base hook' => 'field',
],
];
}
/**
* Implements hook_localgov_role_default().
*/
function localgov_publications_localgov_roles_default(): array {
return [
RolesHelper::EDITOR_ROLE => [
'access publication views',
'add content to books',
'administer book outlines',
'create new books',
'create localgov_publication_page content',
'create localgov_publication_cover_page content',
'delete any localgov_publication_page content',
'delete any localgov_publication_cover_page content',
'delete localgov_publication_page revisions',
'delete localgov_publication_cover_page revisions',
'delete own localgov_publication_page content',
'delete own localgov_publication_cover_page content',
'edit any localgov_publication_page content',
'edit any localgov_publication_cover_page content',
'edit own localgov_publication_page content',
'edit own localgov_publication_cover_page content',
'revert localgov_publication_page revisions',
'revert localgov_publication_cover_page revisions',
'view localgov_publication_page revisions',
'view localgov_publication_cover_page revisions',
],
];
}
/**
* Is the given type one of the publication node types?
*/
function localgov_publications_is_publication_type(string $type): bool {
return $type === 'localgov_publication_page' || $type === 'localgov_publication_cover_page';
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function localgov_publications_theme_suggestions_book_navigation(array $variables): array {
$suggestions = [];
// Only add suggestion on publication pages and publication cover pages.
$node = \Drupal::routeMatch()->getParameter('node');
if (localgov_publications_is_publication_type($node->getType())) {
$suggestions[] = $variables['theme_hook_original'] . '__publication';
}
return $suggestions;
}
/**
* Implements hook_block_access().
*/
function localgov_publications_block_access(Block $block, $operation, AccountInterface $account) {
if ($block->getPluginId() == 'localgov_page_header_block' && $operation == 'view') {
$node = \Drupal::routeMatch()->getParameter('node');
if ($node instanceof NodeInterface && localgov_publications_is_publication_type($node->getType())) {
return AccessResult::forbiddenIf(TRUE)->addCacheableDependency($block);
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for book_admin_edit.
*/
function localgov_publications_form_book_admin_edit_alter(&$form, FormStateInterface $form_state, $form_id): void {
// If we're on the route this module uses for this form, change some wording.
$route_name = \Drupal::routeMatch()->getRouteName();
if ($route_name === 'publication.admin_edit') {
$form['save']['#value'] = t('Save publication pages');
}
}
/**
* Change 'book' to 'publication' in text.
*
* This function can accept either a string or TranslatableMarkup.
* If a TranslatableMarkup was passed, the arguments and options are preserved.
*/
function localgov_publications_book_to_publication(TranslatableMarkup|string $originalString): TranslatableMarkup|string {
// Normalise TranslatableMarkup to a string.
if ($originalString instanceof TranslatableMarkup) {
$string = $originalString->getUntranslatedString();
$arguments = $originalString->getArguments();
$options = $originalString->getOptions();
}
else {
$string = $originalString;
$arguments = [];
$options = [];
}
// We can't just search & replace 'book' with 'publication' in the string, as
// that breaks the code style rule about not passing variables to t() or
// TranslatableMarkup(). So we'll do it like this:
$strings = [
'Book outline' =>
new TranslatableMarkup('Publication outline', $arguments, $options),
'Book' =>
new TranslatableMarkup('Publication', $arguments, $options),
'- Create a new book -' =>
new TranslatableMarkup('- Create a new publication -', $arguments, $options),
'Your page will be part of the selected book' =>
new TranslatableMarkup('Your page will be part of the selected publication', $arguments, $options),
'<div id="edit-book-plid-wrapper"><em>No book selected.</em>' =>
new TranslatableMarkup('<div id="edit-book-plid-wrapper"><em>No publication selected.</em>', $arguments, $options),
'<div id="edit-book-plid-wrapper"><em>This will be the top-level page in this book.</em>' =>
new TranslatableMarkup('<div id="edit-book-plid-wrapper"><em>This will be the top-level page in this publication.</em>', $arguments, $options),
'The parent page in the book. The maximum depth for a book and all child pages is @maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.' =>
new TranslatableMarkup('The parent page in the publication. The maximum depth for a publication and all child pages is @maxdepth. Some pages in the selected publication may not be available as parents if selecting them would exceed this limit.', $arguments, $options),
];
if (isset($strings[$string])) {
return $strings[$string];
}
return $originalString;
}
/**
* Alter the node forms (add and edit)
*
* This function is called from both
* localgov_publications_form_node_form_alter (/node/add)
* and
* localgov_publications_form_node_edit_form_alter (/node/x/edit)
*
* @param array $form
* Form elements.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
* @param string $form_id
* Form ID string.
*/
function _localgov_publications_node_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
// If this form element isn't present there's nothing for us to do here.
if (!isset($form['book'])) {
return;
}
$publication_node_forms = [
'node_localgov_publication_page_form',
'node_localgov_publication_page_edit_form',
];
// Alter the publications node forms.
if (in_array($form_id, $publication_node_forms, TRUE)) {
// Attach JS.
if ($form['book']['#attached']['library'][0] == 'book/drupal.book') {
$form['book']['#attached']['library'][0] = 'localgov_publications/localgov-publications';
}
// Attach our validation.
$form['#validate'][] = 'localgov_publications_validate_node_form';
// All these places in the form contain the word 'book'.
// We want them to say 'publication' instead.
$form['book']['#title'] = localgov_publications_book_to_publication($form['book']['#title']);
$form['book']['bid']['#title'] = localgov_publications_book_to_publication($form['book']['bid']['#title']);
$form['book']['bid']['#description'] = localgov_publications_book_to_publication($form['book']['bid']['#description']);
if (isset($form['book']['bid']['#options']['new'])) {
$form['book']['bid']['#options']['new'] = localgov_publications_book_to_publication($form['book']['bid']['#options']['new']);
}
// New book will be the node ID on the edit page.
$nid = $form_state->getFormObject()->getEntity()->id();
if (isset($form['book']['bid']['#options'][$nid])) {
$form['book']['bid']['#options'][$nid] = localgov_publications_book_to_publication($form['book']['bid']['#options'][$nid]);
}
if (isset($form['book']['pid']['#prefix'])) {
$form['book']['pid']['#prefix'] = localgov_publications_book_to_publication($form['book']['pid']['#prefix']);
}
if (isset($form['book']['pid']['#description'])) {
$form['book']['pid']['#description'] = localgov_publications_book_to_publication($form['book']['pid']['#description']);
}
$bids = array_filter(array_keys($form['book']['bid']['#options']), function ($item) {
return (is_numeric($item) && $item != 0);
});
// Filter non publications from book selector.
$valid_bids = _localgov_publications_valid_bids($bids);
$form['book']['bid']['#options'] = array_filter($form['book']['bid']['#options'], function ($option) use ($valid_bids) {
return (in_array($option, $valid_bids, TRUE) || $option == 0 || !is_numeric($option));
}, ARRAY_FILTER_USE_KEY);
}
// Else, strip out publications from any books.
else {
$bids = array_filter(array_keys($form['book']['bid']['#options']), function ($item) {
return (is_numeric($item) && $item != 0);
});
$valid_bids = _localgov_publications_valid_bids($bids);
$form['book']['bid']['#options'] = array_filter($form['book']['bid']['#options'], function ($option) use ($valid_bids) {
return (!in_array($option, $valid_bids, TRUE) || $option == 0 || !is_numeric($option));
}, ARRAY_FILTER_USE_KEY);
}
}
/**
* Get valid publication book bids.
*
* @param array $bids
* Book ids on the node edit form.
*
* @return array
* Array of top level book page node ids, converted to integers.
*/
function _localgov_publications_valid_bids(array $bids) :array {
$valid_bids = [];
// Only search for valid books if $bids has values.
if (count($bids) !== 0) {
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$valid_bids = $node_storage->getQuery()
->condition('type', 'localgov_publication_page')
->condition('nid', $bids, 'IN')
->accessCheck(TRUE)
->execute();
// Convert arrray to integers for comparision with option values.
$valid_bids = array_map(fn($valid_bid) => (int) $valid_bid, $valid_bids);
}
return $valid_bids;
}
/**
* Implements hook_form_BASE_ID_alter().
*/
function localgov_publications_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id): void {
_localgov_publications_node_form_alter($form, $form_state, $form_id);
}
/**
* Implements hook_form_BASE_ID_alter().
*/
function localgov_publications_form_node_edit_form_alter(&$form, FormStateInterface $form_state, $form_id): void {
_localgov_publications_node_form_alter($form, $form_state, $form_id);
}
/**
* Form validation function.
*
* Ensures that either 'Create a new publication', or an existing publication
* has been chosen from the book field.
*/
function localgov_publications_validate_node_form(&$form, FormStateInterface $form_state): void {
if ($form_state->hasValue('book')) {
$book = $form_state->getValue('book');
if ($book['bid'] === '0') {
$form_state->setErrorByName('book', t("Please choose either 'Create a new publication', or one of your existing publications for this page to be part of."));
}
}
}
/**
* Implements hook_node_links_alter().
*
* If book module has added the "Add child page" link, and we're on a
* publication type page, alter the link, so it creates a
* localgov_publication_page, instead of the default book type.
*/
function localgov_publications_node_links_alter(array &$links, NodeInterface $node, array &$context): void {
if (localgov_publications_is_publication_type($node->getType()) && isset($links['book']['#links']['book_add_child'])) {
$links['book']['#links']['book_add_child']['url'] = Url::fromRoute('node.add', ['node_type' => 'localgov_publication_page'], ['query' => ['parent' => $node->id()]]);
}
}
/**
* Implements hook_preprocess_node().
*/
function localgov_publications_preprocess_node(&$variables): void {
$view_mode = $variables['elements']['#view_mode'];
/** @var \Drupal\node\NodeInterface $node */
$node = $variables['elements']['#node'];
if ($view_mode === 'full' && localgov_publications_is_publication_type($node->getType())) {
$variables['content']['#attached']['library'][] = 'localgov_publications/localgov-publications';
}
}
/**
* Implements hook_modules_installed().
*/
function localgov_publications_modules_installed($modules, $is_syncing) {
if (!$is_syncing && in_array('book', $modules, TRUE)) {
// If book module is being installed, prevent the 'book' node type and its
// dependencies from being installed from its config (or rather, delete it
// -- there's no way to intercept it within the config API).
$extension_path = \Drupal::service('extension.path.resolver')->getPath('module', 'book');
$optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
// Get all of book module's optional config.
$storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION);
$list = $storage->listAll();
$config_to_create = $storage->readMultiple($list);
// Filter this to those config entities that depend on the 'book' node type.
$dependency_manager = new ConfigDependencyManager();
$dependency_manager->setData($config_to_create);
$dependencies = $dependency_manager->getDependentEntities('config', 'node.type.book');
foreach (array_keys($dependencies) as $config_name) {
\Drupal::configFactory()->getEditable($config_name)->delete();
}
\Drupal::configFactory()->getEditable('node.type.book')->delete();
// The mapping of fields to bundles is stored in the key-value store. As we
// removed the content type and its fields on a config level, we also need
// to clear out the book data from here, as it won't be done for us like it
// is when you delete a content type via the UI.
$kvStore = \Drupal::keyValue('entity.definitions.bundle_field_map');
$fieldMap = $kvStore->get('node');
unset($fieldMap['body']['bundles']['book']);
$kvStore->set('node', $fieldMap);
// Clear all caches to ensure there are no references to the Book node left.
drupal_flush_all_caches();
}
}
/**
* Implements hook_module_implements_alter().
*
* Moves our implementations of hook_entity_insert and hook_entity_update to the
* end of the list, so they run after pathauto. If they run before pathauto, we
* don't pick up changes to the URL of cover pages when generating URL aliases
* for the rest of the publication.
*/
function localgov_publications_module_implements_alter(&$implementations, $hook): void {
switch ($hook) {
case 'entity_insert':
case 'entity_update':
$group = $implementations['localgov_publications'];
unset($implementations['localgov_publications']);
$implementations['localgov_publications'] = $group;
break;
}
}
/**
* Implements hook_entity_insert().
*
* NB that we don't implement hook_node_insert to ensure we run after pathauto.
*/
function localgov_publications_entity_insert(EntityInterface $entity): void {
if ($entity instanceof NodeInterface) {
localgov_publications_update_path_aliases($entity);
}
}
/**
* Implements hook_entity_update().
*
* NB that we don't implement hook_node_update to ensure we run after pathauto.
*/
function localgov_publications_entity_update(EntityInterface $entity): void {
if ($entity instanceof NodeInterface) {
localgov_publications_update_path_aliases($entity);
}
}
/**
* Updates the path alias of every page in a publication.
*
* @param \Drupal\node\NodeInterface $node
* Cover page node.
*/
function localgov_publications_update_path_aliases(NodeInterface $node): void {
// Only do anything if we're saving a cover page.
if ($node->getType() !== 'localgov_publication_cover_page') {
return;
}
/** @var \Drupal\book\BookManager $bookManager */
$bookManager = \Drupal::service('book.manager');
/** @var \Drupal\node\NodeInterface[] $publications */
$publications = $node->get('localgov_publication')->referencedEntities();
$publicationPages = [];
foreach ($publications as $publication) {
if (isset($publication->book)) {
// Find the ID of every node in the publication.
$bookPages = $bookManager->bookTreeGetFlat($publication->book);
$publicationPages = array_merge($publicationPages, array_keys($bookPages));
}
}
if (count($publicationPages) === 0) {
return;
}
$pageNodes = Node::loadMultiple($publicationPages);
$pathAutoGenerator = \Drupal::service('pathauto.generator');
foreach ($pageNodes as $pageNode) {
$pathAutoGenerator->updateEntityAlias($pageNode, 'update');
}
}