node.module 60.7 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
2
// $Id$
Dries's avatar
   
Dries committed
3

Dries's avatar
   
Dries committed
4
5
6
7
8
/**
 * @file
 * The core that allows content to be submitted to the site.
 */

9
10
define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);

Dries's avatar
   
Dries committed
11
12
13
14
/**
 * Implementation of hook_help().
 */
function node_help($section) {
15
  switch ($section) {
Dries's avatar
   
Dries committed
16
    case 'admin/help#node':
Dries's avatar
   
Dries committed
17
      $output = t("
Dries's avatar
   
Dries committed
18
19
20
21
22
23
24
      <h3>Nodes</h3>
      <p>The core of the Drupal system is the node. All of the contents of the system are placed in nodes, or extensions of nodes.
      A base node contains:<dl>
      <dt>A Title</dt><dd>Up to 128 characters of text that titles the node.</dd>
      <dt>A Teaser</dt><dd>A small block of text that is meant to get you interested in the rest of node. Drupal will automatically pull a small amount of the body of the node to make the teaser (To configure how long the teaser will be <a href=\"%teaser\">click here</a>). The teaser can be changed if you don't like what Drupal grabs.</dd>
      <dt>The Body</dt><dd>The main text that comprises your content.</dd>
      <dt>A Type</dt><dd>What kind of node is this? Blog, book, forum, comment, unextended, etc.</dd>
25
      <dt>An Author</dt><dd>The author's name. It will either be \"anonymous\" or a valid user. You <em>cannot</em> set it to an arbitrary value.</dd>
Dries's avatar
   
Dries committed
26
27
      <dt>Authored on</dt><dd>The date the node was written.</dd>
      <dt>Changed</dt><dd>The last time this node was changed.</dd>
28
      <dt>Sticky at top of lists</dt><dd>In listings such as the frontpage or a taxonomy overview the teasers of a selected amount of nodes is displayed. If you want to force a node to appear on the top of such a listing, you must set it to 'sticky'. This way it will float to the top of a listing, and it will not be pushed down by newer content.
Dries's avatar
   
Dries committed
29
30
      <dt>Allow user comments</dt><dd>A node can have comments. These comments can be written by other users (Read-write), or only by admins (Read-only).</dd>
      <dt>Revisions</dt><dd>Drupal has a revision system so that you can \"roll back\" to an older version of a post if the new version is not what you want.</dd>
Dries's avatar
   
Dries committed
31
      <dt>Promote to front page</dt><dd>To get people to look at the new stuff on your site you can choose to move it to the front page. The front page is configured to show the teasers from only a few of the total nodes you have on your site (To configure how many teasers <a href=\"%teaser\">click here</a>).</dd>
Dries's avatar
   
Dries committed
32
33
34
35
      <dt>In moderation queue</dt><dd>Drupal has a moderation system. If it is active, a node is in one of three states: approved and published, approved and unpublished, and awaiting approval. If you are moderating a node it should be in the moderation queue.</dd>
      <dt>Votes</dt><dd>If you are moderating a node this counts how many votes the node has gotten. Once a node gets a certain number of vote it will either be approved or dropped.
      <dt>Score</dt><dd>The score of the node is gotten by the votes it is given.</dd>
      <dt>Users</dt><dd>The list of users who have voted on a moderated node.</dd>
36
      <dt>Published</dt><dd>When using Drupal's moderation system a node remains unpublished -- unavailable to non-moderators -- until it is marked Published.</dd></dl>
37
      <p>Now that you know what is in a node, here are some of the types of nodes available.</p>", array("%teaser" => url("admin/node/configure/settings")));
Dries's avatar
   
Dries committed
38

Dries's avatar
   
Dries committed
39
40
41
      foreach (node_list() as $type) {
        $output .= '<h3>'. t('Node type: %module', array('%module' => node_invoke($type, 'node_name'))). '</h3>';
        $output .= implode("\n", module_invoke_all('help', 'node/add#'. $type));
Dries's avatar
   
Dries committed
42
      }
Dries's avatar
   
Dries committed
43

Dries's avatar
   
Dries committed
44
      return $output;
45

Dries's avatar
   
Dries committed
46
    case 'admin/modules#description':
Dries's avatar
   
Dries committed
47
      return t('The core that allows content to be submitted to the site.');
48
49
    case 'admin/node/configure':
    case 'admin/node/configure/settings':
50
      return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
51
    case 'admin/node':
52
      return t('<p>Below is a list of all of the posts on your site. Other forms of content are listed elsewhere (e.g. <a href="%comments">comments</a>).<br />Clicking a title views the post, while clicking an author\'s name edits their user information.<br />Other post-related tasks are available from the menu.</p>', array('%comments' => url('admin/comment')));
53
    case 'admin/node/search':
54
      return t('<p>Enter a simple pattern to search for a post. This can include the wildcard character *.<br />For example, a search for "br*" might return "bread bakers", "our daily bread" and "brenda".</p>');
55
    case 'admin/node/configure/defaults':
56
      return t('<p>This page lets you set the defaults used during creation of nodes for all the different node types.<br /><em>comment:</em> Read/write setting for comments.<br /><em>publish:</em> Is this post publicly viewable, has it been published?<br /><em>promote:</em> Is this post to be promoted to the front page?<br /><em>moderate:</em> Does this post need approval before it can be viewed?<br /><em>sticky:</em> Is this post always visible at the top of lists?<br /><em>revision:</em> Will this post go into the revision system allowing multiple versions to be saved?</p>');
Dries's avatar
   
Dries committed
57
  }
Dries's avatar
   
Dries committed
58
59
60
61

  if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions') {
    return t('The revisions let you track differences between multiple versions of a post.');
  }
Dries's avatar
   
Dries committed
62
63
}

Dries's avatar
   
Dries committed
64
65
66
/**
 * Implementation of hook_cron().
 */
67
function node_cron() {
Dries's avatar
   
Dries committed
68
  db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
69
70
}

Dries's avatar
   
Dries committed
71
/**
72
 * Menu callback; presents node-specific information from "admin/help".
Dries's avatar
   
Dries committed
73
 */
Dries's avatar
   
Dries committed
74
function node_help_page() {
Dries's avatar
   
Dries committed
75
  print theme('page', node_help('admin/help#node'));
Dries's avatar
   
Dries committed
76
77
}

Dries's avatar
   
Dries committed
78
79
80
81
/**
 * Gather a listing of links to nodes.
 *
 * @param $result
82
 *   A DB result object from a query to fetch node objects.  If your query joins the <code>node_comment_statistics</code> table so that the <code>comment_count</code> field is available, a title attribute will be added to show the number of comments.
Dries's avatar
   
Dries committed
83
 * field to be set.
Dries's avatar
   
Dries committed
84
85
86
87
88
89
 * @param $title
 *   A heading for the resulting list.
 *
 * @return
 *   An HTML list suitable as content for a block.
 */
Dries's avatar
   
Dries committed
90
91
function node_title_list($result, $title = NULL) {
  while ($node = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
92
    $items[] = l($node->title, 'node/'. $node->nid, $node->comment_count ? array('title' => format_plural($node->comment_count, '1 comment', '%count comments')) : '');
Dries's avatar
   
Dries committed
93
94
  }

Dries's avatar
   
Dries committed
95
  return theme('node_list', $items, $title);
Dries's avatar
   
Dries committed
96
97
}

Dries's avatar
   
Dries committed
98
99
100
/**
 * Format a listing of links to nodes.
 */
Dries's avatar
   
Dries committed
101
function theme_node_list($items, $title = NULL) {
Dries's avatar
   
Dries committed
102
  return theme('item_list', $items, $title);
Dries's avatar
   
Dries committed
103
104
}

Dries's avatar
   
Dries committed
105
106
107
/**
 * Update the 'last viewed' timestamp of the specified node for current user.
 */
Dries's avatar
   
Dries committed
108
109
110
111
function node_tag_new($nid) {
  global $user;

  if ($user->uid) {
Dries's avatar
   
Dries committed
112
    if (node_last_viewed($nid)) {
Dries's avatar
   
Dries committed
113
      db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
Dries's avatar
   
Dries committed
114
115
    }
    else {
Dries's avatar
   
Dries committed
116
      @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
Dries's avatar
   
Dries committed
117
118
119
120
    }
  }
}

Dries's avatar
   
Dries committed
121
122
123
124
/**
 * Retrieves the timestamp at which the current user last viewed the
 * specified node.
 */
Dries's avatar
   
Dries committed
125
126
function node_last_viewed($nid) {
  global $user;
Dries's avatar
   
Dries committed
127
  static $history;
Dries's avatar
   
Dries committed
128

Dries's avatar
   
Dries committed
129
130
131
132
133
  if (!isset($history[$nid])) {
    $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
  }

  return ($history[$nid]->timestamp ? $history[$nid]->timestamp : 0);
Dries's avatar
   
Dries committed
134
135
136
}

/**
Dries's avatar
   
Dries committed
137
 * Determine whether the supplied timestamp is newer than the user's last view
Dries's avatar
   
Dries committed
138
 * of a given node.
Dries's avatar
   
Dries committed
139
 *
Dries's avatar
   
Dries committed
140
141
142
143
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
Dries's avatar
   
Dries committed
144
 */
Dries's avatar
   
Dries committed
145
146
147
148
function node_is_new($nid, $timestamp) {
  global $user;
  static $cache;

Dries's avatar
Dries committed
149
  if (!isset($cache[$nid])) {
Dries's avatar
   
Dries committed
150
    if ($user->uid) {
Dries's avatar
   
Dries committed
151
      $cache[$nid] = node_last_viewed($nid);
Dries's avatar
   
Dries committed
152
153
154
155
156
157
    }
    else {
      $cache[$nid] = time();
    }
  }

158
  return ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT);
Dries's avatar
   
Dries committed
159
160
}

Dries's avatar
   
Dries committed
161
/**
Dries's avatar
   
Dries committed
162
 * Automatically generate a teaser for the given body text.
Dries's avatar
   
Dries committed
163
 */
Dries's avatar
   
Dries committed
164
165
function node_teaser($body) {

Dries's avatar
   
Dries committed
166
  $size = variable_get('teaser_length', 600);
Dries's avatar
   
Dries committed
167

Dries's avatar
   
Dries committed
168
169
170
171
172
  // find where the delimiter is in the body
  $delimiter = strpos($body, '<!--break-->');

  // If the size is zero, and there is no delimiter, we return the entire body.
  if ($size == 0 && $delimiter == 0) {
Dries's avatar
   
Dries committed
173
174
    return $body;
  }
Dries's avatar
   
Dries committed
175

176
177
178
179
180
  // If the body contains PHP code, do not split it up to prevent parse errors.
  if (strpos($body, '<?') != false) {
    return $body;
  }

Dries's avatar
   
Dries committed
181
  // If a valid delimiter has been specified, use it to chop of the teaser.
Dries's avatar
   
Dries committed
182
  if ($delimiter > 0) {
Dries's avatar
   
Dries committed
183
184
185
    return substr($body, 0, $delimiter);
  }

Dries's avatar
   
Dries committed
186
  // If we have a short body, return the entire body.
Dries's avatar
   
Dries committed
187
188
189
190
  if (strlen($body) < $size) {
    return $body;
  }

Dries's avatar
   
Dries committed
191
192
  // In some cases, no delimiter has been specified (e.g. when posting using
  // the Blogger API). In this case, we try to split at paragraph boundaries.
Dries's avatar
   
Dries committed
193
  if ($length = strpos($body, '</p>', $size)) {
Dries's avatar
   
Dries committed
194
    return substr($body, 0, $length + 4);
Dries's avatar
   
Dries committed
195
196
  }

Dries's avatar
   
Dries committed
197
  if ($length = strpos($body, '<br />', $size)) {
Dries's avatar
   
Dries committed
198
    return substr($body, 0, $length);
Dries's avatar
   
Dries committed
199
200
  }

Dries's avatar
   
Dries committed
201
  if ($length = strpos($body, '<br>', $size)) {
Dries's avatar
   
Dries committed
202
203
204
    return substr($body, 0, $length);
  }

205
  if ($length = strpos($body, "\n", $size)) {
Dries's avatar
   
Dries committed
206
    return substr($body, 0, $length);
Dries's avatar
   
Dries committed
207
208
  }

Dries's avatar
   
Dries committed
209
210
  // When even the first paragraph is too long, try to split at the end of
  // the next sentence.
Dries's avatar
   
Dries committed
211
  if ($length = strpos($body, '. ', $size)) {
Dries's avatar
   
Dries committed
212
213
214
    return substr($body, 0, $length + 1);
  }

Dries's avatar
   
Dries committed
215
  if ($length = strpos($body, '! ', $size)) {
Dries's avatar
   
Dries committed
216
217
218
    return substr($body, 0, $length + 1);
  }

Dries's avatar
   
Dries committed
219
  if ($length = strpos($body, '? ', $size)) {
Dries's avatar
   
Dries committed
220
221
222
    return substr($body, 0, $length + 1);
  }

223
224
225
226
227
228
229
230
231
232
233
234
  if ($length = strpos($body, '。', $size)) {
    return substr($body, 0, $length + 1);
  }

  if ($length = strpos($body, '、', $size)) {
    return substr($body, 0, $length + 1);
  }

  if ($length = strpos($body, '؟ ', $size)) {
    return substr($body, 0, $length + 1);
  }

Dries's avatar
   
Dries committed
235
  // If all else fails, simply truncate the string.
236
  return truncate_utf8($body, $size);
Dries's avatar
   
Dries committed
237
238
}

Dries's avatar
   
Dries committed
239

240
/**
Dries's avatar
   
Dries committed
241
 * Determine the module that defines the node type of the given node.
Dries's avatar
   
Dries committed
242
243
 *
 * @param &$node
Dries's avatar
   
Dries committed
244
 *   Either a node object, a node array, or a string containing the node type.
Dries's avatar
   
Dries committed
245
246
247
248
 * @return
 *   A string containing the name of the defining module.
 */
function node_get_module_name($node) {
Dries's avatar
   
Dries committed
249
  if (is_array($node)) {
Dries's avatar
   
Dries committed
250
    if ($pos = strpos($node['type'], '-')) {
Dries's avatar
   
Dries committed
251
      return substr($node['type'], 0, $pos);
252
253
    }
    else {
Dries's avatar
   
Dries committed
254
      return $node['type'];
Dries's avatar
   
Dries committed
255
    }
Dries's avatar
   
Dries committed
256
257
  }
  else if (is_object($node)) {
Dries's avatar
   
Dries committed
258
    if ($pos = strpos($node->type, '-')) {
Dries's avatar
   
Dries committed
259
      return substr($node->type, 0, $pos);
260
261
    }
    else {
Dries's avatar
   
Dries committed
262
263
      return $node->type;
    }
Dries's avatar
   
Dries committed
264
265
  }
  else if (is_string($node)) {
Dries's avatar
   
Dries committed
266
    if ($pos = strpos($node, '-')) {
Dries's avatar
   
Dries committed
267
      return substr($node, 0, $pos);
268
269
    }
    else {
Dries's avatar
   
Dries committed
270
271
      return $node;
    }
Dries's avatar
   
Dries committed
272
  }
Dries's avatar
   
Dries committed
273
}
Dries's avatar
   
Dries committed
274

275
/**
Dries's avatar
   
Dries committed
276
277
278
 * Get a list of all the defined node types.
 *
 * @return
Dries's avatar
   
Dries committed
279
 *   A list of all node types.
Dries's avatar
   
Dries committed
280
281
282
283
 */
function node_list() {
  $types = array();
  foreach (module_list() as $module) {
Dries's avatar
   
Dries committed
284
285
    if (module_hook($module, 'node_name')) {
      $module_types = module_invoke($module, 'node_types');
Dries's avatar
   
Dries committed
286
287
      if ($module_types) {
        foreach ($module_types as $type) {
Dries's avatar
   
Dries committed
288
          $types[] = $type;
Dries's avatar
   
Dries committed
289
        }
290
291
      }
      else {
Dries's avatar
   
Dries committed
292
        $types[] = $module;
Dries's avatar
   
Dries committed
293
294
295
296
      }
    }
  }
  return $types;
Dries's avatar
   
Dries committed
297
}
Dries's avatar
   
Dries committed
298

299
/**
Dries's avatar
   
Dries committed
300
301
302
303
304
305
306
307
308
309
 * Determine whether a node hook exists.
 *
 * @param &$node
 *   Either a node object, node array, or a string containing the node type.
 * @param $hook
 *   A string containing the name of the hook.
 * @return
 *   TRUE iff the $hook exists in the node type of $node.
 */
function node_hook(&$node, $hook) {
310
  $function = node_get_module_name($node) ."_$hook";
Dries's avatar
   
Dries committed
311
312
313
314

  return function_exists($function);
}

315
/**
Dries's avatar
   
Dries committed
316
317
318
319
320
321
322
323
324
 * Invoke a node hook.
 *
 * @param &$node
 *   Either a node object, node array, or a string containing the node type.
 * @param $hook
 *   A string containing the name of the hook.
 * @param $a2, $a3, $a4
 *   Arguments to pass on to the hook, after the $node argument.
 * @return
Dries's avatar
   
Dries committed
325
 *   The returned value of the invoked hook.
Dries's avatar
   
Dries committed
326
327
 */
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
328
  $function = node_get_module_name($node) ."_$hook";
Dries's avatar
   
Dries committed
329
330

  if (function_exists($function)) {
Dries's avatar
   
Dries committed
331
    return ($function($node, $a2, $a3, $a4));
Dries's avatar
   
Dries committed
332
333
334
  }
}

