node.module 81.8 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':
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
      $output = '<p>'. t('All content in a website is stored and treated as <b>nodes</b>. Therefore nodes are any postings such as blogs, stories, polls and forums. The node module manages these content types and is one of the strengths of Drupal over other content management systems.') .'</p>';
      $output .= '<p>'. t('Treating all content as nodes allows the flexibility of creating new types of content. It also allows you to painlessly apply new features or changes to all content. Comments are not stored as nodes but are always associated with a node.') .'</p>';
      $output .= t('<p>Node module features</p>
<ul>
<li>The list tab provides an interface to search and sort all content on your site.</li>
<li>The configure settings tab has basic settings for content on your site.</li>
<li>The configure content types tab lists all content types for your site and lets you configure their default workflow.</li>
<li>The search tab lets you search all content on your site</li>
</ul>
');
      $output .= t('<p>You can</p>
<ul>
<li>search for content at <a href="%search">search</a>.</li>
<li>administer nodes at <a href="%admin-node-configure-types">administer &gt;&gt; content &gt;&gt; configure &gt;&gt; content types</a>.</li>
</ul>
', array('%search' => url('search'), '%admin-node-configure-types' => url('admin/node/configure/types')));
      $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%node">Node page</a>.', array('%node' => 'http://www.drupal.org/handbook/modules/node/')) .'</p>';
Dries's avatar
   
Dries committed
34
      return $output;
Dries's avatar
   
Dries committed
35
    case 'admin/modules#description':
36
      return t('Allows content to be submitted to the site and displayed on pages.');
37
38
    case 'admin/node/configure':
    case 'admin/node/configure/settings':
39
      return t('<p>Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.</p>');
40
    case 'admin/node':
41
      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')));
42
    case 'admin/node/search':
43
      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
44
  }
Dries's avatar
   
Dries committed
45
46
47
48

  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.');
  }
49
50
51
52

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

Dries's avatar
   
Dries committed
55
56
57
/**
 * Implementation of hook_cron().
 */
58
function node_cron() {
Dries's avatar
   
Dries committed
59
  db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
60
61
}

Dries's avatar
   
Dries committed
62
63
64
65
/**
 * Gather a listing of links to nodes.
 *
 * @param $result
66
 *   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
67
68
69
70
71
72
 * @param $title
 *   A heading for the resulting list.
 *
 * @return
 *   An HTML list suitable as content for a block.
 */
Dries's avatar
   
Dries committed
73
74
function node_title_list($result, $title = NULL) {
  while ($node = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
75
    $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
76
77
  }

Dries's avatar
   
Dries committed
78
  return theme('node_list', $items, $title);
Dries's avatar
   
Dries committed
79
80
}

Dries's avatar
   
Dries committed
81
82
83
/**
 * Format a listing of links to nodes.
 */
Dries's avatar
   
Dries committed
84
function theme_node_list($items, $title = NULL) {
Dries's avatar
   
Dries committed
85
  return theme('item_list', $items, $title);
Dries's avatar
   
Dries committed
86
87
}

Dries's avatar
   
Dries committed
88
89
90
/**
 * Update the 'last viewed' timestamp of the specified node for current user.
 */
Dries's avatar
   
Dries committed
91
92
93
94
function node_tag_new($nid) {
  global $user;

  if ($user->uid) {
Dries's avatar
   
Dries committed
95
    if (node_last_viewed($nid)) {
Dries's avatar
   
Dries committed
96
      db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
Dries's avatar
   
Dries committed
97
98
    }
    else {
Dries's avatar
   
Dries committed
99
      @db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
Dries's avatar
   
Dries committed
100
101
102
103
    }
  }
}

Dries's avatar
   
Dries committed
104
105
106
107
/**
 * Retrieves the timestamp at which the current user last viewed the
 * specified node.
 */
Dries's avatar
   
Dries committed
108
109
function node_last_viewed($nid) {
  global $user;
Dries's avatar
   
Dries committed
110
  static $history;
Dries's avatar
   
Dries committed
111

Dries's avatar
   
Dries committed
112
113
114
115
  if (!isset($history[$nid])) {
    $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
  }

116
  return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
Dries's avatar
   
Dries committed
117
118
119
}

/**
120
 * Decide on the type of marker to be displayed for a given node.
Dries's avatar
   
Dries committed
121
 *
Dries's avatar
   
Dries committed
122
123
124
125
 * @param $nid
 *   Node ID whose history supplies the "last viewed" timestamp.
 * @param $timestamp
 *   Time which is compared against node's "last viewed" timestamp.
126
127
 * @return
 *   One of the MARK constants.
Dries's avatar
   
Dries committed
128
 */
129
function node_mark($nid, $timestamp) {
Dries's avatar
   
Dries committed
130
131
132
  global $user;
  static $cache;

133
134
135
  if (!$user->uid) {
    return MARK_READ;
  }
Dries's avatar
Dries committed
136
  if (!isset($cache[$nid])) {
137
    $cache[$nid] = node_last_viewed($nid);
Dries's avatar
   
Dries committed
138
  }
139
140
141
142
143
144
145
  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
146
147
}

