book.module 26.6 KB
Newer Older
Dries's avatar
 
Dries committed
1
2
<?php

Dries's avatar
   
Dries committed
3
4
/**
 * @file
5
 * Allows users to create and organize related content in an outline.
Dries's avatar
   
Dries committed
6
7
 */

8
use Drupal\book\BookManagerInterface;
9
use Drupal\Component\Utility\String;
10
use Drupal\Core\Entity\EntityInterface;
11
use Drupal\node\NodeInterface;
12
use Drupal\node\NodeTypeInterface;
13
use Drupal\Core\Language\Language;
14
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
15
use Drupal\Core\Template\Attribute;
16

17
/**
18
 * Implements hook_help().
19
20
21
22
23
 */
function book_help($path, $arg) {
  switch ($path) {
    case 'admin/help#book':
      $output = '<h3>' . t('About') . '</h3>';
24
      $output .= '<p>' . t('The Book module is used for creating structured, multi-page content, such as site resource guides, manuals, and wikis. It allows you to create content that has chapters, sections, subsections, or any similarly-tiered structure. For more information, see the <a href="!book">online documentation for the Book module</a>.', array('!book' => 'https://drupal.org/documentation/modules/book')) . '</p>';
25
26
27
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Adding and managing book content') . '</dt>';
28
      $output .= '<dd>' . t('You can assign separate permissions for <em>creating new books</em> as well as <em>creating</em>, <em>editing</em> and <em>deleting</em> book content. Users with the <em>Administer book outlines</em> permission can add <em>any</em> type of content to a book by selecting the appropriate book outline while editing the content. They can also view a list of all books, and edit and rearrange section titles on the <a href="!admin-book">Book administration page</a>.', array('!admin-book' => \Drupal::url('book.admin'))) . '</dd>';
29
      $output .= '<dt>' . t('Book navigation') . '</dt>';
30
      $output .= '<dd>' . t("Book pages have a default book-specific navigation block. This navigation block contains links that lead to the previous and next pages in the book, and to the level above the current page in the book's structure. This block can be enabled on the <a href='!admin-block'>Blocks administration page</a>. For book pages to show up in the book navigation, they must be added to a book outline.", array('!admin-block' => \Drupal::url('block.admin_display'))) . '</dd>';
31
      $output .= '<dt>' . t('Collaboration') . '</dt>';
32
      $output .= '<dd>' . t('Books can be created collaboratively, as they allow users with appropriate permissions to add pages into existing books, and add those pages to a custom table of contents.') . '</dd>';
33
      $output .= '<dt>' . t('Printing books') . '</dt>';
34
      $output .= '<dd>' . t("Users with the <em>View printer-friendly books</em> permission can select the <em>printer-friendly version</em> link visible at the bottom of a book page's content to generate a printer-friendly display of the page and all of its subsections.") . '</dd>';
35
36
      $output .= '</dl>';
      return $output;
37
    case 'admin/structure/book':
38
39
      return '<p>' . t('The book module offers a means to organize a collection of related content pages, collectively known as a book. When viewed, this content automatically displays links to adjacent book pages, providing a simple navigation system for creating and reviewing structured content.') . '</p>';
    case 'node/%/outline':
40
      return '<p>' . t('The outline feature allows you to include pages in the <a href="!book">Book hierarchy</a>, as well as move them within the hierarchy or to <a href="!book-admin">reorder an entire book</a>.', array('!book' => \Drupal::url('book.render'), '!book-admin' => \Drupal::url('book.admin'))) . '</p>';
41
42
43
  }
}

44
/**
45
 * Implements hook_theme().
46
47
48
49
 */
