node.module 66.2 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>
32
      <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>
33
      <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
34

Dries's avatar
   
Dries committed
35
36
37
      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
38
      }
Dries's avatar
   
Dries committed
39

Dries's avatar
   
Dries committed
40
      return $output;
41

Dries's avatar
   
Dries committed
42
    case 'admin/modules#description':
Dries's avatar
   
Dries committed
43
      return t('The core that allows content to be submitted to the site.');
44
45
    case 'admin/node/configure':
    case 'admin/node/configure/settings':
46
      return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
47
    case 'admin/node':
48
      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>).</p><p>Clicking a title views the post, while clicking an author\'s name views their user information.</p>', array('%comments' => url('admin/comment')));
49
    case 'admin/node/search':
50
      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>');
Dries's avatar
   
Dries committed
51
  }
Dries's avatar
   
Dries committed
52
53
54
55

  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.');
  }
56
57
58
59

  if (arg(0) == 'node' && arg(1) == 'add' && $type = arg(2)) {
    return variable_get($type .'_help', '');
  }
Dries's avatar
   
Dries committed
60
61
}

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

Dries's avatar
   
Dries committed
69
70
71
72
/**
 * Gather a listing of links to nodes.
 *
 * @param $result
73
 *   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
74
 * field to be set.
Dries's avatar
   
Dries committed
75
76
77
78
79
80
 * @param $title
 *   A heading for the resulting list.
 *
 * @return
 *   An HTML list suitable as content for a block.
 */
Dries's avatar
   
Dries committed
81
82
function node_title_list($result, $title = NULL) {
  while ($node = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
83
    $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
84
85
  }

Dries's avatar
   
Dries committed
86
  return theme('node_list', $items, $title);
Dries's avatar
   
Dries committed
87
88
}

Dries's avatar
   
Dries committed
89
90
91
/**
 * Format a listing of links to nodes.
 */
Dries's avatar
   
Dries committed
92
function theme_node_list($items, $title = NULL) {
Dries's avatar
   
Dries committed
93
  return theme('item_list', $items, $title);
Dries's avatar
   
Dries committed
94
95
}

Dries's avatar
   
Dries committed
96
97
98
/**
 * Update the 'last viewed' timestamp of the specified node for current user.
 */
Dries's avatar
   
Dries committed
99
100
101
102
function node_tag_new($nid) {
  global $user;

  if ($user->uid) {
Dries's avatar
   
Dries committed
103
    if (node_last_viewed($nid)) {
Dries's avatar
   
Dries committed
104
      db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
Dries's avatar
   
Dries committed
105
106
    }
    else {
Dries's avatar
   
Dries committed
107
      @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
Dries's avatar
   
Dries committed
108
109
110
111
    }
  }
}

Dries's avatar
   
Dries committed
112
113
114
115
/**
 * Retrieves the timestamp at which the current user last viewed the
 * specified node.
 */
Dries's avatar
   
Dries committed
116
117
function node_last_viewed($nid) {
  global $user;
Dries's avatar
   
Dries committed
118
  static $history;
Dries's avatar
   
Dries committed
119

Dries's avatar
   
Dries committed
120
121
122
123
124
  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
125
126
127
}

/**
128
 * Decide on the type of marker to be displayed for a given node.
Dries's avatar
   
Dries committed
129
 *
Dries's avatar
   
Dries committed
130
131
132
133
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
134
135
 * @return
 *   One of the MARK constants.
Dries's avatar
   
Dries committed
136
 */
137
function node_mark($nid, $timestamp) {
Dries's avatar
   
Dries committed
138
139
140
  global $user;
  static $cache;

141
142
143
  if (!$user->uid) {
    return MARK_READ;
  }
Dries's avatar
Dries committed
144
  if (!isset($cache[$nid])) {
145
    $cache[$nid] = node_last_viewed($nid);
Dries's avatar
   
Dries committed
146
  }
147
148
149
150
151
152
153
  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
    return MARK_NEW;
  }
  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
    return MARK_UPDATED;
  }
  return MARK_READ;
Dries's avatar
   
Dries committed
154
155
}

Dries's avatar
   
