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

4
function node_help($section = "admin/help#node") {
5
  global $mod;
6 7 8 9
  $output = "";

  switch ($section) {

10
    case 'admin/help#node':
11 12 13 14
      $output .= "<h3>Nodes</h3>";
      $output .= "<p>The core of the Drupal system is the node. All of the contents of the system are placed in nodes, or extensions of nodes.";
      $output .= "A base node contains:<dl>";
      $output .= "<dt>A Title</dt><dd>Up to 128 characters of text that titles the node.</dd>";
15
      $output .= "<dt>A Teaser</dt><dd>A small block of text that is meant to get you interested in the rest of node. Drupal will automatically pull a small amount of the body of the node to make the teaser (To configure how long the teaser will be %teaser). The teaser can be changed if you don't like what Drupal grabs.</dd>";
16 17 18 19 20
      $output .= "<dt>The Body</dt><dd>The main text that comprises your content.</dd>";
      $output .= "<dt>A Type</dt><dd>What kind of node is this? Blog, book, forum, comment, unextended, etc.</dd>";
      $output .= "<dt>An Author</dt><dd>The author's name. It will either be \"anonymous\" or a valid user. You <i>cannot</i> set it to an arbitrary value.</dd>";
      $output .= "<dt>Authored on</dt><dd>The date the node was written.</dd>";
      $output .= "<dt>Changed</dt><dd>The last time this node was changed.</dd>";
21
      $output .= "<dt>Static on front page</dt><dd>The front page is configured to show the teasers from only a few of the total nodes you have on your site (To configure how many teasers %teaser), but if you think a node is important enough that you want it to stay on the front page enable this.</dd>";
22 23 24 25 26
      $output .= "<dt>Allow user comments</dt><dd>A node can have comments. These comments can be written by other users (Read-write), or only by admins (Read-only).</dd>";
      $output .= "<dt>Attributes</dt><dd>A way to sort nodes.</dd>";
      $output .= "<dt>Revisions</dt><dd>Drupal has a revision system so that you can \"roll back\" to an older version of a node if the new version is not what you want.</dd>";
      $output .= "<dt>Promote to front page</dt><dd>To get people to look at the new stuff on your site you can choose to move it to the front page.</dd>";
      $output .= "<dt>In moderation queue</dt><dd>Drupal has a moderation system. If it is active, a node is in one of three states: approved and published, approved and unpublished, and awaiting approval. If you are moderating a node it should be in the moderation queue.</dd>";
27
      $output .= "<dt>Votes</dt><dd>If you are moderating a node this counts how many votes the node has gotten. Once a node gets a certain number of vote if will either be Approved, or Dropped (To setup the number of votes needed and the promote and dump scores %queue.)</a>.</dd>";
28 29 30 31 32
      $output .= "<dt>Score</dt><dd>The score of the node is gotten by the votes it is given.</dd>";
      $output .= "<dt>Users</dt><dd>The list of users who have voted on a moderated node.</dd>";
      $output .= "<dt>Published</dt><dd>When using Drupal's moderation system a node remains unpublished -- unavaliable to non-moderators -- until it is marked Published.</dd></dl>";
      $output .= "<p>Now that you know what is in a node, here are some of the types of nodes available.</p>";

33
      $output = t($output, array("%teaser" => l(t("click here"), "admin/system/modules/node"), "%queue" => l(t("click here"), "admin/system/modules/queue")));
34

35 36 37
      if ($mod == "admin") {
        foreach (module_list() as $name) {
          if (module_hook($name, "node") && $name != "node") {
38
            $output .= "<h3>". t("Node type: %module", array("%module" => module_invoke($name, "node", "name"))) ."</h3>";
39 40 41
            $output .= module_invoke($name, "node", "description");
          }
        }
42
      }
43 44
      break;

45
    case 'admin/system/modules#description':
46
      $output = t("The core that allows content to be submitted to the site.");
47 48
      break;
    case 'admin/system/modules/node':
49
      $output = t("Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.");
50 51
      break;
    case 'admin/node':
52
      $output = t("Below is a list of all of the nodes in your site. Other forms of content are listed elsewhere (e.g. %comment).<br />Clicking a title views that node, while clicking an author's name edits their user information.<br />Other node-related tasks are available from the menu on the left.", array("%comments" => l(t("comments"), "admin/comment")));
53 54
      break;
    case 'admin/node/search':
55
      $output = t("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\".");
56 57
      break;
    case 'admin/node/settings':
58
      $output = t("This pages lets you set the defaults used during creation of nodes for all the different node types.<br /><b>comment:</b> Read/write setting for comments.<br /><b>publish:</b> Is this node publicly viewable, has it been published?<br /><b>promote:</b> Is this node to be promoted to the front page?<br /><b>moderate:</b> Does this node need approval before it can be viewed?<br /><b>static:</b> Is this node always visible on the front page?<br /><b>revision:</b> Will this node go into the revision system allowing multiple versions to be saved?");
59 60
      break;

61
  }
62 63

  return $output;
64 65
}