Dries's avatar
   
Dries committed
148
/**
149
 * Automatically generate a teaser for a node body in a given format.
Dries's avatar
   
Dries committed
150
 */
151
function node_teaser($body, $format = NULL) {
Dries's avatar
   
Dries committed
152

Dries's avatar
   
Dries committed
153
  $size = variable_get('teaser_length', 600);
Dries's avatar
   
Dries committed
154

Dries's avatar
   
Dries committed
155
156
157
  // find where the delimiter is in the body
  $delimiter = strpos($body, '<!--break-->');

158
  // If the size is zero, and there is no delimiter, the entire body is the teaser.
Dries's avatar
   
Dries committed
159
  if ($size == 0 && $delimiter == 0) {
Dries's avatar
   
Dries committed
160
161
    return $body;
  }
Dries's avatar
   
Dries committed
162

163
164
165
166
167
  // We check for the presence of the PHP evaluator filter in the current
  // format. If the body contains PHP code, we do not split it up to prevent
  // parse errors.
  if (isset($format)) {
    $filters = filter_list_format($format);
168
    if (isset($filters['filter/1']) && strpos($body, '<?') !== false) {
169
170
      return $body;
    }
171
172
  }

Dries's avatar
   
Dries committed
173
  // If a valid delimiter has been specified, use it to chop of the teaser.
Dries's avatar
   
Dries committed
174
  if ($delimiter > 0) {
Dries's avatar
   
Dries committed
175
176
177
    return substr($body, 0, $delimiter);
  }

178
  // If we have a short body, the entire body is the teaser.
Dries's avatar
   
Dries committed
179
180
181
182
  if (strlen($body) < $size) {
    return $body;
  }

Dries's avatar
   
Dries committed
183
184
  // 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.
185
  // When even the first paragraph is too long, we try to split at the end of
Dries's avatar
   
Dries committed
186
  // the next sentence.
Dries's avatar
Dries committed
187
  $breakpoints = array('</p>' => 4, '<br />' => 0, '<br>' => 0, "\n" => 0, '. ' => 1, '! ' => 1, '? ' => 1, '。' => 1, '؟ ' => 1);
188
189
190
191
  foreach ($breakpoints as $point => $charnum) {
    if ($length = strpos($body, $point, $size)) {
      return substr($body, 0, $length + $charnum);
    }
192
  }
Dries's avatar
Dries committed
193

194
  // If all else fails, we simply truncate the string.
195
  return truncate_utf8($body, $size);
Dries's avatar
   
Dries committed
196
197
}

198
199
200
201
function _node_names($op = '', $node = NULL) {
  static $node_names, $node_list;

  if (!isset($node_names)) {
202
    $node_names = module_invoke_all('node_info');
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    foreach ($node_names as $type => $value) {
      $node_list[$type] = $value['name'];
    }
  }
  if ($node) {
    if (is_array($node)) {
      $type = $node['type'];
    }
    elseif (is_object($node)) {
      $type = $node->type;
    }
    elseif (is_string($node)) {
      $type = $node;
    }
    if (!isset($node_names[$type])) {
      return FALSE;
    }
  }
  switch ($op) {
    case 'base':
      return $node_names[$type]['base'];
    case 'list':
      return $node_list;
    case 'name':
      return $node_list[$type];
  }
}

231
/**
232
 * Determine the basename for hook_load etc.
Dries's avatar
   
Dries committed
233
 *
234
 * @param $node
Dries's avatar
   
Dries committed
235
 *   Either a node object, a node array, or a string containing the node type.
Dries's avatar
   
Dries committed
236
 * @return
237
 *   The basename for hook_load, hook_nodeapi etc.
Dries's avatar
   
Dries committed
238
 */
239
240
241
function node_get_base($node) {
  return _node_names('base', $node);
}
242

243
244
245
246
247
248
249
250
251
252
/**
 * Determine the human readable name for a given type.
 *
 * @param $node
 *   Either a node object, a node array, or a string containing the node type.
 * @return
 *   The human readable name of the node type.
 */
function node_get_name($node) {
  return _node_names('name', $node);
Dries's avatar
   
Dries committed
253
}
Dries's avatar
   
Dries committed
254

255
/**
256
 * Return the list of available node types.
Dries's avatar
   
Dries committed
257
 *
258
259
 * @param $node
 *   Either a node object, a node array, or a string containing the node type.
Dries's avatar
   
Dries committed
260
 * @return
261
 *   An array consisting ('#type' => name) pairs.
Dries's avatar
   
Dries committed
262
 */
263
264
function node_get_types() {
  return _node_names('list');
Dries's avatar
   
Dries committed
265
}
Dries's avatar
   
Dries committed
266

267
/**
Dries's avatar
   
Dries committed
268
269
270
271
272
273
274
275
276
277
 * 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) {
278
  return module_hook(node_get_base($node), $hook);
Dries's avatar
   
Dries committed
279
280
}

281
/**
Dries's avatar
   
Dries committed
282
283
284
285
286
287
288
289
290
 * 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
291
 *   The returned value of the invoked hook.
Dries's avatar
   
Dries committed
292
293
 */
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
294
295
  if (node_hook($node, $hook)) {
    $function = node_get_base($node) ."_$hook";
Dries's avatar
   
Dries committed
296
    return ($function($node, $a2, $a3, $a4));
Dries's avatar
   
Dries committed
297
298
299
  }
}