Dries committed
156
/**
Dries's avatar
   
Dries committed
157
 * Automatically generate a teaser for the given body text.
Dries's avatar
   
Dries committed
158
 */
Dries's avatar
   
Dries committed
159
160
function node_teaser($body) {

Dries's avatar
   
Dries committed
161
  $size = variable_get('teaser_length', 600);
Dries's avatar
   
Dries committed
162

Dries's avatar
   
Dries committed
163
164
165
166
167
  // 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
168
169
    return $body;
  }
Dries's avatar
   
Dries committed
170

171
172
173
174
175
  // 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
176
  // If a valid delimiter has been specified, use it to chop of the teaser.
Dries's avatar
   
Dries committed
177
  if ($delimiter > 0) {
Dries's avatar
   
Dries committed
178
179
180
    return substr($body, 0, $delimiter);
  }

Dries's avatar
   
Dries committed
181
  // If we have a short body, return the entire body.
Dries's avatar
   
Dries committed
182
183
184
185
  if (strlen($body) < $size) {
    return $body;
  }

Dries's avatar
   
Dries committed
186
187
  // 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
188
  if ($length = strpos($body, '</p>', $size)) {
Dries's avatar
   
Dries committed
189
    return substr($body, 0, $length + 4);
Dries's avatar
   
Dries committed
190
191
  }

Dries's avatar
   
Dries committed
192
  if ($length = strpos($body, '<br />', $size)) {
Dries's avatar
   
Dries committed
193
    return substr($body, 0, $length);
Dries's avatar
   
Dries committed
194
195
  }

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

200
  if ($length = strpos($body, "\n", $size)) {
Dries's avatar
   
Dries committed
201
    return substr($body, 0, $length);
Dries's avatar
   
Dries committed
202
203
  }

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

Dries's avatar
   
Dries committed
210
  if ($length = strpos($body, '! ', $size)) {
Dries's avatar
   
Dries committed
211
212
213
    return substr($body, 0, $length + 1);
  }

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

218
219
220
221
222
223
224
225
226
227
228
229
  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
230
  // If all else fails, simply truncate the string.
231
  return truncate_utf8($body, $size);
Dries's avatar
   
Dries committed
232
233
}

Dries's avatar
   
Dries committed
234

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

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

294
/**
Dries's avatar
   
Dries committed
295
296
297
298
299
300
301
302
303
304
 * 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) {
305
  $function = node_get_module_name($node) ."_$hook";
Dries's avatar
   
Dries committed
306
307
308
309

  return function_exists($function);
}

310
/**
Dries's avatar
   
Dries committed
311
312
313
314
315
316
317
318
319
 * 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
320
 *   The returned value of the invoked hook.
Dries's avatar
   
Dries committed
321
322
 */
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
323
  $function = node_get_module_name($node) ."_$hook";
Dries's avatar
   
Dries committed
324
325

  if (function_exists($function)) {
Dries's avatar
   
Dries committed
326
    return ($function($node, $a2, $a3, $a4));
Dries's avatar
   
Dries committed
327
328
329
  }
}

Dries's avatar
   
Dries committed
330
331
332
333
334
335
336
337
338
339
340
341
/**
 * 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
342
function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
Dries's avatar
   
Dries committed
343
344
  $return = array();
  foreach (module_list() as $name) {
Dries's avatar
   
Dries committed
345
    $function = $name .'_nodeapi';
Dries's avatar
   
Dries committed
346
    if (function_exists($function)) {
Dries's avatar
   
Dries committed
347
      $result = $function($node, $op, $a3, $a4);
348
      if (is_array($result)) {
Dries's avatar
   
Dries committed
349
350
        $return = array_merge($return, $result);
      }
Steven Wittens's avatar
Steven Wittens committed
351
      else if (isset($result)) {
352
353
        $return[] = $result;
      }
Dries's avatar
   
Dries committed
354
355
356
357
358
    }
  }
  return $return;
}

Dries's avatar
   
Dries committed
359
360
361
362
363
364
365
366
/**
 * 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
367
368
 * @param $reset
 *   Whether to reset the internal node_load cache.
Dries's avatar
   
Dries committed
369
370
371
372
 *
 * @return
 *   A fully-populated node object.
 */