Dries's avatar
Dries committed
66 67 68 69
/*
** Accepts a DB result object which can be used to fetch node objects.
** Returns an HTML list suitable as content for a block.
*/
Dries's avatar
Dries committed
70 71
function node_title_list($result, $title = NULL) {
  while ($node = db_fetch_object($result)) {
Dries's avatar
Dries committed
72
    $number = module_invoke("comment", "num_all", $node->nid);
73
    $items[] = l($node->title, "node/view/$node->nid", array("title" => format_plural($number, "%count comment", "%count comments")));
Dries's avatar
Dries committed
74 75
  }

76 77 78
  return theme("theme_node_list", $items, $title);
}

79 80
function theme_node_list($items, $title = NULL) {
  return theme("theme_item_list", $items, $title);
Dries's avatar
Dries committed
81 82
}

83 84 85 86 87
// Update the 'last viewed' timestamp of the specified node for current user.
function node_tag_new($nid) {
  global $user;

  if ($user->uid) {
88
    $result = db_query("SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d", $user->uid, $nid);
89
    if (db_fetch_object($result)) {
90
      db_query("UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d", time(), $user->uid, $nid);
91 92
    }
    else {
93
      db_query("INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)", $user->uid, $nid, time());
94 95 96 97 98 99 100 101 102 103 104
    }
  }
}

/*
** Retrieves the timestamp at which the current user last viewed the
** specified node.
*/
function node_last_viewed($nid) {
  global $user;

105
  $history = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
106 107 108 109 110 111 112 113 114 115 116 117 118
  return ($history->timestamp ? $history->timestamp : 0);
}

/**
 * Determines whether the supplied timestamp is newer than the user's last view of a given node
 *
 * @param $nid       node-id twhose history supplies the 'last viewed' timestamp
 * @param $timestamp time which is compared against node's 'last veiwed' timestamp
*/
function node_is_new($nid, $timestamp) {
  global $user;
  static $cache;

Dries's avatar
Dries committed
119
  if (!isset($cache[$nid])) {
120
    if ($user->uid) {
121
      $history = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d", $user->uid, $nid));
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
      $cache[$nid] = $history->timestamp ? $history->timestamp : 0;
    }
    else {
      $cache[$nid] = time();
    }
  }

  if ($timestamp > $cache[$nid]) {
    return 1;
  }
  else {
    return 0;
  }
}

137 138
function node_teaser($body) {

Dries's avatar
Dries committed
139 140 141 142 143 144 145 146 147 148
  $size = variable_get("teaser_length", 600);

  /*
  ** If the size is zero, teasers are disabled so we
  ** return the entire body.
  */

  if ($size == 0) {
    return $body;
  }
149 150

  /*
Dries's avatar
Dries committed
151
  ** If a valid delimiter has been specified, use it to
152 153
  ** chop of the teaser.  The delimiter can be outside
  ** the allowed range but no more than a factor two.
Dries's avatar
Dries committed
154 155
  */

Dries's avatar
Dries committed
156
  $delimiter = strpos($body, "<!--break-->");
157
  if ($delimiter > 0) {
Dries's avatar
Dries committed
158 159 160
    return substr($body, 0, $delimiter);
  }

161 162 163 164 165 166 167 168
  /*
  ** If we have a short body, return the entire body:
  */

  if (strlen($body) < $size) {
    return $body;
  }

Dries's avatar
Dries committed
169 170 171 172
  /*
  ** In some cases no delimiter has been specified (eg.
  ** when posting using the Blogger API) in which case
  ** we try to split at paragraph boundaries.
173 174
  */

Dries's avatar
Dries committed
175
  if ($length = strpos($body, "<br />", $size)) {
176
    return substr($body, 0, $length);
Dries's avatar
Dries committed
177 178 179
  }

  if ($length = strpos($body, "<br>", $size)) {
180
    return substr($body, 0, $length);
Dries's avatar
Dries committed
181 182 183
  }

  if ($length = strpos($body, "</p>", $size)) {
184 185 186 187 188
    return substr($body, 0, $length);
  }

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

191
  /*
Dries's avatar
Dries committed
192
  ** When even the first paragraph is too long, try to
Dries's avatar
Dries committed
193
  ** split at the end of the next sentence.
194 195
  */

Dries's avatar
Dries committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  if ($length = strpos($body, ". ", $size)) {
    return substr($body, 0, $length + 1);
  }

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

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

  /*
  ** Nevermind, we split it the hard way ...
  */
211

Dries's avatar
Dries committed
212
  return substr($body, 0, $size);
213 214
}

215
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
216
  if (is_array($node)) {
217
    $function = $node["type"] ."_$hook";
218 219
  }
  else if (is_object($node)) {
220
    $function = $node->type ."_$hook";
221 222
  }
  else if (is_string($node)) {
223
    $function = $node ."_$hook";
224 225 226
  }

  if (function_exists($function)) {
227
    return ($function($node, $a2, $a3, $a4));
228 229 230
  }
}

231 232 233 234 235 236 237 238 239 240 241 242 243 244
function node_invoke_nodeapi(&$node, $op, $arg = 0) {
  $return = array();
  foreach (module_list() as $name) {
    $function = $name ."_nodeapi";
    if (function_exists($function)) {
      $result = $function($node, $op, $arg);
      if (isset($result)) {
        $return = array_merge($return, $result);
      }
    }
  }
  return $return;
}