Dries's avatar
   
Dries committed
300
301
302
303
/**
 * Invoke a hook_nodeapi() operation in all modules.
 *
 * @param &$node
Dries's avatar
   
Dries committed
304
 *   A node object.
Dries's avatar
   
Dries committed
305
306
307
308
309
310
311
 * @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
312
function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
Dries's avatar
   
Dries committed
313
  $return = array();
314
  foreach (module_implements('nodeapi') as $name) {
Dries's avatar
   
Dries committed
315
    $function = $name .'_nodeapi';
316
317
318
319
320
321
    $result = $function($node, $op, $a3, $a4);
    if (is_array($result)) {
      $return = array_merge($return, $result);
    }
    else if (isset($result)) {
      $return[] = $result;
Dries's avatar
   
Dries committed
322
323
324
325
326
    }
  }
  return $return;
}

Dries's avatar
   
Dries committed
327
328
329
/**
 * Load a node object from the database.
 *
330
331
 * @param $param
 *   Either the nid of the node or an array of conditions to match against in the database query
Dries's avatar
   
Dries committed
332
333
 * @param $revision
 *   Which numbered revision to load. Defaults to the current version.
Dries's avatar
   
Dries committed
334
335
 * @param $reset
 *   Whether to reset the internal node_load cache.
Dries's avatar
   
Dries committed
336
337
338
339
 *
 * @return
 *   A fully-populated node object.
 */
340
function node_load($param = array(), $revision = NULL, $reset = NULL) {
Dries's avatar
   
Dries committed
341
342
343
344
345
346
  static $nodes = array();

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

347
348
  if (is_numeric($param)) {
    $cachable = $revision == NULL;
349
    if ($cachable && isset($nodes[$param])) {
350
351
352
      return $nodes[$param];
    }
    $cond = 'n.nid = '. $param;
Dries's avatar
   
Dries committed
353
  }
354
355
  else {
    // Turn the conditions into a query.
356
    foreach ($param as $key => $value) {
357
358
359
      $cond[] = 'n.'. db_escape_string($key) ." = '". db_escape_string($value) ."'";
    }
    $cond = implode(' AND ', $cond);
Dries's avatar
   
Dries committed
360
361
  }

Dries's avatar
   
Dries committed
362
  // Retrieve the node.
363
364
  if ($revision) {
    $node = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, r.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. $cond), $revision));
Dries's avatar
   
Dries committed
365
  }
366
367
  else {
    $node = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.vid, n.type, n.status, n.created, n.changed, n.comment, n.promote, n.moderate, n.sticky, r.timestamp AS revision_timestamp, r.title, r.body, r.teaser, r.log, r.format, u.uid, u.name, u.picture, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. $cond)));
Dries's avatar
   
Dries committed
368
369
  }

370
371
372
373
374
375
376
  if ($node->nid) {
    // Call the node specific callback (if any) and piggy-back the
    // results to the node or overwrite some values.
    if ($extra = node_invoke($node, 'load')) {
      foreach ($extra as $key => $value) {
        $node->$key = $value;
      }
Dries's avatar
   
Dries committed
377
378
    }

379
380
381
382
383
    if ($extra = node_invoke_nodeapi($node, 'load')) {
      foreach ($extra as $key => $value) {
        $node->$key = $value;
      }
    }
Dries's avatar
   
Dries committed
384
385
  }

Dries's avatar
   
Dries committed
386
  if ($cachable) {
387
    $nodes[$param] = $node;
Dries's avatar
   
Dries committed
388
389
  }

Dries's avatar
   
Dries committed
390
391
392
  return $node;
}

Dries's avatar
   
Dries committed
393
394
395
/**
 * Save a node object into the database.
 */
