book.module 36.9 KB
Newer Older
Dries Buytaert's avatar
 
Dries Buytaert committed
1
<?php
2
// $Id$
Dries Buytaert's avatar
 
Dries Buytaert committed
3

Dries Buytaert's avatar
   
Dries Buytaert committed
4
5
6
7
8
/**
 * @file
 * Allows users to collaboratively author a book.
 */

9
/**
10
 * Implementation of hook_node_info().
11
 */
12
function book_node_info() {
13
  return array('book' => array('name' => t('book page'), 'base' => 'book'));
Dries Buytaert's avatar
   
Dries Buytaert committed
14
15
}

16
17
18
/**
 * Implementation of hook_perm().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
19
function book_perm() {
20
    return array('create book pages', 'maintain books', 'edit own book pages', 'see printer-friendly version');
Dries Buytaert's avatar
   
Dries Buytaert committed
21
22
}

23
24
25
/**
 * Implementation of hook_access().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
26
function book_access($op, $node) {
Dries Buytaert's avatar
   
Dries Buytaert committed
27
  global $user;
Dries Buytaert's avatar
   
Dries Buytaert committed
28

29
  if ($op == 'create') {
Dries Buytaert's avatar
   
Dries Buytaert committed
30
31
    // Only registered users can create book pages.  Given the nature
    // of the book module this is considered to be a good/safe idea.
32
    return user_access('create book pages');
Dries Buytaert's avatar
   
Dries Buytaert committed
33
34
  }

35
  if ($op == 'update') {
Dries Buytaert's avatar
   
Dries Buytaert committed
36
37
38
    // Only registered users can update book pages.  Given the nature
    // of the book module this is considered to be a good/safe idea.
    // One can only update a book page if there are no suggested updates
39
40
    // of that page waiting for approval.  That is, only updates that
    // don't overwrite the current or pending information are allowed.
41

42
43
44
45
46
47
    if ((user_access('maintain books') && !$node->moderate) || ($node->uid == $user->uid && user_access('edit own book pages'))) {
      return TRUE;
    }
    else {
       // do nothing. node-access() will determine further access
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
48
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
49
50
}

Dries Buytaert's avatar
   
Dries Buytaert committed
51
52
53
/**
 * Implementation of hook_link().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
54
function book_link($type, $node = 0, $main = 0) {
Dries Buytaert's avatar
   
Dries Buytaert committed
55
56
57

  $links = array();

58
  if ($type == 'node' && isset($node->parent)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
59
    if (!$main) {
Dries Buytaert's avatar
   
Dries Buytaert committed
60
61
62
      if (book_access('create', $node)) {
        $links[] = l(t('add child page'), "node/add/book/parent/$node->nid");
      }
63
      if (user_access('see printer-friendly version')) {
64
65
66
        $links[] = l(t('printer-friendly version'),
                     'book/export/html/'. $node->nid,
                     array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')));
67
      }
Dries Buytaert's avatar
   
Dries Buytaert committed
68
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
69
70
  }

Dries Buytaert's avatar
   
Dries Buytaert committed
71
  return $links;
Dries Buytaert's avatar
   
Dries Buytaert committed
72
73
}

Dries Buytaert's avatar
   
Dries Buytaert committed
74
75
76
/**
 * Implementation of hook_menu().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
77
function book_menu($may_cache) {
Dries Buytaert's avatar
   
Dries Buytaert committed
78
79
  $items = array();

Dries Buytaert's avatar
   
Dries Buytaert committed
80
  if ($may_cache) {
81
82
83
84
85
86
87
88
89
    $items[] = array(
      'path' => 'book',
      'title' => t('books'),
      'access' => user_access('access content'),
      'type' => MENU_NORMAL_ITEM,
      'weight' => 5);
    $items[] = array(
      'path' => 'node/add/book',
      'title' => t('book page'),
90
      'access' => user_access('create book pages'));
91
92
93
    $items[] = array(
      'path' => 'admin/node/book',
      'title' => t('books'),
Dries Buytaert's avatar
   
Dries Buytaert committed
94
95
      'callback' => 'book_admin',
      'access' => user_access('administer nodes'),
96
97
98
99
100
101
      'type' => MENU_LOCAL_TASK,
      'weight' => -1);
    $items[] = array(
      'path' => 'admin/node/book/list',
      'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK);
102
103
104
    $items[] = array(
      'path' => 'admin/node/book/orphan',
      'title' => t('orphan pages'),
Dries Buytaert's avatar
   
Dries Buytaert committed
105
      'callback' => 'book_admin_orphan',
106
      'type' => MENU_LOCAL_TASK,
Dries Buytaert's avatar
   
Dries Buytaert committed
107
      'weight' => 8);
108
109
110
    $items[] = array(
      'path' => 'book',
      'title' => t('books'),
Dries Buytaert's avatar
   
Dries Buytaert committed
111
112
113
      'callback' => 'book_render',
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM);
114
    $items[] = array(
115
116
      'path' => 'book/export',
      'callback' => 'book_export',
117
      'access' => user_access('access content'),
Dries Buytaert's avatar
   
Dries Buytaert committed
118
      'type' => MENU_CALLBACK);
Dries Buytaert's avatar
   
Dries Buytaert committed
119
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
120
121
122
123
124
  else {
    // To avoid SQL overhead, check whether we are on a node page and whether the
    // user is allowed to maintain books.
    if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('maintain books')) {
      // Only add the outline-tab for non-book pages:
125
      $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.nid = %d AND n.type != 'book'"), arg(1));
Dries Buytaert's avatar
   
Dries Buytaert committed
126
      if (db_num_rows($result) > 0) {
127
128
129
130
131
132
133
        $items[] = array(
          'path' => 'node/'. arg(1) .'/outline',
          'title' => t('outline'),
          'callback' => 'book_outline',
          'access' => user_access('maintain books'),
          'type' => MENU_LOCAL_TASK,
          'weight' => 2);
Dries Buytaert's avatar
   
Dries Buytaert committed
134
135
136
      }
    }
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
137
138
139
140

  return $items;
}

141
142
143
/**
 * Implementation of hook_block().
 *
Dries Buytaert's avatar
   
Dries Buytaert committed
144
145
 * Displays the book table of contents in a block when the current page is a
 * single-node view of a book node.
146
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
147
function book_block($op = 'list', $delta = 0) {
Dries Buytaert's avatar
   
Dries Buytaert committed
148
  $block = array();
Dries Buytaert's avatar
   
Dries Buytaert committed
149
150
  if ($op == 'list') {
    $block[0]['info'] = t('Book navigation');
151
    return $block;
Dries Buytaert's avatar
   
Dries Buytaert committed
152
  }
153
  else if ($op == 'view') {
Dries Buytaert's avatar
   
Dries Buytaert committed
154
155
    // Only display this block when the user is browsing a book:
    if (arg(0) == 'node' && is_numeric(arg(1))) {
156
      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1));
Dries Buytaert's avatar
   
Dries Buytaert committed
157
158
159
160
161
162
163
164
165
166
167
      if (db_num_rows($result) > 0) {
        $node = db_fetch_object($result);

        $path = book_location($node);
        $path[] = $node;

        $expand = array();
        foreach ($path as $key => $node) {
          $expand[] = $node->nid;
        }

168
        $block['subject'] = check_plain($path[0]->title);
Dries Buytaert's avatar
   
Dries Buytaert committed
169
170
171
        $block['content'] = book_tree($expand[0], 5, $expand);
      }
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
172

173
174
    return $block;
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
175
176
}

177
178
179
/**
 * Implementation of hook_load().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
180
function book_load($node) {
Dries Buytaert's avatar
   
Dries Buytaert committed
181
  global $user;
Dries Buytaert's avatar
   
Dries Buytaert committed
182

183
  $book = db_fetch_object(db_query('SELECT parent, weight FROM {book} WHERE vid = %d', $node->vid));
Dries Buytaert's avatar
   
Dries Buytaert committed
184

Dries Buytaert's avatar
   
Dries Buytaert committed
185
  if (arg(2) == 'edit' && !user_access('administer nodes')) {
Dries Buytaert's avatar
   
Dries Buytaert committed
186
187
    // If a user is about to update a book page, we overload some
    // fields to reflect the changes.
Dries Buytaert's avatar
   
Dries Buytaert committed
188
189
190
191
192
193
    if ($user->uid) {
      $book->uid = $user->uid;
      $book->name = $user->name;
    }
    else {
      $book->uid = 0;
194
      $book->name = '';
Dries Buytaert's avatar
   
Dries Buytaert committed
195
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
196
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
197

Dries Buytaert's avatar
   
Dries Buytaert committed
198
  return $book;
Dries Buytaert's avatar
   
Dries Buytaert committed
199
200
}

201
202
203
/**
 * Implementation of hook_insert().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
204
function book_insert($node) {
205
  db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
Dries Buytaert's avatar
   
Dries Buytaert committed
206
}
Dries Buytaert's avatar
   
Dries Buytaert committed
207

208
209
210
/**
 * Implementation of hook_update().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
211
function book_update($node) {
212
  if ($node->revision) {
213
    db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
214
215
216
217
  }
  else {
    db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid);
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
218
}
Dries Buytaert's avatar
   
Dries Buytaert committed
219

220
221
222
/**
 * Implementation of hook_delete().
 */