function book_theme() {
  return array(
    'book_navigation' => array(
50
      'variables' => array('book_link' => NULL),
51
      'template' => 'book-navigation',
52
    ),
53
54
55
56
    'book_tree' => array(
      'render element' => 'tree',
      'template' => 'book-tree',
    ),
57
58
59
    'book_link' => array(
      'render element' => 'element',
    ),
60
    'book_export_html' => array(
61
      'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
62
      'template' => 'book-export-html',
63
64
    ),
    'book_admin_table' => array(
65
      'render element' => 'form',
66
      'file' => 'book.admin.inc',
67
    ),
Dries's avatar
Dries committed
68
    'book_all_books_block' => array(
69
      'render element' => 'book_menus',
70
71
72
      'template' => 'book-all-books-block',
    ),
    'book_node_export_html' => array(
73
      'variables' => array('node' => NULL, 'children' => NULL),
74
      'template' => 'book-node-export-html',
Dries's avatar
Dries committed
75
    ),
76
77
78
  );
}

79
/**
80
 * Implements hook_permission().
81
 */
82
function book_permission() {
83
  return array(
84
85
86
87
88
89
90
    'administer book outlines' => array(
      'title' => t('Administer book outlines'),
    ),
    'create new books' => array(
      'title' => t('Create new books'),
    ),
    'add content to books' => array(
91
      'title' => t('Add content and child pages to books'),
92
93
    ),
    'access printer-friendly version' => array(
94
      'title' => t('View printer-friendly books'),
95
96
      'description' => t('View a book page and all of its sub-pages as a single document for ease of printing. Can be performance heavy.'),
    ),
97
  );
Dries's avatar
   
Dries committed
98
99
}

100
/**
101
 * Implements hook_entity_type_build().
102
 */
103
104
105
function book_entity_type_build(array &$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $entity_types['node']
106
107
108
    ->setFormClass('book_outline', 'Drupal\book\Form\BookOutlineForm')
    ->setLinkTemplate('book-outline-form', 'book.outline')
    ->setLinkTemplate('book-remove-form', 'book.remove');
109
110
}

Dries's avatar
   
Dries committed
111
/**
112
 * Implements hook_node_links_alter().
Dries's avatar
   
Dries committed
113
 */
114
115
116
117
118
119
120
121
122
123
124
125
function book_node_links_alter(array &$node_links, NodeInterface $node, array &$context) {
  if ($context['view_mode'] != 'rss') {
    $account = \Drupal::currentUser();

    if (isset($node->book['depth'])) {
      if ($context['view_mode'] == 'full' && node_is_page($node)) {
        $child_type = \Drupal::config('book.settings')->get('child_type');
        $access_controller = \Drupal::entityManager()->getAccessController('node');
        if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_controller->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < MENU_MAX_DEPTH) {
          $links['book_add_child'] = array(
            'title' => t('Add child page'),
            'href' => 'node/add/' . $child_type,
126
            'query' => array('parent' => $node->id()),
127
128
129
130
131
132
133
134
135
136
          );
        }

        if ($account->hasPermission('access printer-friendly version')) {
          $links['book_printer'] = array(
            'title' => t('Printer-friendly version'),
            'href' => 'book/export/html/' . $node->id(),
            'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.'))
          );
        }
137
      }
Dries's avatar
   
Dries committed
138
    }
139

140
141
142
143
144
145
146
    if (!empty($links)) {
      $node_links['book'] = array(
        '#theme' => 'links__node__book',
        '#links' => $links,
        '#attributes' => array('class' => array('links', 'inline')),
      );
    }
147
  }
Dries's avatar
   
Dries committed
148
149
}

150
151
152
153
/**
 * Implements hook_menu_link_defaults().
 */
function book_menu_link_defaults() {
154
  $links['book.admin'] = array(
155
156
    'link_title' => 'Books',
    'description' => "Manage your site's book outlines.",
157
    'parent' => 'system.admin_structure',
158
159
    'route_name' => 'book.admin',
  );
160
  $links['book.render'] = array(
161
162
163
164
165
166
167
168
169
    'link_title' => 'Books',
    'route_name' => 'book.render',
    // @todo what to do about MENU_SUGGESTED_ITEM, maybe specify no menu_name?
    'type' => MENU_SUGGESTED_ITEM,
  );

  return $links;
}