396
function node_save(&$node) {
397
  global $user;
Dries's avatar
   
Dries committed
398

399
  $node->is_new = false;
Dries's avatar
   
Dries committed
400

Dries's avatar
   
Dries committed
401
  // Apply filters to some default node fields:
Dries's avatar
   
Dries committed
402
  if (empty($node->nid)) {
Dries's avatar
   
Dries committed
403
    // Insert a new node.
404
    $node->is_new = true;
Dries's avatar
   
Dries committed
405

Dries's avatar
   
Dries committed
406
    // Set some required fields:
407
408
409
    if (!$node->created) {
      $node->created = time();
    }
Dries's avatar
   
Dries committed
410
    $node->nid = db_next_id('{node}_nid');
411
412
413
414
415
416
417
    $node->vid = db_next_id('{node_revisions}_vid');;
  }
  else {
    // We need to ensure that all node fields are filled.
    $node_current = node_load($node->nid);
    foreach ($node as $field => $data) {
      $node_current->$field = $data;
Dries's avatar
   
Dries committed
418
    }
419
    $node = $node_current;
Dries's avatar
   
Dries committed
420

421
422
423
424
425
426
    if ($node->revision) {
      $node->old_vid = $node->vid;
      $node->vid = db_next_id('{node_revisions}_vid');
      // We always update the timestamp for new revisions.
      $node->changed = time();
    }
Dries's avatar
   
Dries committed
427
428
  }

429
  if (!$node->changed) {
Dries's avatar
   
Dries committed
430
    $node->changed = time();
431
  }
Dries's avatar
   
Dries committed
432

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
  // Split off revisions data to another structure
  $revisions_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
                     'title' => $node->title, 'body' => $node->body,
                     'teaser' => $node->teaser, 'log' => $node->log, 'timestamp' => $node->changed,
                     'uid' => $user->uid, 'format' => $node->format);
  $revisions_table_types = array('nid' => '%d', 'vid' => '%d',
                     'title' => "'%s'", 'body' => "'%s'",
                     'teaser' => "'%s'", 'log' => "'%s'", 'timestamp' => '%d',
                     'uid' => '%d', 'format' => '%d');
  $node_table_values = array('nid' => $node->nid, 'vid' => $node->vid,
                    'title' => $node->title, 'type' => $node->type, 'uid' => $node->uid,
                    'status' => $node->status, 'created' => $node->created,
                    'changed' => $node->changed, 'comment' => $node->comment,
                    'promote' => $node->promote, 'moderate' => $node->moderate,
                    'sticky' => $node->sticky);
  $node_table_types = array('nid' => '%d', 'vid' => '%d',
                    'title' => "'%s'", 'type' => "'%s'", 'uid' => '%d',
                    'status' => '%d', 'created' => '%d',
                    'changed' => '%d', 'comment' => '%d',
                    'promote' => '%d', 'moderate' => '%d',
                    'sticky' => '%d');

  //Generate the node table query and the
  //the node_revisions table query
  if ($node->is_new) {
    $node_query = 'INSERT INTO {node} ('. implode(', ', array_keys($node_table_types)) .') VALUES ('. implode(', ', $node_table_types) .')';
    $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
  }
  else {
    $arr = array();
    foreach ($node_table_types as $key => $value) {
      $arr[] = $key .' = '. $value;
    }
    $node_table_values[] = $node->nid;
    $node_query = 'UPDATE {node} SET '. implode(', ', $arr) .' WHERE nid = %d';
    if ($node->revision) {
      $revisions_query = 'INSERT INTO {node_revisions} ('. implode(', ', array_keys($revisions_table_types)) .') VALUES ('. implode(', ', $revisions_table_types) .')';
    }
    else {
      $arr = array();
      foreach ($revisions_table_types as $key => $value) {
        $arr[] = $key .' = '. $value;
Dries's avatar
   
Dries committed
475
      }
476
477
      $revisions_table_values[] = $node->vid;
      $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
Dries's avatar
   
Dries committed
478
    }
479
  }
Dries's avatar
   
Dries committed
480

481
482
483
  // Insert the node into the database:
  db_query($node_query, $node_table_values);
  db_query($revisions_query, $revisions_table_values);
Dries's avatar
   
Dries committed
484

485
486
487
488
489
490
  // Call the node specific callback (if any):
  if ($node->is_new) {
    node_invoke($node, 'insert');
    node_invoke_nodeapi($node, 'insert');
  }
  else {
Dries's avatar
   
Dries committed
491
492
    node_invoke($node, 'update');
    node_invoke_nodeapi($node, 'update');
Dries's avatar
   
Dries committed
493
494
  }

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

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

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

521
522
523
524
  if ($node->log != '' && !$teaser && $node->moderate) {
    $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. $node->log .'</div>';
  }

Dries's avatar
   
Dries committed
525
526
  // The 'view' hook can be implemented to overwrite the default function
  // to display nodes.
Dries's avatar
   
Dries committed
527
  if (node_hook($node, 'view')) {
Dries's avatar
   
Dries committed
528
    node_invoke($node, 'view', $teaser, $page);
Dries's avatar
   
Dries committed
529
530
  }
  else {
Dries's avatar
   
Dries committed
531
    $node = node_prepare($node, $teaser);
Dries's avatar
   
Dries committed
532
  }
Dries's avatar
   
Dries committed
533
534
  // Allow modules to change $node->body before viewing.
  node_invoke_nodeapi($node, 'view', $teaser, $page);
535
536
537
  if ($links) {
    $node->links = module_invoke_all('link', 'node', $node, !$page);
  }
538
539
540
541
542
543
544
  // unset unused $node part so that a bad theme can not open a security hole
  if ($teaser) {
    unset($node->body);
  }
  else {
    unset($node->teaser);
  }
Dries's avatar
   
Dries committed
545
546

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

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

Dries's avatar
   
Dries committed
563
564
565
/**
 * Generate a page displaying a single node, along with its comments.
 */
Dries's avatar
   
