/[drupal]/contributions/sandbox/pwolanin/test/outline.module
ViewVC logotype

Contents of /contributions/sandbox/pwolanin/test/outline.module

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


Revision 1.2 - (show annotations) (download) (as text)
Sat Mar 17 03:02:16 2007 UTC (2 years, 8 months ago) by pwolanin
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +53 -56 lines
File MIME type: text/x-php
update for 6.x menu system
1 <?php
2 // $Id: outline.module,v 1.1 2007/01/16 03:04:41 pwolanin Exp $
3
4 /**
5 * @file
6 * A module to enable nodes to be organized into a hierarchical outline
7 *
8 * An outline is a set of pages tied together in sequence, with links and menus to
9 * guide users from section to section or to the previous page or the next.
10 * Users can collaborate in writing the outline, reviewing or modifying the pages,
11 * and putting them in the right order.
12 */
13
14 /**
15 * Implementation of hook_perm().
16 */
17 function outline_perm() {
18 return array('outline posts', 'create new outlines', 'see printer-friendly version');
19 }
20
21 /**
22 * Implementation of hook_link().
23 */
24 function outline_link($type, $node = NULL, $teaser = FALSE) {
25
26 $links = array();
27 if ($type == 'node' && isset($node->outid)) {
28 if (!$teaser) {
29 $child_type = $node->child_type;
30 if (node_access('create', $child_type)) {
31 $type_url = str_replace('_', '-', $child_type);
32 $links['outline_add_child'] = array(
33 'title' => t('Add child page'),
34 'href' => "node/add/". $type_url,
35 'query' => 'parent='. $node->nid,
36 );
37 }
38 if (user_access('see printer-friendly version')) {
39 $links['outline_printer'] = array(
40 'title' => t('Printer-friendly version'),
41 'href' => 'outline/export/html/'. $node->nid,
42 'attributes' => array('title' => t('Show a printer-friendly version of this page and its sub-pages in the outline.'))
43 );
44 }
45 }
46 }
47 return $links;
48 }
49
50 /**
51 * Implementation of hook_menu().
52 */
53 function outline_menu() {
54 $items = array();
55
56 $items['admin/content/outline'] = array(
57 'title' => t('Outlines'),
58 'description' => t("Manage site's outlines and orphaned outline pages."),
59 'page callback' => 'outline_admin',
60 'access arguments' => 'administer nodes');
61 $items['admin/content/outline/list'] = array(
62 'title' => t('List'),
63 'type' => MENU_DEFAULT_LOCAL_TASK);
64 $items['admin/content/outline/orphan'] = array(
65 'title' => t('Orphan pages'),
66 'page callback' => 'drupal_get_form',
67 'page arguments' => array('outline_admin_orphan'),
68 'type' => MENU_LOCAL_TASK,
69 'weight' => 5);
70 $items['admin/content/outline/settings'] = array(
71 'title' => t('Outline settings'),
72 'page callback' => 'drupal_get_form',
73 'page arguments' => array('outline_admin_settings'),
74 'weight' => 8,
75 'type' => MENU_LOCAL_TASK);
76 $items['outline'] = array(
77 'title' => t('Outlines'),
78 'page callback' => 'outline_list',
79 'type' => MENU_NORMAL_ITEM,
80 'access arguments' => 'access content');
81 $items['outline/export'] = array(
82 'page callback' => 'outline_export',
83 'access arguments' => 'see printer-friendly version',
84 'type' => MENU_CALLBACK);
85
86
87 $items['node/%node/outline'] = array(
88 'title' => t('Outline'),
89 'page callback' => 'outline_node',
90 'page arguments' => array(1),
91 'access callback' => '_outline_node_access',
92 'access arguments' => array(1),//'outline posts',
93 'type' => MENU_LOCAL_TASK,
94 'weight' => 2);
95
96 return $items;
97 }
98
99 function _outline_node_access($node) {
100 return user_access('outline posts') && node_access('view', $node);
101 }
102
103 /**
104 * Implementation of hook_init().
105 */
106 function outline_init() {
107 // Add the CSS for this module
108 drupal_add_css(drupal_get_path('module', 'outline') .'/outline.css');
109 }
110
111
112 /**
113 * Implementation of hook_block().
114 *
115 * The navigation block displays the outline table of contents in a block when the
116 * current page is a single-node view a node that's in the outline hierarchy.
117 */
118 function outline_block($op = 'list', $delta = 0) {
119 $block = array();
120 if ($op == 'list') {
121 $block['navigation']['info'] = t('Outline navigation');
122 return $block;
123 }
124 elseif ($op == 'view') {
125 // Outline navigation block. Only display this block when the user is
126 // browsing a node in the outline hierarchy
127 if (arg(0) == 'node' && is_numeric(arg(1))) {
128 $this_node = node_load(arg(1));
129 if (!empty($this_node->outid)) {
130 $path = outline_location($this_node);
131 $path[] = $this_node;
132
133 $expand = array();
134 foreach ($path as $key => $node) {
135 $expand[] = $node->nid;
136 }
137 $block['content'] = outline_page_menu_tree($this_node->outid,$path[0]->nid, 5, $expand);
138 $block['subject'] = check_plain($path[0]->title);
139 }
140 }
141 return $block;
142 }
143 }
144
145
146 /**
147 * Menu callback: change outline module administrative settings
148 */
149 function outline_admin_settings() {
150
151 $form['toc_default_depth'] = array(
152 '#type' => 'select',
153 '#title' => t('Default depth for table of contents display for posts newly added to an outline.'),
154 '#default_value' => variable_get('toc_default_depth', 5),
155 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30)),
156 );
157
158 $form['#redirect'] = "admin/content/outline";
159
160 return system_settings_form($form);
161 }
162
163 /**
164 * Helper function: generate a form for setting the page's options
165 */
166 function _outline_options_form($node, &$form, $collapsed = TRUE) {
167
168 $access = user_access('outline posts') || user_access('administer nodes');
169
170 $form['outline_options'] = array(
171 '#type' => 'fieldset',
172 '#title' => t('Outline options'),
173 '#weight' => 30,
174 '#collapsible' => $collapsed && $access,
175 '#collapsed' => $collapsed,
176 );
177 $form['outline_options']['weight'] = array(
178 '#type' => 'weight',
179 '#title' => t('Weight'),
180 '#default_value' => (isset($node->weight) ? $node->weight : 0),
181 '#delta' => 15,
182 '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
183 '#access' => TRUE,
184 );
185
186 $types = node_get_types();
187 $options = array();
188 foreach ($types as $nt) {
189 $options[$nt->type] = $nt->name;
190 }
191 $form['outline_options']['child_type'] = array(
192 '#type' => 'select',
193 '#title' => t('Content type for child pages'),
194 '#default_value' => ($node->child_type ? $node->child_type : $node->type),
195 '#options' => $options,
196 '#access' => $access,
197 );
198 $form['outline_options']['toc_depth'] = array(
199 '#type' => 'select',
200 '#title' => t('Preferred depth for table of contents display'),
201 '#default_value' => (!empty($node->toc_depth) ? $node->toc_depth : variable_get('toc_default_depth', 1)),
202 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30)),
203 '#description' => t('The table of contents on the outline page will be displayed to this depth (depending upon your theme).'),
204 '#access' => $access,
205 );
206 }
207
208 /**
209 * Helper function: generate a form for setting the parent
210 */
211 function _outline_parent_form($node, &$form) {
212
213 $exclude = array();
214
215 if (array_key_exists($node->outid, $all_outlines = _list_outlines())) {
216 $form_parent_title = t('Position in the outline %outline', array('%outline' => $all_outlines[$node->outid]));
217 }
218 else {
219 // Maybe an orphaned page; set a generic heading.
220 $form_parent_title = t('Parent');
221 }
222 if (isset($node->nid)) {
223 $exclude[] = $node->nid; //cannot select the current node (or its children)
224 }
225
226 $form['parent'] = array(
227 '#type' => 'select',
228 '#title' => $form_parent_title,
229 '#default_value' => $node->parent,
230 '#options' => outline_get_parents($exclude, $node->outid),
231 '#weight' => -4,
232 '#description' => t('The parent that this page belongs under. "Top level" means the highest level within this outline.'),
233 );
234 }
235
236
237 /**
238 * Helper function: return an array of available outlines as (outid => title).
239 */
240 function _list_outlines($orderby = 'ORDER BY n.title ASC'){
241 static $outlines = NULL;
242
243 if (empty($outlines)) {
244 $result = db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {outline_hierarchy} oh JOIN {node} n ON n.nid = oh.nid WHERE oh.parent = 0 ". $orderby));
245 $outlines = array();
246 while ($node = db_fetch_object($result)) {
247 $outlines[$node->nid] = $node->title;
248 }
249 }
250 return $outlines;
251 }
252
253
254 /**
255 * Helper function: generate a select form listing available outlines.
256 */
257 function _outline_select_form($default_outid = 0, $nid, $note = ''){
258
259 $outlines = _list_outlines();
260 if (user_access('create new outlines') && $default_outid != $nid) {
261 $outlines[0] = t('New, independent outline');
262 }
263 $form = array();
264 $form['outid'] = array(
265 '#type' => 'select',
266 '#title' => t('Select an outline'),
267 '#default_value' => $default_outid,
268 '#options' => $outlines,
269 '#description' => t('Select the outline you wish to add/move your page to.'),
270 '#weight' => 50,
271 );
272 $form['nid'] = array(
273 '#type' => 'value',
274 '#value' => $nid,
275 );
276 if ($note) {
277 $form['note'] = array(
278 '#value' => '<p>'. $note. '</p>',
279 '#weight' => 52,
280 );
281 }
282 return $form;
283 }
284
285
286 function outline_update_form($node) {
287 $form = array();
288
289 if ($node->parent && $node->nid != $node->outid) {
290 _outline_parent_form($node, $form);
291 $form['move_page'] = array(
292 '#value' => '<h2 class="outline-form">'. t('Move this post to a different outline.'). '</h2>',
293 '#weight' => 40,
294 );
295
296 $form['may_remove'] = array(
297 '#value' => '<h2 class="outline-form">'. t('Remove this post from the outline.'). '</h2>'. '<p>'. t('Child pages (if any) will be orphaned; you should move them first.'). '</p>',
298 '#weight' => 60,
299 );
300 //TODO: links to child pages
301 $form['remove'] = array(
302 '#type' => 'submit',
303 '#value' => t('Remove from outline'),
304 '#weight' => 65,
305 );
306
307 }
308 else {
309 $form['outline_heading'] = array(
310 '#value' => '<p>'. t('%title is an outline.', array('%title' => $node->title)). '</p>',
311 '#weight' => -10,
312 );
313 $form['move_page'] = array(
314 '#value' => '<h2 class="outline-form">'. t('Combine this outline with a different outline.'). '</h2>',
315 '#weight' => 40,
316 );
317 }
318 _outline_options_form($node, $form, FALSE);
319 $form['update'] = array(
320 '#type' => 'submit',
321 '#value' => t('Update options'),
322 '#weight' => 35,
323 );
324
325 $children_note = t("Note: All of this pages's children will be moved together to the new outline. If you do not wish that, please update the child pages first.");
326 $form = array_merge($form,_outline_select_form($node->outid, $node->nid, $children_note));
327 $form['select'] = array(
328 '#type' => 'submit',
329 '#value' => t('Select outline'),
330 '#weight' => 55,
331 );
332 return $form;
333 }
334
335 function outline_add_form($node) {
336 $form = _outline_select_form(0, $node->nid);
337 $form['add_note'] = array(
338 '#value' => '<h2 class="outline-form">'. t('Add this post to the selected outline.'). '</h2>',
339 '#weight' => 40,
340 );
341 $form['add'] = array(
342 '#type' => 'submit',
343 '#value' => t('Add to outline'),
344 '#weight' => 55,
345 );
346 $form['weight'] = array (
347 '#type' => 'value',
348 '#value' => 0,
349 );
350 $form['toc_depth'] = array (
351 '#type' => 'value',
352 '#value' => variable_get('toc_default_depth', 1),
353 );
354 $form['#base'] = 'outline_update_form';
355 return $form;
356 }
357
358 /**
359 * Menu callback: handle all outline operations on individual nodes.
360 */
361 function outline_node($node){
362
363 drupal_set_title(check_plain($node->title));
364
365 if (isset($node->outid)) { // node is already part of the outline
366 return drupal_get_form('outline_update_form', $node);
367 }
368 else {
369 return drupal_get_form('outline_add_form', $node);
370 }
371
372 }
373
374
375 /**
376 * Implementation of hook_form_alter()
377 */
378 function outline_form_alter($form_id, &$form) {
379 //drupal_set_message($form_id. ' <hr />'.nl2br(print_r($form,1)));
380 if ($form_id == 'node_delete_confirm') {
381 $node = node_load($form['nid']['#value']);
382 if (isset($node->outid)) {
383 if ($node->nid == $node->outid) {
384 $form['outline'] = array(
385 '#value' => t('Note that all outline pages belonging to this outline (if any) will no longer be associated with any outline.'),
386 );
387 }
388 else {
389 $form['outline'] = array(
390 '#value' => t('Note that child pages in the outline (if any) will be orphaned. You may want to move any child pages first.'),
391 );
392 }
393 }
394 }
395 elseif (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
396 $node = $form['#node'];
397
398 // looking for url like node/add/page?parent=$nid
399 if (arg(1) == 'add') {
400 $parent = $_GET['parent'];
401 if (!empty($parent) && is_numeric($parent)) {
402 $p_node = node_load($parent);
403 }
404 // Only permit this method if the new node type matches the
405 // selected child type of the parent.
406 if (!empty($p_node) && $p_node->child_type == $node->type) {
407 // Inherit outline values from parent.
408 $node->parent = $p_node->nid;
409 $node->outid = $p_node->outid;
410 $node->child_type = $p_node->child_type;
411 }
412 }
413
414 if (isset($node->outid) && isset($node->parent)) {
415 $form['outid'] = array (
416 '#type' => 'value',
417 '#value' => $node->outid,
418 );
419 if ($node->parent) { // Not an outline
420 _outline_parent_form($node, $form);
421 }
422 _outline_options_form($node, $form, TRUE);
423
424 }
425 }
426 return $form;
427 }
428
429
430 /**
431 * Recursivly update each child page when moving a node to a different outline.
432 *
433 * @param $parent
434 * the nid of the parent that is being moved.
435 * @param $outid
436 * the outid (outline ID) of the outline the parent is moving to.
437 * @param $children
438 * the hierarchy of the outline that the node with nid==$parent is moving
439 * from, as generated by function outline_tree.
440 */
441 function outline_update_children_outid($parent, $outid, &$children) {
442
443 if (isset($children[$parent])) {
444 // TDOD - rewrite this as non-recursive using the tree array
445 foreach($children[$parent] as $child) {
446 outline_update_children_outid($child->nid, $outid, $children);
447 db_query("UPDATE {outline_hierarchy} SET outid = %d WHERE nid = %d", $outid, $child->nid);
448 }
449 }
450 }
451
452 /**
453 * Handle outline add and update form submissions.
454 */
455 function outline_update_form_submit($form_id, $form_values) {
456 $op = $form_values['op'];
457 $node = node_load($form_values['nid']);
458
459 $node->weight = $form_values['weight'];
460 $node->toc_depth = $form_values['toc_depth'];
461 $orig_outid = $node->outid;
462 $node->outid = $form_values['outid'];
463
464 if ($op == t('Add to outline')) {
465 if ($node->outid == 0) { // A new outline
466 $node->parent = 0;
467 $node->outid = $node->nid;
468 $node->child_type = $node->type;
469 }
470 else {
471 $node->parent = $form_values['outid'];
472 $node->child_type = db_result(db_query("SELECT child_type FROM {outline_hierarchy} WHERE nid = %d", $form_values['outid']));
473 }
474 }
475 else {
476 if ($node->outid == 0) {
477 $node->parent = 0;
478 $node->outid = $node->nid;
479 }
480 else {
481 $node->parent = $form_values['parent'];
482 }
483 $node->child_type = $form_values['child_type'];
484 }
485
486 switch ($op) {
487 case t('Add to outline'):
488 outline_nodeapi($node, 'insert');
489 drupal_set_message(t('The post has been added to the outline.').'<br />'.t('You can now update where in the outline you want to locate the post.'));
490 cache_clear_all();
491 return 'node/'. $node->nid. '/outline';
492 break;
493 case t('Update options'):
494 outline_nodeapi($node, 'update');
495 drupal_set_message(t('The outline has been updated.'));
496 cache_clear_all();
497 break;
498 case t('Remove from outline'):
499 outline_nodeapi($node, 'delete');
500 drupal_set_message(t('The post has been removed from the outline. Child pages may have been orphaned.'));
501 cache_clear_all();
502 break;
503 case t('Select outline'):
504 if ($orig_outid != $node->outid) {
505 outline_update_children_outid($node->nid, $node->outid, outline_tree($orig_outid));
506 outline_nodeapi($node, 'update');
507 drupal_set_message(t('The post and its children have been relocated to the choosen outline.').'<br/>'.t('You can now update where in the outline you want to locate the post.'));
508 cache_clear_all();
509 return 'node/'. $node->nid. '/outline';
510 }
511 else {
512 drupal_set_message(t('You selected the outline that this post already belongs to. No changes were made.'));
513 }
514 break;
515 }
516
517 return "node/". $node->nid;
518 }
519
520 /**
521 * Determine the path in the outline tree from the top to the parent of the node.
522 *
523 * @param node
524 * a node in the outline hierarchy for which to compute the path
525 * @return
526 * an array of nodes representing the path of from the top of
527 * the outline to the parent of the given node. Returns an empty array if
528 * the node does not exist or is not part of a outline hierarchy.
529 *
530 */
531 function outline_location($node) {
532
533 $nodes = array();
534
535 $pages = pages_of_outline_by_nid($node->outid);
536 if (isset($pages[$node->parent])) {
537 $nodes[0]= $pages[$node->parent];
538 $i = 0;
539 while ($nodes[$i]->parent) {
540 $nodes[] = $pages[$nodes[$i++]->parent];
541 }
542 }
543 return array_reverse($nodes);
544 }
545
546 /**
547 * Given a node, this function returns an array of 'outline node' objects
548 * representing the path in the outline tree from the given node down to
549 * the last sibling of it.
550 *
551 * @param $node
552 * A outline node object where the path starts.
553 *
554 * @return
555 * An array of outline node objects representing the path nodes from the
556 * given node. Returns an empty array if the node does not exist or
557 * is not part of a outline hierarchy or there are no siblings.
558 */
559 // TODO: rewrite as non-recursive
560 function outline_location_down($node, $nodes = array()) {
561 $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, oh.parent, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.status = 1 AND oh.parent = %d ORDER BY oh.weight DESC, n.title DESC'), $node->nid));
562 if ($last_direct_child) {
563 $nodes[] = $last_direct_child;
564 $nodes = outline_location_down($last_direct_child, $nodes);
565 }
566 return $nodes;
567 }
568
569 /**
570 * Fetches the node object of the previous page of the outline.
571 */
572 function outline_prev($node) {
573 // If the parent is zero, we are at the start of a outline so there is no previous.
574 if ($node->parent == 0) {
575 return NULL;
576 }
577
578 //$children = outline_tree($node->outid); TODO- use this instead
579
580 // Previous on the same level:
581 $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE oh.parent = %d AND n.status = 1 AND (oh.weight < %d OR (oh.weight = %d AND n.title < '%s')) ORDER BY oh.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title));
582 if ($direct_above) {
583 // Get last leaf of $above.
584 $path = outline_location_down($direct_above);
585
586 return $path ? (count($path) > 0 ? array_pop($path) : NULL) : $direct_above;
587 }
588 else {
589 // Direct parent:
590 $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.nid = %d AND n.status = 1'), $node->parent));
591 return $prev;
592 }
593 }
594
595 /**
596 * Fetches the node object of the next page of the outline.
597 */
598 function outline_next($node) {
599 // get first direct child
600 $children = outline_tree($node->outid);
601 if (isset($children[$node->nid][0])) {
602 return $children[$node->nid][0];
603 }
604
605 // No direct child: get next for this level or any parent in this outline.
606 $path = outline_location($node); // Path to top-level node including this one's parent.
607 $path[] = $node;
608
609 while (($leaf = array_pop($path)) && count($path)) {
610 $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE oh.parent = %d AND n.status = 1 AND (oh.weight > %d OR (oh.weight = %d AND n.title > '%s')) ORDER BY oh.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title));
611 if ($next) {
612 return $next;
613 }
614 }
615 }
616
617 /**
618 * Create formatted html for the table of contents displayed on $node.
619 *
620 * @ingroup themeable
621 */
622 function theme_outline_toc($node) {
623 $toc = '<div class="outline-toc"><h3>' . t('Table of contents') . '</h3>';
624 $toc .= outline_page_menu_tree($node->outid, $node->nid, $node->toc_depth);
625 $toc .= '</div>';
626 return $toc;
627 }
628
629 /**
630 * Implementation of hook_nodeapi().
631 *
632 * Handles all load/view/submit/insert/update/delete/delete revision opertions
633 * necessary for outlines
634 */
635 function outline_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
636 switch ($op) {
637 case 'load': //all types in outline handled here
638 return db_fetch_array(db_query('SELECT * FROM {outline_hierarchy} WHERE nid = %d', $node->nid));
639 break;
640 case 'view':
641 if (!$teaser) {
642 // check if it is a node in the outline hierarchy:
643 if (isset($node->outid)) {
644 $path = outline_location($node);
645 // Construct the breadcrumb:
646 $node->breadcrumb = array(); // Overwrite the trail with a outline trail.
647 $node->breadcrumb[] = array ('path' => 'outline', 'title' => t('outlines'));
648 foreach ($path as $level) {
649 $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title);
650 }
651 $node->breadcrumb[] = array('path' => 'node/'. $node->nid);
652 menu_set_location($node->breadcrumb);
653
654 $node->content['outline_navigation'] = array(
655 '#value' => theme('outline_page_navigation', $node),
656 '#weight' => 200,
657 );
658
659 }
660 }
661 return $node;
662 break;
663 case 'submit':
664 // Require revisions for non-administrators.
665 if (isset($node->outid) && !user_access('administer nodes')) {
666 $node->revision = TRUE;
667 }
668 break;
669 case 'update':
670 if (isset($node->outid)) {
671 db_query("UPDATE {outline_hierarchy} SET parent = %d, weight = %d, child_type = '%s', toc_depth = %d WHERE nid = %d", $node->parent, $node->weight, $node->child_type, $node->toc_depth, $node->nid);
672 }
673 break;
674 case 'insert':
675 if (isset($node->outid)){
676 db_query("INSERT INTO {outline_hierarchy} (nid, outid, parent, child_type, weight, toc_depth) VALUES (%d, %d, %d, '%s', %d, %d)", $node->nid, $node->outid, $node->parent, $node->child_type, $node->weight, $node->toc_depth);
677 }
678 break;
679 case 'delete':
680 if (isset($node->outid)) {
681 db_query('DELETE FROM {outline_hierarchy} WHERE nid = %d', $node->nid);
682 }
683 break;
684 }
685 }
686
687 /**
688 * Prepares the links to children (TOC) and forward/backward
689 * navigation for a node presented as a book page.
690 *
691 * @ingroup themeable
692 */
693 function theme_outline_page_navigation($node) {
694 $output = '';
695 $links = '';
696
697 if ($node->nid) {
698 $tree = outline_page_menu_tree($node->outid, $node->nid, $node->toc_depth);
699
700 if ($node->parent != 0) {
701 if ($prev = outline_prev($node)) {
702 drupal_add_link(array('rel' => 'prev', 'href' => url('node/'. $prev->nid)));
703 $links .= l(t('‹ ') . $prev->title, 'node/'. $prev->nid, array('attributes' => array('class' => 'page-previous', 'title' => t('Go to previous page'))));
704 }
705 drupal_add_link(array('rel' => 'up', 'href' => url('node/'. $node->parent)));
706 if ($node->parent != $node->outid){
707 $links .= l(t('up'), 'node/'. $node->parent, array('attributes' => array('class' => 'page-up', 'title' => t('Go to parent page'))));
708 }
709 else {
710 $links .= l(t('up'), 'node/'. $node->outid, array('attributes' => array('class' => 'page-up', 'title' => t('Go to outline cover'))));
711 }
712
713 if ($next = outline_next($node)) {
714 drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid)));
715 $links .= l($next->title . t(' ›'), 'node/'. $next->nid, array('attributes' => array('class' => 'page-next', 'title' => t('Go to next page'))));
716 }
717 }
718 if ($tree || $links) {
719 $output = '<div class="outline-navigation">';
720 if ($tree) {
721 $output .= $tree;
722 }
723 if ($links) {
724 $output .= '<div class="page-links clear-block">'. $links .'</div>';
725 }
726 $output .= '</div>';
727 }
728 }
729
730 return $output;
731 }
732
733 /**
734 * Returns an array of titles and nid entries of outline pages where the array keys are the nid values
735 */
736 function pages_of_outline_by_nid($outid) {
737 static $bynid = array();
738
739 if (!isset($bynid[$outid])){
740 $pages= pages_of_outline($outid);
741 foreach ($pages as $node) {
742 $bynid[$outid][$node->nid] = $node;
743 }
744 }
745 return $bynid[$outid];
746 }
747
748 /**
749 * Returns an array of titles and nid entries of outline pages in table of contents order
750 */
751 function pages_of_outline($outid) {
752 static $outline = array();
753
754 if (!isset($outline[$outid])){
755 $outline[$outid] = array();
756 $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, oh.parent, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.status = 1 AND oh.outid = %d ORDER BY oh.weight ASC, n.title ASC'), $outid);
757
758 while ($node = db_fetch_object($result)) {
759 $outline[$outid][] = $node;
760 }
761 }
762 return $outline[$outid];
763 }
764
765 /**
766 * Returns an array corresponding to the tree of titles and nid entries of an outline's nodes in table of contents order
767 */
768 function outline_tree($outid) {
769 static $children = array();
770
771 if (!isset($children[$outid])) {
772 $pages = pages_of_outline($outid);
773 $children[$outid] = array();
774 foreach ($pages as $node) {
775 if (!isset($children[$outid][$node->parent])) {
776 $children[$outid][$node->parent] = array();
777 }
778 $children[$outid][$node->parent][] = $node;
779 }
780 }
781 return $children[$outid];
782 }
783
784 /**
785 * This is a helper function for _outline_get_parents
786 */
787 function _outline_toc_recurse($nid, $indent, $toc, &$children, $exclude) {
788 if (isset($children[$nid])) {
789 foreach ($children[$nid] as $node) {
790 if (empty($exclude) || !in_array($node->nid, $exclude)) {
791 $toc[$node->nid] = $indent .' '. $node->title;
792 $toc = _outline_toc_recurse($node->nid, $indent .'--', $toc, $children, $exclude);
793 }
794 }
795 }
796 return $toc;
797 }
798
799 /**
800 * Returns an array of titles and nid entries of outline pages in table of contents order
801 * for use in a form select.
802 */
803 function outline_get_parents($exclude = array(), $outid) {
804
805 $children = outline_tree($outid);
806 $toc = array();
807 if (!in_array($outid, $exclude)) {
808 $toc[$outid] = '<'. t('top-level') .'>';
809 }
810 $toc = _outline_toc_recurse($outid, '', $toc, $children, $exclude);
811
812 return $toc;
813 }
814
815 /**
816 * This is a default theme display function for outline_tree()
817 *
818 * @ingroup themeable
819 */
820 function theme_outline_tree_display($nid, $depth, $children, $unfold = NULL) {
821 $output = '';
822 if ($depth > 0) {
823 if (isset($children[$nid])) {
824 $output .= '<ul class="menu">';
825 foreach ($children[$nid] as $node) {
826 if (isset($children[$node->nid])) {
827 if (empty($unfold)){
828 if ($depth == 1) {
829 $output .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid);
830 }
831 else {
832 $output .= '<li class="expanded">' . l($node->title, 'node/'. $node->nid);
833 $output .= theme_outline_tree_display($node->nid, $depth - 1, $children, $unfold);
834 }
835 }
836 else {
837 if ($depth == 1 || !in_array($node->nid, $unfold)) {
838 $output .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid);
839 }
840 else {
841 $output .= '<li class="expanded">' . l($node->title, 'node/'. $node->nid);
842 $output .= theme_outline_tree_display($node->nid, $depth - 1, $children, $unfold);
843 }
844 }
845 }
846 else {
847 $output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid);
848 }
849 $output .= '</li>';
850 }
851 $output .= '</ul>';
852 }
853 }
854
855 return $output;
856 }
857
858 /**
859 * Returns an themed list representing the outline nodes as a tree.
860 */
861 function outline_page_menu_tree($outid, $parent = 0, $depth = 1, $unfold = array()) {
862
863 $children = outline_tree($outid);
864 return theme('outline_tree_display',$parent, $depth, $children, $unfold);
865 }
866
867 /**
868 * Menu callback; prints a listing of all outlines
869 */
870 function outline_list() {
871
872 $breadcrumb = array (l( t('Home'),''));
873 drupal_set_breadcrumb($breadcrumb);
874
875 $outlines = _list_outlines('ORDER BY oh.weight ASC, n.title ASC');
876
877 $output = '';
878 $items = array();
879 foreach ($outlines as $nid => $title) {
880 $items[] = l($title, 'node/'. $nid);
881 }
882 if (count($items)) {
883 $output .= theme('item_list', $items);
884 }
885
886 return $output;
887 }
888
889 /**
890 * Menu callback; Generates various representation of a outline page with
891 * all descendants and prints the requested representation to output.
892 *
893 * The function delegates the generation of output to helper functions.
894 * The function name is derived by prepending 'outline_export_' to the
895 * given output type. So, e.g., a type of 'html' results in a call to
896 * the function outline_export_html().
897 *
898 * @param type
899 * - a string encoding the type of output requested.
900 * The following types are currently supported in outline module
901 * html: HTML (printer friendly output)
902 * Other types are supported in contributed modules.
903 * @param nid
904 * - an integer representing the node id (nid) of the node to export
905 *
906 */
907 function outline_export($type = 'html', $nid = 0) {
908 $type = drupal_strtolower($type);
909 $node_result = db_query(db_rewrite_sql('SELECT n.nid, n.title, oh.parent, oh.outid FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.nid = %d'), $nid);
910 if (db_num_rows($node_result) > 0) {
911 $node = db_fetch_object($node_result);
912 }
913 $depth = count(outline_location($node)) + 1;
914 $export_function = 'outline_export_' . $type;
915
916 if (function_exists($export_function)) {
917 print call_user_func($export_function, $nid, $depth);
918 }
919 else {
920 drupal_set_message(t('Unknown export format.'));
921 drupal_not_found();
922 }
923 }
924
925 /**
926 * This function is called by outline_export() to generate HTML for export.
927 *
928 * The given node is /embedded to its absolute depth in a top level
929 * section/. For example, a child node with depth 2 in the hierarchy
930 * is contained in (otherwise empty) &lt;div&gt; elements
931 * corresponding to depth 0 and depth 1. This is intended to support
932 * WYSIWYG output - e.g., level 3 sections always look like level 3
933 * sections, no matter their depth relative to the node selected to be
934 * exported as printer-friendly HTML.
935 *
936 * @param nid
937 * - an integer representing the node id (nid) of the node to export
938 * @param depth
939 * - an integer giving the depth in the outline hierarchy of the node
940 * which is to be exported
941 *
942 * @return
943 * - string containing HTML representing the node and its children in
944 * the outline hierarchy
945 */
946 function outline_export_html($nid, $depth) {
947 if (user_access('see printer-friendly version')) {
948 $node = node_load($nid);
949 for ($i = 1; $i < $depth; $i++) {
950 $content .= "<div class=\"section-$i\">\n";
951 }
952 $content .= outline_recurse($nid, $depth, 'outline_node_visitor_html_pre', 'outline_node_visitor_html_post');
953 for ($i = 1; $i < $depth; $i++) {
954 $content .= "</div>\n";
955 }
956 return theme('outline_export_html', check_plain($node->title), $content);
957 }
958 else {
959 drupal_access_denied();
960 }
961 }
962
963 /**
964 * How the outline's HTML export should be themed
965 *
966 * @ingroup themeable
967 */
968 function theme_outline_export_html($title, $content) {
969 global $base_url;
970 $html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
971 $html .= '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
972 $html .= "<head>\n<title>". $title ."</title>\n";
973 $html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
974 $html .= '<base href="'. $base_url .'/" />' . "\n";
975 $html .= "<style type=\"text/css\">\n@import url(misc/print.css);\n</style>\n";
976 $html .= "</head>\n<body>\n". $content . "\n</body>\n</html>\n";
977 return $html;
978 }
979
980 /**
981 * Traverse the outline tree and apply functions to each node.
982 *
983 * Applies the $visit_pre() callback to each
984 * node, is called recursively for each child of the node (in weight,
985 * title order). Finally appends the output of the $visit_post()
986 * callback to the output before returning the generated output.
987 *
988 * @param nid
989 * - the node id (nid) of the root node of the outline hierarchy.
990 * @param depth
991 * - the depth of the given node in the outline hierarchy.
992 * @param visit_pre
993 * - a function callback to be called upon visiting a node in the tree
994 * @param visit_post
995 * - a function callback to be called after visiting a node in the tree,
996 * but before recursively visiting children.
997 * @return
998 * - the output generated in visiting each node
999 */
1000 function outline_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) {
1001 $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.status = 1 AND n.nid = %d ORDER BY oh.weight ASC, n.title ASC'), $nid);
1002 while ($page = db_fetch_object($result)) {
1003 // Load the node:
1004 $node = node_load($page->nid);
1005
1006 if ($node) {
1007 if (function_exists($visit_pre)) {
1008 $output .= call_user_func($visit_pre, $node, $depth, $nid);
1009 }
1010 else {
1011 $output .= outline_node_visitor_html_pre($node, $depth, $nid);
1012 }
1013
1014 $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE n.status = 1 AND oh.parent = %d ORDER BY oh.weight ASC, n.title ASC'), $node->nid);
1015 while ($childpage = db_fetch_object($children)) {
1016 $childnode = node_load($childpage->nid);
1017 if ($childnode->nid != $node->nid) {
1018 $output .= outline_recurse($childnode->nid, $depth + 1, $visit_pre, $visit_post);
1019 }
1020 }
1021 if (function_exists($visit_post)) {
1022 $output .= call_user_func($visit_post, $node, $depth);
1023 }
1024 else {
1025 # default
1026 $output .= outline_node_visitor_html_post($node, $depth);
1027 }
1028 }
1029 }
1030
1031 return $output;
1032 }
1033
1034 /**
1035 * Generates printer-friendly HTML for a node. This function
1036 * is a 'pre-node' visitor function for outline_recurse().
1037 *
1038 * @param $node
1039 * - the node to generate output for.
1040 * @param $depth
1041 * - the depth of the given node in the hierarchy. This
1042 * is used only for generating output.
1043 * @param $nid
1044 * - the node id (nid) of the given node. This
1045 * is used only for generating output.
1046 * @return
1047 * - the HTML generated for the given node.
1048 */
1049 function outline_node_visitor_html_pre($node, $depth, $nid) {
1050 // Output the content:
1051 $node = node_build_content($node, FALSE, FALSE);
1052 // Allow modules to change $node->body before viewing.
1053 node_invoke_nodeapi($node, 'print', $node->body, FALSE);
1054
1055 $output .= "<div id=\"node-". $node->nid ."\" class=\"section-$depth\">\n";
1056 $output .= "<h1 class=\"outline-heading\">". check_plain($node->title) ."</h1>\n";
1057
1058 if ($node->body) {
1059 $output .= drupal_render($node->content);
1060 }
1061 return $output;
1062 }
1063
1064 /**
1065 * Finishes up generation of printer-friendly HTML after visiting a
1066 * node. This function is a 'post-node' visitor function for
1067 * outline_recurse().
1068 */
1069 function outline_node_visitor_html_post($node, $depth) {
1070 return "</div>\n";
1071 }
1072
1073 function _outline_admin_table($nodes = array()) {
1074 $form = array(
1075 '#theme' => 'outline_admin_table',
1076 '#tree' => TRUE,
1077 );
1078
1079 foreach ($nodes as $node) {
1080 if (isset($node->outid) && ($node->nid == $node->outid)) {
1081 $result = db_query('SELECT nid FROM {outline_hierarchy} WHERE parent = %d AND outid = %d', $node->outid, $node->outid);
1082 while ($chapters = db_fetch_object($result)) {
1083 $chapter = node_load($chapters->nid);
1084 $form = array_merge($form, _outline_admin_table_tree($chapter, 0));
1085 }
1086 }
1087 else {
1088 $form = array_merge($form, _outline_admin_table_tree($node, 0));
1089 }
1090 }
1091
1092 return $form;
1093 }
1094
1095 function _outline_admin_table_tree($node, $depth) {
1096 $form = array();
1097
1098 $form[] = array(
1099 'nid' => array('#type' => 'value', '#value' => $node->nid),
1100 'depth' => array('#type' => 'value', '#value' => $depth),
1101 'title' => array(
1102 '#type' => 'textfield',
1103 '#default_value' => $node->title,
1104 '#maxlength' => 255,
1105 ),
1106 'weight' => array(
1107 '#type' => 'weight',
1108 '#default_value' => $node->weight,
1109 '#delta' => 15,
1110 ),
1111 );
1112
1113 $children = db_query(db_rewrite_sql('SELECT n.nid, oh.weight FROM {node} n INNER JOIN {outline_hierarchy} oh ON n.nid = oh.nid WHERE oh.parent = %d ORDER BY oh.weight ASC, n.title ASC'), $node->nid);
1114 while ($child = db_fetch_object($children)) {
1115 $form = array_merge($form, _outline_admin_table_tree(node_load($child->nid), $depth + 1));
1116 }
1117
1118 return $form;
1119 }
1120
1121 /**
1122 * Format the outline administration form.
1123 *
1124 * @ingroup themeable
1125 */
1126 function theme_outline_admin_table($form) {
1127 $header = array(t('Title'), t('Weight'), array('data' => t('Operations'), 'colspan' => '4'));
1128
1129 $rows = array();
1130 foreach (element_children($form) as $key) {
1131 $nid = $form[$key]['nid']['#value'];
1132 $pid = $form[0]['nid']['#value'];
1133 $rows[] = array(
1134 '<div style="padding-left: '. (25 * $form[$key]['depth']['#value']) .'px;">'. drupal_render($form[$key]['title']) .'</div>',
1135 drupal_render($form[$key]['weight']),
1136 l(t('view'), 'node/'. $nid), </