Dries's avatar
   
Dries committed
335
336
337
338
339
340
341
342
343
344
345
346
/**
 * Invoke a hook_nodeapi() operation in all modules.
 *
 * @param &$node
 *   Either a node object, node array, or a string containing the node type.
 * @param $op
 *   A string containing the name of the nodeapi operation.
 * @param $a3, $a4
 *   Arguments to pass on to the hook, after the $node and $op arguments.
 * @return
 *   The returned value of the invoked hooks.
 */
Dries's avatar
   
Dries committed
347
function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
Dries's avatar
   
Dries committed
348
349
  $return = array();
  foreach (module_list() as $name) {
Dries's avatar
   
Dries committed
350
    $function = $name .'_nodeapi';
Dries's avatar
   
Dries committed
351
    if (function_exists($function)) {
Dries's avatar
   
Dries committed
352
      $result = $function($node, $op, $a3, $a4);
353
      if (is_array($result)) {
Dries's avatar
   
Dries committed
354
355
        $return = array_merge($return, $result);
      }
Steven Wittens's avatar
Steven Wittens committed
356
      else if (isset($result)) {
357
358
        $return[] = $result;
      }
Dries's avatar
   
Dries committed
359
360
361
362
363
    }
  }
  return $return;
}

Dries's avatar
   