223
function book_delete(&$node) {
224
  db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
225
226
}

227
/**
228
 * Implementation of hook_submit().
229
 */
230
function book_submit(&$node) {
231
232
  // Set default values for non-administrators.
  if (!user_access('administer nodes')) {
233
234
235
    $node->weight = 0;
    $node->revision = 1;
  }
236
}
237

238
239
240
241
/**
 * Implementation of hook_validate().
 */
function book_validate($node) {
242
  node_validate_title($node);
Dries Buytaert's avatar
   
Dries Buytaert committed
243
244
}

245
246
247
/**
 * Implementation of hook_form().
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
248
function book_form(&$node) {
249
  $form['parent'] = array(
250
251
    '#type' => 'select', '#title' => t('Parent'), '#default_value' => ($node->parent ? $node->parent : arg(4)), '#options' => book_toc($node->nid), '#weight' => -15,
    '#description' => t('The parent that this page belongs in. Note that pages whose parent is &lt;top-level&gt; are regarded as independent, top-level books.')
252
  );
Dries Buytaert's avatar
   
Dries Buytaert committed
253

254
  $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#default_value' => $node->title);
255
  $form['body'] = array(
256
    '#type' => 'textarea', '#title' => t('Body'), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE
257
258
  );
  $form = array_merge($form, filter_form($node->format));
259

260
  $form['log'] = array(
261
262
263
    '#type' => 'fieldset', '#title' => t('Log message'), '#collapsible' => TRUE, '#collapsed' => TRUE
  );
  $form['log']['message'] = array(
264
    '#type' => 'textarea', '#default_value' => $node->log, '#weight' => 18,
265
    '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.')
266
  );
Dries Buytaert's avatar
   
Dries Buytaert committed
267

268
  if (user_access('administer nodes')) {
269
    $form['weight'] = array(
270
271
        '#type' => 'weight', '#title' => t('Weight'),  '#default_value' => $node->weight, '#delta' => 15, '#weight' => -14,
        '#description' => t('Pages at a given level are ordered first by weight and then by title.')
272
    );
Dries Buytaert's avatar
   
Dries Buytaert committed
273
274
  }
  else {
Dries Buytaert's avatar
   
Dries Buytaert committed
275
276
    // If a regular user updates a book page, we create a new revision
    // authored by that user:
277
    $form['revision'] = array('#type' => 'hidden', '#value' => 1);
Dries Buytaert's avatar
   
Dries Buytaert committed
278
279
  }

280
  return $form;
Dries Buytaert's avatar
   
Dries Buytaert committed
281
282
}

283
/**
Dries Buytaert's avatar
   
Dries Buytaert committed
284
285
 * Implementation of function book_outline()
 * Handles all book outline operations.
286
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
287
function book_outline() {
Dries Buytaert's avatar
   
Dries Buytaert committed
288

289
290
  $op = $_POST['op'];
  $edit = $_POST['edit'];
291
  $node = node_load(arg(1));
Dries Buytaert's avatar
   
Dries Buytaert committed
292

Dries Buytaert's avatar
   
Dries Buytaert committed
293
294
295
  if ($node->nid) {
    switch ($op) {
      case t('Add to book outline'):
296
297
        db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $edit['parent'], $edit['weight']);
        db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $edit['log'], $node->vid);
298
        drupal_set_message(t('The post has been added to the book.'));
Dries Buytaert's avatar
   
Dries Buytaert committed
299
300
301
302
        drupal_goto("node/$node->nid");
        break;

      case t('Update book outline'):
303
304
        db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $edit['parent'], $edit['weight'], $node->vid);
        db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $edit['log'], $node->vid);
305
        drupal_set_message(t('The book outline has been updated.'));
Dries Buytaert's avatar
   
Dries Buytaert committed
306
307
308
309
310
        drupal_goto("node/$node->nid");
        break;

      case t('Remove from book outline'):
        db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
311
        drupal_set_message(t('The post has been removed from the book.'));
Dries Buytaert's avatar
   
Dries Buytaert committed
312
313
314
315
        drupal_goto("node/$node->nid");
        break;

      default:
316
        $page = db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
317

318
        $form['parent'] = array(
319
320
          '#type' => 'select', '#title' => t('Parent'), '#default_value' => $page->parent,
          '#options' => book_toc($node->nid), '#description' => t('The parent page in the book.')
321
        );
322

323
        $form['weight'] = array(
324
325
          '#type' => 'weight', '#title' => t('Weight'), '#default_value' => $page->weight, '#delta' => 15,
          '#description' => t('Pages at a given level are ordered first by weight and then by title.')
326
        );
327

328
        $form['log'] = array(
329
          '#type' => 'textarea', '#title' => t('Log message'),
330
          '#default_value' => $node->log, '#description' => t('An explanation to help other authors understand your motivations to put this post into the book.')
331
        );
Dries Buytaert's avatar
   
Dries Buytaert committed
332
333

        if ($page->nid) {
334
335
          $form['update'] = array('#type' => 'submit', '#value' => t('Update book outline'));
          $form['remove'] = array('#type' => 'submit', '#value' => t('Remove from book outline'));
Dries Buytaert's avatar
   
Dries Buytaert committed
336
337
        }
        else {
338
          $form['add'] = array('#type' => 'submit', '#value' => t('Add to book outline'));
Dries Buytaert's avatar
   
Dries Buytaert committed
339
        }
Dries Buytaert's avatar
   
Dries Buytaert committed
340

341
        drupal_set_title(check_plain($node->title));
342
        return drupal_get_form('book_outline', $form);
Dries Buytaert's avatar
   
Dries Buytaert committed
343
344
345
346
    }
  }
}

347
348
349
/**
 * Return the path (call stack) to a certain book page.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
350
function book_location($node, $nodes = array()) {
351
  $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent));
Dries Buytaert's avatar
   
Dries Buytaert committed
352
353
354
355
356
357
358
  if ($parent->title) {
    $nodes = book_location($parent, $nodes);
    array_push($nodes, $parent);
  }
  return $nodes;
}

359
360
361
/**
 * Accumulates the nodes up to the root of the book from the given node in the $nodes array.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
362
function book_location_down($node, $nodes = array()) {
363
  $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid));
Dries Buytaert's avatar
   
Dries Buytaert committed
364
365
366
367
368
369
370
  if ($last_direct_child) {
    array_push($nodes, $last_direct_child);
    $nodes = book_location_down($last_direct_child, $nodes);
  }
  return $nodes;
}

371
/**
372
 * Fetches the node object of the previous page of the book.
373
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
374
function book_prev($node) {
Dries Buytaert's avatar
   
Dries Buytaert committed
375
  // If the parent is zero, we are at the start of a book so there is no previous.
Dries Buytaert's avatar
   
Dries Buytaert committed
376
377
378
379
  if ($node->parent == 0) {
    return NULL;
  }

Dries Buytaert's avatar
   
Dries Buytaert committed
380
  // Previous on the same level:
381
  $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title));
Dries Buytaert's avatar
   
Dries Buytaert committed
382
  if ($direct_above) {
Dries Buytaert's avatar
   
Dries Buytaert committed
383
    // Get last leaf of $above.
Dries Buytaert's avatar
   
Dries Buytaert committed
384
    $path = book_location_down($direct_above);
Dries Buytaert's avatar
   
Dries Buytaert committed
385
386

    return $path ? (count($path) > 0 ? array_pop($path) : NULL) : $direct_above;
Dries Buytaert's avatar
   
Dries Buytaert committed
387
388
  }
  else {
Dries Buytaert's avatar
   
Dries Buytaert committed
389
    // Direct parent:
390
    $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1 AND n.moderate = 0'), $node->parent));
Dries Buytaert's avatar
   
Dries Buytaert committed
391
392
393
394
    return $prev;
  }
}

395
/**
396
 * Fetches the node object of the next page of the book.
397
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
398
399
function book_next($node) {
  // get first direct child
400
  $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight ASC, n.title ASC'), $node->nid));
Dries Buytaert's avatar
   
Dries Buytaert committed
401
402
403
404
  if ($child) {
    return $child;
  }

Dries Buytaert's avatar
   
Dries Buytaert committed
405
406
  // No direct child: get next for this level or any parent in this book.
  array_push($path = book_location($node), $node); // Path to top-level node including this one.
407

Dries Buytaert's avatar
   
Dries Buytaert committed
408
  while (($leaf = array_pop($path)) && count($path)) {
409
    $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title));
Dries Buytaert's avatar
   
Dries Buytaert committed
410
411
412
413
414
415
    if ($next) {
      return $next;
    }
  }
}

416
417
418
419
420
421
/**
 * Returns the content of a given node.  If $teaser if true, returns
 * the teaser rather than full content.  Displays the most recently
 * approved revision of a node (if any) unless we have to display this
 * page in the context of the moderation queue.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
422
function book_content($node, $teaser = FALSE) {
423
  $op = $_POST['op'];
Dries Buytaert's avatar
   
Dries Buytaert committed
424

425
426
  // Extract the page body.
  $node = node_prepare($node, $teaser);
Dries Buytaert's avatar
   
Dries Buytaert committed
427

Dries Buytaert's avatar
   
Dries Buytaert committed
428
429
430
  return $node;
}

431
432
433
434
435
436
/**
 * Implementation of hook_view().
 *
 * If not displayed on the main page, we render the node as a page in the
 * book with extra links to the previous and next pages.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
437
function book_view(&$node, $teaser = FALSE, $page = FALSE) {
Dries Buytaert's avatar
   
Dries Buytaert committed
438
  $node = book_content($node, $teaser);
Dries Buytaert's avatar
   
Dries Buytaert committed
439
440
}

441
/**
Dries Buytaert's avatar
   
Dries Buytaert committed
442
443
444
 * Implementation of hook_nodeapi().
 *
 * Appends book navigation to all nodes in the book.
445
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
446
447
448
449
function book_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'view':
      if (!$teaser) {
450
        $book = db_fetch_array(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
Dries Buytaert's avatar
   
Dries Buytaert committed
451
        if ($book) {
452
          if ($node->moderate && user_access('administer nodes')) {
453
            drupal_set_message(t("The post has been submitted for moderation and won't be accessible until it has been approved."));
454
455
          }

Dries Buytaert's avatar
   
Dries Buytaert committed
456
457
458
          foreach ($book as $key => $value) {
            $node->$key = $value;
          }
459
460
461
462
463
464
465
466
467
468
469

          $path = book_location($node);
          // Construct the breadcrumb:
          $node->breadcrumb = array(); // Overwrite the trail with a book trail.
          foreach ($path as $level) {
            $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' =>  $level->title);
          }
          $node->breadcrumb[] = array('path' => 'node/'. $node->nid);

          $node->body .= theme('book_navigation', $node);

470
471
472
          if ($page) {
            menu_set_location($node->breadcrumb);
          }
Dries Buytaert's avatar
   
Dries Buytaert committed
473
        }
Dries Buytaert's avatar
   
Dries Buytaert committed
474
      }
Dries Buytaert's avatar
   
Dries Buytaert committed
475
      break;
Dries Buytaert's avatar
   
Dries Buytaert committed
476
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
477
}
Dries Buytaert's avatar
   
Dries Buytaert committed
478

479
480
481
/**
 * Prepares both the custom breadcrumb trail and the forward/backward
 * navigation for a node presented as a book page.
482
483
 *
 * @ingroup themeable
484
 */