170
/**
Dries's avatar
Dries committed
171
172
 * Returns an array of all books.
 *
173
174
 * @todo Remove in favor of BookManager Service. http://drupal.org/node/1963894
 *
Dries's avatar
Dries committed
175
176
 * This list may be used for generating a list of all the books, or for building
 * the options for a form select.
177
178
179
 *
 * @return
 *   An array of all books.
180
 */
Dries's avatar
Dries committed
181
function book_get_books() {
182
  return \Drupal::service('book.manager')->getAllBooks();
183
}
184

Dries's avatar
Dries committed
185
/**
186
 * Implements hook_form_BASE_FORM_ID_alter() for node_form().
187
 *
188
 * Adds the book form element to the node form.
189
190
 *
 * @see book_pick_book_nojs_submit()
Dries's avatar
Dries committed
191
 */
192
function book_form_node_form_alter(&$form, &$form_state, $form_id) {
193
  $node = $form_state['controller']->getEntity();
194
  $account = \Drupal::currentUser();
195
  $access = $account->hasPermission('administer book outlines');
196
  if (!$access) {
197
    if ($account->hasPermission('add content to books') && ((!empty($node->book['bid']) && !$node->isNew()) || book_type_is_allowed($node->getType()))) {
198
199
      // Already in the book hierarchy, or this node type is allowed.
      $access = TRUE;
Dries's avatar
Dries committed
200
    }
201
  }
Dries's avatar
Dries committed
202

203
  if ($access) {
204
205
    $collapsed = !($node->isNew() && !empty($node->book['pid']));
    $form = \Drupal::service('book.manager')->addFormElements($form, $form_state, $node, $account, $collapsed);
206
    // Since the "Book" dropdown can't trigger a form submission when
207
    // JavaScript is disabled, add a submit button to do that. book.admin.css hides
208
209
210
211
212
213
    // this button when JavaScript is enabled.
    $form['book']['pick-book'] = array(
      '#type' => 'submit',
      '#value' => t('Change book (update list of parents)'),
      '#submit' => array('book_pick_book_nojs_submit'),
      '#weight' => 20,
214
      '#attached' => array(
215
        'css' => array(drupal_get_path('module', 'book') . '/css/book.admin.css'),
216
      ),
217
    );
218
    $form['#entity_builders'][] = 'book_node_builder';
219
  }
Dries's avatar
Dries committed
220
}
221

222
223
224
225
226
227
228
229
230
/**
 * Entity form builder to add the book information to the node.
 *
 * @todo: Remove this in favor of an entity field.
 */
function book_node_builder($entity_type, $entity, &$form, &$form_state) {
  $entity->book = $form_state['values']['book'];
}

231
/**
232
 * Form submission handler for node_form().
233
234
235
236
237
 *
 * This handler is run when JavaScript is disabled. It triggers the form to
 * rebuild so that the "Parent item" options are changed to reflect the newly
 * selected book. When JavaScript is enabled, the submit button that triggers
 * this handler is hidden, and the "Book" dropdown directly triggers the
238
 * book_form_update() Ajax callback instead.
239
240
 *
 * @see book_form_update()
241
 * @see book_form_node_form_alter()
242
243
 */
function book_pick_book_nojs_submit($form, &$form_state) {
244
  $node = $form_state['controller']->getEntity();
245
  $node->book = $form_state['values']['book'];
246
247
248
  $form_state['rebuild'] = TRUE;
}

249
250
251
/**
 * Renders a new parent page select element when the book selection changes.
 *
252
 * This function is called via Ajax when the selected book is changed on a node
253
254
255
256
257
258
 * or book outline form.
 *
 * @return
 *   The rendered parent page select element.
 */
function book_form_update($form, $form_state) {
259
  return $form['book']['pid'];
260
261
}