Dries committed
566
function node_show($node, $cid) {
Dries's avatar
   
Dries committed
567
  $output = node_view($node, FALSE, TRUE);
Dries's avatar
   
Dries committed
568

Dries's avatar
   
Dries committed
569
570
  if (function_exists('comment_render') && $node->comment) {
    $output .= comment_render($node, $cid);
Dries's avatar
   
Dries committed
571
572
  }

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

Dries's avatar
   
Dries committed
576
  return $output;
Dries's avatar
   
Dries committed
577
578
}

Dries's avatar
   
Dries committed
579
580
581
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
582
function node_perm() {
Dries's avatar
   
Dries committed
583
  return array('administer nodes', 'access content');
Dries's avatar
   
Dries committed
584
585
}

Dries's avatar
   
Dries committed
586
587
588
/**
 * Implementation of hook_search().
 */
589
590
591
592
function node_search($op = 'search', $keys = null) {
  switch ($op) {
    case 'name':
      return t('content');
593

Dries's avatar
Dries committed
594
595
596
    case 'reset':
      variable_del('node_cron_last');
      return;
597

598
599
600
    case 'status':
      $last = variable_get('node_cron_last', 0);
      $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND moderate = 0'));
Steven Wittens's avatar
Steven Wittens committed
601
      $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)', $last, $last, $last));
602
      return array('remaining' => $remaining, 'total' => $total);
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626

    case 'admin':
      $form = array();
      // Output form for defining rank factor weights.
      $form['content_ranking'] = array('#type' => 'fieldset', '#title' => t('Content ranking'));
      $form['content_ranking']['#theme'] = 'node_search_admin';
      $form['content_ranking']['info'] = array('#type' => 'markup', '#value' => '<em>'. t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence. Zero means the property is ignored.') .'</em>');

      $ranking = array('node_rank_relevance' => t('Keyword relevance'),
                       'node_rank_recent' => t('Recently posted'));
      if (module_exist('comment')) {
        $ranking['node_rank_comments'] = t('Number of comments');
      }
      if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) {
        $ranking['node_rank_views'] = t('Number of views');
      }

      // Note: reversed to reflect that higher number = higher ranking.
      $options = drupal_map_assoc(range(0, 10));
      foreach ($ranking as $var => $title) {
        $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5));
      }
      return $form;

627
    case 'search':
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
      // Build matching conditions
      list($join1, $where1) = _db_rewrite_sql();
      $arguments1 = array();
      $conditions1 = 'n.status = 1';

      if ($type = search_query_extract($keys, 'type')) {
        $types = array();
        foreach (explode(',', $type) as $t) {
          $types[] = "n.type = '%s'";
          $arguments1[] = $t;
        }
        $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
        $keys = search_query_insert($keys, 'type');
      }

      if ($category = search_query_extract($keys, 'category')) {
        $categories = array();
        foreach (explode(',', $category) as $c) {
          $categories[] = "tn.tid = %d";
          $arguments1[] = $c;
        }
        $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
        $join1 .= ' INNER JOIN {term_node} tn ON n.nid = tn.nid';
        $keys = search_query_insert($keys, 'category');
      }

      // Build ranking expression (we try to map each parameter to a
      // uniform distribution in the range 0..1).
      $ranking = array();
      $arguments2 = array();
      $join2 = '';
      // Used to avoid joining on node_comment_statistics twice
      $stats_join = false;
      if ($weight = (int)variable_get('node_rank_relevance', 5)) {
        // Average relevance values hover around 0.15
        $ranking[] = '%d * i.relevance';
        $arguments2[] = $weight;
      }
      if ($weight = (int)variable_get('node_rank_recent', 5)) {
        // Exponential decay with half-life of 6 months, starting at last indexed node
        $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)';
        $arguments2[] = $weight;
        $arguments2[] = (int)variable_get('node_cron_last', 0);
        $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
        $stats_join = true;
      }
      if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
        // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
        $scale = variable_get('node_cron_comments_scale', 0.0);
        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))';
        $arguments2[] = $weight;
        $arguments2[] = $scale;
        if (!$stats_join) {
          $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
        }
      }
      if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) &&
          $weight = (int)variable_get('node_rank_views', 5)) {
        // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
        $scale = variable_get('node_cron_views_scale', 0.0);
        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))';
        $arguments2[] = $weight;
        $arguments2[] = $scale;
        $join2 .= ' LEFT JOIN {node_counter} nc ON n.nid = nc.nid';
      }
      $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score';

      // Do search
      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);

      // Load results