485
function theme_book_navigation($node) {
Dries Buytaert's avatar
   
Dries Buytaert committed
486
  if ($node->nid) {
487
    $output .= '<div class="book">';
Dries Buytaert's avatar
   
Dries Buytaert committed
488
489

    if ($tree = book_tree($node->nid)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
490
      $output .= '<div class="tree">'. $tree .'</div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
491
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
492

Dries Buytaert's avatar
   
Dries Buytaert committed
493
    if ($prev = book_prev($node)) {
494
      drupal_add_link(array('rel' => 'prev', 'href' => url('node/'. $prev->nid)));
495
      $links .= '<div class="prev">';
Dries Buytaert's avatar
   
Dries Buytaert committed
496
      $links .= l(t('previous'), 'node/'. $prev->nid, array('title' => t('View the previous page.')));
497
      $links .= '</div>';
498
      $titles .= '<div class="prev">'. check_plain($prev->title) .'</div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
499
500
    }
    else {
Dries Buytaert's avatar
   
Dries Buytaert committed
501
      $links .= '<div class="prev">&nbsp;</div>'; // Make an empty div to fill the space.
Dries Buytaert's avatar
   
Dries Buytaert committed
502
503
    }
    if ($next = book_next($node)) {
504
      drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid)));
505
      $links .= '<div class="next">';
Dries Buytaert's avatar
   
Dries Buytaert committed
506
      $links .= l(t('next'), 'node/'. $next->nid, array('title' => t('View the next page.')));
507
      $links .= '</div>';
508
      $titles .= '<div class="next">'. check_plain($next->title) .'</div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
509
510
    }
    else {
Dries Buytaert's avatar
   
Dries Buytaert committed
511
      $links .= '<div class="next">&nbsp;</div>'; // Make an empty div to fill the space.
Dries Buytaert's avatar
   
Dries Buytaert committed
512
513
    }
    if ($node->parent) {
514
      drupal_add_link(array('rel' => 'index', 'href' => url('node/'. $node->parent)));
515
      $links .= '<div class="up">';
Dries Buytaert's avatar
   
Dries Buytaert committed
516
      $links .= l(t('up'), 'node/'. $node->parent, array('title' => t('View this page\'s parent section.')));
517
      $links .= '</div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
518
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
519

520
    $output .= '<div class="nav">';
Dries Buytaert's avatar
   
Dries Buytaert committed
521
522
    $output .= ' <div class="links">'. $links .'</div>';
    $output .= ' <div class="titles">'. $titles .'</div>';
523
524
    $output .= '</div>';
    $output .= '</div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
525
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
526

527
  return $output;
Dries Buytaert's avatar
   
Dries Buytaert committed
528
}
Dries Buytaert's avatar
 