Dries's avatar
Dries committed
262
/**
263
 * Gets the book menu tree for a page and returns it as a linear array.
264
 *
Dries's avatar
Dries committed
265
266
 * @param $book_link
 *   A fully loaded menu link that is part of the book hierarchy.
267
 *
268
 * @return
Dries's avatar
Dries committed
269
270
 *   A linear array of menu links in the order that the links are shown in the
 *   menu, so the previous and next pages are the elements before and after the
271
272
273
 *   element corresponding to the current node. The children of the current node
 *   (if any) will come immediately after it in the array, and links will only
 *   be fetched as deep as one level deeper than $book_link.
274
 */
Dries's avatar
Dries committed
275
function book_get_flat_menu($book_link) {
276
  $flat = &drupal_static(__FUNCTION__, array());
Dries's avatar
Dries committed
277

278
279
280
281
282
  if (!isset($flat[$book_link['nid']])) {
    // Call bookTreeAllData() to take advantage of caching.
    $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['bid'], $book_link, $book_link['depth'] + 1);
    $flat[$book_link['nid']] = array();
    _book_flatten_menu($tree, $flat[$book_link['nid']]);
Dries's avatar
   
Dries committed
283
  }
284

285
  return $flat[$book_link['nid']];
Dries's avatar
   
Dries committed
286
287
}

288
/**
289
290
291
292
293
 * Recursively converts a tree of menu links to a flat array.
 *
 * @param $tree
 *   A tree of menu links in an array.
 * @param $flat
294
 *   A flat array of the menu links from $tree, passed by reference.
295
296
 *
 * @see book_get_flat_menu().
297
 */
Dries's avatar
Dries committed
298
299
function _book_flatten_menu($tree, &$flat) {
  foreach ($tree as $data) {
300
301
302
    $flat[$data['link']['nid']] = $data['link'];
    if ($data['below']) {
      _book_flatten_menu($data['below'], $flat);
Dries's avatar
Dries committed
303
    }
Dries's avatar
   
Dries committed
304
  }
Dries's avatar
Dries committed
305
}
Dries's avatar
   
Dries committed
306

Dries's avatar
Dries committed
307
308
/**
 * Fetches the menu link for the previous page of the book.
309
310
311
312
313
314
315
 *
 * @param $book_link
 *   A fully loaded menu link that is part of the book hierarchy.
 *
 * @return
 *   A fully loaded menu link for the page before the one represented in
 *   $book_link.
Dries's avatar
Dries committed
316
317
318
 */
function book_prev($book_link) {
  // If the parent is zero, we are at the start of a book.
319
  if ($book_link['pid'] == 0) {
Dries's avatar
Dries committed
320
    return NULL;
Dries's avatar
   
Dries committed
321
  }
Dries's avatar
Dries committed
322
  // Assigning the array to $flat resets the array pointer for use with each().
323
  $flat = book_get_flat_menu($book_link);
Dries's avatar
Dries committed
324
325
326
327
  $curr = NULL;
  do {
    $prev = $curr;
    list($key, $curr) = each($flat);
328
  } while ($key && $key != $book_link['nid']);
Dries's avatar
Dries committed
329

330
331
332
  if ($key == $book_link['nid']) {
    /** @var \Drupal\book\BookManagerInterface $book_manager */
    $book_manager = \Drupal::service('book.manager');
Dries's avatar
Dries committed
333
    // The previous page in the book may be a child of the previous visible link.
334
    if ($prev['depth'] == $book_link['depth']) {
Dries's avatar
Dries committed
335
      // The subtree will have only one link at the top level - get its data.
336
      $tree = $book_manager->bookMenuSubtreeData($prev);
337
      $data = array_shift($tree);
Dries's avatar
Dries committed
338
339
340
341
      // The link of interest is the last child - iterate to find the deepest one.
      while ($data['below']) {
        $data = end($data['below']);
      }
342
      $book_manager->bookLinkTranslate($data['link']);
Dries's avatar
Dries committed
343
344
345
      return $data['link'];
    }
    else {
346
      $book_manager->bookLinkTranslate($prev);
Dries's avatar
Dries committed
347
348
      return $prev;
    }
Dries's avatar
   
Dries committed
349
350
351
  }
}