Dries's avatar
   
Dries committed
373
374
375
376
377
378
379
380
381
382
383
384
385
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
386
  // Turn the conditions into a query.
Dries's avatar
   
Dries committed
387
  foreach ($conditions as $key => $value) {
Dries's avatar
   
Dries committed
388
    $cond[] = 'n.'. db_escape_string($key) ." = '". db_escape_string($value) ."'";
Dries's avatar
   
Dries committed
389
390
  }

Dries's avatar
   
Dries committed
391
  // Retrieve the node.
Dries's avatar
   
Dries committed
392
  $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
393
  $node = drupal_unpack($node);
Dries's avatar
   
Dries committed
394

Dries's avatar
   
Dries committed
395
  // Unserialize the revisions and user data fields.
Dries's avatar
   
Dries committed
396
397
398
399
  if ($node->revisions) {
    $node->revisions = unserialize($node->revisions);
  }

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

Dries's avatar
   
Dries committed
408
409
410
411
412
413
  if ($extra = node_invoke_nodeapi($node, 'load')) {
    foreach ($extra as $key => $value) {
      $node->$key = $value;
    }
  }

Dries's avatar
   
Dries committed
414
  // Return the desired revision.
Dries's avatar
   
Dries committed
415
  if (!is_null($revision) && is_array($node->revisions[$revision])) {
416
   $node = $node->revisions[$revision]['node'];
Dries's avatar
   
Dries committed
417
418
  }

Dries's avatar
   
Dries committed
419
420
421
422
  if ($cachable) {
    $nodes[$conditions['nid']] = $node;
  }

Dries's avatar
   
Dries committed
423
424
425
  return $node;
}

Dries's avatar
   
Dries committed
426
427
428
/**
 * Save a node object into the database.
 */
429
function node_save($node) {
Dries's avatar
   
Dries committed
430
  // Fetch fields to save to node table:
Dries's avatar
   
Dries committed
431
  $fields = node_invoke_nodeapi($node, 'fields');
Dries's avatar
   
Dries committed
432

Dries's avatar
   
Dries committed
433
  // Serialize the revisions field:
Dries's avatar
   
Dries committed
434
435
436
437
  if ($node->revisions) {
    $node->revisions = serialize($node->revisions);
  }

Dries's avatar
   
Dries committed
438
  // Apply filters to some default node fields:
Dries's avatar
   
Dries committed
439
  if (empty($node->nid)) {
Dries's avatar
   
Dries committed
440
    // Insert a new node.
Dries's avatar
   
Dries committed
441

Dries's avatar
   
Dries committed
442
    // Set some required fields:
443
444
445
    if (!$node->created) {
      $node->created = time();
    }
446
447
448
    if (!$node->changed) {
      $node->changed = time();
    }
Dries's avatar
   
Dries committed
449
    $node->nid = db_next_id('{node}_nid');
Dries's avatar
   
Dries committed
450

Dries's avatar
   
Dries committed
451
    // Prepare the query:
Dries's avatar
   
Dries committed
452
    foreach ($node as $key => $value) {
453
      if (in_array((string) $key, $fields)) {
Dries's avatar
   
Dries committed
454
        $k[] = db_escape_string($key);
Dries's avatar
   
Dries committed
455
456
        $v[] = $value;
        $s[] = "'%s'";
Dries's avatar
   
Dries committed
457
458
459
      }
    }

Dries's avatar
   
Dries committed
460
    $keysfmt = implode(', ', $s);
Dries's avatar
   
Dries committed
461
    // We need to quote the placeholders for the values.
Dries's avatar
   
Dries committed
462
463
    $valsfmt = "'". implode("', '", $s) ."'";

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

Dries's avatar
   
Dries committed
467
    // Call the node specific callback (if any):
Dries's avatar
   
Dries committed
468
469
    node_invoke($node, 'insert');
    node_invoke_nodeapi($node, 'insert');
Dries's avatar
   
Dries committed
470
471
  }
  else {
Dries's avatar
   
Dries committed
472
    // Update an existing node.
Dries's avatar
   
Dries committed
473

Dries's avatar
   
Dries committed
474
    // Set some required fields:
Dries's avatar
   
Dries committed
475
476
    $node->changed = time();

Dries's avatar
   
Dries committed
477
    // Prepare the query:
Dries's avatar
   
Dries committed
478
479
    foreach ($node as $key => $value) {
      if (in_array($key, $fields)) {
Dries's avatar
   
Dries committed
480
        $q[] = db_escape_string($key) ." = '%s'";
Dries's avatar
   
Dries committed
481
        $v[] = $value;
Dries's avatar
   
Dries committed
482
483
      }
    }
Dries's avatar
   
Dries committed
484

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

Dries's avatar
   
Dries committed
488
    // Call the node specific callback (if any):
Dries's avatar
   
Dries committed
489
490
    node_invoke($node, 'update');
    node_invoke_nodeapi($node, 'update');
Dries's avatar
   
Dries committed
491
492
  }

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

Dries's avatar
   
Dries committed
496
  // Return the node ID:
Dries's avatar
   
Dries committed
497
498
499
  return $node->nid;
}

