/[drupal]/contributions/modules/outline/outline.module
ViewVC logotype

Contents of /contributions/modules/outline/outline.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.58 - (show annotations) (download) (as text)
Mon May 4 21:35:56 2009 UTC (6 months, 3 weeks ago) by cvsadmin
Branch: MAIN
CVS Tags: HEAD
Changes since 1.57: +1 -28 lines
File MIME type: text/x-php
task #350303: Move license and donation information to places fitting with cvs.drupal.org guidelines http://drupal.org/node/439226
1 <?php
2 // $Id: outline.module,v 1.57 2009/04/07 09:11:14 augustin Exp $
3
4 /**
5 * @file
6 * A module to enhance the core book.module, adding more customability and flexibility.
7 *
8 */
9
10 // functions are in this order:
11 ////// GENERAL HOOKS /////////////////////////////////////////////////////////////////////////////////////
12 ////// FORM HANDLING /////////////////////////////////////////////////////////////////////////////////////
13 ////// OWN FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////
14 ////// MENU CALLBACKS ////////////////////////////////////////////////////////////////////////////////////
15 ////// THEME FUNCTIONS ///////////////////////////////////////////////////////////////////////////////////
16
17
18
19
20
21 //////////////////////////////////////////////////////////////////////////////////////////////////////////
22 ////// GENERAL HOOKS /////////////////////////////////////////////////////////////////////////////////////
23 //////////////////////////////////////////////////////////////////////////////////////////////////////////
24 ////// Listed in alphabetical order //////////////////////////////////////////////////////////////////////
25 //////////////////////////////////////////////////////////////////////////////////////////////////////////
26
27 /**
28 * Implementation of hook_form_alter().
29 *
30 * @see book_form_alter()
31 */
32 function outline_form_alter(&$form, $form_state, $form_id) {
33
34 // Site wide settings.
35 if ('book_admin_settings' == $form_id) {
36 $form['book_child_type']['#description'] .= '<br /><strong>' . t('This setting is the site-wide default. Outline.module allows you to have a per-book default and a per node value.') . '</strong>';
37 }
38
39 $node_form = FALSE;
40 // Add or remove node form items according to outline perm settings.
41 if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
42 $node_form = TRUE;
43 $node = $form['#node'];
44 if(isset($node->nid)) {
45 _outline_load_node($node);
46 }
47 $book_action = user_access('administer book outlines');
48 if (user_access('add content to books') && ((!empty($node->book['mlid']) && !empty($node->nid))
49 || book_type_is_allowed($node->type))) {
50 $book_action = TRUE;
51 }
52 $action = outline_check_action($node, 'node_form', $book_action);
53 switch ($action) {
54 case 'create':
55 // Copied from book_form_alter();
56 _book_add_form_elements($form, $node);
57 $form['book']['pick-book'] = array(
58 '#type' => 'submit',
59 '#value' => t('Change book (update list of parents)'),
60 '#submit' => array('node_form_submit_build_node'),
61 '#weight' => 20,
62 );
63 break;
64 case 'delete':
65 unset($form['book']);
66 // fallthrough to set default values.
67 case 'leave off':
68 if (!empty($node->outline)) { // The book is outlined, but the user has no permission to change the book settings.
69 $form['bid'] = array(
70 '#type' => 'value',
71 '#value' => $node->book['bid'],
72 );
73 $form['mlid'] = array(
74 '#type' => 'value',
75 '#value' => $node->book['mlid'],
76 );
77 }
78 break;
79 }
80 if (!empty($form['book'])) {
81 $form['book']['bid']['#options'] = _outline_book_options($node);
82 }
83
84 }
85
86 // Node outline tab and node form.
87 if (!empty($form['book']) && ('book_outline_form' == $form_id // Form on node outline tab, or (below) form on node edit tab.
88 || $node_form)) {
89
90 $access = TRUE; // TODO. See book_form_alter().
91 if ($access) {
92 // We replace the ahah callback with our own.
93 $form['book']['bid']['#ahah']['path'] = 'outline/js/form';
94 $form['book']['bid']['#ahah']['wrapper'] = 'outline-ahah-wrapper';
95 // The weights are added to make sure the wrapper includes only and all what we want.
96 $form['book']['plid']['#weight'] = 11;
97
98 $form['book']['outline-ahah-wrapper-begin'] = array(
99 '#value' => ' ',
100 '#prefix' => '<div id="outline-ahah-wrapper">',
101 '#weight' => 10,
102 );
103 $form['book']['outline-ahah-wrapper-end'] = array(
104 '#value' => ' ',
105 '#suffix' => '</div>',
106 '#weight' => 12,
107 );
108 // Add our own form elements.
109 $bid = isset($form['#node']->book['bid']) ? $form['#node']->book['bid'] : 0;
110 outline_add_elements_form($form, $bid);
111 }
112 }
113
114 // In addition to the fields above, we need to add a submit handler for the form on the outline tab.
115 if ('book_outline_form' == $form_id) {
116 $form['#submit'][] = 'outline_tab_form_submit';
117 }
118
119 }
120
121 /**
122 * Implementation of hook_help().
123 */
124 function outline_help($path, $arg) {
125 // TODO: http://drupal.org/node/114774#hook-help
126 switch ($path) {
127 case 'admin/help#outline':
128 return t('<p>Outline module enhances the functionality of book.module. It allows to have a more fine grained customization of each book, adding more settings and permissions to it.</p>');
129
130 case 'admin/content/book/settings':
131 return t('<p>You may also look at the <a href="!outline-setting-url">outline.module setting page</a> for more relevant settings.</p>', array('!outline-setting-url' => url('admin/settings/outline')));
132
133 case 'admin/content/book/%':
134 return t('See the menu setting to insert other pages into menu outline.');
135
136 case 'admin/build/menu-customize/%':
137 // TODO: if (% is_book outline) ...
138 return t('check the book setting.');
139
140 case 'admin/settings/outline':
141 return t('<p>You may also look at the <a href="!book-setting-url">book.module setting page</a> for more relevant settings.</p>', array('!book-setting-url' => url('admin/content/book/settings')));
142
143 }
144 }
145
146 /**
147 * Implementation of hook_init().
148 */
149 function outline_init() {
150 drupal_add_css(drupal_get_path('module', 'outline') .'/stylesheet.css');
151 }
152
153 /**
154 * Implementation of hook_link().
155 */
156 function outline_link($type, $node = NULL, $teaser = FALSE) {
157 global $user;
158 $links = array();
159
160 if ('node' == $type && isset($node->book)) {
161 $bid = $node->outline['bid'];
162 if (!$teaser) {
163 $action = outline_check_perm($node, FALSE, 'add');
164 if ($action) {
165 if (outline_check_author_perm($bid, $user->uid)) {
166 $child_type = variable_get('book_child_type', 'book');
167 $node_type = $node->outline['books'][$bid]['child_type'];
168 $child_type_book_default = $node->outline['books'][$bid]['default_child_type'];
169 if ('<default>' != $node_type) { // A node defining its own child type.
170 $child_type = $node_type;
171 }
172 elseif ('<default>' != $child_type_book_default) {
173 $child_type = $child_type_book_default;
174 }
175
176 if (node_access('create', $child_type) && 1 == $node->status && $node->book['depth'] < MENU_MAX_DEPTH) {
177 $links['outline_add_child'] = array(
178 'title' => t('Add child !node_type', array( '!node_type' => node_get_types('name', $child_type))),
179 'href' => "node/add/". str_replace('_', '-', $child_type),
180 'query' => "parent=". $node->book['mlid'],
181 );
182 }
183 }
184 }
185 }
186 }
187 return $links;
188 }
189
190 /**
191 * Implementation of hook_link_alter().
192 */
193 function outline_link_alter(&$links, $node) {
194 // Since hook_link_alter is called several times, it's easier to unset the book_add_child link
195 // which has already been replaced by our own in outline_link().
196 if (isset($links['book_add_child'])) {
197 unset($links['book_add_child']);
198 }
199 }
200
201 /**
202 * Implementation of hook_menu().
203 */
204 function outline_menu() {
205 $items = array();
206
207 $items['admin/settings/outline'] = array(
208 'title' => 'Outline settings',
209 'description' => 'Set the site-wide default the TOC depth.',
210 'page callback' => 'drupal_get_form',
211 'page arguments' => array('outline_admin_settings'),
212 'access arguments' => array('administer book outlines'));
213 $items['outline/js/form'] = array(
214 'page callback' => 'outline_form_update',
215 'access arguments' => array('access content'),
216 'type' => MENU_CALLBACK,
217 );
218 $items['admin/content/book'] = array(
219 'title' => 'Books',
220 'description' => "Manage your site's book outlines.",
221 'page callback' => 'outline_admin_overview',
222 'access arguments' => array('administer book outlines'),
223 );
224 $items['admin/content/book/%node/permission'] = array(
225 'title' => 'Edit the book permissions',
226 'page callback' => 'drupal_get_form',
227 'page arguments' => array('outline_permission_form', 3),
228 'access callback' => '_book_outline_access',
229 'access arguments' => array(3),
230 'type' => MENU_CALLBACK,
231 );
232
233 return $items;
234 }
235
236 /**
237 * Implementation of hook_nodeapi().
238 */
239 function outline_nodeapi(&$node, $op, $teaser, $page) {
240 global $user;
241
242 switch ($op) {
243 case 'delete revision':
244 db_query('DELETE FROM {outline_book} WHERE book_vid = %d', $node->vid);
245 db_query('DELETE FROM {outline_node} WHERE vid = %d', $node->vid);
246 break;
247 case 'load':
248 // If we need, we can completely override the book meta data, thereby controlling which book navigation it will show. // TODO
249
250 $info = NULL;
251 $result = db_query('SELECT n.*, b.book_vid, b.uid, b.default_child_type, b.default_toc_depth, b.book_role_perm, b.book_user_perm, b.restricted_types
252 FROM {outline_node} n JOIN {outline_book} b ON n.book_vid = b.book_vid WHERE nid = %d AND vid = %d ORDER BY is_default DESC',
253 $node->nid, $node->vid);
254 while ($row = db_fetch_array($result)) {
255 if ($row['is_default']) {
256 $info['outline']['bid'] = $row['bid'];
257 }
258 $info['outline']['books'][$row['bid']] = $row;
259 }
260 return $info;
261 case 'prepare':
262 // Prepare defaults in cases they are needed but book.module didn't prepare them.
263 // Why doesn't book.module prepare the node in every case, regardless to permissions?
264 // Is there any side effect to it?
265 if (empty($node->nid) && isset($_GET['parent']) && is_numeric($_GET['parent'])) {
266 // Load outline property to $node object.
267 $parent = book_link_load($_GET['parent']);
268 $result = db_query('SELECT b.* FROM {outline_book} b JOIN {node} n ON b.bid = n.nid
269 WHERE b.bid = %d AND n.vid = b.book_vid', $parent['bid']);
270 $node->outline = array();
271 $row = db_fetch_array($result);
272 $node->outline['bid'] = $row['bid'];
273 $node->outline['books'][$row['bid']] = $row;
274 }
275
276 if (empty($node->book) && !user_access('add content to books') && !user_access('administer book outlines')) {
277 $node->book = array();
278 if (empty($node->nid) && isset($_GET['parent']) && is_numeric($_GET['parent'])) {
279 // Handle "Add child page" links:
280 $parent = book_link_load($_GET['parent']);
281 if ($parent && $parent['access']) {
282 $node->book['bid'] = $parent['bid'];
283 $node->book['plid'] = $parent['mlid'];
284 $node->book['menu_name'] = $parent['menu_name'];
285 }
286 }
287 // Set defaults.
288 $node->book += _book_link_defaults(!empty($node->nid) ? $node->nid : 'new');
289 }
290 else {
291 if (isset($node->book['bid']) && !isset($node->book['original_bid'])) {
292 $node->book['original_bid'] = $node->book['bid'];
293 }
294 }
295 // Find the depth limit for the parent select.
296 if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) {
297 $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book);
298 }
299 break;
300
301 case 'view':
302 if (!$teaser) {
303 if (!empty($node->book['bid']) && NODE_BUILD_NORMAL == $node->build_mode) {
304 $node->content['outline_navigation'] = array(
305 '#value' => theme('outline_navigation', $node),
306 '#weight' => 101,
307 );
308
309 }
310 }
311 break;
312 case 'update':
313 case 'insert':
314 _outline_update_outline($node);
315 break;
316 }
317 }
318
319 /**
320 * Implementation of hook_perm().
321 */
322 function outline_perm() {
323 //return array('administer outlines', 'outline posts', 'create new volumes', 'see printer-friendly version');
324 }
325
326 /**
327 * Implementation of hook_theme()
328 */
329 function outline_theme() {
330 return array(
331 'outline_navigation' => array(
332 'arguments' => array('node' => NULL),
333 'template' => 'Outline-navigation', // It is modified from the original book-navigation template.
334 ),
335 );
336 }
337
338 /**
339 * Implementation of hook_theme_registry_alter().
340 */
341 function outline_theme_registry_alter(&$theme_registry) {
342 // We cannot use this function to alter the book_navigation theme registry,
343 // because otherwise book.module would call template_preprocess_outline_navigation
344 // with the wrong argument.
345 // We must completely obliterate it instead.
346 $theme_registry['book_navigation'] = NULL;
347 }
348
349 //////////////////////////////////////////////////////////////////////////////////////////////////////////
350 ////// FORM HANDLING /////////////////////////////////////////////////////////////////////////////////////
351 //////////////////////////////////////////////////////////////////////////////////////////////////////////
352
353 /**
354 * Form builder: edit the book outline permissions.
355 *
356 */
357 function outline_permission_form(&$form_state, $node) {
358 $bid = $node->book['bid'];
359 if ($node->nid != $bid) {
360 drupal_goto('admin/content/book/' . $bid . '/permission');
361 }
362 drupal_set_title(t('Outline permissions for %book', array('%book' => $node->title)));
363 $form = array();
364
365 $breadcrumb = array();
366 $breadcrumb[] = l(t('Home'), NULL);
367 $breadcrumb[] = l(t('Administer'), 'admin');
368 $breadcrumb[] = l(t('Content management'), 'admin/content');
369 $breadcrumb[] = l(t('Books'), 'admin/content/book');
370 drupal_set_breadcrumb($breadcrumb);
371
372 $perms = array(
373 'add' => array(
374 'title' => t('Add content'),
375 'description' => t('Grant the permission to add or move nodes into this book.'),
376 'book_perm' => t('add content to books'),
377 'per_role_perm' => TRUE,
378 ),
379 'author' => array(
380 'title' => t('Authorship'),
381 'description' => t('If any user is set below, only nodes authored by these users can be added to the book.
382 Thus, you can have a single author or a multi-authors book, where the authors are specified.') . ' ' .
383 t('Make sure that the authors themselves have the right to add nodes to their own book!') . '<br />' .
384 t('If no user is specified, nodes authored by anybody can be added to the outline.') . '<br />' .
385 t('Users with the %perm can add any node authored by anybody in any case.', array('%perm' => t('administer book outlines'))),
386 'book_perm' => t('add content to books'),
387 'per_role_perm' => FALSE,
388 ),
389 );
390
391 $form['bid'] = array(
392 '#type' => 'value',
393 '#value' => $bid,
394 );
395
396 $form['vid'] = array(
397 '#type' => 'value',
398 '#value' => $node->vid,
399 );
400
401 $restrict_per_role = $node->outline['books'][$bid]['book_role_perm'];
402
403 $form['restrict_per_role_previous_value'] = array(
404 '#type' => 'value',
405 '#value' => $restrict_per_role,
406 );
407
408 $form['restrict_per_role'] = array(
409 '#type' => 'checkbox',
410 '#title' => t('Restrict to specific roles.'),
411 '#default_value' => $restrict_per_role,
412 '#description' => t('Check this box to restrict or expand all the permissions below to specific roles for this book.') . '<br />' .
413 t('Uncheck it to remove all such restrictions and use the book module default permissions.'),
414 );
415
416 $form['perm'] = array(
417 '#type' => 'fieldset',
418 '#title' => t('Permissions for this book'),
419 '#collapsible' => FALSE,
420 '#tree' => TRUE,
421 );
422
423 foreach ($perms AS $name => $perm) {
424 $form['perm'][$name] = array(
425 '#type' => 'fieldset',
426 '#title' => $perm['title'],
427 '#collapsible' => TRUE,
428 '#collapsed' => FALSE, //TRUE,
429 '#tree' => TRUE,
430 );
431
432 $form['perm'][$name]['description'] = array(
433 '#value' => '<p>' . $perm['description'] . '</p>',
434 );
435
436 // Per ROLE form elements.
437
438 if ($restrict_per_role && $perm['per_role_perm']) {
439 $roles = user_roles(TRUE);
440 $restrict_per_role_description = '<p>' .
441 t('Currently only the users who have the role listed above have this permission for this book.') . '<br />' .
442 t('Uncheck the %restrict box if you want to use the book module defaults instead.',
443 array('%restrict' => t('Restrict to specific roles.')))
444 . '</p>';
445 $role_default_value = array();
446 $result = db_query("SELECT * FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'role'", $bid, $name);
447 while ($role = db_fetch_object($result)) {
448 $role_default_value[$role->type_id] = $role->type_id;
449 }
450 }
451 elseif ($perm['per_role_perm']) {
452 $roles = array();
453 $role_default_value = array();
454 $restrict_per_role_description = '<p>' . t('Currently all users with the %perm have this permission for this book.<br />
455 Check the %restrict box if you want to restrict or expand the permissions to specific roles.',
456 array('%perm' => $perm['book_perm'], '%restrict' => t('Restrict to specific roles.'))) . '</p>';
457 }
458
459 if ($perm['per_role_perm']) {
460 $form['perm'][$name]['roles'] = array(
461 '#type' => 'checkboxes',
462 '#title' => t('Roles'),
463 '#default_value' => $role_default_value,
464 '#options' => $roles,
465 '#description' => $restrict_per_role_description,
466 );
467 }
468 else {
469 $form['perm'][$name]['roles'] = array(
470 '#type' => 'value',
471 '#value' => FALSE,
472 );
473 }
474
475 // Per USER form elements.
476
477 $users = array();
478 $user_default_value = array();
479 $result = db_query("SELECT * FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'user'",
480 $bid, $name);
481 while ($item = db_fetch_object($result)) {
482 $user = user_load(array('uid' => $item->type_id));
483 $users[$user->uid] = $user->name;
484 $user_default_value[$user->uid] = $user->uid;
485 }
486 $description = t('Uncheck a user name to revoke the permission.');
487 if (empty($users)) {
488 $description = t('No individual user have this permission.');
489 }
490
491 $form['perm'][$name]['users'] = array(
492 '#type' => 'checkboxes',
493 '#title' => t('Users'),
494 '#default_value' => array(),
495 '#options' => $users,
496 '#description' => $description,
497 '#default_value' => $user_default_value,
498 );
499
500 $form['perm'][$name]['new_user'] = array(
501 '#type' => 'textfield',
502 '#title' => t('Add a new user'),
503 '#description' => t('Individually selected users will be given this additional permission regardless of their roles or other permissions.'),
504 '#default_value' => '',
505 '#autocomplete_path' => 'user/autocomplete',
506 );
507 }
508
509
510 $form['submit'] = array(
511 '#type' => 'submit',
512 '#value' => t('Submit'),
513 );
514
515 return $form;
516 }
517
518 /**
519 * Form submit callback.
520 *
521 */
522 function outline_permission_form_submit($form, &$form_state) {
523 $v = $form_state['values'];
524 $book_user_perm = FALSE;
525
526 foreach ($v['perm'] AS $perm_name => $perm) {
527
528 // Save permissions per role:
529 if ($v['restrict_per_role_previous_value'] && FALSE !== $perm['roles']) {
530 db_query("DELETE FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'role'", $v['bid'], $perm_name);
531 foreach ($perm['roles'] AS $role_id) {
532 if ($role_id) {
533 db_query("INSERT INTO {outline_perm} (bid, perm, type, type_id) VALUES (%d, '%s', 'role', %d)",
534 $v['bid'], $perm_name, $role_id);
535 }
536 }
537 }
538
539 // Save permissions per user:
540 db_query("DELETE FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'user'", $v['bid'], $perm_name);
541 if ($perm['new_user']) {
542 $user = user_load(array('name' => $perm['new_user']));
543 if ('author' != $perm_name) {
544 $book_user_perm = TRUE;
545 }
546 db_query("INSERT INTO {outline_perm} (bid, perm, type, type_id) VALUES (%d, '%s', 'user', %d)",
547 $v['bid'], $perm_name, $user->uid);
548 }
549 foreach ($perm['users'] AS $user_id) {
550 if ($user_id) {
551 if ('author' != $perm_name) {
552 $book_user_perm = TRUE;
553 }
554 db_query("INSERT INTO {outline_perm} (bid, perm, type, type_id) VALUES (%d, '%s', 'user', %d)",
555 $v['bid'], $perm_name, $user_id);
556 }
557 }
558
559 }
560
561 db_query('UPDATE {outline_book} SET book_role_perm = %d, book_user_perm = %d WHERE bid = %d AND book_vid = %d',
562 $v['restrict_per_role'], $book_user_perm, $v['bid'], $v['vid']);
563 drupal_set_message(t('The permissions have been saved'), 'status');
564 }
565
566 /**
567 * Submit handler for the form on the node outline tab.
568 *
569 * See also related handling in outline_nodeapi().
570 */
571 function outline_tab_form_submit($form, &$form_state) {
572 $node = $form['#node'];
573 _outline_update_outline($node);
574 }
575
576 /**
577 * Common helper fonction for outline_tab_form_submit() and node forms.
578 *
579 * Performs all additions and updates to the book outline through node addition,
580 * node editing, node deletion, or the outline tab.
581 * See _book_update_outline().
582 *
583 * This function has to catter for all the following scenarios (list non exhaustive):
584 *
585 * - A node is created, but not outlined:
586 * $node->is_new = 1;
587 * $node->book['bid'] = 0;
588 * $node->revision = 0;
589 * $node->outline = '';
590 * -> do nothing.
591 *
592 * - A non-outlined node is edited and is used to create a new book without a revision:
593 * $node->is_new = '';
594 * $node->book['bid'] = $node->nid;
595 * $node->revision = 0;
596 * $node->outline = '';
597 * -> INSERT.
598 *
599 * - A non-outlined node is edited and is outlined in an existing book without a revision:
600 * $node->is_new = '';
601 * $node->book['bid'] = $bid; // i.e. !empty().
602 * $node->revision = 0;
603 * $node->outline = '';
604 * -> INSERT.
605 *
606 * - An outlined node is edited without a revision:
607 * $node->is_new = '';
608 * $node->book['bid'] = $bid; // i.e. !empty().
609 * $node->revision = 0;
610 * -> UPDATE.
611 *
612 */
613 function _outline_update_outline($node) {
614 // Check whether there exist an outline record for this node.
615 // $node->outline is passed empty, so we set it here.
616 _outline_load_node($node);
617
618 $bid = FALSE;
619 if (!empty($node->book['bid'])) { // The book form was set.
620 $bid = $node->book['bid'];
621 $mlid = $node->book['mlid'];
622 $o = $node->outline;
623 }
624 elseif (!empty($node->bid)) { // There was no book form. Get values if applicable.
625 $bid = $node->bid;
626 $mlid = $node->mlid;
627 $o = $node->outline;
628 }
629
630 // Set defaults for users without proper permissions:
631 if ($bid && !isset($node->book['outline'])) {
632 // TODO: bugfix use proper default values.
633 $b = array();
634 $b['child_type'] = isset($o['books'][$bid]['child_type']) ? $o['books'][$bid]['child_type'] : '<default>';
635 $b['toc_depth'] = isset($o['books'][$bid]['toc_depth']) ? $o['books'][$bid]['toc_depth'] : -1;
636 $b['book_toc_depth'] = isset($o['books'][$bid]['default_toc_depth']) ? $o['books'][$bid]['default_toc_depth'] : -1;
637 }
638 elseif (isset($node->book['outline'])) {
639 $b = $node->book['outline'];
640 }
641 else { // Nothing to do.
642 return;
643 }
644
645 // Update an existing page which is already outlined, and without making a new revision:
646 if ($bid && empty($node->is_new) && empty($node->revision) && !empty($node->outline)) {
647 // The vid doesn't change.
648 if ($bid == $node->nid) { // We are updating a book cover.
649 db_query("UPDATE {outline_book}
650 SET uid = %d, default_child_type = '%s', default_toc_depth = %d, book_role_perm = %d, book_user_perm = %d
651 WHERE book_vid = %d",
652 $node->uid, $b['child_type'], $b['book_toc_depth'], $o['books'][$bid]['book_role_perm'],
653 $o['books'][$bid]['book_user_perm'], $node->vid);
654 db_query("UPDATE {outline_node} SET mlid = %d, child_type = '%s', toc_depth = %d WHERE vid = %d",
655 $mlid, $b['child_type'], $b['toc_depth'], $node->vid);
656 }
657 elseif (isset($node->outline)) { // We are upating a page in the book.
658 db_query("UPDATE {outline_node} SET mlid = %d, child_type = '%s', toc_depth = %d WHERE vid = %d",
659 $mlid, $b['child_type'], $b['toc_depth'], $node->vid);
660 }
661 return; // No INSERT to be done in this case.
662 }
663
664 // Create a new page, or insert a new revision, or insert a previously not outlined node:
665 if ($bid) { // The page is added to a book.
666 if ($bid == $node->nid) { // We are creating a new book.
667 db_query("INSERT INTO {outline_book} (bid, book_vid, uid, default_child_type, default_toc_depth, book_role_perm, book_user_perm)
668 VALUES (%d, %d, %d, '%s', '%s', %d, %d)",
669 $bid, $node->vid, $node->uid, $b['child_type'], $b['book_toc_depth'], $o['books'][$bid]['book_role_perm'],
670 $o['books'][$bid]['book_user_perm']);
671 db_query("INSERT INTO {outline_node} (mlid, nid, vid, bid, book_vid, is_default, child_type, toc_depth)
672 VALUES (%d, %d, %d, %d, %d, 1, '%s', %d)",
673 $mlid, $node->nid, $node->vid, $bid, $node->vid, $b['child_type'], $b['toc_depth']);
674 // Update the vid in {outline_node}, so that the JOIN is up to date.
675 db_query('UPDATE {outline_node} SET book_vid = %d WHERE bid = %d', $node->vid, $node->nid);
676 }
677 else {
678 $book_vid = db_result(db_query('SELECT vid FROM {node} WHERE nid = %d', $bid));
679 db_query("INSERT INTO {outline_node} (mlid, nid, vid, bid, book_vid, is_default, child_type, toc_depth)
680 VALUES (%d, %d, %d, %d, %d, 1, '%s', %d)",
681 $mlid, $node->nid, $node->vid, $bid, $book_vid, $b['child_type'], $b['toc_depth']);
682
683 }
684 }
685 }
686
687 /**
688 * Form builder: add exta settings form elements to the node forms and on the outline tab.
689 *
690 * @param $bid: 0 if <none>, 'new' if <create a new book> or the bid of the chosen book.
691 */
692 function outline_add_elements_form(&$form, $bid) {
693 global $user;
694 $node = $form['#node'];
695
696 if (!$bid) { // we are not inserting the node in a book.
697 $form['book']['outline'] = array(); // Unset the outline form.
698 return;
699 }
700
701 $is_book_cover = FALSE;
702 if ('new' == $bid || (!empty($node->nid) && $bid == $node->nid)) {
703 $is_book_cover = TRUE;
704 }
705
706 $access = user_access('administer book outlines');
707 if (!$access) {
708 return;
709 }
710
711 $form['book']['outline'] = array(
712 '#type' => 'fieldset',
713 '#title' => t('Outline options'),
714 '#collapsible' => FALSE,
715 '#collapsed' => FALSE,
716 '#tree' => TRUE,
717 '#weight' => 11,
718 '#access' => $access,
719 );
720
721 //
722 // Child page node type.
723 //
724
725 $types = node_get_types();
726 $book_allowed_types = variable_get('book_allowed_types', array('book'));
727 $nt_options = array();
728 $nt_options['<default>'] = '<use the book default>';
729 foreach ($book_allowed_types as $nt) {
730 $nt_options[$nt] = $types[$nt]->name;
731 }
732 $site_default_type = variable_get('book_child_type', 'book');
733 $nt_description = t('What node type do you want to chose for child pages?');
734
735 if ($is_book_cover) {
736 $nt_description .= '<br />'. t('The site default is currenty %nt but it may be changed any time', array('%nt' => $site_default_type));
737 $nt_options['<default>'] = '<use the site default>';
738
739 } else {
740 $default_child_type = isset($node->outline['books'][$bid]['default_child_type'])
741 ? $node->outline['books'][$bid]['default_child_type'] : '<default>';
742 if ('<default>' == $default_child_type) {
743 $default_child_type = $site_default_type;
744 }
745 $nt_description .= '<br />'. t('The book default is currenty %nt but it may be changed any time', array('%nt' => $default_child_type));
746 }
747
748 $form['book']['outline']['child_type'] = array(
749 '#type' => 'select',
750 '#title' => t('Content type for child pages'),
751 '#description' => $nt_description,
752 '#default_value' => (empty($node->outline['books'][$bid]['child_type']) ? '<default>' : $node->outline['books'][$bid]['child_type']),
753 '#options' => $nt_options,
754 '#access' => $access,
755 );
756
757 //
758 // TOC depth for child pages:
759 //
760
761 $toc_options = drupal_map_assoc(array(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
762 $toc_options[-1] = '<use the site default>';
763 $toc_description = t('The table of contents on the outline page will be displayed to this depth (depending upon your theme).');
764 $site_default_toc_depth = variable_get('toc_default_depth', 5);
765
766 if ($is_book_cover) {
767 $book_toc_description = $toc_description;
768 $book_toc_description .= '<br />'. t('This value will apply for the whole book, though it can be overridden at the node level if wanted.');
769 $book_toc_description .= '<br />'. t('The site default is currenty %toc but it may be changed any time',
770 array('%toc' => $site_default_toc_depth));
771
772 $form['book']['outline']['book_toc_depth'] = array(
773 '#type' => 'select',
774 '#title' => t('Preferred depth for table of contents display <em>for child pages</em>'),
775 '#default_value' => (empty($node->outline['books'][$bid]['default_toc_depth']) ? -1 : $node->outline['books'][$bid]['default_toc_depth']),
776 '#options' => $toc_options,
777 '#description' => $book_toc_description,
778 '#access' => $access,
779 );
780
781 }
782
783 //
784 // TOC depth for each node:
785 //
786
787 $toc_options[-1] = '<use the book default>';
788
789 $default_toc_depth = isset($node->outline['books'][$bid]['default_toc_depth'])
790 ? $node->outline['books'][$bid]['default_toc_depth'] : '<default>';
791 if (-1 == $default_toc_depth) {
792 $default_toc_depth = $site_default_toc_depth;
793 }
794 $toc_description .= '<br />'. t('The book default is currenty %toc but it may be changed any time', array('%toc' => $default_toc_depth));
795
796 $form['book']['outline']['toc_depth'] = array(
797 '#type' => 'select',
798 '#title' => t('Preferred depth for table of contents display'),
799 '#default_value' => (empty($node->outline['books'][$bid]['toc_depth']) ? -1 : $node->outline['books'][$bid]['toc_depth']),
800 '#options' => $toc_options,
801 '#description' => $toc_description,
802 '#access' => $access,
803 );
804 }
805
806 /**
807 * Form builder, for the admin setting pages (book and outline's).
808 */
809 function outline_admin_settings_form() {
810
811 $description = t('This value will be overriden by a per-book specific setting, if there is one.');
812 $description .= t("This setting only makes a difference with outline's alternative navigation box.");
813 $form['toc_default_depth'] = array(
814 '#type' => 'select',
815 '#title' => t('Default depth for table of contents display for posts newly added to an outline'),
816 '#description' => $description,
817 '#default_value' => variable_get('toc_default_depth', 5),
818 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9)),
819 );
820
821 return $form;
822 }
823
824
825 //////////////////////////////////////////////////////////////////////////////////////////////////////////
826 ////// OWN FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////
827 //////////////////////////////////////////////////////////////////////////////////////////////////////////
828
829 /**
830 * Check if there are any authorships restriction to nodes added in outline.
831 *
832 * @param $uid int:
833 * $user->uid.
834 * @return bool:
835 * whether the user is allowed to perform the operation or not.
836 */
837 function outline_check_author_perm($bid, $uid) {
838
839 $count = db_result(db_query("SELECT COUNT(*) FROM {outline_perm} WHERE bid = %d AND perm = 'author' AND type = 'user'", $bid));
840 if (!$count) {
841 return TRUE; // There are no record, so no restriction apply.
842 }
843
844 $count = db_result(db_query("SELECT COUNT(*) FROM {outline_perm} WHERE bid = %d AND perm = 'author' AND type = 'user'
845 AND type_id = %d", $bid, $uid));
846 if ($count) {
847 return TRUE; // Nodes by this author are allowed.
848 }
849
850 return FALSE; // Nodes by this author are not allowed.
851 }
852
853 /**
854 * Returns an array of books a user can add or move nodes to.
855 *
856 * This array can be directly used in a form.
857 */
858 function _outline_book_options($node) {
859 global $user;
860 static $options;
861
862 if (isset($options)) {
863 return $options;
864 }
865
866 // See book.module book_form_alter().
867 $options = array();
868 $nid = isset($node->nid) ? $node->nid : 'new';
869
870 if (isset($node->nid) && ($nid == $node->book['original_bid']) && (0 == $node->book['parent_depth_limit'])) {
871 // This is the top level node in a maximum depth book and thus cannot be moved.
872 $options[$node->nid] = $node->title;
873 }
874 else {
875 foreach (book_get_books() as $book) {
876 $book_node = node_load($book['nid']);
877 if (outline_check_perm($book_node, user_access('add content to books'), 'add')) {
878 if ($book_node->book['bid'] == $node->book['original_bid']) { // We can leave the node in the current book.
879 $options[$book['nid']] = $book['title'];
880 }
881 elseif (outline_check_author_perm($book_node->book['bid'], $node->uid)) { // For other books, see authorship restrictions.
882 $options[$book['nid']] = $book['title'];
883 }
884 }
885 }
886 }
887 if (user_access('create new books') && ('new' == $nid || ($nid != $node->book['original_bid']))) {
888 // The node can become a new book, if it is not one already.
889 $options = array($nid => '<'. t('create a new book') .'>') + $options;
890 }
891 if (!$node->book['mlid']) {
892 // The node is not currently in a the hierarchy.
893 $options = array(0 => '<'. t('none') .'>') + $options;
894 }
895
896 // If 'none' is the only option, unset everything.
897 if (1 == count($options) && isset($options[0]) && '<'. t('none') .'>' == $options[0]) {
898 $options = array();
899 }
900 return $options;
901 }
902
903 /**
904 * Load the $node->outline property into the node object.
905 *
906 * The node object passed to some hooks are not fully loaded and do not contain the $node->outline property.
907 * We cannot use node_load() as it will override the original values of other $node properties.
908 */
909 function _outline_load_node(&$node) {
910 static $outline;
911
912 if (isset($outline) && empty($node->old_vid)) { // When creating a new revision, we must force a reload.
913 $node->outline = $outline;
914 }
915
916 if (empty($node->outline)) {
917 // When creating a new revision to a node, we need to check the record for the older revision.
918 $current_vid = $node->vid;
919 if (!empty($node->old_vid)) {
920 $node->vid = $node->old_vid;
921 }
922 $load_outline = outline_nodeapi($node, 'load', 0, 1);
923 $node->outline = $load_outline['outline'];
924 $outline = $load_outline['outline'];
925 $node->vid = $current_vid;
926 }
927 }
928
929 /**
930 * Check whether the user is permitted to perform the action or not.
931 *
932 * @param $outline_action (bool):
933 * It is actually the default book.module permission.
934 */
935 function outline_check_perm($node, $outline_action, $perm) {
936 global $user;
937 $bid = $node->outline['bid'];
938
939 if (user_access('administer book outlines')) {
940 $outline_action = TRUE;
941 }
942 elseif ($node->outline['books'][$bid]['book_role_perm']) {
943 $rids = array();
944 foreach ($user->roles AS $rid => $role) {
945 $rids[$rid] = $rid;
946 }
947 $args = array();
948 $args[] = $bid;
949 $args[] = 'add';
950 $args += $rids;
951 $count = db_result(db_query("SELECT COUNT(*) FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'role'
952 AND type_id IN (" . db_placeholders($rids) . ")", $args));
953 $outline_action = !empty($count);
954 }
955
956 if (!$outline_action && $node->outline['books'][$bid]['book_user_perm']) {
957 $count = db_result(db_query("SELECT COUNT(*) FROM {outline_perm} WHERE bid = %d AND perm = '%s' AND type = 'user'
958 AND type_id = %d", $bid, $perm, $user->uid));
959 $outline_action = !empty($count);
960 }
961
962 return $outline_action;
963 }
964
965 /**
966 * Check whether we need to overrule book.module's behavior, and if so in which way.
967 *
968 * @param: $node node object.
969 * @param: $context, string identifying the context (who is calling this function). // TODO: when all features are implemented, check whether $context is needed. Remove if necessary.
970 * @param: $book_action, bool: whether book.module believes the user has the perm or not.
971 * @return: $action string.
972 */
973 function outline_check_action($node, $context, $book_action) {
974
975 if (empty($node->outline)) { // Creating a new node. Book.module didn't set anything for lack of permission.
976 $options = _outline_book_options($node);
977 if (!empty($options)) {
978 return 'create';
979 }
980 else {
981 return 'leave off';
982 }
983 }
984
985 $outline_action = outline_check_perm($node, $book_action, 'add');
986
987 switch (TRUE) {
988 case $book_action AND $outline_action:
989 // Both modules agree the user has such a perm. Some extra checks might be needed.
990 return 'leave on';
991 case !$book_action AND !$outline_action:
992 // Both modules agree the use has no such perm. Do nothing.
993 return 'leave off';
994 case $book_action AND !$outline_action:
995 // Overule book.module: remove the appropriate items.
996 return 'delete';
997 case !$book_action AND $outline_action:
998 // Overule book.module: add the appropriate items.
999 return 'create';
1000 }
1001
1002 }
1003
1004 /**
1005 * Format the menu links for the TOC of the current page.
1006 *
1007 * @param $node
1008 * The node object for the current page.
1009 */
1010 function outline_page_toc($node) {
1011 $book_link = $node->book;
1012 $tree = menu_tree_all_data($book_link['menu_name']);
1013 $tree = _outline_page_toc_find_current_page($tree, $book_link);
1014 $depth = _outline_get_actual_toc_depth($node);
1015 $tree = _outline_page_toc_limit_depth($tree, $depth);
1016 if ($tree) {
1017 return menu_tree_output($tree);
1018 }
1019 }
1020
1021 /**
1022 * Helper function for outline_page_toc().
1023 *
1024 * The array structure returned by menu_tree_all_data has strange keys
1025 * containing the number 50000, the node title and its mlid.
1026 * I have not been able to find which part of core set such strange keys.
1027 * Therefore, in this function, I don't care what the keys are named.
1028 *
1029 * @param: $tree
1030 * The part of the menu array returned by menu_tree_all_data()
1031 * where we're looking for the portion of the array which is starting with the current page.
1032 * @param $book_link
1033 * The menu array for the current page.
1034 *
1035 * @return $found
1036 * The part of the menu array returned by menu_tree_all_data()
1037 * that we want to display on the current page.
1038 *
1039 */
1040 function _outline_page_toc_find_current_page($tree, $book_link) {
1041 if (!empty($tree)) {
1042 foreach ($tree AS $key => $data) {
1043 if ($data['link']['mlid'] == $book_link['mlid']) {
1044 $found = $tree[$key]['below'];
1045 return $found;
1046 }
1047 if (!empty($data['below'])) {
1048 foreach ($data['below'] AS $key_below => $below) {
1049 $found = _outline_page_toc_find_current_page(array($key_below => $below), $book_link);
1050 if ($found) {
1051 return $found;
1052 }
1053 }
1054 }
1055 }
1056 }
1057 }
1058
1059 /**
1060 * Helper function for outline_page_toc().
1061 *
1062 * @param $tree
1063 * The part of the menu array returned by menu_tree_all_data()
1064 * that we have to curtail according to depth.
1065 *
1066 * @param $depth
1067 * Integer, the number of levels we have left to go down before we cut whatever is below.
1068 */
1069 function _outline_page_toc_limit_depth($tree, $depth) {
1070 if (0 == $depth) {
1071 return NULL;
1072 }
1073
1074 if (is_array($tree)) {
1075 foreach ($tree AS $key => $branch) {
1076 if (!empty($branch['below'])) {
1077 $tree[$key]['below'] = _outline_page_toc_limit_depth($branch['below'], $depth - 1);
1078 }
1079 }
1080 }
1081
1082 return $tree;
1083 }
1084
1085 /**
1086 * Helper function: get the actual TOC depth to use on a node view.
1087 *
1088 * The TOC depth can be set at the node level, at the book level, or at the site level.
1089 * Return the most appropriate depth set.
1090 */
1091 function _outline_get_actual_toc_depth($node) {
1092 $bid = $node->outline['bid'];
1093 // Node level:
1094 if ( -1 != $node->outline['books'][$bid]['toc_depth']) {
1095 return $node->outline['books'][$bid]['toc_depth'];
1096 }
1097 // Book level:
1098 if ( -1 != $node->outline['books'][$bid]['default_toc_depth']) {
1099 return $node->outline['books'][$bid]['default_toc_depth'];
1100 }
1101 // Site level:
1102 return variable_get('toc_default_depth', 5);
1103 }
1104
1105
1106 //////////////////////////////////////////////////////////////////////////////////////////////////////////
1107 ////// MENU CALLBACKS ////////////////////////////////////////////////////////////////////////////////////
1108 //////////////////////////////////////////////////////////////////////////////////////////////////////////
1109
1110
1111 /**
1112 * Returns an administrative overview of all books.
1113 *
1114 * We override book_admin_overview() so that we can add our own operations.
1115 */
1116 function outline_admin_overview() {
1117 $rows = array();
1118 foreach (book_get_books() as $book) {
1119 $rows[] = array('data' => array(
1120 l(t('edit'), 'node/' . $book['nid'] . '/edit'),
1121 l(t('permissions'), 'admin/content/book/' . $book['nid'] . '/permission'),
1122 l(t('outline'), 'admin/content/book/'. $book['nid']),
1123 array('data' => l($book['title'], $book['href'], $book['options']), 'class' => 'book-title')
1124 ));
1125 }
1126 $headers = array(array('data' => t('Operations'), 'colspan' => 3), array('data' => t('Book'), 'width' => '100%', 'class' => 'book-title'));
1127
1128 return theme('table', $headers, $rows, array('id' => 'outline-admin-overview'));
1129 }
1130
1131 /**
1132 * Menu callback: change outline module administrative settings
1133 */
1134 function outline_admin_settings() {
1135 $form = outline_admin_settings_form();
1136 return system_settings_form($form);
1137 }
1138
1139
1140
1141 /**
1142 * AJAX callback to replace the book parent select options.
1143 *
1144 * This function is modified from the original book_form_update().
1145 *
1146 * This function is called when the selected book is changed. It updates the
1147 * cached form (either the node form or the book outline form) and returns
1148 * rendered output to be used to replace the select containing the possible
1149 * parent pages in the newly selected book.
1150 *
1151 * @param $build_id
1152 * The form's build_id.
1153