352
/**
Dries's avatar
Dries committed
353
 * Fetches the menu link for the next page of the book.
354
355
356
357
358
 *
 * @param $book_link
 *   A fully loaded menu link that is part of the book hierarchy.
 *
 * @return
359
 *   A fully loaded book link for the page after the one represented in
360
 *   $book_link.
361
 */
Dries's avatar
Dries committed
362
363
function book_next($book_link) {
  // Assigning the array to $flat resets the array pointer for use with each().
364
  $flat = book_get_flat_menu($book_link);
Dries's avatar
Dries committed
365
  do {
366
    list($key, ) = each($flat);
367
  }
368
  while ($key && $key != $book_link['nid']);
369

370
371
372
373
374
375
  if ($key == $book_link['nid']) {
    $next = current($flat);
    if ($next) {
      \Drupal::service('book.manager')->bookLinkTranslate($next);
    }
    return $next;
Dries's avatar
   
Dries committed
376
  }
Dries's avatar
Dries committed
377
}
Dries's avatar
   
Dries committed
378

Dries's avatar
Dries committed
379
/**
380
381
382
383
384
385
386
 * Formats the menu links for the child pages of the current page.
 *
 * @param $book_link
 *   A fully loaded menu link that is part of the book hierarchy.
 *
 * @return
 *   HTML for the links to the child pages of the current page.
Dries's avatar
Dries committed
387
388
389
 */
function book_children($book_link) {
  $flat = book_get_flat_menu($book_link);
390

Dries's avatar
Dries committed
391
392
393
394
395
396
  $children = array();

  if ($book_link['has_children']) {
    // Walk through the array until we find the current page.
    do {
      $link = array_shift($flat);
397
    }
398
    while ($link && ($link['nid'] != $book_link['nid']));
Dries's avatar
Dries committed
399
    // Continue though the array and collect the links whose parent is this page.
400
    while (($link = array_shift($flat)) && $link['pid'] == $book_link['nid']) {
Dries's avatar
Dries committed
401
402
403
      $data['link'] = $link;
      $data['below'] = '';
      $children[] = $data;
Dries's avatar
   
Dries committed
404
405
    }
  }
406

407
  if ($children) {
408
    $elements = \Drupal::service('book.manager')->bookTreeOutput($children);
409
410
411
    return drupal_render($elements);
  }
  return '';
Dries's avatar
   
Dries committed
412
413
}

414
/**
415
 * Implements hook_node_load().
416
 */