699
700
      $results = array();
      foreach ($find as $item) {
701
        $node = node_load($item);
702
703
704
705
706
707
708
709
710
711
712

        // Get node output (filtered and with module-specific fields).
        if (node_hook($node, 'view')) {
          node_invoke($node, 'view', false, false);
        }
        else {
          $node = node_prepare($node, false);
        }
        // Allow modules to change $node->body before viewing.
        node_invoke_nodeapi($node, 'view', false, false);

713
714
715
        // Fetch comments for snippet
        $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');

Dries's avatar
Dries committed
716
        $extra = node_invoke_nodeapi($node, 'search result');
717
        $results[] = array('link' => url('node/'. $item),
718
                           'type' => node_get_name($node),
719
                           'title' => $node->title,
720
                           'user' => theme('username', $node),
721
                           'date' => $node->changed,
722
                           'node' => $node,
Dries's avatar
Dries committed
723
                           'extra' => $extra,
724
                           'snippet' => search_excerpt($keys, $node->body));
725
726
      }
      return $results;
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772

    case 'form':
      $form = array();

      // Keyword boxes
      $form['advanced'] = array('#type' => 'fieldset', '#title' => t('Advanced search'), '#collapsible' => true, '#collapsed' => true, '#attributes' => array('class' => 'search-advanced'));

      $form['advanced']['keywords'] = array('#type' => 'markup', '#prefix' => '<div class="criterium">', '#suffix' => '</div>');
      $form['advanced']['keywords']['or'] = array('#type' => 'textfield', '#title' => t('Containing any of the words'), '#size' => 30, '#maxlength' => 255);
      $form['advanced']['keywords']['phrase'] = array('#type' => 'textfield', '#title' => t('Containing the phrase'), '#size' => 30, '#maxlength' => 255);
      $form['advanced']['keywords']['negative'] = array('#type' => 'textfield', '#title' => t('Containing none of the words'), '#size' => 30, '#maxlength' => 255);

      // Taxonomy box
      if ($taxonomy = module_invoke('taxonomy', 'form_all')) {
        $form['advanced']['category'] = array('#type' => 'select', '#title' => t('Only in the category'), '#prefix' => '<div class="criterium">', '#suffix' => '</div>', '#options' => $taxonomy, '#extra' => 'size="10"', '#multiple' => true);
      }

      // Node types
      $types = node_get_types();
      $form['advanced']['type'] = array('#type' => 'checkboxes', '#title' => t('Only of the type'), '#prefix' => '<div class="criterium">', '#suffix' => '</div>', '#options' => $types, '#multiple' => true);
      $form['advanced']['submit'] = array('#type' => 'submit', '#value' => t('Advanced Search'), '#prefix' => '<div class="action">', '#suffix' => '</div>');
      return $form;

    case 'post':
      // Insert extra restrictions into the search keywords string.
      $edit = &$_POST['edit'];
      if (is_array($edit['type'])) {
        $keys = search_query_insert($keys, 'type', implode(',', array_keys($edit['type'])));
      }
      if (is_array($edit['category'])) {
        $keys = search_query_insert($keys, 'category', implode(',', $edit['category']));
      }
      if ($edit['or'] != '') {
        if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $edit['or'], $matches)) {
          $keys = $keys .' '. implode(' OR ', $matches[1]);
        }
      }
      if ($edit['negative'] != '') {
        if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $edit['negative'], $matches)) {
          $keys = $keys .' -'. implode(' -', $matches[1]);
        }
      }
      if ($edit['phrase'] != '') {
        $keys .= ' "'. str_replace('"', ' ', $edit['phrase']) .'"';
      }
      return trim($keys);
773
  }
Dries's avatar
   
Dries committed
774
775
}

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
function theme_node_search_admin($form) {
  $output = form_render($form['info']);

  $header = array(t('Factor'), t('Weight'));
  foreach (element_children($form['factors']) as $key) {
    $row = array();
    $row[] = $form['factors'][$key]['#title'];
    unset($form['factors'][$key]['#title']);
    $row[] = form_render($form['factors'][$key]);
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);

  $output .= form_render($form);
  return $output;
}

Dries's avatar
   
Dries committed
793
/**
Dries's avatar
   
Dries committed
794
 * Menu callback; presents general node configuration options.
Dries's avatar
   
Dries committed
795
796
797
 */
function node_configure() {

798
  $form['default_nodes_main'] = array(
799
800
801
    '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
    '#options' =>  drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
    '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
802
803
804
  );

  $form['teaser_length'] = array(
805
806
    '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
    '#options' => array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'),
807
808
      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')),
809
    '#description' => 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.")
810
811
812
  );

  $form['node_preview'] = array(
813
814
    '#type' => 'radios', '#title' => t('Preview post'), '#default_value' => variable_get('node_preview', 0),
    '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
815
  );
Dries's avatar
   
Dries committed
816

817
  return system_settings_form('node_configure', $form);
Dries's avatar
   
Dries committed
818
819
}

Dries's avatar
   
Dries committed
820
821
822
/**
 * Retrieve the comment mode for the given node ID (none, read, or read/write).
 */
Dries's avatar
   
Dries committed
823
function node_comment_mode($nid) {
Dries's avatar
   
Dries committed
824
825
  static $comment_mode;
  if (!isset($comment_mode[$nid])) {
Dries's avatar
   
Dries committed
826
    $comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
Dries's avatar
   
Dries committed
827
828
  }
  return $comment_mode[$nid];
Dries's avatar
   
Dries committed
829
830
}

Dries's avatar
   
Dries committed
831
832
833
/**
 * Implementation of hook_link().
 */