Dries's avatar
   
Dries committed
500
501
502
503
504
505
506
507
508
/**
 * 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.
509
510
 * @param $links
 *   Whether or not to display node links. Links are omitted for node previews.
Dries's avatar
   
Dries committed
511
512
513
514
 *
 * @return
 *   An HTML representation of the themed node.
 */
515
function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
Dries's avatar
   
Dries committed
516
  $node = array2object($node);
Dries's avatar
   
Dries committed
517

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

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

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

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

Dries's avatar
   
Dries committed
553
554
555
/**
 * Generate a page displaying a single node, along with its comments.
 */
Dries's avatar
   
Dries committed
556
function node_show($node, $cid) {
Dries's avatar
   
Dries committed
557
  $output = node_view($node, FALSE, TRUE);
Dries's avatar
   
Dries committed
558

Dries's avatar
   
Dries committed
559
560
  if (function_exists('comment_render') && $node->comment) {
    $output .= comment_render($node, $cid);
Dries's avatar
   
Dries committed
561
562
  }

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

Dries's avatar
   
Dries committed
566
  return $output;
Dries's avatar
   
Dries committed
567
568
}

Dries's avatar
   
Dries committed
569
570
571
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
572
function node_perm() {
Dries's avatar
   
Dries committed
573
  return array('administer nodes', 'access content');
Dries's avatar
   
Dries committed
574
575
}

Dries's avatar
   
Dries committed
576
577
578
/**
 * Implementation of hook_search().
 */
579
580
581
582
function node_search($op = 'search', $keys = null) {
  switch ($op) {
    case 'name':
      return t('content');
Dries's avatar
Dries committed
583
584
585
    case 'reset':
      variable_del('node_cron_last');
      return;
586
587
588
589
590
    case 'status':
      $last = variable_get('node_cron_last', 0);
      $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND moderate = 0'));
      $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last));
      return array('remaining' => $remaining, 'total' => $total);
591
    case 'search':
592
      list($join, $where) = _db_rewrite_sql();
Dries's avatar
   
Dries committed
593
      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join .' INNER JOIN {users} u ON n.uid = u.uid', 'n.status = 1 AND '. $where);
594
595
596
      $results = array();
      foreach ($find as $item) {
        $node = node_load(array('nid' => $item));
Dries's avatar
Dries committed
597
        $extra = node_invoke_nodeapi($node, 'search result');
598
599
600
601
602
        $results[] = array('link' => url('node/'. $item),
                           'type' => node_invoke($node, 'node_name'),
                           'title' => $node->title,
                           'user' => format_name($node),
                           'date' => $node->changed,
Dries's avatar
Dries committed
603
                           'extra' => $extra,
604
605
606
607
                           'snippet' => search_excerpt($keys, check_output($node->body, $node->format)));
      }
      return $results;
  }
Dries's avatar
   
Dries committed
608
609
}

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

Dries's avatar
   
Dries committed
618
  $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.'));
619
  $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
620
  $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
621

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

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

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

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