417
function book_node_load($nodes) {
418
  $result = db_query("SELECT * FROM {book} WHERE nid IN (:nids)", array(':nids' =>  array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
419
420
  foreach ($result as $record) {
    $nodes[$record['nid']]->book = $record;
421
422
    $nodes[$record['nid']]->book['link_path'] = 'node/' . $record['nid'];
    $nodes[$record['nid']]->book['link_title'] = $nodes[$record['nid']]->label();
423
424
  }
}
425

426
/**
427
 * Implements hook_node_view().
428
 */
429
function book_node_view(EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
430
  if ($view_mode == 'full') {
431
    if (!empty($node->book['bid']) && empty($node->in_preview)) {
432
      $book_navigation = array( '#theme' => 'book_navigation', '#book_link' => $node->book);
433
      $node->content['book_navigation'] = array(
434
        '#markup' => drupal_render($book_navigation),
435
        '#weight' => 100,
436
437
438
439
440
        '#attached' => array(
          'css' => array(
            drupal_get_path('module', 'book') . '/css/book.theme.css',
          ),
        ),
441
442
443
      );
    }
  }
444
445
}

446
/**
447
 * Implements hook_node_presave().
448
 */
449
function book_node_presave(EntityInterface $node) {
450
  // Always save a revision for non-administrators.
451
  if (!empty($node->book['bid']) && !\Drupal::currentUser()->hasPermission('administer nodes')) {
452
    $node->setNewRevision();
453
454
  }
  // Make sure a new node gets a new menu link.
455
  if ($node->isNew()) {
456
    $node->book['nid'] = NULL;
457
458
  }
}
459

460
/**
461
 * Implements hook_node_insert().
462
 */
463
function book_node_insert(EntityInterface $node) {
464
  $book_manager = \Drupal::service('book.manager');
465
466
467
  if (!empty($node->book['bid'])) {
    if ($node->book['bid'] == 'new') {
      // New nodes that are their own book.
468
      $node->book['bid'] = $node->id();
469
    }
470
    $book_manager->updateOutline($node);
471
472
  }
}
473

474
/**
475
 * Implements hook_node_update().
476
 */
477
function book_node_update(EntityInterface $node) {
478
  $book_manager = \Drupal::service('book.manager');
479
480
481
  if (!empty($node->book['bid'])) {
    if ($node->book['bid'] == 'new') {
      // New nodes that are their own book.
482
      $node->book['bid'] = $node->id();
483
    }
484
    $book_manager->updateOutline($node);
485
486
  }
}
487

488
/**
489
 * Implements hook_node_predelete().
490
 */
491
function book_node_predelete(EntityInterface $node) {
492
  if (!empty($node->book['bid'])) {
493
494
495
    /** @var \Drupal\book\BookManagerInterface $book_manager */
    $book_manager = \Drupal::service('book.manager');
    $book_manager->deleteFromBook($node->book['nid']);
496
497
498
499
  }
}

/**
500
 * Implements hook_node_prepare_form().
501
 */
502
function book_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
503
  // Get BookManager service
504
  $book_manager = \Drupal::service('book.manager');
505

506
  // Prepare defaults for the add/edit form.
507
  $account = \Drupal::currentUser();
508
  if (empty($node->book) && ($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines'))) {
509
510
    $node->book = array();

511
    $query = \Drupal::request()->query;
512
    if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) {
513
      // Handle "Add child page" links:
514
      $parent = $book_manager->loadBookLink($query->get('parent'), TRUE);
515
516
517

      if ($parent && $parent['access']) {
        $node->book['bid'] = $parent['bid'];
518
        $node->book['pid'] = $parent['nid'];
519
      }
520
521
    }
    // Set defaults.
522
523
    $node_ref = !$node->isNew() ? $node->id() : 'new';
    $node->book += $book_manager->getLinkDefaults($node_ref);
524
525
526
527
528
529
530
531
  }
  else {
    if (isset($node->book['bid']) && !isset($node->book['original_bid'])) {
      $node->book['original_bid'] = $node->book['bid'];
    }
  }
  // Find the depth limit for the parent select.
  if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) {
532
    $node->book['parent_depth_limit'] = $book_manager->getParentDepthLimit($node->book);
Dries's avatar
Dries committed
533
534
535
536
  }
}

/**
537
 * Implements hook_form_FORM_ID_alter() for node_delete_confirm().
538
539
540
541
 *
 * Alters the confirm form for a single node deletion.
 *
 * @see node_delete_confirm()
Dries's avatar
Dries committed
542
543
544
545
546
547
 */
function book_form_node_delete_confirm_alter(&$form, $form_state) {
  $node = node_load($form['nid']['#value']);

  if (isset($node->book) && $node->book['has_children']) {
    $form['book_warning'] = array(
548
      '#markup' => '<p>' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())) . '</p>',
Dries's avatar
Dries committed
549
550
      '#weight' => -10,
    );
Dries's avatar
   
Dries committed
551
  }
Dries's avatar
   
Dries committed
552
}
Dries's avatar
   
Dries committed
553

554
/**
555
 * Implements hook_preprocess_HOOK() for block templates.
556
557
 */
function book_preprocess_block(&$variables) {
558
  if ($variables['configuration']['module'] == 'book') {
559
    $variables['attributes']['role'] = 'navigation';
560
561
562
  }
}

563
/**
564
565
566
 * Prepares variables for book listing block templates.
 *
 * Default template: book-all-books-block.html.twig.
567
 *
568
569
570
 * All non-renderable elements are removed so that the template has full access
 * to the structured data but can also simply iterate over all elements and
 * render them (as in the default template).
571
 *
572
573
 * @param array $variables
 *   An associative array containing the following key:
574
575
 *   - book_menus: An associative array containing renderable menu links for all
 *     book menus.
576
577
578
579
580
581
582
583
584
585
 */