Dries committed
364
365
366
367
368
369
370
371
/**
 * Load a node object from the database.
 *
 * @param $conditions
 *   An array of conditions to match against in the database query. Most calls
 *   will simply use array('nid' => 52).
 * @param $revision
 *   Which numbered revision to load. Defaults to the current version.
Dries's avatar
   
Dries committed
372
373
 * @param $reset
 *   Whether to reset the internal node_load cache.
Dries's avatar
   
Dries committed
374
375
376
377
 *
 * @return
 *   A fully-populated node object.
 */
Dries's avatar
   
Dries committed
378
379
380
381
382
383
384
385
386
387
388
389
390
function node_load($conditions, $revision = NULL, $reset = NULL) {
  static $nodes = array();

  if ($reset) {
    $nodes = array();
  }

  $cachable = (count($conditions) == 1 && isset($conditions['nid']) && $revision == NULL);

  if ($cachable && isset($nodes[$conditions['nid']])) {
    return $nodes[$conditions['nid']];
  }

Dries's avatar
   
Dries committed
391
  // Turn the conditions into a query.
Dries's avatar
   
Dries committed
392
  foreach ($conditions as $key => $value) {
Dries's avatar
   
Dries committed
393
    $cond[] = 'n.'. db_escape_string($key) ." = '". db_escape_string($value) ."'";
Dries's avatar
   
Dries committed
394
395
  }

Dries's avatar
   
Dries committed
396
  // Retrieve the node.
Dries's avatar
   
Dries committed
397
  $node = db_fetch_object(db_query('SELECT n.*, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE '. implode(' AND ', $cond)));
Dries's avatar
   
Dries committed
398
  $node = drupal_unpack($node);
Dries's avatar
   
Dries committed
399

Dries's avatar
   
Dries committed
400
  // Unserialize the revisions and user data fields.
Dries's avatar
   
Dries committed
401
402
403
404
  if ($node->revisions) {
    $node->revisions = unserialize($node->revisions);
  }

Dries's avatar
   
Dries committed
405
406
  // Call the node specific callback (if any) and piggy-back the
  // results to the node or overwrite some values.
Dries's avatar
   
Dries committed
407
  if ($extra = node_invoke($node, 'load')) {
Dries's avatar
   
Dries committed
408
409
410
411
412
    foreach ($extra as $key => $value) {
      $node->$key = $value;
    }
  }

Dries's avatar
   
Dries committed
413
414
415
416
417
418
  if ($extra = node_invoke_nodeapi($node, 'load')) {
    foreach ($extra as $key => $value) {
      $node->$key = $value;
    }
  }

Dries's avatar
   
Dries committed
419
  // Return the desired revision.
Dries's avatar
   
Dries committed
420
  if (!is_null($revision) && is_array($node->revisions[$revision])) {
421
   $node = $node->revisions[$revision]['node'];
Dries's avatar
   
Dries committed
422
423
  }

Dries's avatar
   
Dries committed
424
425
426
427
  if ($cachable) {
    $nodes[$conditions['nid']] = $node;
  }

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

Dries's avatar
   
Dries committed
431
432
433
/**
 * Save a node object into the database.
 */
434
function node_save($node) {
Dries's avatar
   
Dries committed
435
  // Fetch fields to save to node table:
Dries's avatar
   
Dries committed
436
  $fields = node_invoke_nodeapi($node, 'fields');
Dries's avatar
   
Dries committed
437

Dries's avatar
   
Dries committed
438
  // Serialize the revisions field:
Dries's avatar
   
Dries committed
439
440
441
442
  if ($node->revisions) {
    $node->revisions = serialize($node->revisions);
  }

Dries's avatar
   
Dries committed
443
  // Apply filters to some default node fields:
Dries's avatar
   
Dries committed
444
  if (empty($node->nid)) {
Dries's avatar
   
Dries committed
445
    // Insert a new node.
Dries's avatar
   
Dries committed
446

Dries's avatar
   
Dries committed
447
    // Set some required fields:
448
449
450
    if (!$node->created) {
      $node->created = time();
    }
451
452
453
    if (!$node->changed) {
      $node->changed = time();
    }
Dries's avatar
   
Dries committed
454
    $node->nid = db_next_id('{node}_nid');
Dries's avatar
   
Dries committed
455

Dries's avatar
   
Dries committed
456
    // Prepare the query:
Dries's avatar
   
Dries committed
457
458
    foreach ($node as $key => $value) {
      if (in_array($key, $fields)) {
Dries's avatar
   
Dries committed
459
        $k[] = db_escape_string($key);
Dries's avatar
   
Dries committed
460
461
        $v[] = $value;
        $s[] = "'%s'";
Dries's avatar
   
Dries committed
462
463
464
      }
    }

Dries's avatar
   
Dries committed
465
    $keysfmt = implode(', ', $s);
Dries's avatar
   
Dries committed
466
    // We need to quote the placeholders for the values.
Dries's avatar
   
Dries committed
467
468
    $valsfmt = "'". implode("', '", $s) ."'";

Dries's avatar
   
Dries committed
469
    // Insert the node into the database:
Dries's avatar
   
Dries committed
470
    db_query("INSERT INTO {node} (". implode(", ", $k) .") VALUES(". implode(", ", $s) .")", $v);
Dries's avatar
   
Dries committed
471

Dries's avatar
   
Dries committed
472
    // Call the node specific callback (if any):
Dries's avatar
   
Dries committed
473
474
    node_invoke($node, 'insert');
    node_invoke_nodeapi($node, 'insert');
Dries's avatar
   
Dries committed
475
476
  }
  else {
Dries's avatar
   
Dries committed
477
    // Update an existing node.
Dries's avatar
   
Dries committed
478

Dries's avatar
   
Dries committed
479
    // Set some required fields:
Dries's avatar
   
Dries committed
480
481
    $node->changed = time();

Dries's avatar
   
Dries committed
482
    // Prepare the query:
Dries's avatar
   
Dries committed
483
484
    foreach ($node as $key => $value) {
      if (in_array($key, $fields)) {
Dries's avatar
   
Dries committed
485
        $q[] = db_escape_string($key) ." = '%s'";
Dries's avatar
   
Dries committed
486
        $v[] = $value;
Dries's avatar
   
Dries committed
487
488
      }
    }
Dries's avatar
   
Dries committed
489

Dries's avatar
   
Dries committed
490
    // Update the node in the database:
Dries's avatar
   
Dries committed
491
    db_query("UPDATE {node} SET ". implode(', ', $q) ." WHERE nid = '$node->nid'", $v);
Dries's avatar
   
Dries committed
492

Dries's avatar
   
Dries committed
493
    // Call the node specific callback (if any):
Dries's avatar
   
Dries committed
494
495
    node_invoke($node, 'update');
    node_invoke_nodeapi($node, 'update');
Dries's avatar
   
Dries committed
496
497
  }

Dries's avatar
   
Dries committed
498
  // Clear the cache so an anonymous poster can see the node being added or updated.
Dries's avatar
   
Dries committed
499
500
  cache_clear_all();

Dries's avatar
   
Dries committed
501
  // Return the node ID:
Dries's avatar
   
Dries committed
502
503
504
  return $node->nid;
}

Dries's avatar
   
Dries committed
505
506
507
508
509
510
511
512
513
/**
 * Generate a display of the given node.
 *
 * @param $node
 *   A node array or node object.
 * @param $teaser
 *   Whether to display only the teaser for the node.
 * @param $page
 *   Whether the node is being displayed by itself as a page.
514
515
 * @param $links
 *   Whether or not to display node links. Links are omitted for node previews.
Dries's avatar
   
Dries committed
516
517
518
519
 *
 * @return
 *   An HTML representation of the themed node.
 */
520
function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
Dries's avatar
   
Dries committed
521
  $node = array2object($node);
Dries's avatar
   
Dries committed
522

523
  // Remove the delimiter (if any) that separates the teaser from the body.
Dries's avatar
   
Dries committed
524
  // TODO: this strips legitimate uses of '<!--break-->' also.
Dries's avatar
   
Dries committed
525
  $node->body = str_replace('<!--break-->', '', $node->body);
Dries's avatar
   
Dries committed
526

Dries's avatar
   
Dries committed
527
528
  // The 'view' hook can be implemented to overwrite the default function
  // to display nodes.
Dries's avatar
   
Dries committed
529
  if (node_hook($node, 'view')) {
Dries's avatar
   
Dries committed
530
    node_invoke($node, 'view', $teaser, $page);
Dries's avatar
   
Dries committed
531
532
  }
  else {
Dries's avatar
   
Dries committed
533
    $node = node_prepare($node, $teaser);
Dries's avatar
   
Dries committed
534
  }
Dries's avatar
   
Dries committed
535
536
  // Allow modules to change $node->body before viewing.
  node_invoke_nodeapi($node, 'view', $teaser, $page);
537
538
539
  if ($links) {
    $node->links = module_invoke_all('link', 'node', $node, !$page);
  }
Dries's avatar
   
Dries committed
540
541

  return theme('node', $node, $teaser, $page);
Dries's avatar
   
Dries committed
542
}
Dries's avatar
   