Dries's avatar
   
Dries committed
647
    if ($main == 1 && $node->teaser && $node->readmore) {
Dries's avatar
   
Dries committed
648
      $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
649
    }
Dries's avatar
   
Dries committed
650
651
  }

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

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

Dries's avatar
   
Dries committed
661
662
  if ($may_cache) {
    $items[] = array('path' => 'admin/node', 'title' => t('content'),
Dries's avatar
   
Dries committed
663
      'callback' => 'node_admin',
Dries's avatar
   
Dries committed
664
      'access' => user_access('administer nodes'));
665
666
    $items[] = array('path' => 'admin/node/action', 'title' => t('content'),
      'type' => MENU_CALLBACK);
Dries's avatar
   
Dries committed
667
668
669
670
    $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
671
672
      'access' => user_access('administer nodes'),
      'type' => MENU_LOCAL_TASK);
Dries's avatar
   
Dries committed
673
674
    $items[] = array('path' => 'admin/node/configure/settings', 'title' => t('settings'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
675
676
    $items[] = array('path' => 'admin/node/configure/types', 'title' => t('content types'),
      'callback' => 'node_types',
Dries's avatar
   
Dries committed
677
678
679
680
681
682
683
684
      '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
685

Dries's avatar
   
Dries committed
686
    $items[] = array('path' => 'node', 'title' => t('content'),
Dries's avatar
   
Dries committed
687
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
688
689
690
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM);
    $items[] = array('path' => 'node/add', 'title' => t('create content'),
Dries's avatar
   
Dries committed
691
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
692
693
694
695
696
697
698
      '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)));
699
700
      if ($node->nid) {
        $items[] = array('path' => 'node/'. arg(1), 'title' => t('view'),
Dries's avatar
   
Dries committed
701
          'callback' => 'node_page',
702
703
704
705
706
707
708
709
          '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
710
          'type' => MENU_LOCAL_TASK);
711
712
713
714
715
716
717
718

        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
719
      }
720
    }
721
722
723
724
725
726
727
    else if (arg(0) == 'admin' && arg(1) == 'node' && arg(2) == 'configure' && arg(3) == 'types' && is_string(arg(4))) {
      $items[] = array('path' => 'admin/node/configure/types/'. arg(4),
        'title' => t("'%name' content type", array('%name' => node_invoke(arg(4), 'node_name'))),
        'callback' => 'node_types_configure',
        'access' => user_access('administer nodes'),
        'type' => MENU_CALLBACK);
    }
Dries's avatar
   
Dries committed
728
729
730
731
732
  }

  return $items;
}

733
734
735
736
737
function node_last_changed($nid) {
  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
  return ($node->changed);
}

Dries's avatar
   
Dries committed
738
/**
Dries's avatar
   
Dries committed
739
 * Generate the content administration overview.
Dries's avatar
   
Dries committed
740
 */
Dries's avatar
   
Dries committed
741
function node_admin_nodes() {
742
743
744
  /*
  ** Operations
  */
Dries's avatar
   
Dries committed
745
  $operations = array(
746
747
748
749
750
751
    'approve' =>   array(t('Approve the selected posts'), 'UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d'),
    'promote' =>   array(t('Promote the selected posts'), 'UPDATE {node} SET status = 1, promote = 1 WHERE nid = %d'),
    'sticky' =>    array(t('Make the selected posts sticky'), 'UPDATE {node} SET status = 1, sticky = 1 WHERE nid = %d'),
    'demote' =>    array(t('Demote the selected posts'), 'UPDATE {node} SET promote = 0 WHERE nid = %d'),
    'unpublish' => array(t('Unpublish the selected posts'), 'UPDATE {node} SET status = 0 WHERE nid = %d'),
    'delete' =>    array(t('Delete the selected posts'), '')
Dries's avatar
   
Dries committed
752
  );
Dries's avatar
   
Dries committed
753

754
  // Handle operations
755
756
  $op = $_POST['op'];

757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
  $edit = $_POST['edit'];
  if (($op == t('Update') || $op == t('Delete')) && isset($edit['operation']) && isset($edit['nodes'])) {
    $edit['nodes'] = array_diff($edit['nodes'], array(0));
    if (count($edit['nodes']) == 0) {
      form_set_error('', t('Please select some items to perform the update on.'));
    }
    else {
      if ($operations[$edit['operation']][1]) {
        // Flag changes
       $operation = $operations[$edit['operation']][1];
        foreach ($edit['nodes'] as $nid => $value) {
          if ($value) {
            db_query($operation, $nid);
          }
        }
        drupal_set_message(t('The update has been performed.'));
      }
      else if ($edit['operation'] == 'delete') {
        // Mass delete
        if ($edit['confirm']) {
          foreach ($edit['nodes'] as $nid => $value) {
            node_delete(array('nid' => $nid, 'confirm' => 1));
          }
          drupal_set_message(t('The items have been deleted.'));
        }
        else {
          $list = '<ul>';
          foreach ($edit['nodes'] as $nid => $value) {
            if ($value) {
              $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
              $list .= '<li>'. form_hidden('nodes]['. $nid, 1) . $title .'</li>';
            }
          }
          $list .= '</ul>';

          $output = '<h3>'. t('Are you sure you want to delete these items?') .'</h3>'. $list;
          $output .= form_hidden('operation', 'delete');
          $output .= form_hidden('confirm', 1);
          $output .= form_submit(t('Delete'));
          $output = form($output);
          return $output;
        }
Dries's avatar
   
Dries committed
799
800
      }
    }
801
  }
Dries's avatar
   
Dries committed
802

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
  /*
  ** Filters
  */
  $node_types = drupal_map_assoc(node_list());
  foreach ($node_types as $k => $v) {
    $node_types[$k] = node_invoke($v, 'node_name');
  }
  // Merge all vocabularies into one for retrieving $value below
  $taxonomy = taxonomy_form_all();
  $terms = array();
  foreach ($taxonomy as $key => $value) {
    $terms = $terms + $value;
  }
  // Regular filters
  $filters = array(
818
    'status'   => array('title' => t('status'),
819
820
821
822
823
824
825
826
827
828
                        'options' => array('status-1'   => t('published'),     'status-0' => t('not published'),
                                           'moderate-1' => t('in moderation'), 'moderate-0' => t('not in moderation'),
                                           'promote-1'  => t('promoted'),      'promote-0' => t('not promoted'),
                                           'sticky-1'   => t('sticky'),        'sticky-0' => t('not sticky'))),
    'type'     => array('title' => t('type'),              'where' => "n.type = '%s'",
                        'options' => $node_types),
    'category' => array('title' => t('category'),          'where' => 'tn.tid = %d',
                        'options' => $terms, 'join' => 'INNER JOIN {term_node} tn ON n.nid = tn.nid'));

  // Initialize/reset filters
829
  if (!isset($_SESSION['node_overview_filter']) || !is_array($_SESSION['node_overview_filter']) || $op == t('Reset')) {
830
831
832
833
834
835
836
837
838
839
840
841
842
843
    $_SESSION['node_overview_filter'] = array();
  }
  $session = &$_SESSION['node_overview_filter'];
  $filter = $edit['filter'];
  if (($op == t('Filter') || $op == t('Refine')) && isset($filter)) {
    if (isset($filters[$filter]['options'][$edit[$filter]])) {
      $session[] = array($filter, $edit[$filter]);
    }
  }
  if ($op == t('Undo')) {
    array_pop($session);
  }
  if ($op != '') {
    drupal_goto('admin/node');
Dries's avatar
   
Dries committed
844
845
  }

846
847
848
849
850
851
852
853
854
855
856
857
  /*
  ** Form
  */
  $output .= '<div id="node-admin-filter">';
  // Existing filters
  $form = '<ul>';
  $i = 0;
  foreach ($session as $filter) {
    list($type, $value) = $filter;
    $params = array('%a' => '<strong>'. $filters[$type]['title'] .'</strong>', '%b' => '<strong>'. $filters[$type]['options'][$value] .'</strong>');
    $form .= '<li>'. ($i++ ? t('<em>and</em> where <strong>%a</strong> is <strong>%b</strong>', $params) : t('<strong>%a</strong> is <strong>%b</strong>', $params)) .'</li>';
  }
Dries's avatar
Dries committed
858

859
860
861
  // New filter form
  $filters['category']['options'] = $taxonomy;
  $values = '';
Dries's avatar
   
Dries committed
862
863
  $options = array();
  foreach ($filters as $key => $value) {
864
865
    $options[$key] = $value['title'];
    $b .= form_select('', $key, 1, $filters[$key]['options']);
Steven Wittens's avatar
Steven Wittens committed
866
  }
Dries's avatar
   
Dries committed
867

868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
  $buttons = '';
  if (count($options)) {
    $form .= '<li><dl class="multiselect">';
    $a = '';
    foreach ($options as $key => $value) {
      $a .= form_radio($value, 'filter', $key);
    }
    if (!$i) {
      $form .= t('<dd class="a">%a</dd> <dt>is</dt> <dd class="b">%b</dd>', array('%a' => $a, '%b' => $b));
    }
    else {
      $form .= t('<dt><em>and</em> where</dt> <dd class="a">%a</dd> <dt>is</dt> <dd class="b">%b</dd>', array('%a' => $a, '%b' => $b));
    }
    $form .= '</dl>';
    $buttons = form_submit(count($session) ? t('Refine') : t('Filter'));
  }
  if (count($session)) {
    $buttons .= form_submit(t('Undo')) . form_submit(t('Reset'));
  }
  $form .= '<div class="container-inline" id="node-admin-buttons">'. $buttons .'</div>';
  $form .= '</li></ul>';
  $output .= form_group(t('Show only items where'), $form);

  // Build query
  $where = $args = array();
  $join = '';
  foreach ($session as $filter) {
    list($key, $value) = $filter;
    if ($key == 'status') {
      // Note: no exploit hole as $value has already been checked
      list($key, $value) = explode('-', $value, 2);
      $where[] = 'n.'. $key .' = %d';
    }
    else {
      $where[] = $filters[$key]['where'];
    }
    $args[] = $value;
    $join .= $filters[$key]['join'];
  }
  $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
  $result = pager_query('SELECT n.*, u.name, u.uid FROM {node} n '. $join .' INNER JOIN {users} u ON n.uid = u.uid '. $where, 50, 0, NULL, $args);
909
910
911
912

  // 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
913
914
  $options = array();
  foreach ($operations as $key => $value) {
915
    $options[$key] = $value[0];
Dries's avatar
   
Dries committed
916
  }
917

918
  $form = form_select(NULL, 'operation', 'approve', $options, NULL, ($disabled ? 'disabled="disabled"' : ''));
919
  $form .= form_submit(t('Update'), 'op', ($disabled ? array('disabled' => 'disabled') : array()));
Dries's avatar
   
Dries committed
920

921
922
  $output .= form_group(t('Update options'), "<div class=\"container-inline\">$form</div>");
  $output .= '</div>';
Dries's avatar
   
Dries committed
923

Dries's avatar
   
Dries committed
924
  // Overview table:
Dries's avatar
Dries committed
925
  $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
Dries's avatar
   
Dries committed
926

927
  $destination = drupal_get_destination();
Dries's avatar
   
Dries committed
928
  while ($node = db_fetch_object($result)) {
929
930
931
932
933
    $rows[] = array(form_checkbox(NULL, 'nodes]['. $node->nid, 1, 0),
                    l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed)),
                    node_invoke($node, 'node_name'),
                    format_name($node),
                    ($node->status ? t('published') : t('not published')),
934
                    l(t('edit'), 'node/'. $node->nid .'/edit', array(), $destination));
Dries's avatar
   
Dries committed
935
  }