function template_preprocess_book_all_books_block(&$variables) {
  // Remove all non-renderable elements.
  $elements = $variables['book_menus'];
  $variables['book_menus'] = array();
  foreach (element_children($elements) as $index) {
    $variables['book_menus'][$index] = $elements[$index];
  }
}

Dries's avatar
Dries committed
586
/**
587
588
589
 * Prepares variables for book navigation templates.
 *
 * Default template: book-navigation.html.twig.
Dries's avatar
Dries committed
590
 *
591
592
 * @param array $variables
 *   An associative array containing the following key:
593
 *   - book_link: An associative array of book link properties.
594
 *     Properties used: bid, link_title, depth, pid, nid.
595
 */
596
597
598
599
600
function template_preprocess_book_navigation(&$variables) {
  $book_link = $variables['book_link'];

  // Provide extra variables for themers. Not needed by default.
  $variables['book_id'] = $book_link['bid'];
601
  $variables['book_title'] = String::checkPlain($book_link['link_title']);
602
  $variables['book_url'] = \Drupal::url('node.view', array('node' => $book_link['bid']));
603
604
  $variables['current_depth'] = $book_link['depth'];
  $variables['tree'] = '';
605

606
  if ($book_link['nid']) {
607
    $variables['tree'] = book_children($book_link);
Dries's avatar
   
Dries committed
608

609
610
    $build = array();

Dries's avatar
Dries committed
611
    if ($prev = book_prev($book_link)) {
612
      $prev_href = \Drupal::url('node.view', array('node' => $prev['nid']));
613
614
615
616
      $build['#attached']['drupal_add_html_head_link'][][] = array(
        'rel' => 'prev',
        'href' => $prev_href,
      );
617
      $variables['prev_url'] = $prev_href;
618
      $variables['prev_title'] = String::checkPlain($prev['title']);
Dries's avatar
   
Dries committed
619
    }
620

621
622
623
624
    /** @var \Drupal\book\BookManagerInterface $book_manager */
    $book_manager = \Drupal::service('book.manager');
    if ($book_link['pid'] && $parent = $book_manager->loadBookLink($book_link['pid'])) {
      $parent_href = \Drupal::url('node.view', array('node' => $book_link['pid']));
625
626
627
628
      $build['#attached']['drupal_add_html_head_link'][][] = array(
        'rel' => 'up',
        'href' => $parent_href,
      );
629
      $variables['parent_url'] = $parent_href;
630
      $variables['parent_title'] = String::checkPlain($parent['title']);
Dries's avatar
   
Dries committed
631
    }
632

Dries's avatar
Dries committed
633
    if ($next = book_next($book_link)) {
634
      $next_href = \Drupal::url('node.view', array('node' => $next['nid']));
635
636
637
638
      $build['#attached']['drupal_add_html_head_link'][][] = array(
        'rel' => 'next',
        'href' => $next_href,
      );
639
      $variables['next_url'] = $next_href;
640
      $variables['next_title'] = String::checkPlain($next['title']);
Dries's avatar
   
Dries committed
641
    }
642
  }
Dries's avatar
   
Dries committed
643

644
645
646
647
  if (!empty($build)) {
    drupal_render($build);
  }

648
649
650
651
652
653
654
655
656
657
658
  $variables['has_links'] = FALSE;
  // Link variables to filter for values and set state of the flag variable.
  $links = array('prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title');
  foreach ($links as $link) {
    if (isset($variables[$link])) {
      // Flag when there is a value.
      $variables['has_links'] = TRUE;
    }
    else {
      // Set empty to prevent notices.
      $variables[$link] = '';
659
    }
Dries's avatar
   
Dries committed
660
  }
Dries's avatar
   
Dries committed
661
}
Dries's avatar
 
Dries committed
662