245
function node_load($conditions, $revision = -1) {
246 247 248 249 250 251 252 253 254 255 256 257 258

  /*
  ** Turn the conditions into a query:
  */

  foreach ($conditions as $key => $value) {
    $cond[] = "n.". check_query($key) ." = '". check_query($value) ."'";
  }

  /*
  ** Retrieve the node:
  */

259
  $node = db_fetch_object(db_query("SELECT n.*, u.uid, u.name FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE ". implode(" AND ", $cond)));
260 261 262 263 264 265 266 267 268 269 270 271 272 273

  /*
  ** Unserialize the revisions field:
  */

  if ($node->revisions) {
    $node->revisions = unserialize($node->revisions);
  }

  /*
  ** Call the node specific callback (if any) and piggy-back the
  ** results to the node or overwrite some values:
  */

274
  if ($extra = node_invoke($node, "load")) {
275 276 277 278 279
    foreach ($extra as $key => $value) {
      $node->$key = $value;
    }
  }

280 281 282 283 284 285 286
  /*
  ** Return the desired revision
  */
  if ($revision != -1 && isset($node->revisions[$revision])) {
    $node = $node->revisions[$revision]["node"];
  }

287 288 289
  return $node;
}

290
function node_save($node) {
291

292 293 294
  /*
  ** Fetch fields to save to node table:
  */
295
  $fields = node_invoke_nodeapi($node, "fields");
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

  /*
  ** Serialize the revisions field:
  */

  if ($node->revisions) {
    $node->revisions = serialize($node->revisions);
  }

  /*
  ** Apply filters to some default node fields:
  */

  if (empty($node->nid)) {

    /*
    ** Insert a new node:
    */

Dries's avatar
Dries committed
315
    // Set some required fields:
316 317 318
    if (!$node->created) {
      $node->created = time();
    }
Dries's avatar
Dries committed
319
    $node->changed = time();
320
    $node->nid = db_next_id("node_nid");
321

Dries's avatar
Dries committed
322
    // Prepare the query:
323 324 325
    foreach ($node as $key => $value) {
      if (in_array($key, $fields)) {
        $k[] = check_query($key);
326 327
        $v[] = $value;
        $s[] = "'%s'";
328 329 330
      }
    }

331 332 333 334
    $keysfmt = implode(", ", $s);
    // need to quote the placeholders for the values
    $valsfmt = "'". implode("', '", $s) ."'";

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

Dries's avatar
Dries committed
338
    // Call the node specific callback (if any):
339
    node_invoke($node, "insert");
340
    node_invoke_nodeapi($node, "insert");
341 342 343 344 345 346 347
  }
  else {

    /*
    ** Update an existing node:
    */

Dries's avatar
Dries committed
348
    // Set some required fields:
349 350
    $node->changed = time();

Dries's avatar
Dries committed
351
    // Prepare the query:
352 353
    foreach ($node as $key => $value) {
      if (in_array($key, $fields)) {
354 355
        $q[] = check_query($key) ." = '%s'";
        $v[] = $value;
356 357 358
      }
    }

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

Dries's avatar
Dries committed
362
    // Call the node specific callback (if any):
363
    node_invoke($node, "update");
364
    node_invoke_nodeapi($node, "update");
365 366
  }

Dries's avatar
Dries committed
367
  /*
368 369
  ** Clear the cache so an anonymous poster can see the node being
  ** added or updated.
Dries's avatar
Dries committed
370 371 372 373
  */

  cache_clear_all();

374 375 376 377 378 379 380 381
  /*
  ** Return the node ID:
  */

  return $node->nid;

}

382
function node_view($node, $main = 0) {
383

Dries's avatar
Dries committed
384
  $node = array2object($node);
385

Dries's avatar
Dries committed
386 387
  /*
  ** Remove the delimiter (if any) that seperates the teaser from the
Dries's avatar
Dries committed
388
  ** body. TODO: this strips legitimate uses of '<!--break-->' also.
Dries's avatar
Dries committed
389 390
  */

Dries's avatar
Dries committed
391
  $node->body = str_replace("<!--break-->", "", $node->body);
Dries's avatar
Dries committed
392

393 394 395 396 397 398
  /*
  ** The "view" hook can be implemented to overwrite the default function
  ** to display nodes.
  */

  if (module_hook($node->type, "view")) {
399
    node_invoke($node, "view", $main);
400 401 402 403 404 405 406
  }
  else {

    /*
    ** Default behavior:
    */

407 408
    $node->teaser = check_output($node->teaser);
    $node->body = check_output($node->body);
409

410
    theme("node", $node, $main);
411 412 413
  }
}

414 415
function node_show($node, $cid) {

Dries's avatar
Dries committed
416 417 418 419 420 421 422
  if (node_access("view", $node)) {

    node_view($node);

    if (function_exists("comment_render") && $node->comment) {
      comment_render($node, $cid);
    }
423 424 425 426 427 428

    /*
    ** Update the history table, stating that this user viewed this node.
    */

    node_tag_new($node->nid);
Dries's avatar
Dries committed
429 430 431
  }
}