Dries's avatar
   
Dries committed
936

Dries's avatar
   
Dries committed
937
  if ($pager = theme('pager', NULL, 50, 0)) {
Dries's avatar
   
Dries committed
938
939
940
941
942
    $rows[] = array(array('data' => $pager, 'colspan' => '7'));
  }

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

Dries's avatar
   
Dries committed
945
  $output .= theme('table', $header, $rows);
946
  return form($output, 'post', url('admin/node/action'));
Dries's avatar
Dries committed
947
948
}

949
950
function node_types() {
  $header = array(t('Type'), t('Operations'));
Kjartan's avatar
Kjartan committed
951

952
953
954
  $rows = array();
  foreach (node_list() as $type) {
    $rows[] = array(node_invoke($type, 'node_name'), l(t('configure'), 'admin/node/configure/types/'. $type));
955
956
  }

957
958
  print theme('page', theme('table', $header, $rows));
}
959

960
function node_types_configure() {
961
962
963
  // Go to the listing page when we submit this form, system_settings_save() calls drupal_goto().
  if ($_POST['op']) {
    $_GET['q'] = 'admin/node/configure/types';
964
  }
965
  system_settings_save();
Kjartan's avatar
Kjartan committed
966

967
968
969
970
971
972
  $type = arg(4);

  $group = form_textarea(t('Explanation or submission guidelines'), $type .'_help', variable_get($type .'_help', ''), 70, 5, t('This text will be displayed at the top of the %type submission form. It is useful for helping or instructing your users.', array('%type' => node_invoke($type, 'node_name'))));
  $group .= form_select(t('Minimum number of words'), 'minimum_'. $type .'_size', variable_get('minimum_'. $type .'_size', 0), drupal_map_assoc(array(0, 10, 25, 50, 75, 100, 125, 150, 175, 200)), t('The minimum number of words a %type must be to be considered valid. This can be useful to rule out submissions that do not meet the site\'s standards, such as short test posts.', array('%type' => node_invoke($type, 'node_name'))));
  $output = form_group(t('Submission form'), $group);
  $output .= form_group(t('Workflow'), implode('', node_invoke_nodeapi($type, 'settings')));
973

974
  print theme('page', system_settings_form($output));
975
976
}