Dries committed
543

Dries's avatar
   
Dries committed
544
545
546
/**
 * Apply filters to a node in preparation for theming.
 */
Dries's avatar
   
Dries committed
547
function node_prepare($node, $teaser = FALSE) {
Dries's avatar
   
Dries committed
548
  $node->readmore = (strlen($node->teaser) < strlen($node->body));
Dries's avatar
   
Dries committed
549
  if ($teaser == FALSE) {
550
    $node->body = check_output($node->body, $node->format);
Dries's avatar
   
Dries committed
551
552
  }
  else {
553
    $node->teaser = check_output($node->teaser, $node->format);
Dries's avatar
   
Dries committed
554
  }
Dries's avatar
   
Dries committed
555
  return $node;
Dries's avatar
   
Dries committed
556
557
}

Dries's avatar
   
Dries committed
558
559
560
/**
 * Generate a page displaying a single node, along with its comments.
 */
Dries's avatar
   
Dries committed
561
function node_show($node, $cid) {
Dries's avatar
   
Dries committed
562
  $output = node_view($node, FALSE, TRUE);
Dries's avatar
   
Dries committed
563

Dries's avatar
   
Dries committed
564
565
  if (function_exists('comment_render') && $node->comment) {
    $output .= comment_render($node, $cid);
Dries's avatar
   
Dries committed
566
567
  }

Dries's avatar
   
Dries committed
568
569
  // Update the history table, stating that this user viewed this node.
  node_tag_new($node->nid);
Dries's avatar
   
Dries committed
570

Dries's avatar
   
Dries committed
571
  return $output;
Dries's avatar
   
Dries committed
572
573
}