432 433
function node_access($op, $node = 0) {

434 435
  if (user_access("administer nodes")) {
    return 1;
436 437
  }

Dries's avatar
Dries committed
438 439 440
  /*
  ** Convert the node to an object if necessary:
  */
441

Dries's avatar
Dries committed
442
  $node = array2object($node);
443

Dries's avatar
Dries committed
444 445 446
  /*
  ** Construct a function:
  */
447

Dries's avatar
Dries committed
448 449 450 451 452 453
  if ($node->type) {
    $type = $node->type;
  }
  else {
    $type = $node;
  }
Dries's avatar
Dries committed
454

455 456
  // Can't use node_invoke:
  // the access hook takes the $op parameter before the $node parameter.
457
  return module_invoke($type, "access", $op, $node);
458 459
}

460
function node_perm() {
461
  return array("administer nodes", "access content");
462 463
}

464 465
function node_search($keys) {

Kjartan's avatar
Kjartan committed
466 467 468 469 470 471 472 473 474 475 476 477
  // Return the results of performing a search using the indexed search
  // for this particular type of node.
  //
  // Pass an array to the "do_search" function which dictates what it
  // will search through, and what it will search for
  //
  // "keys"'s value is the keywords entered by the user
  //
  // "type"'s value is used to identify the node type in the search
  // index.
  //
  // "select"'s value is used to relate the data from the specific nodes
Dries's avatar
Dries committed
478
  // table to the data that the search_index table has in it, and the the
Kjartan's avatar
Kjartan committed
479 480
  // do_search functino will rank it.
  //
Dries's avatar
Dries committed
481
  // The select must always provide the following fields - lno, title,
Kjartan's avatar
Kjartan committed
482 483
  // created, uid, name, count
  //
484
  $find = do_search(array("keys" => $keys, "type" => "node", "select" => "select s.lno as lno, n.title as title, n.created as created, u.uid as uid, u.name as name, s.count as count FROM {search_index} s, {node} n INNER JOIN {users} u ON n.uid = u.uid WHERE s.lno = n.nid AND s.type = 'node' AND s.word like '%' AND n.status = 1"));
Dries's avatar
Dries committed
485

486 487 488
  return $find;
}

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

494 495 496
  return $output;
}

Dries's avatar
Dries committed
497
function node_conf_filters() {
498 499
  $output .= form_select(t("Filter HTML tags"), "filter_html", variable_get("filter_html", 0), array(0 => t("Do not filter"), 1 => t("Strip tags"), 2 => t("Escape tags")), t("How to deal with HTML and PHP tags in user-contributed content. If set to \"Strip tags\", dangerous tags are removed.  If set to \"Escape tags\", all HTML is escaped and presented as it was typed."));
  $output .= form_textfield(t("Allowed HTML tags"), "allowed_html", variable_get("allowed_html", "<a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul>"), 64, 255, t("If \"Strip tags\" is selected, optionally specify tags which should not be stripped.  'STYLE' attributes, 'ON*' attributes and unclosed tags are always stripped."));
Dries's avatar
Dries committed
500
  $output .= form_select(t("Rewrite old URLs"), "rewrite_old_urls", variable_get("rewrite_old_urls", 0), array(t("Disabled"), t("Enabled")), t("The introduction of 'clean URLs' in Drupal 4.2.0 breaks internal URLs that date back from Drupal 4.1.0 and before.  If enabled, this filter will attempt to rewrite the old style URLs to avoid broken links.  If <code>mod_rewrite</code> is available on your system, use the rewrite rules in Drupal's <code>.htaccess</code> file instead as these will also correct external referrers."));
Dries's avatar
Dries committed
501
  $output .= "<hr />";
Dries's avatar
Dries committed
502 503 504
  return $output;
}

505 506 507 508
function node_escape_html($text) {
  return htmlspecialchars($text);
}

Dries's avatar
Dries committed
509 510 511 512 513 514
function node_filter_html($text) {
  $text = strip_tags($text, variable_get("allowed_html", ""));
  return $text;
}

function node_filter_link($text) {
515 516
  $pat = '\[{2}([^\|]+)(\|([^\|]+)?)?\]{2}';                   // [link|description]
  return ereg_replace($pat, $dst, $text);
517 518
}

519
function node_comment_mode($nid) {
520 521
  static $comment_mode;
  if (!isset($comment_mode[$nid])) {
522
    $comment_mode[$nid] = db_result(db_query("SELECT comment FROM {node} WHERE nid = %d", $nid));
523 524
  }
  return $comment_mode[$nid];
525 526
}

Dries's avatar
Dries committed
527
function node_filter($text) {
528
  if (variable_get("filter_html", 0) == 1) {
Dries's avatar
Dries committed
529 530 531 532 533 534
    $text = node_filter_html($text);
  }

  if (variable_get("rewrite_old_urls", 0)) {
    $text = rewrite_old_urls($text);
  }
535

536
  if (variable_get("filter_html", 0) == 2) {
537 538 539
    $text = node_escape_html($text);
  }

540
  return trim($text);
Dries's avatar
Dries committed
541 542
}