663
/**
664
 * Prepares variables for book export templates.
665
 *
666
 * Default template: book-export-html.html.twig.
667
 *
668
669
670
671
672
 * @param array $variables
 *   An associative array containing:
 *   - title: The title of the book.
 *   - contents: Output of each book page.
 *   - depth: The max depth of the book.
673
674
 */
function template_preprocess_book_export_html(&$variables) {
675
  global $base_url;
676
  $language_interface = \Drupal::languageManager()->getCurrentLanguage();
677

678
  $variables['title'] = String::checkPlain($variables['title']);
679
  $variables['base_url'] = $base_url;
680
  $variables['language'] = $language_interface;
681
  $variables['language_rtl'] = ($language_interface->direction == Language::DIRECTION_RTL);
682
  $variables['head'] = drupal_get_html_head();
683
684

  // HTML element attributes.
685
  $attributes = array();
686
  $attributes['lang'] = $language_interface->id;
687
688
  $attributes['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
  $variables['html_attributes'] = new Attribute($attributes);
689
690
}

691
/**
692
693
694
 * Prepares variables for single node export templates.
 *
 * Default template: book-node-export-html.html.twig.
Dries's avatar
Dries committed
695
 *
696
697
 * @param array $variables
 *   An associative array containing the following keys:
698
699
700
 *   - node: The node that will be output.
 *   - children: All the rendered child nodes within the current node. Defaults
 *     to an empty string.
701
 */
702
703
function template_preprocess_book_node_export_html(&$variables) {
  $variables['depth'] = $variables['node']->book['depth'];
704
  $variables['title'] = String::checkPlain($variables['node']->label());
705
  $variables['content'] = $variables['node']->rendered;
706
707
}

708
709
710
711
712
713
714
/**
 * Implements template_preprocess_HOOK() for theme_book_tree().
 */
function template_preprocess_book_tree(&$variables) {
  $variables['tree'] = $variables['tree']['#children'];
}

715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
/**
 * Returns HTML for a book link and subtree.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: Structured array data for a book link.
 *
 * @ingroup themeable
 */
function theme_book_link(array $variables) {
  $element = $variables['element'];
  $sub_menu = '';

  if ($element['#below']) {
    $sub_menu = drupal_render($element['#below']);
  }
  $element['#localized_options']['set_active_class'] = TRUE;
  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
  return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
}

736
/**
737
 * Determines if a given node type is in the list of types allowed for books.
738
739
740
741
742
743
 *
 * @param string $type
 *   A node type.
 *
 * @return bool
 *   A Boolean TRUE if the node type can be included in books; otherwise, FALSE.
744
 */
Dries's avatar
Dries committed
745
function book_type_is_allowed($type) {
746
  return in_array($type, \Drupal::config('book.settings')->get('allowed_types'));
747
748
}

Dries's avatar
Dries committed
749
/**
750
 * Implements hook_node_type_update().
Dries's avatar
Dries committed
751
 *
752
753
 * Updates book.settings configuration object if the machine-readable name of a
 * node type is changed.
Dries's avatar
Dries committed
754
 */
755
function book_node_type_update(NodeTypeInterface $type) {
756
  if ($type->getOriginalId() != $type->id()) {
757
    $config = \Drupal::config('book.settings');
758
    // Update the list of node types that are allowed to be added to books.
759
    $allowed_types = $config->get('allowed_types');
760
    $old_key = array_search($type->getOriginalId(), $allowed_types);
761

762
    if ($old_key !== FALSE) {
763
      $allowed_types[$old_key] = $type->id();
764
      // Ensure that the allowed_types array is sorted consistently.
765
      // @see BookSettingsForm::submitForm()
766
      sort($allowed_types);
767
      $config->set('allowed_types', $allowed_types);
768
769
770
    }

    // Update the setting for the "Add child page" link.
771
    if ($config->get('child_type') == $type->getOriginalId()) {
772
      $config->set('child_type', $type->id());
773
    }
774
    $config->save();
Dries's avatar
Dries committed
775
  }
Dries's avatar
   
Dries committed
776
}