Dries's avatar
   
Dries committed
574
575
576
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
577
function node_perm() {
Dries's avatar
   
Dries committed
578
  return array('administer nodes', 'access content');
Dries's avatar
   
Dries committed
579
580
}

Dries's avatar
   
Dries committed
581
582
583
/**
 * Implementation of hook_search().
 */
584
585
586
587
function node_search($op = 'search', $keys = null) {
  switch ($op) {
    case 'name':
      return t('content');
Dries's avatar
Dries committed
588
589
590
    case 'reset':
      variable_del('node_cron_last');
      return;
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
    case 'search':
      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. node_access_join_sql() .' INNER JOIN {users} u ON n.uid = u.uid', 'n.status = 1 AND '. node_access_where_sql());
      $results = array();
      foreach ($find as $item) {
        $node = node_load(array('nid' => $item));
        $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $item));
        $results[] = array('link' => url('node/'. $item),
                           'type' => node_invoke($node, 'node_name'),
                           'title' => $node->title,
                           'user' => format_name($node),
                           'date' => $node->changed,
                           'extra' => format_plural($comments, '1 comment', '%count comments'),
                           'snippet' => search_excerpt($keys, check_output($node->body, $node->format)));
      }
      return $results;
  }
Dries's avatar
   
Dries committed
607
608
}

Dries's avatar
   
Dries committed
609
/**
Dries's avatar
   
Dries committed
610
 * Menu callback; presents general node configuration options.
Dries's avatar
   
Dries committed
611
612
613
614
615
616
 */
function node_configure() {
  if ($_POST) {
    system_settings_save();
  }

Dries's avatar
   
Dries committed
617
  $output .= form_select(t('Number of posts on main page'), 'default_nodes_main', variable_get('default_nodes_main', 10), drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)), t('The default maximum number of posts to display per page on overview pages such as the main page.'));
618
  $output .= form_select(t('Length of trimmed posts'), 'teaser_length', variable_get('teaser_length', 600), array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'), 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'), 1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')), t("The maximum number of characters used in the trimmed version of a post.  Drupal will use this setting to determine at which offset long posts should be trimmed.  The trimmed version of a post is typically used as a teaser when displaying the post on the main page, in XML feeds, etc.  To disable teasers, set to 'Unlimited'. Note that this setting will only affect new or updated content and will not affect existing teasers."));