543
function node_link($type, $node = 0, $main = 0) {
Dries's avatar
Dries committed
544

Dries's avatar
Dries committed
545 546
  $links = array();

Dries's avatar
Dries committed
547
  if ($type == "node") {
Kjartan's avatar
Kjartan committed
548 549 550
    if ($node->links) {
      $links = $node->links;
    }
551

552
    if ($main == 1 && $node->teaser && strlen($node->teaser) != strlen($node->body)) {
553
      $links[] = l(t("read more"), "node/view/$node->nid", array("title" => t("Read the rest of this posting."), "class" => "read-more"));
Dries's avatar
Dries committed
554
    }
555 556

    if (user_access("administer nodes")) {
557
       $links[] = l(t("administer"), "admin/node/edit/$node->nid", array("title" => t("Administer this node.")));
558
    }
Dries's avatar
Dries committed
559 560
  }

561
  if ($type == "system") {
562
    menu("node/add", t("create content"), NULL, 1);
563

564
    if (user_access("administer nodes")) {
565 566 567 568 569
      menu("admin/node", t("content"), "node_admin");
      menu("admin/node/search", t("search"), "node_admin", 8);
      menu("admin/node/help", t("help"), "node_help", 9);
      menu("admin/node/edit", t("edit post"), "node_admin", 0, 1);
      menu("admin/node/settings", t("settings"), "node_admin", 8);
570
    }
571 572
  }

Dries's avatar
Dries committed
573
  return $links;
Dries's avatar
Dries committed
574 575
}

576
function node_admin_edit($node) {
Dries's avatar
Dries committed
577

578
  if (is_numeric($node)) {
579
    $node = node_load(array("nid" => $node));
580
  }
581

582
  $output .= node_form($node);
583

584 585 586 587 588 589 590
  /*
  ** Edit revisions:
  */

  if ($node->revisions) {
    $output .= "<h3>". t("Edit revisions") ."</h3>";
    $output .= "<table border=\"1\" cellpadding=\"2\" cellspacing=\"2\">";
591
    $output .= " <tr><th>". t("older revisions") ."</th><th colspan=\"3\">". t("operations") ."</th></tr>";
592
    foreach ($node->revisions as $key => $revision) {
593
      $output .= " <tr><td>". t("revision #%r revised by %u on %d", array("%r" => $key, "%u" => format_name(user_load(array("uid" => $revision["uid"]))), "%d" => format_date($revision["timestamp"], "small"))) . ($revision["history"] ? "<br /><small>". $revision["history"] ."</small>" : "") ."</td><td>". l(t("view revision"), "node/view/$node->nid", array(), "revision=$key") ."</td><td>". l(t("rollback revision"), "admin/node/rollback+revision/$node->nid/$key") ."</td><td>". l(t("delete revision"), "admin/node/delete+revision/$node->nid/$key") ."</td></tr>";
594 595 596 597
    }
    $output .= "</table>";
  }

598
  /*
599
  ** Display the node form extensions:
600
  */
601
  $output .= implode("\n", module_invoke_all("node_link", $node));
Dries's avatar
Dries committed
602

603
  return $output;
604 605 606

}

607
function node_admin_nodes() {
608 609 610 611 612 613 614 615 616 617
  $filters = array(
    array(t("View posts that are new or updated"), "ORDER BY n.changed DESC"),
    array(t("View posts that need approval"), "WHERE n.status = 0 OR n.moderate = 1 ORDER BY n.changed DESC"),
    array(t("View posts that are promoted"), "WHERE n.status = 1 AND n.promote = 1 ORDER BY n.changed DESC"),
    array(t("View posts that are not promoted"), "WHERE n.status = 1 AND n.promote = 0 ORDER BY n.changed DESC"),
    array(t("View posts that are static"), "WHERE n.status = 1 AND n.static = 1 ORDER BY n.changed DESC"),
    array(t("View posts that are unpublished"), "WHERE n.status = 0 AND n.moderate = 0 ORDER BY n.changed DESC")
   );

  $operations = array(
Dries's avatar
Dries committed
618
    array(t("Approve the selected posts"), "UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d"),
619 620 621
    array(t("Promote the selected posts"), "UPDATE {node} SET status = 1, promote = 1 WHERE nid = %d"),
    array(t("Make the selected posts static"), "UPDATE {node} SET status = 1, static = 1 WHERE nid = %d"),
    array(t("Demote the selected posts"), "UPDATE {node} SET promote = 0 WHERE nid = %d"),
622
    array(t("Unpublish the selected posts"), "UPDATE {node} SET status = 0 WHERE nid = %d")
623
  );
624

625 626 627 628 629 630 631 632
  /*
  ** Handle operations:
  */

  if (empty($_SESSION["node-overview-filter"])) {
    $_SESSION["node-overview-filter"] = 0;
  }

Dries's avatar
Dries committed
633
  if (isset($_POST["edit"]["filter"])) {
634 635 636
    $_SESSION["node-overview-filter"] = $_POST["edit"]["filter"];
  }

Dries's avatar
Dries committed
637
  if (isset($_POST["edit"]["operation"])) {
638 639 640
    $operation = $operations[$_POST["edit"]["operation"]][1];
    foreach ($_POST["edit"]["status"] as $nid => $value) {
      if ($value) {
Dries's avatar
Dries committed
641
        db_query($operation, $nid);
642 643 644 645 646 647 648
      }
    }

    $output = status(t("the update has been performed."));
  }

  $filter = $_SESSION["node-overview-filter"];
Dries's avatar
Dries committed
649

650 651 652 653 654 655 656 657
  /*
  ** Render filter form:
  */

  $options = array();
  foreach ($filters as $key => $value) {
    $options[] = $value[0];
  }
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
  $form  = form_select(NULL, "filter", $filter, $options);
  $form .= form_submit(t("Go"));

  $output .= "<h3>". t("Filter options") ."</h3>";
  $output .= "<div class=\"container-inline\">$form</div>";

  /*
  ** Render operations form:
  */

  $options = array();
  foreach ($operations as $key => $value) {
    $options[] = $value[0];
  }

  $form = form_select(NULL, "operation", 0, $options);
  $form .= form_submit(t("Go"));

  $output .= "<h3>". t("Update options") ."</h3>";
  $output .= "<div class=\"container-inline\">$form</div>";

  /*
  ** Overview table:
  */

684
  $result = pager_query("SELECT n.*, u.name, u.uid FROM {node} n INNER JOIN {users} u ON n.uid = u.uid ". $filters[$filter][1], 50);
685
  $header = array(NULL, t("title"), t("type"), t("author"), t("status"), array("data" => t("operations"), "colspan" => 2));
686

687
  while ($node = db_fetch_object($result)) {
688
    $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, "node/view/$node->nid") ." ". (node_is_new($node->nid, $node->changed) ? theme_mark() : ""), module_invoke($node->type, "node", "name"), format_name($node), ($node->status ? t("published") : t("not published")), l(t("edit node"), "admin/node/edit/$node->nid"), l(t("delete node"), "admin/node/delete/$node->nid"));
