node.module 82.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
function _node_names($op = '', $node = NULL) {
199
200
  static $node_names = array();
  static $node_list = array();
201

202
  if (empty($node_names)) {
203
    $node_names = module_invoke_all('node_info');
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
231
    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];
  }
}

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

244
245
246
247
248
249
250
251
252
253
/**
 * 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
254
}
Dries's avatar
   
Dries committed
255

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

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

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

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

Dries's avatar
   
Dries committed
328
329
330
/**
 * Load a node object from the database.
 *
331
332
 * @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
333
334
 * @param $revision
 *   Which numbered revision to load. Defaults to the current version.
Dries's avatar
   
Dries committed
335
336
 * @param $reset
 *   Whether to reset the internal node_load cache.
Dries's avatar
   
Dries committed
337
338
339
340
 *
 * @return
 *   A fully-populated node object.
 */
341
function node_load($param = array(), $revision = NULL, $reset = NULL) {
Dries's avatar
   
Dries committed
342
343
344
345
346
347
  static $nodes = array();

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

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

Dries's avatar
   
Dries committed
363
  // Retrieve the node.
364
365
  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
366
  }
367
368
  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
369
370
  }

371
372
373
374
375
376
377
  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
378
379
    }

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

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

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

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

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

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

Dries's avatar
   
Dries committed
407
    // Set some required fields:
408
409
410
    if (!$node->created) {
      $node->created = time();
    }
Dries's avatar
   
Dries committed
411
    $node->nid = db_next_id('{node}_nid');
412
413
414
415
416
417
418
    $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
419
    }
420
    $node = $node_current;
Dries's avatar
   
Dries committed
421

422
423
424
425
    if ($node->revision) {
      $node->old_vid = $node->vid;
      $node->vid = db_next_id('{node_revisions}_vid');
    }
Dries's avatar
   
Dries committed
426
427
  }

428
429
  // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
  $node->changed = time();
Dries's avatar
   
Dries committed
430

431
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
  // 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
473
      }
474
475
      $revisions_table_values[] = $node->vid;
      $revisions_query = 'UPDATE {node_revisions} SET '. implode(', ', $arr) .' WHERE vid = %d';
Dries's avatar
   
Dries committed
476
    }
477
  }
Dries's avatar
   
Dries committed
478

479
480
481
  // 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
482

483
484
485
486
487
488
  // 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
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
  cache_clear_all();
Dries's avatar
   
Dries committed
495
496
}

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

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

519
520
521
522
  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
523
524
  // The 'view' hook can be implemented to overwrite the default function
  // to display nodes.
Dries's avatar
   
Dries committed
525
  if (node_hook($node, 'view')) {
Dries's avatar
   
Dries committed
526
    node_invoke($node, 'view', $teaser, $page);
Dries's avatar
   
Dries committed
527
528
  }
  else {
Dries's avatar
   
Dries committed
529
    $node = node_prepare($node, $teaser);
Dries's avatar
   
Dries committed
530
  }
Dries's avatar
   
Dries committed
531
532
  // Allow modules to change $node->body before viewing.
  node_invoke_nodeapi($node, 'view', $teaser, $page);
533
534
535
  if ($links) {
    $node->links = module_invoke_all('link', 'node', $node, !$page);
  }
536
537
538
539
540
541
542
  // 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
543
544

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

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

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

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

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

Dries's avatar
   
Dries committed
574
  return $output;
Dries's avatar
   
Dries committed
575
576
}

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

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

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

596
597
598
    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
599
      $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));
600
      return array('remaining' => $remaining, 'total' => $total);
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624

    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;

625
    case 'search':
626
627
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
      // 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
697
698
      $results = array();
      foreach ($find as $item) {
699
        $node = node_load($item);
700
701
702
703
704
705
706
707
708
709
710

        // 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);

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

Dries's avatar
Dries committed
714
        $extra = node_invoke_nodeapi($node, 'search result');
715
        $results[] = array('link' => url('node/'. $item),
716
                           'type' => node_get_name($node),
717
                           'title' => $node->title,
718
                           'user' => theme('username', $node),
719
                           'date' => $node->changed,
720
                           'node' => $node,
Dries's avatar
Dries committed
721
                           'extra' => $extra,
722
                           'snippet' => search_excerpt($keys, $node->body));
723
724
      }
      return $results;
725
726
727
728
729

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

      // Keyword boxes
730
      $form['advanced'] = array('#type' => 'fieldset', '#title' => t('Advanced search'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#attributes' => array('class' => 'search-advanced'));
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750

      $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'];
751
      if (isset($edit['type']) && is_array($edit['type'])) {
752
753
        $keys = search_query_insert($keys, 'type', implode(',', array_keys($edit['type'])));
      }
754
      if (isset($edit['category']) && is_array($edit['category'])) {
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
        $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);
771
  }
Dries's avatar
   
Dries committed
772
773
}