Dries's avatar
   
Dries committed
619
  $output .= form_radios(t('Preview post'), 'node_preview', variable_get('node_preview', 0), array(t('Optional'), t('Required')), t('Must users preview posts before submitting?'));
Dries's avatar
   
Dries committed
620

Dries's avatar
   
Dries committed
621
  print theme('page', system_settings_form($output));
Dries's avatar
   
Dries committed
622
623
}

Dries's avatar
   
Dries committed
624
625
626
/**
 * Retrieve the comment mode for the given node ID (none, read, or read/write).
 */
Dries's avatar
   
Dries committed
627
function node_comment_mode($nid) {
Dries's avatar
   
Dries committed
628
629
  static $comment_mode;
  if (!isset($comment_mode[$nid])) {
Dries's avatar
   
Dries committed
630
    $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
Dries's avatar
   
Dries committed
631
632
  }
  return $comment_mode[$nid];
Dries's avatar
   
Dries committed
633
634
}

Dries's avatar
   
Dries committed
635
636
637
/**
 * Implementation of hook_link().
 */
638
function node_link($type, $node = 0, $main = 0) {
Dries's avatar
   
Dries committed
639
640
  $links = array();

Dries's avatar
   
Dries committed
641
  if ($type == 'node') {
Dries's avatar
   
Dries committed
642
    if (array_key_exists('links', $node)) {
Kjartan's avatar
Kjartan committed
643
644
      $links = $node->links;
    }
Dries's avatar
   
Dries committed
645

Dries's avatar
   
Dries committed
646
    if ($main == 1 && $node->teaser && $node->readmore) {
Dries's avatar
   
Dries committed
647
      $links[] = l(t('read more'), "node/$node->nid", array('title' => t('Read the rest of this posting.'), 'class' => 'read-more'));
Dries's avatar
   
Dries committed
648
    }
Dries's avatar
   
Dries committed
649
650
  }

Dries's avatar
   
Dries committed
651
  return $links;
Dries's avatar
   
Dries committed
652
653
}

Dries's avatar
   