834
function node_link($type, $node = 0, $main = 0) {
Dries's avatar
   
Dries committed
835
836
  $links = array();

Dries's avatar
   
Dries committed
837
  if ($type == 'node') {
Dries's avatar
   
Dries committed
838
    if (array_key_exists('links', $node)) {
Kjartan's avatar
Kjartan committed
839
840
      $links = $node->links;
    }
Dries's avatar
   
Dries committed
841

Dries's avatar
   
Dries committed
842
    if ($main == 1 && $node->teaser && $node->readmore) {
Dries's avatar
   
Dries committed
843
      $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
844
    }
Dries's avatar
   
Dries committed
845
846
  }

Dries's avatar
   
Dries committed
847
  return $links;
Dries's avatar
   
Dries committed
848
849
}

Dries's avatar
   
Dries committed
850
851
852
/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
853
function node_menu($may_cache) {
Dries's avatar
   
Dries committed
854
855
  $items = array();

Dries's avatar
   
Dries committed
856
857
  if ($may_cache) {
    $items[] = array('path' => 'admin/node', 'title' => t('content'),
Dries's avatar
   
Dries committed
858
      'callback' => 'node_admin',
Dries's avatar
   
Dries committed
859
      'access' => user_access('administer nodes'));
860
861
    $items[] = array('path' => 'admin/node/action', 'title' => t('content'),
      'type' => MENU_CALLBACK);
Dries's avatar
   
Dries committed
862
863
    $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
864
    $items[] = array('path' => 'admin/settings/node', 'title' => t('posts'),
Dries's avatar
   
Dries committed
865
      'callback' => 'node_configure',
866
867
      'access' => user_access('administer nodes'));
    $items[] = array('path' => 'admin/settings/content-types', 'title' => t('content types'),
868
      'callback' => 'node_types_configure',
869
      'access' => user_access('administer nodes'));
Dries's avatar
   
Dries committed
870
871
872
873
874
875
    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
876

Dries's avatar
   
Dries committed
877
    $items[] = array('path' => 'node', 'title' => t('content'),
Dries's avatar
   
Dries committed
878
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
879
880
881
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM);
    $items[] = array('path' => 'node/add', 'title' => t('create content'),
Dries's avatar
   
Dries committed
882
      'callback' => 'node_page',
Dries's avatar
   
Dries committed
883
884
885
886
887
888
      'access' => user_access('access content'),
      'type' => MENU_ITEM_GROUPING,
      'weight' => 1);
  }
  else {
    if (arg(0) == 'node' && is_numeric(arg(1))) {
889
      $node = node_load(arg(1));
890
891
      if ($node->nid) {
        $items[] = array('path' => 'node/'. arg(1), 'title' => t('view'),
Dries's avatar
   
Dries committed
892
          'callback' => 'node_page',
893
894
895
896
897
898
899
900
          '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
901
          'type' => MENU_LOCAL_TASK);
902
        $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'),
903
          'callback' => 'node_delete_page',
904
905
906
          'access' => node_access('delete', $node),
          'weight' => 1,
          'type' => MENU_CALLBACK);
907
        if (user_access('administer nodes') && db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', arg(1))) > 1) {
908
909
910
911
912
913
          $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
914
      }
915
    }
916
917
    else if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'content-types' && is_string(arg(3))) {
      $items[] = array('path' => 'admin/settings/content-types/'. arg(3),
918
        'title' => t("'%name' content type", array('%name' => node_get_name(arg(3)))),
919
920
        'type' => MENU_CALLBACK);
    }
Dries's avatar
   
Dries committed
921
922
923
924
925
  }

  return $items;
}

926
927
928
929
930
function node_last_changed($nid) {
  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
  return ($node->changed);
}

931
932
933
934
/*
** Node operations
*/
function node_operations() {
Dries's avatar
   
Dries committed
935
  $operations = array(
936
937
938
939
940
941
    '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
942
  );
943
944
  return $operations;
}
Dries's avatar
   
Dries committed
945

946
947
948
949
/*
** Filters
*/
function node_filters() {
950
951
  // Regular filters
  $filters = array(
952
    'status'   => array('title' => t('status'),
953
954
955
956
                        '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'))),
957
    'type'     => array('title' => t('type'), 'where' => "n.type = '%s'",
958
                        'options' => node_get_types()));
959
960
961
  // Merge all vocabularies into one for retrieving $value below
  if ($taxonomy = module_invoke('taxonomy', 'form_all')) {
    $terms = array();
962
    foreach ($taxonomy as $value) {
963
964
965
966
967
968
969
970
      $terms = $terms + $value;
    }
    $filters['category'] = array('title' => t('category'), 'where' => 'tn.tid = %d',
                                 'options' => $terms, 'join' => 'INNER JOIN {term_node} tn ON n.nid = tn.nid');
  }
  if (isset($filters['category'])) {
    $filters['category']['options'] = $taxonomy;
  }
Dries's avatar
   
Dries committed
971

972
973
974
975
976
  return $filters;
}

function node_build_filter_query() {
  $filters = node_filters();
977
978
979
980

  // Build query
  $where = $args = array();
  $join = '';
981
  foreach ($_SESSION['node_overview_filter'] as $filter) {
982
983
984
985
986
987
988
989
990
991
992
993
994
    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) : '';
995

996
997
  return array('where' => $where, 'join' => $join, 'args' => $args);
}
998