774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
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
791
/**
Dries's avatar
   
Dries committed
792
 * Menu callback; presents general node configuration options.
Dries's avatar
   
Dries committed
793
794
795
 */
function node_configure() {

796
  $form['default_nodes_main'] = array(
797
798
799
    '#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.')
800
801
802
  );

  $form['teaser_length'] = array(
803
804
    '#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'),
805
806
      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')),
807
    '#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.")
808
809
810
  );

  $form['node_preview'] = array(
811
812
    '#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?')
813
  );
Dries's avatar
   
Dries committed
814

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

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

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

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

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

Dries's avatar
   
Dries committed
845
  return $links;
Dries's avatar
   
Dries committed
846
847
}

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

Dries's avatar
   
Dries committed
854
855
  if ($may_cache) {
    $items[] = array('path' => 'admin/node', 'title' => t('content'),
Steven Wittens's avatar
Steven Wittens committed
856
      'callback' => 'node_admin_nodes',
Dries's avatar
   
Dries committed
857
858
859
      'access' => user_access('administer nodes'));
    $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
      'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
Steven Wittens's avatar
Steven Wittens committed
860
861
862
863
864
865
866
867
868
869
870
    $items[] = array('path' => 'admin/node/action', 'title' => t('content'),
      'callback' => 'node_admin_nodes',
      'type' => MENU_CALLBACK);

    if (module_exist('search')) {
      $items[] = array('path' => 'admin/node/search', 'title' => t('search'),
        'callback' => 'node_admin_search',
        'access' => user_access('administer nodes'),
        'type' => MENU_LOCAL_TASK);
    }

871
    $items[] = array('path' => 'admin/settings/node', 'title' => t('posts'),
Dries's avatar
   
Dries committed
872
      'callback' => 'node_configure',
873
874
      'access' => user_access('administer nodes'));
    $items[] = array('path' => 'admin/settings/content-types', 'title' => t('content types'),
875
      'callback' => 'node_types_configure',
876
      'access' => user_access('administer nodes'));
Dries's avatar
   
Dries committed
877

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

  return $items;
}

931
932
933
934
935
function node_last_changed($nid) {
  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
  return ($node->changed);
}

936
937
938
939
/*
** Node operations
*/
function node_operations() {
Dries's avatar
   
Dries committed
940
  $operations = array(
941
942
943
944
945
946
    '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
947
  );
948
949
  return $operations;
}
Dries's avatar
   
Dries committed
950

951
952
953
954
/*
** Filters
*/
function node_filters() {
955
956
  // Regular filters
  $filters = array(
957
    'status'   => array('title' => t('status'),
958
959
960
961
                        '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'))),
962
    'type'     => array('title' => t('type'), 'where' => "n.type = '%s'",
963
                        'options' => node_get_types()));
964
965
966
  // Merge all vocabularies into one for retrieving $value below
  if ($taxonomy = module_invoke('taxonomy', 'form_all')) {
    $terms = array();
967
    foreach ($taxonomy as $value) {
968
969
970
971
972
973
974
975
      $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
976

977
978
979
980
981
  return $filters;
}

function node_build_filter_query() {
  $filters = node_filters();
982
983
984
985

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

1001
1002
  return array('where' => $where, 'join' => $join, 'args' => $args);
}
1003

1004
1005
1006
1007
1008
1009
function node_filter_form() {
  $session = &$_SESSION['node_overview_filter'];
  $session = is_array($session) ? $session : array();
  $filters = node_filters();

  $i = 0;
1010
  $form['filters'] = array('#type' => 'fieldset', '#title' => t('Show only items where'), '#theme' => 'node_filters');
1011
1012
1013
  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>');
1014
    $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $filters[$type]['options'][$value])));
Dries's avatar
   
Dries committed
1015
  }
1016

1017
1018
  foreach ($filters as $key => $filter) {
    $names[$key] = $filter['title'];
1019
    $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
1020
1021
  }

1022
1023
  $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')));
1024
  if (count($session)) {
1025
1026
    $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
1027
  }
Dries's avatar
   
Dries committed
1028

1029
1030
1031
1032
1033
1034
  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']);
1035
  $output .= '</div>';
1036
1037
1038
  $output .= form_render($form);
  return $output;
}
Dries's avatar
   
Dries committed
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
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;
}


1070
function node_filter_form_submit() {
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
  global $form_values;
  $op = $_POST['op'];
  $filters = node_filters();
  switch ($op) {
    case t('Filter'): case t('Refine'):
      if (isset($form_values['filter'])