Dries Buytaert committed
529

530
531
532
/**
 * This is a helper function for book_toc().
 */
533
function book_toc_recurse($nid, $indent, $toc, $children, $exclude) {
Dries Buytaert's avatar
   
Dries Buytaert committed
534
535
  if ($children[$nid]) {
    foreach ($children[$nid] as $foo => $node) {
536
537
538
539
      if (!$exclude || $exclude != $node->nid) {
        $toc[$node->nid] = $indent .' '. $node->title;
        $toc = book_toc_recurse($node->nid, $indent .'--', $toc, $children, $exclude);
      }
Dries Buytaert's avatar
   
Dries Buytaert committed
540
541
542
543
544
545
    }
  }

  return $toc;
}

546
547
548
/**
 * Returns an array of titles and nid entries of book pages in table of contents order.
 */
549
function book_toc($exclude = 0) {
550
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title'));
Dries Buytaert's avatar
   
Dries Buytaert committed
551

Dries Buytaert's avatar
   
Dries Buytaert committed
552
  while ($node = db_fetch_object($result)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
553
554
555
556
    if (!$children[$node->parent]) {
      $children[$node->parent] = array();
    }
    array_push($children[$node->parent], $node);
Dries Buytaert's avatar
   
Dries Buytaert committed
557
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
558

559
560
  $toc = array();

Dries Buytaert's avatar
   
Dries Buytaert committed
561
562
  // If the user is an administrator, add the top-level book page;
  // only administrators can start new books.
563
564
  if (user_access('administer nodes')) {
    $toc[0] = '<'. t('top-level') .'>';
Dries Buytaert's avatar
   
Dries Buytaert committed
565
566
  }

567
  $toc = book_toc_recurse(0, '', $toc, $children, $exclude);
Dries Buytaert's avatar
   
Dries Buytaert committed
568

Dries Buytaert's avatar
   
Dries Buytaert committed
569
570
571
  return $toc;
}

572
573
574
/**
 * This is a helper function for book_tree()
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
575
function book_tree_recurse($nid, $depth, $children, $unfold = array()) {
Dries Buytaert's avatar
   
Dries Buytaert committed
576
  if ($depth > 0) {
Dries Buytaert's avatar
   
Dries Buytaert committed
577
578
    if ($children[$nid]) {
      foreach ($children[$nid] as $foo => $node) {
Dries Buytaert's avatar
   
Dries Buytaert committed
579
580
581
        if (in_array($node->nid, $unfold)) {
          if ($tree = book_tree_recurse($node->nid, $depth - 1, $children, $unfold)) {
            $output .= '<li class="expanded">';
Dries Buytaert's avatar
   
Dries Buytaert committed
582
583
            $output .= l($node->title, 'node/'. $node->nid);
            $output .= '<ul>'. $tree .'</ul>';
Dries Buytaert's avatar
   
Dries Buytaert committed
584
585
586
            $output .= '</li>';
          }
          else {
Dries Buytaert's avatar
   
Dries Buytaert committed
587
            $output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
Dries Buytaert's avatar
   
Dries Buytaert committed
588
589
590
591
          }
        }
        else {
          if ($tree = book_tree_recurse($node->nid, 1, $children)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
592
            $output .= '<li class="collapsed">'. l($node->title, 'node/'. $node->nid) .'</li>';
Dries Buytaert's avatar
   
Dries Buytaert committed
593
594
          }
          else {
Dries Buytaert's avatar
   
Dries Buytaert committed
595
            $output .= '<li class="leaf">'. l($node->title, 'node/'. $node->nid) .'</li>';
Dries Buytaert's avatar
   
Dries Buytaert committed
596
          }
Dries Buytaert's avatar
   
Dries Buytaert committed
597
        }
Dries Buytaert's avatar
   
Dries Buytaert committed
598
599
      }
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
600
601
602
603
604
  }

  return $output;
}

605
606
607
608
/**
 * Returns an HTML nested list (wrapped in a menu-class div) representing the book nodes
 * as a tree.
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
609
function book_tree($parent = 0, $depth = 3, $unfold = array()) {
610
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
Dries Buytaert's avatar
   
Dries Buytaert committed
611

Dries Buytaert's avatar
   
Dries Buytaert committed
612
613
614
615
  while ($node = db_fetch_object($result)) {
    $list = $children[$node->parent] ? $children[$node->parent] : array();
    array_push($list, $node);
    $children[$node->parent] = $list;
Dries Buytaert's avatar
   
Dries Buytaert committed
616
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
617

Dries Buytaert's avatar
   
Dries Buytaert committed
618
  if ($tree = book_tree_recurse($parent, $depth, $children, $unfold)) {
619
    return '<div class="menu"><ul>'. $tree .'</ul></div>';
Dries Buytaert's avatar
   
Dries Buytaert committed
620
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
621
622
}

623
/**
624
 * Menu callback; prints a listing of all books.
625
 */
Dries Buytaert's avatar
   
Dries Buytaert committed
626
function book_render() {
627
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title'));
Dries Buytaert's avatar
   
Dries Buytaert committed
628

629
630
631
632
633
634
  $books = array();
  while ($node = db_fetch_object($result)) {
    $books[] = l($node->title, 'node/'. $node->nid);
  }

  return theme('item_list', $books);
Dries Buytaert's avatar
   
Dries Buytaert committed
635
636
}

637
/**
638
639
640
 * Menu callback; Generates various representation of a book page with
 * all descendants and prints the requested representation to output.
 *
641
642
643
644
 * The function delegates the generation of output to helper functions.
 * The function name is derived by prepending 'book_export_' to the
 * given output type.  So, e.g., a type of 'html' results in a call to
 * the function book_export_html().
645
646
647
 *
 * @param type
 *   - a string encoding the type of output requested.
648
649
650
 *       The following types are currently supported in book module
 *          html: HTML (printer friendly output)
 *       Other types are supported in contributed modules.
651
652
653
 * @param nid
 *   - an integer representing the node id (nid) of the node to export
 *
654
 */
655
function book_export($type = 'html', $nid = 0) {
656
  $type = drupal_strtolower($type);
657
658
659
660
661
  $depth = _book_get_depth($nid);
  $export_function = 'book_export_' . $type;

  if (function_exists($export_function)) {
    print call_user_func($export_function, $nid, $depth);
662
663
  }
  else {
664
    drupal_set_message('Unknown export format');
665
    drupal_not_found();
666
  }
667
}
Dries Buytaert's avatar
   
Dries Buytaert committed
668

669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
/**
 * This function is called by book_export() to generate HTML for export.
 *
 * The given node is /embedded to its absolute depth in a top level
 * section/.  For example, a child node with depth 2 in the hierarchy
 * is contained in (otherwise empty) &lt;div&gt; elements
 * corresponding to depth 0 and depth 1.  This is intended to support
 * WYSIWYG output - e.g., level 3 sections always look like level 3
 * sections, no matter their depth relative to the node selected to be
 * exported as printer-friendly HTML.
 *
 * @param nid
 *   - an integer representing the node id (nid) of the node to export
 * @param depth
 * - an integer giving the depth in the book hierarchy of the node
       which is to be exported
 * @return
 * - string containing HTML representing the node and its children in
       the book hierarchy
*/
function book_export_html($nid, $depth) {
  if (user_access('see printer-friendly version')) {
    global $base_url;
    for ($i = 1; $i < $depth; $i++) {
      $output .= "<div class=\"section-$i\">\n";
    }
    $output .= book_recurse($nid, $depth, 'book_node_visitor_html_pre', 'book_node_visitor_html_post');
    for ($i = 1; $i < $depth; $i++) {
      $output .= "</div>\n";

    }
    $html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
    $html .= '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
    $html .= "<head>\n<title>". check_plain($node->title) ."</title>\n";
    $html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
    $html .= '<base href="'. $base_url .'/" />' . "\n";
    $html .= "<style type=\"text/css\">\n@import url(misc/print.css);\n</style>\n";
    $html .= "</head>\n<body>\n". $output . "\n</body>\n</html>\n";
    return $html;
  }
  else {
    drupal_access_denied();
  }
}

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
/**
 * How the book's HTML export should be themed
 *
 * @ingroup themeable
 */
function theme_book_export_html($title, $content) {
  global $base_url;
  $html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
  $html .= '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">';
  $html .= "<head>\n<title>". $title ."</title>\n";
  $html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  $html .= '<base href="'. $base_url .'/" />' . "\n";
  $html .= "<style type=\"text/css\">\n@import url(misc/print.css);\n</style>\n";
  $html .= "</head>\n<body>\n". $content . "\n</body>\n</html>\n";
  return $html;
}

731
/**
732
733
734
 * Given a node, this function returns the depth of the node in its hierarchy.
 * A root node has depth 1, and children of a node of depth n have depth (n+1).
 *
735
736
 * @param nid
 *   - the nid of the node whose depth to compute.
737
738
739
 * @return
 *   - the depth of the given node in its hierarchy.  Returns 0 if the node
 *  does not exist or is not part of a book hierarchy.
740
 */
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
function _book_get_depth($nid) {
  $depth = 0;
  if ($nid) {
    while ($nid) {
      $result = db_query(db_rewrite_sql('SELECT b.parent FROM {book} b WHERE b.nid = %d'), $nid);
      $obj =  db_fetch_object($result);
      $parent = $obj->parent;
      if ($nid == $parent->parent) {
        $nid = 0;
      }
      else {
        $nid = $parent;
      }
      $depth++;
    }
    return $depth;
  }
  else {
    return 0;
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
761
762
}

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
/**
 * Traverses the book tree.  Applies the $visit_pre() callback to each
 * node, is called recursively for each child of the node (in weight,
 * title order).  Finally appends the output of the $visit_post()
 * callback to the output before returning the generated output.
 *
 * @param nid
 *  - the node id (nid) of the root node of the book hierarchy.
 * @param depth
 *  - the depth of the given node in the book hierarchy.
 * @param visit_pre
 *  - a function callback to be called upon visiting a node in the tree
 * @param visit_post
 *  - a function callback to be called after visiting a node in the tree,
 *    but before recursively visiting children.
 * @return
 *  - the output generated in visiting each node
 */
function book_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) {
782
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
783
  while ($page = db_fetch_object($result)) {
Dries Buytaert's avatar
   
Dries Buytaert committed
784
    // Load the node:
785
    $node = node_load($page->nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
786

Dries Buytaert's avatar
   
Dries Buytaert committed
787
    if ($node) {
788
789
      if (function_exists($visit_pre)) {
        $output .= call_user_func($visit_pre, $node, $depth, $nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
790
      }
791
792
      else {
        $output .= book_node_visitor_html_pre($node, $depth, $nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
793
      }
Dries Buytaert's avatar
   
Dries Buytaert committed
794

795
      $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $node->nid);
796
      while ($childpage = db_fetch_object($children)) {
797
          $childnode = node_load($childpage->nid);
798
          if ($childnode->nid != $node->nid) {
799
              $output .= book_recurse($childnode->nid, $depth + 1, $visit_pre, $visit_post);
800
801
802
          }
      }
      if (function_exists($visit_post)) {
803
        $output .= call_user_func($visit_post, $node, $depth);
804
      }
805
806
      else {
        # default
807
        $output .= book_node_visitor_html_post($node, $depth);
808
      }
Dries Buytaert's avatar
   
Dries Buytaert committed
809
    }
Dries Buytaert's avatar
   
Dries Buytaert committed
810
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
811

Dries Buytaert's avatar
   
Dries Buytaert committed
812
813
  return $output;
}
Dries Buytaert's avatar
   
Dries Buytaert committed
814

815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
/**
 * Generates printer-friendly HTML for a node.  This function
 * is a 'pre-node' visitor function for book_recurse().
 *
 * @param $node
 *   - the node to generate output for.
 * @param $depth
 *   - the depth of the given node in the hierarchy. This
 *   is used only for generating output.
 * @param $nid
 *   - the node id (nid) of the given node. This
 *   is used only for generating output.
 * @return
 *   - the HTML generated for the given node.
 */
830
function book_node_visitor_html_pre($node, $depth, $nid) {
831
832
833
834
835
  // Output the content:
  if (node_hook($node, 'content')) {
    $node = node_invoke($node, 'content');
  }
  // Allow modules to change $node->body before viewing.
836
  node_invoke_nodeapi($node, 'print', $node->body, false);
837

838
839
  $output .= "<div id=\"node-". $node->nid ."\" class=\"section-$depth\">\n";
  $output .= "<h1 class=\"book-heading\">". check_plain($node->title) ."</h1>\n";
840
841
842
843
844
845
846
847
848
849
850
851

  if ($node->body) {
    $output .= $node->body;
  }
  return $output;
}

/**
 * Finishes up generation of printer-friendly HTML after visiting a
 * node. This function is a 'post-node' visitor function for
 * book_recurse().
 */
852
function book_node_visitor_html_post($node, $depth) {
853
854
855
  return "</div>\n";
}

856
857
858
859
860
861
862
863
864
865
866
function _book_admin_table($nodes = array()) {
  $form = array(
    '#theme' => 'book_admin_table',
    '#tree' => TRUE,
  );

  foreach ($nodes as $node) {
    $form = array_merge($form, _book_admin_table_tree($node, 0));
  }

  return $form;
867
868
}

869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
function _book_admin_table_tree($node, $depth) {
  $form = array();

  $form[] = array(
    'nid' => array('#type' => 'value', '#value' => $node->nid),
    'depth' => array('#type' => 'value', '#value' => $depth),
    'title' => array(
      '#type' => 'textfield',
      '#default_value' => $node->title,
      '#maxlength' => 255,
    ),
    'weight' => array(
      '#type' => 'weight',
      '#default_value' => $node->weight,
      '#delta' => 15,
    ),
885
  );
886
887
888
889
890
891
892

  $children = db_query(db_rewrite_sql('SELECT n.nid, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $node->nid);
  while ($child = db_fetch_object($children)) {
    $form = array_merge($form, _book_admin_table_tree(node_load($child->nid), $depth + 1));
  }

  return $form;
Dries Buytaert's avatar
   
Dries Buytaert committed
893
894
}

895
896
function theme_book_admin_table($form) {
  $header = array(t('Title'), t('Weight'), array('data' => t('Operations'), 'colspan' => '3'));
Dries Buytaert's avatar
   
Dries Buytaert committed
897

898
  $rows = array();
899
900
901
902
903
904
905
906
907
  foreach (element_children($form) as $key) {
    $nid = $form[$key]['nid']['#value'];
    $rows[] = array(
      '<div style="padding-left: '. (25 * $form[$key]['depth']['#value']) .'px;">'. form_render($form[$key]['title']) .'</div>',
      form_render($form[$key]['weight']),
      l(t('view'), 'node/'. $nid),
      l(t('edit'), 'node/'. $nid .'/edit'),
      l(t('delete'), 'node/'. $nid .'/delete')
    );
Dries Buytaert's avatar
   
Dries Buytaert committed
908
909
  }

910
  return theme('table', $header, $rows);
Dries Buytaert's avatar
   
Dries Buytaert committed
911
912
}

913
914
915
/**
 * Display an administrative view of the hierarchy of a book.
 */
916
function book_admin_edit($nid) {
917
  $node = node_load($nid);
918
  if ($node->nid) {
919
    drupal_set_title(check_plain($node->title));
920
    $form = array();
921

922
923
924
925
926
    $form['table'] = _book_admin_table(array($node));
    $form['save'] = array(
      '#type' => 'submit',
      '#value' => t('Save book pages'),
    );
Dries Buytaert's avatar
   
Dries Buytaert committed
927

928
    return drupal_get_form('book_admin_edit', $form);
Dries Buytaert's avatar
   
Dries Buytaert committed
929
  }
930
931
932
  else {
    drupal_not_found();
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
933
934
}

935
/**
936
 * Menu callback; displays a listing of all orphaned book pages.
937
 */
938
939
function book_admin_orphan() {
  $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid'));
Dries Buytaert's avatar
   
Dries Buytaert committed
940

941
942
943
944
945
946
947
948
949
950
  $pages = array();
  while ($page = db_fetch_object($result)) {
    $pages[$page->nid] = $page;
  }

  $orphans = array();
  if (count($pages)) {
    foreach ($pages as $page) {
      if ($page->parent && empty($pages[$page->parent])) {
        $orphans[] = node_load($page->nid);
Dries Buytaert's avatar
   
Dries Buytaert committed
951
      }
Dries Buytaert's avatar
   
Dries Buytaert committed