Dries's avatar
   
Dries committed
977
/**
Dries's avatar
   
Dries committed
978
 * Generate an overview table of older revisions of a node.
Dries's avatar
   
Dries committed
979
 */
Dries's avatar
   
Dries committed
980
function node_revision_overview($nid) {
Dries's avatar
   
Dries committed
981
982
  if (user_access('administer nodes')) {
    $node = node_load(array('nid' => $nid));
Dries's avatar
   
Dries committed
983

984
985
    drupal_set_title($node->title);

Dries's avatar
   
Dries committed
986
    if ($node->revisions) {
Dries's avatar
   
Dries committed
987
      $header = array(t('Older revisions'), array('colspan' => '3', 'data' => t('Operations')));
Dries's avatar
   
Dries committed
988
989

      foreach ($node->revisions as $key => $revision) {
990
        $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
991
      }
Dries's avatar
   
Dries committed
992
      $output .= theme('table', $header, $rows);
Dries's avatar
   
Dries committed
993
994
995
996
997
998
999
    }
  }

  return $output;
}


Dries's avatar
   
Dries committed
1000
1001
1002
/**
 * Return the revision with the specified revision number.
 */
Dries's avatar
   
Dries committed
1003
function node_revision_load($node, $revision) {
Dries's avatar
   
Dries committed
1004
  return $node->revisions[$revision]['node'];
Dries's avatar
   
Dries committed
1005
1006
}

Dries's avatar
   
Dries committed
1007
1008
1009
/**
 * Create and return a new revision of the given node.
 */
Dries's avatar
   
Dries committed
1010
1011
1012
function node_revision_create($node) {
  global $user;

1013
  // "Revision" is the name of the field used to indicate that we have to
Dries's avatar
   
Dries committed
1014
  // create a new revision of a node.
Dries's avatar
   
Dries committed
1015
  if ($node->nid && $node->revision) {
Dries's avatar
   
Dries committed
1016
    $prev = node_load(array('nid' => $node->nid));
Dries's avatar
   
Dries committed
1017
1018
    $node->revisions = $prev->revisions;
    unset($prev->revisions);
Dries's avatar
   
Dries committed
1019
    $node->revisions[] = array('uid' => $user->uid, 'timestamp' => time(), 'node' => $prev, 'history' => $node->history);
Dries's avatar
   
Dries committed
1020
1021