689
  }
690

Dries's avatar
Dries committed
691
  if ($pager = pager_display(NULL, 50, 0, "admin")) {
692
    $rows[] = array(array("data" => $pager, "colspan" => 7));
Dries's avatar
Dries committed
693
  }
694

695 696 697
  $output .= "<h3>". $filters[$filter][0] ."</h3>";
  $output .= table($header, $rows);
  return form($output);
Dries's avatar
Dries committed
698 699
}

700
function node_admin_settings($edit) {
701
  $op = $_POST["op"];
Kjartan's avatar
Kjartan committed
702

703 704 705 706 707 708 709 710
  if ($op == t("Save configuration")) {
    /*
    ** Save the configuration options:
    */

    foreach ($edit as $name => $value) {
      variable_set($name, $value);
    }
711
    $output = status(t("the content settings have been saved."));
712 713 714 715 716 717 718 719 720 721
  }

  if ($op == t("Reset to defaults")) {
    /*
    ** Reset the configuration options to their default value:
    */

    foreach ($edit as $name => $value) {
      variable_del($name);
    }
722
    $output = status(t("the content settings have been reset to their default values."));
723 724
  }

725
  $header = array_merge(array(t("type")), array_keys(node_invoke_nodeapi($node, "settings")));
726 727 728 729
  foreach (module_list() as $name) {
    if (module_hook($name, "node")) {
      $node->type = $name;
      $cols = array();
730
      foreach (node_invoke_nodeapi($node, "settings") as $setting) {
731 732 733 734 735
        $cols[] = array("data" => $setting, "align" => "center", "width" => 55);
      }
      $rows[] = array_merge(array(module_invoke($name, "node", "name")), $cols);
    }
  }
Kjartan's avatar
Kjartan committed
736

737
  $output .= table($header, $rows);
738

739
  /* This is an idea for the future.
740 741 742
  foreach (module_list() as $name) {
    if (module_hook($name, "node")) {
      $node->type = $name;
Kjartan's avatar
Kjartan committed
743

744
      // Create table() data:
745
      $header = array_keys(node_invoke_nodeapi($node, "settings"));
746
      $cols = array();
747
      foreach (node_invoke_nodeapi($node, "settings") as $setting) {
748 749 750 751 752 753
        $cols[] = array("data" => $setting, "align" => "center", "width" => 75);
      }

      $output .= "<h2>". module_invoke($name, "node", "name") ."</h2>";
      $output .= table($header, array($cols));
      $output .= "<br /><br />";
754 755
    }
  }
756
  */
757 758 759

  $output .= form_submit(t("Save configuration"));
  $output .= form_submit(t("Reset to defaults"));
Kjartan's avatar
Kjartan committed
760

761
  return form($output);
762 763 764

}

Dries's avatar
Dries committed
765 766 767 768 769 770 771 772 773 774 775 776
/*
** Return the revision with the specified revision number.
*/

function node_revision_load($node, $revision) {
  return $node->revisions[$revision]["node"];
}

/*
** Create and return a new revision of the given node.
*/