Dries committed
654
655
656
/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
657
function node_menu($may_cache) {
Dries's avatar
   
Dries committed
658
659
  $items = array();

Dries's avatar
   
Dries committed
660
661
  if ($may_cache) {
    $items[] = array('path' => 'admin/node', 'title' => t('content'),
Dries's avatar
   
Dries committed
662
      'callback' => 'node_admin',
Dries's avatar
   
Dries committed
663
664
665
666
667
      'access' => user_access('administer nodes'));
    $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
    $items[] = array('path' => 'admin/node/configure', 'title' => t('configure'),
      'callback' => 'node_configure',
Dries's avatar
   
Dries committed
668
669
      'access' => user_access('administer nodes'),
      'type' => MENU_LOCAL_TASK);
Dries's avatar
   
Dries committed
670
671
672
673
674
675
676
677
678
679
680
681
    $items[] = array('path' => 'admin/node/configure/settings', 'title' => t('settings'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
    $items[] = array('path' => 'admin/node/configure/defaults', 'title' => t('default workflow'),
      'callback' => 'node_default_settings',
      'access' => user_access('administer nodes'),
      'type' => MENU_LOCAL_TASK);
    if (module_exist('search')) {
      $items[] = array('path' => 'admin/node/search', 'title' => t('search'),
        'callback' => 'node_admin',
        'access' => user_access('administer nodes'),
        'type' => MENU_LOCAL_TASK);
    }
Dries's avatar
   
Dries committed
682

Dries's avatar
   
Dries committed
683
    $items[] = array('path' => 'node', 'title' => t('content'),
Dries's avatar
   
Dries committed
684
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
685
686
687
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM);
    $items[] = array('path' => 'node/add', 'title' => t('create content'),
Dries's avatar
   
Dries committed
688
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
689
690
691
692
693
694
695
      'access' => user_access('access content'),
      'type' => MENU_ITEM_GROUPING,
      'weight' => 1);
  }
  else {
    if (arg(0) == 'node' && is_numeric(arg(1))) {
      $node = node_load(array('nid' => arg(1)));
696
697
      if ($node->nid) {
        $items[] = array('path' => 'node/'. arg(1), 'title' => t('view'),
Dries's avatar
   
Dries committed
698
          'callback' => 'node_page',
699
700
701
702
703
704
705
706
          'access' => node_access('view', $node),
          'type' => MENU_CALLBACK);
        $items[] = array('path' => 'node/'. arg(1) .'/view', 'title' => t('view'),
            'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
        $items[] = array('path' => 'node/'. arg(1) .'/edit', 'title' => t('edit'),
          'callback' => 'node_page',
          'access' => node_access('update', $node),
          'weight' => 1,
Dries's avatar
   
Dries committed
707
          'type' => MENU_LOCAL_TASK);
708
709
710
711
712
713
714
715

        if ($node->revisions) {
          $items[] = array('path' => 'node/'. arg(1) .'/revisions', 'title' => t('revisions'),
            'callback' => 'node_page',
            'access' => user_access('administer nodes'),
            'weight' => 2,
            'type' => MENU_LOCAL_TASK);
        }
Dries's avatar
   
Dries committed
716
      }
717
    }
Dries's avatar
   
Dries committed
718
719
720
721
722
  }

  return $items;
}

Dries's avatar
   
Dries committed
723
/**
Dries's avatar
   
Dries committed
724
 * Generate the content administration overview.
Dries's avatar
   
Dries committed
725
 */
Dries's avatar
   
Dries committed
726
function node_admin_nodes() {
Dries's avatar
   
Dries committed
727
  $filters = array(
Dries's avatar
   
Dries committed
728
729
730
731
    array(t('View posts that are new or updated'), 'ORDER BY n.changed DESC'),
    array(t('View posts that need approval'), 'WHERE n.status = 0 OR n.moderate = 1 ORDER BY n.changed DESC'),
    array(t('View posts that are promoted'), 'WHERE n.status = 1 AND n.promote = 1 ORDER BY n.changed DESC'),
    array(t('View posts that are not promoted'), 'WHERE n.status = 1 AND n.promote = 0 ORDER BY n.changed DESC'),
Dries's avatar
   
Dries committed
732
    array(t('View posts that are sticky'), 'WHERE n.status = 1 AND n.sticky = 1 ORDER BY n.changed DESC'),
Dries's avatar
   
Dries committed
733
    array(t('View posts that are unpublished'), 'WHERE n.status = 0 AND n.moderate = 0 ORDER BY n.changed DESC')
Dries's avatar
   
Dries committed
734
735
736
   );

  $operations = array(
Dries's avatar
   
Dries committed
737
738
    array(t('Approve the selected posts'), 'UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d'),
    array(t('Promote the selected posts'), 'UPDATE {node} SET status = 1, promote = 1 WHERE nid = %d'),
Dries's avatar
   
Dries committed
739
    array(t('Make the selected posts sticky'), 'UPDATE {node} SET status = 1, sticky = 1 WHERE nid = %d'),
Dries's avatar
   
Dries committed
740
741
    array(t('Demote the selected posts'), 'UPDATE {node} SET promote = 0 WHERE nid = %d'),
    array(t('Unpublish the selected posts'), 'UPDATE {node} SET status = 0 WHERE nid = %d')
Dries's avatar
   
Dries committed
742
  );
Dries's avatar
   
Dries committed
743

Dries's avatar
   
Dries committed
744
  // Handle operations:
Dries's avatar
   
Dries committed
745
746
  if (empty($_SESSION['node_overview_filter'])) {
    $_SESSION['node_overview_filter'] = 0;
Dries's avatar
   
Dries committed
747
748
  }

749
750
751
  $op = $_POST['op'];

  if ($op == t('Filter') && isset($_POST['edit']['filter'])) {
Dries's avatar
   
Dries committed
752
    $_SESSION['node_overview_filter'] = $_POST['edit']['filter'];
Dries's avatar
   
Dries committed
753
754
  }

755
  if ($op == t('Update') && isset($_POST['edit']['operation']) && isset($_POST['edit']['status'])) {
Dries's avatar
   
Dries committed
756
757
    $operation = $operations[$_POST['edit']['operation']][1];
    foreach ($_POST['edit']['status'] as $nid => $value) {
Dries's avatar
   
Dries committed
758
      if ($value) {
Dries's avatar
Dries committed
759
        db_query($operation, $nid);
Dries's avatar
   
Dries committed
760
761
762
      }
    }

Dries's avatar
   
Dries committed
763
    drupal_set_message(t('The update has been performed.'));
Dries's avatar
   
Dries committed
764
765
  }

Dries's avatar
   
Dries committed
766
  $filter = $_SESSION['node_overview_filter'];
Dries's avatar
Dries committed
767

Dries's avatar
   
Dries committed
768
  // Render filter form:
Dries's avatar
   
Dries committed
769
770
771
772
  $options = array();
  foreach ($filters as $key => $value) {
    $options[] = $value[0];
  }
Dries's avatar
   
Dries committed
773

Dries's avatar
   
Dries committed
774
  $form  = form_select(NULL, 'filter', $filter, $options);
775
  $form .= form_submit(t('Filter'));
Dries's avatar
   
Dries committed
776

Dries's avatar
   
Dries committed
777
  $output .= '<h3>'. t('Filter options') .'</h3>';
Dries's avatar
   
Dries committed
778
779
  $output .= "<div class=\"container-inline\">$form</div>";

Dries's avatar
   
Dries committed
780
  // Render operations form:
781
782
783
784
785
  $result = pager_query('SELECT n.*, u.name, u.uid FROM {node} n INNER JOIN {users} u ON n.uid = u.uid '. $filters[$filter][1], 50);

  // Make sure the update controls are disabled if we don't have any rows to select from.
  $disabled = !db_num_rows($result);

Dries's avatar
   
Dries committed
786
787
788
789
790
  $options = array();
  foreach ($operations as $key => $value) {
    $options[] = $value[0];
  }

791
792
  $form = form_select(NULL, 'operation', 0, $options, NULL, ($disabled ? 'disabled="disabled"' : ''));
  $form .= form_submit(t('Update'), 'op', ($disabled ? array('disabled' => 'disabled') : array()));
Dries's avatar
   
Dries committed
793

Dries's avatar
   
Dries committed
794
  $output .= '<h3>'. t('Update options') .'</h3>';
Dries's avatar
   
Dries committed
795
796
  $output .= "<div class=\"container-inline\">$form</div>";

Dries's avatar
   
Dries committed
797
  // Overview table:
Dries's avatar
   
Dries committed
798
  $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), array('data' => t('Operations'), 'colspan' => '2'));
Dries's avatar
   
Dries committed
799

Dries's avatar
   
Dries committed
800
  while ($node = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
801
    $rows[] = array(form_checkbox(NULL, 'status]['. $node->nid, 1, 0), l($node->title, 'node/'. $node->nid) .' '. (node_is_new($node->nid, $node->changed) ? theme_mark() : ''), node_invoke($node, 'node_name'), format_name($node), ($node->status ? t('published') : t('not published')), l(t('edit'), 'node/'. $node->nid .'/edit'), l(t('delete'), 'admin/node/delete/'. $node->nid));
Dries's avatar
   
Dries committed
802
  }
Dries's avatar
   
Dries committed
803

Dries's avatar
   
Dries committed
804
  if ($pager = theme('pager', NULL, 50, 0)) {
Dries's avatar
   
Dries committed
805
806
807
808
809
    $rows[] = array(array('data' => $pager, 'colspan' => '7'));
  }

  if (!$rows) {
    $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '7'));
Dries's avatar
   
Dries committed
810
  }
Dries's avatar
   
Dries committed
811

Dries's avatar
   
Dries committed
812
813
  $output .= '<h3>'. $filters[$filter][0] .'</h3>';
  $output .= theme('table', $header, $rows);
Dries's avatar
   
Dries committed
814
  return form($output);
Dries's avatar
Dries committed
815
816
}

Dries's avatar
   
Dries committed
817
818
819
/**
 * Menu callback; presents the interface for setting node defaults.
 */
Dries's avatar
   