999
1000
1001
1002
1003
1004
function node_filter_form() {
  $session = &$_SESSION['node_overview_filter'];
  $session = is_array($session) ? $session : array();
  $filters = node_filters();

  $i = 0;
1005
  $form['filters'] = array('#type' => 'fieldset', '#title' => t('Show only items where'), '#theme' => 'node_filters');
1006
1007
1008
  foreach ($session as $filter) {
    list($type, $value) = $filter;
    $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>');
1009
    $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $filters[$type]['options'][$value])));
Dries's avatar
   
Dries committed
1010
  }
1011

1012
1013
  foreach ($filters as $key => $filter) {
    $names[$key] = $filter['title'];
1014
    $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
1015
1016
  }

1017
1018
  $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
  $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
1019
  if (count($session)) {
1020
1021
    $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
1022
  }
Dries's avatar
   
Dries committed
1023

1024
1025
1026
1027
1028
1029
  return drupal_get_form('node_filter_form', $form);
}

function theme_node_filter_form(&$form) {
  $output .= '<div id="node-admin-filter">';
  $output .= form_render($form['filters']);
1030
  $output .= '</div>';
1031
1032
1033
  $output .= form_render($form);
  return $output;
}
Dries's avatar
   
Dries committed
1034

1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
function theme_node_filters(&$form) {
  $output .= '<ul>';
  if (sizeof($form['current'])) {
    foreach (element_children($form['current']) as $key) {
      $output .= '<li>' . form_render($form['current'][$key]) . '</li>';
    }
  }

  $output .= '<li><dl class="multiselect">' . (sizeof($form['current']) ? '<dt><em>and</em> where</dt>' : '') . '<dd class="a">';
  foreach(element_children($form['filter']) as $key) {
    $output .= form_render($form['filter'][$key]);
  }
  $output .= '</dd>';

  $output .= '<dt>is</dt>' . '<dd class="b">';

  foreach(element_children($form['status']) as $key) {
    $output .= form_render($form['status'][$key]);
  }
  $output .= '</dd>';


  $output .= '</dl>';
  $output .= '<div class="container-inline" id="node-admin-buttons">'. form_render($form['buttons']) .'</div>';
  $output .= '</li></ul><br class="clear" />';

  return $output;
}


function node_filter_form_execute() {
  global $form_values;
  $op = $_POST['op'];
  $filters = node_filters();
  switch ($op) {
    case t('Filter'): case t('Refine'):
      if (isset($form_values['filter'])) {
        $filter = $form_values['filter'];
        if (isset($filters[$filter]['options'][$form_values[$filter]])) {
          $_SESSION['node_overview_filter'][] = array($filter, $form_values[$filter]);
        }
      }
      break;
    case t('Undo'):
      array_pop($_SESSION['node_overview_filter']);
      break;
    case t('Reset'):
      $_SESSION['node_overview_filter'] = array();
      break;
    case t('Update'):
      return;
  }
  if ($op != '') {
    drupal_goto('admin/node');
  }
}

/**
 * Generate the content administration overview.
 */
function node_admin_nodes_execute($form_id, $edit) {
  $operations = node_operations();
  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.'));
  }
  drupal_goto('admin/node');
}

function node_admin_nodes_validate($form_id, $edit) {
  $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.'));
  }
}

function node_admin_nodes() {
  global $form_values;
  $output = node_filter_form();

  $filter = node_build_filter_query();

  $result = pager_query('SELECT n.*, u.name, u.uid FROM {node} n '. $filter['join'] .' INNER JOIN {users} u ON n.uid = u.uid '. $filter['where'] .' ORDER BY n.changed DESC', 50, 0, NULL, $filter['args']);

  $form['options'] = array(
1126
1127
    '#type' => 'fieldset', '#title' => t('Update options'),
    '#prefix' => "<div class=\"container-inline\">" , '#suffix' => "</div>"
1128
1129
1130
1131
1132
  );
  $options = array();
  foreach (node_operations() as $key => $value) {
    $options[$key] = $value[0];
  }
1133
1134
  $form['options']['operation'] = array('#type' => 'select', '#options' => $options,  '#default_value' => 'approve');
  $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
Dries's avatar
   
Dries committed
1135

1136
  $destination = drupal_get_destination();
Dries's avatar
   
Dries committed
1137
  while ($node = db_fetch_object($result)) {
1138
    $nodes[$node->nid] = '';
1139
1140
1141
1142
1143
    $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed)));
    $form['name'][$node->nid] =  array('#value' => node_get_name($node));
    $form['username'][$node->nid] = array('#value' => theme('username', $node));
    $form['status'][$node->nid] =  array('#value' =>  ($node->status ? t('published') : t('not published')));
    $form['operations'][$node->nid] = array('#value' => l(t('edit'), 'node/'. $node->nid .'/edit', array(), $destination));
Dries's avatar
   
Dries committed
1144
  }
1145
  $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
1146
  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
Dries's avatar
   
Dries committed
1147

1148
1149
  $form['#method'] = 'post';
  $form['#action'] =  url('admin/node/action');