777 778 779
function node_revision_create($node) {
  global $user;

780 781 782 783 784
  /*
  ** 'revision' is the name of the field used to indicicate that we
  ** have to create a new revision of a node.
  */

785
  if ($node->nid && $node->revision) {
Dries's avatar
Dries committed
786 787 788 789
    $prev = node_load(array("nid" => $node->nid));
    $node->revisions = $prev->revisions;
    unset($prev->revisions);
    $node->revisions[] = array("uid" => $user->uid, "timestamp" => time(), "node" => $prev, "history" => $node->history);
790 791 792 793 794
  }

  return $node;
}

Dries's avatar
Dries committed
795 796 797
/*
** Roll-back to the revision with the specified revision number.
*/
798

Dries's avatar
Dries committed
799 800
function node_revision_rollback($node, $revision) {
  global $user;
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835

  /*
  ** Extract the specified revision:
  */

  $rev = $node->revisions[$revision]["node"];

  /*
  ** Inherit all the past revisions:
  */

  $rev->revisions = $node->revisions;

  /*
  ** Save the original/current node:
  */

  $rev->revisions[] = array("uid" => $user->uid, "timestamp" => time(), "node" => $node);

  /*
  ** Remove the specified revision:
  */

  unset($rev->revisions[$revision]);

  /*
  ** Save the node:
  */

  foreach ($node as $key => $value) {
    $filter[] = $key;
  }

  node_save($rev, $filter);

Dries's avatar
Dries committed
836
  watchdog("special", "$node->type: rollbacked to revision #$revision of '$node->title'");
837 838
}

Dries's avatar
Dries committed
839 840 841 842 843
/*
** Delete the revision with specified revision number.
*/

function node_revision_delete($node, $revision) {
844 845

  unset($node->revisions[$revision]);
846

847
  node_save($node, array("nid", "revisions"));
848

Dries's avatar
Dries committed
849
  watchdog("special", "$node->type: removed revision #$revision of '$node->title'");
850 851
}

Dries's avatar
Dries committed
852 853 854 855 856 857 858 859 860 861 862
/*
** Return a list of all the existing revision numbers.
*/

function node_revision_list($node) {
  if (is_array($node->revisions)) {
    return array_keys($node->revisions);
  }
  else {
    return array();
  }
863 864
}

Dries's avatar
Dries committed
865
function node_admin() {
866 867
  $op = $_POST["op"];
  $edit = $_POST["edit"];
868

Dries's avatar
Dries committed
869
  if (user_access("administer nodes")) {
870

Dries's avatar
Dries committed
871 872 873 874
    if (empty($op)) {
      $op = arg(2);
    }

875 876 877
    /*
    ** Compile a list of the administrative links:
    */
878 879
    switch ($op) {
      case "search":
880
        $output = search_type("node", url("admin/node/search"), $_POST["keys"]);
881 882
        break;
      case "edit":
883
        $output = node_admin_edit(arg(3));
884
        break;
Dries's avatar
Dries committed
885
      case "delete":
886
        $output = node_delete(array("nid" => arg(3)));
Dries's avatar
Dries committed
887
        break;
888
      case "rollback revision":
889 890
        $output = node_revision_rollback(node_load(array("nid" => arg(3))), arg(4));
        $output .= node_admin_edit(arg(3));
891
        break;
892
      case "delete revision":
893 894
        $output = node_revision_delete(node_load(array("nid" => arg(3))), arg(4));
        $output .= node_admin_edit(arg(3));
895
        break;
Dries's avatar
Dries committed
896
      case t("Preview"):
897
        $edit = node_validate($edit, $error);
898
        $output = node_preview($edit, $error);
899
        break;
Dries's avatar
Dries committed
900
      case t("Submit"):
901
        $output = node_submit($edit);
902 903
        break;
      case t("Delete"):
904
        $output = node_delete($edit);
905
        break;
906 907 908
      case t("Save configuration"):
      case t("Reset to defaults"):
      case "settings":
909
        $output = node_admin_settings($edit);
910
        break;
911
      default:
912
        $output = node_admin_nodes();
913
    }
914
    return $output;
915 916
  }
  else {
917
    return message_access();
Dries's avatar
Dries committed
918 919 920
  }
}

Dries's avatar
Dries committed
921
function node_block($op = "list", $delta = 0) {
922

Dries's avatar
Dries committed
923 924 925 926 927 928
  if ($op == "list") {
    $blocks[0]["info"] = t("Syndicate");
    return $blocks;
  }
  else {
    $block["subject"] = t("Syndicate");
929
    $block["content"] = "<div style=\"text-align: center;\">". l("<img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" alt=\"XML\" title=\"XML\" />", "node/feed", array("title" => t("Read the XML version of this page."))) ."</div>";
930

Dries's avatar
Dries committed
931 932
    return $block;
  }
933 934
}

935
function node_feed($nodes = 0, $channel = array()) {
936
  global $base_url, $languages;
937

938
  /*
Dries's avatar
Dries committed
939 940 941 942 943
  ** A generic function for generating RSS feeds from a set of nodes.
  **   - $nodes should be an object as returned by db_query() which contains
  **     the nid field.
  **   - $channel is an associative array containing title, link, and
  **     description keys.
944
  */
945

946
  if (!$nodes) {
947
    $nodes = db_query_range("SELECT nid FROM {node} WHERE promote = '1' AND status = '1' ORDER BY created DESC", 0, 15);
948
  }
949

950
  while ($node = db_fetch_object($nodes)) {
951 952 953
    /*
    ** Load the specified node:
    */
Dries's avatar
Dries committed
954

955
    $item = node_load(array("nid" => $node->nid));
956
    $link = url("node/view/$node->nid");
957
    $items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body), array("pubDate" => date("r", $item->changed)));