Dries committed
820
function node_default_settings() {
Dries's avatar
   
Dries committed
821
  $op = $_POST['op'];
Dries's avatar
   
Dries committed
822
  $edit = $_POST['edit'];
Kjartan's avatar
Kjartan committed
823

Dries's avatar
   
Dries committed
824
  if ($op == t('Save configuration')) {
Dries's avatar
   
Dries committed
825
    // Save the configuration options:
826
827
828
    foreach ($edit as $name => $value) {
      variable_set($name, $value);
    }
Dries's avatar
   
Dries committed
829
    drupal_set_message(t('The content settings have been saved.'));
830
831
  }

Dries's avatar
   
Dries committed
832
  if ($op == t('Reset to defaults')) {
Dries's avatar
   
Dries committed
833
    // Reset the configuration options to their default value:
834
835
836
    foreach ($edit as $name => $value) {
      variable_del($name);
    }
Dries's avatar
   
Dries committed
837
    drupal_set_message(t('The content settings have been reset to their default values.'));
838
839
  }

Dries's avatar
   
Dries committed
840
  $header = array_merge(array(t('type')), array_keys(node_invoke_nodeapi($node, 'settings')));
Dries's avatar
   
Dries committed
841
  foreach (node_list() as $type) {
842
    $node = new StdClass();
Dries's avatar
   
Dries committed
843
844
    $node->type = $type;
    $cols = array();
Dries's avatar
   
Dries committed
845
846
    foreach (node_invoke_nodeapi($node, 'settings') as $setting) {
      $cols[] = array('data' => $setting, 'align' => 'center', 'width' => 55);
847
    }
Dries's avatar
   
Dries committed
848
    $rows[] = array_merge(array(node_invoke($node, 'node_name')), $cols);
849
  }
Kjartan's avatar
Kjartan committed
850

Dries's avatar
   
Dries committed
851
  $output .= theme('table', $header, $rows);
852

Dries's avatar
   
Dries committed
853
854
  $output .= form_submit(t('Save configuration'));
  $output .= form_submit(t('Reset to defaults'));
Kjartan's avatar
Kjartan committed
855

Dries's avatar
   
Dries committed
856
  print theme('page', form($output));
857
858
}

Dries's avatar
   
Dries committed
859
/**
Dries's avatar
   
Dries committed
860
 * Generate an overview table of older revisions of a node.
Dries's avatar
   
Dries committed
861
 */
Dries's avatar
   
Dries committed
862
function node_revision_overview($nid) {
Dries's avatar
   
Dries committed
863
864
  if (user_access('administer nodes')) {
    $node = node_load(array('nid' => $nid));
Dries's avatar
   
Dries committed
865

866
867
    drupal_set_title($node->title);

Dries's avatar
   
Dries committed
868
    if ($node->revisions) {
Dries's avatar
   
Dries committed
869
      $header = array(t('Older revisions'), array('colspan' => '3', 'data' => t('Operations')));
Dries's avatar
   
Dries committed
870
871

      foreach ($node->revisions as $key => $revision) {
872
        $rows[] = array(t('revision #%r revised by %u on %d', array('%r' => $key, '%u' => format_name(user_load(array('uid' => $revision['uid']))), '%d' => format_date($revision['timestamp'], 'small'))) . ($revision['history'] ? '<br /><small>'. $revision['history'] .'</small>' : ''), l(t('view'), "node/$node->nid", array(), "revision=$key"), l(t('rollback'), "node/$node->nid/rollback-revision/$key"), l(t('delete'), "node/$node->nid/delete-revision/$key"));
Dries's avatar
   
Dries committed
873
      }
Dries's avatar
   
Dries committed
874
      $output .= theme('table', $header, $rows);
Dries's avatar
   
Dries committed
875
876
877
878
879
880
881
    }
  }

  return $output;
}


Dries's avatar
   
Dries committed
882
883
884
/**
 * Return the revision with the specified revision number.
 */
Dries's avatar
   
Dries committed
885
function node_revision_load($node, $revision) {
Dries's avatar
   
Dries committed
886
  return $node->revisions[$revision]['node'];
Dries's avatar
   
Dries committed
887
888
}

Dries's avatar
   
Dries committed
889
890
891
/**
 * Create and return a new revision of the given node.
 */
Dries's avatar
   
Dries committed
892
893
894
function node_revision_create($node) {
  global $user;

895
  // "Revision" is the name of the field used to indicate that we have to
Dries's avatar
   
Dries committed
896
  // create a new revision of a node.
Dries's avatar
   
Dries committed
897
  if ($node->nid && $node->revision) {
Dries's avatar
   
Dries committed
898
    $prev = node_load(array('nid' => $node->nid));
Dries's avatar
   
Dries committed
899
900
    $node->revisions = $prev->revisions;
    unset($prev->revisions);
Dries's avatar
   
Dries committed
901
    $node->revisions[] = array('uid' => $user->uid, 'timestamp' => time(), 'node' => $prev, 'history' => $node->history);
Dries's avatar
   
Dries committed
902
903
904
905
906
  }

  return $node;
}

Dries's avatar
   
Dries committed
907
908
909
/**
 * Roll back to the revision with the specified revision number.
 */
Dries's avatar
   
Dries committed
910
function node_revision_rollback($nid, $revision) {
Dries's avatar
   
Dries committed
911
  global $user;
Dries's avatar
   
Dries committed
912

Dries's avatar
   
Dries committed
913
914
  if (user_access('administer nodes')) {
    $node = node_load(array('nid' => $nid));
Dries's avatar
   
Dries committed
915

Dries's avatar
   
Dries committed
916
    // Extract the specified revision:
Dries's avatar
   
Dries committed
917
    $rev = $node->revisions[$revision]['node'];
Dries's avatar
   
Dries committed
918

Dries's avatar
   
Dries committed
919
    // Inherit all the past revisions:
Dries's avatar
   
Dries committed
920
    $rev->revisions = $node->revisions;
Dries's avatar
   
Dries committed
921

Dries's avatar
   
Dries committed
922
    // Save the original/current node:
Dries's avatar
   
Dries committed
923
    $rev->revisions[] = array('uid' => $user->uid, 'timestamp' => time(), 'node' => $node);
Dries's avatar
   
Dries committed
924

Dries's avatar
   
Dries committed
925
    // Remove the specified revision:
Dries's avatar
   
Dries committed
926
    unset($rev->revisions[$revision]);
Dries's avatar
   
Dries committed
927

Dries's avatar
   
Dries committed
928
    // Save the node:
Dries's avatar
   
Dries committed
929
930
931
    foreach ($node as $key => $value) {
      $filter[] = $key;
    }
Dries's avatar
   
Dries committed
932

Dries's avatar
   
Dries committed
933
934
    node_save($rev, $filter);

Dries's avatar
   
Dries committed
935
    drupal_set_message(t('Rolled back to revision %revision of %title', array('%revision' => "<em>#$revision</em>", '%title' => "<em>$node->title</em>")));
936
    drupal_goto('node/'. $nid .'/revisions');
Dries's avatar
   
Dries committed
937
  }
Dries's avatar
   
Dries committed