958 959
  }

960
  $output .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
Kjartan's avatar
Kjartan committed
961
  $output .= "<!DOCTYPE rss [<!ENTITY % HTMLlat1 PUBLIC \"-//W3C//ENTITIES Latin 1 for XHTML//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent\">]>\n";
962
  if (!$channel["version"]) $channel["version"] = "0.92";
963
  if (!$channel["title"]) $channel["title"] = variable_get("site_name", "drupal") ." - ". variable_get("site_slogan", "");
964
  if (!$channel["link"]) $channel["link"] = $base_url;
965
  if (!$channel["description"]) $channel["description"] = variable_get("site_mission", "");
966 967
  foreach ($languages as $key => $value) break;
  if (!$channel["language"]) $channel["language"] = $key ? $key : "en";
968
  $output .= "<rss version=\"". $channel["version"] . "\">\n";
969
  $output .= format_rss_channel($channel["title"], $channel["link"], $channel["description"], $items, $channel["language"]);
970 971
  $output .= "</rss>\n";

972
  header("Content-Type: text/xml");
973 974 975
  print $output;
}

976
function node_validate($node, &$error) {
977
  global $user;
978
  $error = array();
979 980 981 982 983

  /*
  ** Convert the node to an object if necessary:
  */

Dries's avatar
Dries committed
984
  $node = array2object($node);
985 986 987 988 989

  /*
  ** Validate the title field:
  */

990 991 992 993 994
  if (isset($node->title)) {
    $node->title = strip_tags($node->title);
    if (!$node->title) {
      $error["title"] = theme("theme_error", t("You have to specify a valid title."));
    }
995 996
  }

997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
  /*
  ** Common default values:
  */

  $node->teaser = node_teaser($node->body);

  /*
  ** Create a new revision when required:
  */

  $node = node_revision_create($node);

1009 1010 1011 1012 1013 1014
  if (user_access("administer nodes")) {

    /*
    ** Setup default values if required:
    */

1015 1016
    if (!$node->created) {
      $node->created = time();
1017 1018
    }

1019 1020
    if (!$node->date) {
      $node->date = date("M j, Y g:i a", $node->created);
1021
    }
Kjartan's avatar
Kjartan committed
1022

1023 1024 1025
    if (!is_numeric($node->status)) {
      $node->status = 1;
    }
1026 1027 1028 1029 1030

    /*
    ** Validate the "authored by"-field:
    */

1031
    if (empty($node->name) || empty($node->uid)){
1032 1033 1034 1035 1036 1037 1038 1039
      /*
      ** The use of empty() is mandatory in the context of usernames
      ** as the empty string denotes the anonymous user.  In case we
      ** are dealing with an anomymous user we set the user ID to 0.
      */
      $node->uid = 0;
    }
    else if ($account = user_load(array("name" => $node->name))) {
1040
      $node->uid = $account->uid;
1041 1042
    }
    else {
1043
      $error["name"] = theme("theme_error", t("The name '%u' does not exist.", array ("%u" => $node->name)));
1044 1045 1046 1047 1048 1049
    }

    /*
    ** Validate the "authored on"-field:
    */

1050 1051
    if (strtotime($node->date) > 1000) {
      $node->created = strtotime($node->date);
1052 1053
    }
    else {
1054
      $error["date"] = theme("theme_error", t("You have to specifiy a valid date."));
1055 1056
    }
  }
1057 1058 1059
  else {
    // Validate for normal users:
    $node->uid = $user->uid ? $user->uid : 0;
Kjartan's avatar
Kjartan committed
1060
    // Force defaults in case people modify the form:
Kjartan's avatar
Kjartan committed
1061 1062 1063 1064 1065
    $node->status = variable_get("node_status_$node->type", 1);
    $node->promote = variable_get("node_promote_$node->type", 1);
    $node->moderate = variable_get("node_moderate_$node->type", 0);
    $node->static = variable_get("node_static_$node->type", 0);
    $node->revision = variable_get("node_revision_$node->type", 0);
Kjartan's avatar
Kjartan committed
1066 1067 1068
    unset($node->created);
  }

1069 1070 1071 1072
  /*
  ** Do node type specific validation checks.
  */

1073
  $result = node_invoke($node, "validate");
1074
  $error = $error + (is_array($result) ? $result : array()) + node_invoke_nodeapi($node, "validate");
1075

1076
  return $node;
1077 1078
}

1079

1080
function node_form($edit, $error = NULL) {
1081

Dries's avatar
Dries committed
1082 1083 1084 1085 1086 1087 1088
  /*
  ** Save the referer.  We record where the user came from such that we
  ** can redirect him after having completed the node forms.
  */

  referer_save();

1089 1090 1091 1092
  /*
  ** Validate the node:
  */

1093
  if (!$error) {
Kjartan's avatar