drupal/modules/node/node.module

1589 lines
47 KiB
Plaintext

<?php
// $Id$
define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
function node_help($section = 'admin/help#node') {
global $mod;
$output = '';
switch ($section) {
case 'admin/help#node':
$output .= t("
<h3>Nodes</h3>
<p>The core of the Drupal system is the node. All of the contents of the system are placed in nodes, or extensions of nodes.
A base node contains:<dl>
<dt>A Title</dt><dd>Up to 128 characters of text that titles the node.</dd>
<dt>A Teaser</dt><dd>A small block of text that is meant to get you interested in the rest of node. Drupal will automatically pull a small amount of the body of the node to make the teaser (To configure how long the teaser will be <a href=\"%teaser\">click here</a>). The teaser can be changed if you don't like what Drupal grabs.</dd>
<dt>The Body</dt><dd>The main text that comprises your content.</dd>
<dt>A Type</dt><dd>What kind of node is this? Blog, book, forum, comment, unextended, etc.</dd>
<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>
<dt>Authored on</dt><dd>The date the node was written.</dd>
<dt>Changed</dt><dd>The last time this node was changed.</dd>
<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 <a href=\"%teaser\">click here</a>), but if you think a node is important enough that you want it to stay on the front page enable this.</dd>
<dt>Allow user comments</dt><dd>A node can have comments. These comments can be written by other users (Read-write), or only by admins (Read-only).</dd>
<dt>Attributes</dt><dd>A way to sort nodes.</dd>
<dt>Revisions</dt><dd>Drupal has a revision system so that you can \"roll back\" to an older version of a post if the new version is not what you want.</dd>
<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>
<dt>In moderation queue</dt><dd>Drupal has a moderation system. If it is active, a node is in one of three states: approved and published, approved and unpublished, and awaiting approval. If you are moderating a node it should be in the moderation queue.</dd>
<dt>Votes</dt><dd>If you are moderating a node this counts how many votes the node has gotten. Once a node gets a certain number of vote it will either be approved or dropped.
<dt>Score</dt><dd>The score of the node is gotten by the votes it is given.</dd>
<dt>Users</dt><dd>The list of users who have voted on a moderated node.</dd>
<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>
<p>Now that you know what is in a node, here are some of the types of nodes available.</p>", array("%teaser" => url("admin/system/modules/node")));
if ($mod == 'admin') {
foreach (node_list() as $type) {
$output .= '<h3>'. t('Node type: %module', array('%module' => node_invoke($type, 'node_name'))). '</h3>';
$output .= implode("\n", module_invoke_all('help', 'node/add#'. $type));
}
}
break;
case 'admin/system/modules#description':
$output = t('The core that allows content to be submitted to the site.');
break;
case 'admin/system/modules/node':
$output = t('Settings for the core of Drupal. Almost everything is a node so these settings will affect most of the site.');
break;
case 'admin/node':
$output = t("Below is a list of all of the nodes in your site. Other forms of content are listed elsewhere (e.g. <a href=\"%comments\">comments</a>).<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' => url('admin/comment')));
break;
case 'admin/node/search':
$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\".");
break;
case 'admin/node/settings':
$output = t('This page lets you set the defaults used during creation of nodes for all the different node types.<br /><strong>comment:</strong> Read/write setting for comments.<br /><strong>publish:</strong> Is this node publicly viewable, has it been published?<br /><strong>promote:</strong> Is this node to be promoted to the front page?<br /><strong>moderate:</strong> Does this node need approval before it can be viewed?<br /><strong>static:</strong> Is this node always visible on the front page?<br /><strong>revision:</strong> Will this node go into the revision system allowing multiple versions to be saved?');
break;
}
return $output;
}
function node_cron() {
db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
}
function node_help_page() {
print theme('page', node_help());
}
/*
** Accepts a DB result object which can be used to fetch node objects.
** Returns an HTML list suitable as content for a block.
*/
function node_title_list($result, $title = NULL) {
while ($node = db_fetch_object($result)) {
$number = module_invoke('comment', 'num_all', $node->nid);
$items[] = l($node->title, "node/view/$node->nid", array('title' => format_plural($number, '%count comment', '%count comments')));
}
return theme('node_list', $items, $title);
}
function theme_node_list($items, $title = NULL) {
return theme('item_list', $items, $title);
}
// Update the 'last viewed' timestamp of the specified node for current user.
function node_tag_new($nid) {
global $user;
if ($user->uid) {
$result = db_query('SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d', $user->uid, $nid);
if (db_fetch_object($result)) {
db_query('UPDATE {history} SET timestamp = %d WHERE uid = %d AND nid = %d', time(), $user->uid, $nid);
}
else {
db_query('INSERT INTO {history} (uid, nid, timestamp) VALUES (%d, %d, %d)', $user->uid, $nid, time());
}
}
}
/*
** Retrieves the timestamp at which the current user last viewed the
** specified node.
*/
function node_last_viewed($nid) {
global $user;
$history = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = '$user->uid' AND nid = %d", $nid));
return ($history->timestamp ? $history->timestamp : NODE_NEW_LIMIT);
}
/**
* Determines whether the supplied timestamp is newer than the user's last view
* of a given node
*
* @param $nid node-id whose history supplies the 'last viewed' timestamp
* @param $timestamp time which is compared against node's 'last viewed'
* timestamp
*/
function node_is_new($nid, $timestamp) {
global $user;
static $cache;
if (!isset($cache[$nid])) {
if ($user->uid) {
$history = db_fetch_object(db_query('SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d', $user->uid, $nid));
$cache[$nid] = $history->timestamp ? $history->timestamp : 0;
}
else {
$cache[$nid] = time();
}
}
return ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT);
}
function node_teaser($body) {
$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;
}
/*
** If a valid delimiter has been specified, use it to
** chop of the teaser. The delimiter can be outside
** the allowed range but no more than a factor two.
*/
$delimiter = strpos($body, '<!--break-->');
if ($delimiter > 0) {
return substr($body, 0, $delimiter);
}
/*
** If we have a short body, return the entire body:
*/
if (strlen($body) < $size) {
return $body;
}
/*
** 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.
*/
if ($length = strpos($body, '</p>', $size)) {
return substr($body, 0, $length + 4);
}
if ($length = strpos($body, '<br />', $size)) {
return substr($body, 0, $length);
}
if ($length = strpos($body, '<br>', $size)) {
return substr($body, 0, $length);
}
if ($length = strpos($body, "\n", $size)) {
return substr($body, 0, $length);
}
/*
** When even the first paragraph is too long, try to
** split at the end of the next sentence.
*/
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 ...
*/
return substr($body, 0, $size);
}
/*
* Determines the module that defines the node type of the given node.
*
* @param &$node
* Either a node object, node array, or a string containing the node type.
* @return
* A string containing the name of the defining module.
*/
function node_get_module_name($node) {
if (is_array($node)) {
if ($pos = strpos($node['type'], '/')) {
return substr($node['type'], 0, $pos);
} else {
return $node['type'];
}
}
else if (is_object($node)) {
if ($pos = strpos($node->type, '/')) {
return substr($node->type, 0, $pos);
} else {
return $node->type;
}
}
else if (is_string($node)) {
if ($pos = strpos($node, '/')) {
return substr($node, 0, $pos);
} else {
return $node;
}
}
}
/*
* Get a list of all the defined node types.
*
* @return
* An list of all node types.
*/
function node_list() {
$types = array();
foreach (module_list() as $module) {
if (module_hook($module, 'node_name')) {
$module_types = module_invoke($module, 'node_types');
if ($module_types) {
foreach ($module_types as $type) {
$types[] = $type;
}
} else {
$types[] = $module;
}
}
}
return $types;
}
/*
* 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) {
$function = node_get_module_name($node) ."_$hook";
return function_exists($function);
}
/*
* 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
* The returned value of the invoked hook is returned.
*/
function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
$function = node_get_module_name($node) ."_$hook";
if (function_exists($function)) {
return ($function($node, $a2, $a3, $a4));
}
}
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;
}
function node_load($conditions, $revision = -1) {
/*
** Turn the conditions into a query:
*/
foreach ($conditions as $key => $value) {
$cond[] = 'n.'. check_query($key) ." = '". check_query($value) ."'";
}
/*
** Retrieve the node:
*/
$node = db_fetch_object(db_query('SELECT n.*, u.uid, u.name, u.data FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE '. implode(' AND ', $cond)));
$node = drupal_unpack($node);
/*
** Unserialize the revisions and user data fields:
*/
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:
*/
if ($extra = node_invoke($node, 'load')) {
foreach ($extra as $key => $value) {
$node->$key = $value;
}
}
/*
** Return the desired revision
*/
if ($revision != -1 && isset($node->revisions[$revision])) {
$node = $node->revisions[$revision]['node'];
}
return $node;
}
function node_save($node) {
/*
** Fetch fields to save to node table:
*/
$fields = node_invoke_nodeapi($node, 'fields');
/*
** 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:
*/
// Set some required fields:
if (!$node->created) {
$node->created = time();
}
$node->changed = time();
$node->nid = db_next_id('{node}_nid');
// Prepare the query:
foreach ($node as $key => $value) {
if (in_array($key, $fields)) {
$k[] = check_query($key);
$v[] = $value;
$s[] = "'%s'";
}
}
$keysfmt = implode(', ', $s);
// need to quote the placeholders for the values
$valsfmt = "'". implode("', '", $s) ."'";
// Insert the node into the database:
db_query("INSERT INTO {node} (". implode(", ", $k) .") VALUES(". implode(", ", $s) .")", $v);
// Call the node specific callback (if any):
node_invoke($node, 'insert');
node_invoke_nodeapi($node, 'insert');
}
else {
/*
** Update an existing node:
*/
// Set some required fields:
$node->changed = time();
// Prepare the query:
foreach ($node as $key => $value) {
if (in_array($key, $fields)) {
$q[] = check_query($key) ." = '%s'";
$v[] = $value;
}
}
// Update the node in the database:
db_query("UPDATE {node} SET ". implode(', ', $q) ." WHERE nid = '$node->nid'", $v);
// Call the node specific callback (if any):
node_invoke($node, 'update');
node_invoke_nodeapi($node, 'update');
}
/*
** Clear the cache so an anonymous poster can see the node being
** added or updated.
*/
cache_clear_all();
/*
** Return the node ID:
*/
return $node->nid;
}
function node_view($node, $main = 0, $page = 0) {
$node = array2object($node);
/*
** Remove the delimiter (if any) that seperates the teaser from the
** body. TODO: this strips legitimate uses of '<!--break-->' also.
*/
$node->body = str_replace('<!--break-->', '', $node->body);
/*
** The 'view' hook can be implemented to overwrite the default function
** to display nodes.
*/
if (node_hook($node, 'view')) {
return node_invoke($node, 'view', $main, $page);
}
else {
/*
** Default behavior:
*/
return theme('node', node_prepare($node, $main), $main, $page);
}
}
function node_prepare($node, $main = 0) {
if ($main == 0) {
$node->body = check_output($node->body);
}
else {
$node->teaser = check_output($node->teaser);
}
return $node;
}
function node_show($node, $cid) {
if (node_access('view', $node)) {
$output = node_view($node, 0, 1);
if (function_exists('comment_render') && $node->comment) {
$output .= comment_render($node, $cid);
}
/*
** Update the history table, stating that this user viewed this node.
*/
node_tag_new($node->nid);
return $output;
}
else {
drupal_set_message(message_access());
}
}
function node_access($op, $node = 0) {
if (user_access('administer nodes')) {
return 1;
}
/*
** Convert the node to an object if necessary:
*/
$node = array2object($node);
// Can't use node_invoke:
// the access hook takes the $op parameter before the $node parameter.
return module_invoke(node_get_module_name($node), 'access', $op, $node);
}
function node_perm() {
return array('administer nodes', 'access content');
}
function node_search($keys) {
// 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
// table to the data that the search_index table has in it, and the the
// do_search functino will rank it.
//
// The select must always provide the following fields - lno, title,
// created, uid, name, count
//
$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"));
return array(t('Matching nodes ranked in order of relevance'), $find);
}
function node_settings() {
$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.'));
$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'."));
$output .= form_radios(t('Preview post'), 'node_preview', variable_get('node_preview', 0), array(t('Optional'), t('Required')), t('Must users preview posts before submitting?'));
return $output;
}
function node_comment_mode($nid) {
static $comment_mode;
if (!isset($comment_mode[$nid])) {
$comment_mode[$nid] = db_result(db_query('SELECT comment FROM {node} WHERE nid = %d', $nid));
}
return $comment_mode[$nid];
}
function node_link($type, $node = 0, $main = 0) {
$links = array();
if ($type == 'node') {
if ($node->links) {
$links = $node->links;
}
if ($main == 1 && $node->teaser && strlen($node->teaser) != strlen($node->body)) {
$links[] = l(t('read more'), "node/view/$node->nid", array('title' => t('Read the rest of this posting.'), 'class' => 'read-more'));
}
if (user_access('administer nodes')) {
$links[] = l(t('administer'), "admin/node/edit/$node->nid", array('title' => t('Administer this node.')));
}
if (user_access('administer nodes') && $node->revisions) {
$links[] = l(t('revisions'), "node/revisions/$node->nid", array('title' => t('Administer revisions.')));
}
}
if ($type == 'system') {
menu('node/add', t('create content'), 'node_page', 1, MENU_HIDE_NOCHILD);
if (user_access('administer nodes')) {
menu('admin/node', t('content'), 'node_admin');
menu('admin/node/search', t('search'), 'node_admin', 8);
menu('admin/node/help', t('help'), 'node_help_page', 9);
menu('admin/node/edit', t('edit post'), 'node_admin', 0, MENU_HIDE);
menu('admin/node/settings', t('settings'), 'node_admin', 8);
}
if (user_access('access content')) {
menu('node', t('content'), 'node_page', 0, MENU_HIDE);
}
}
return $links;
}
function node_admin_edit($node) {
if (is_numeric($node)) {
$node = node_load(array('nid' => $node));
}
$output .= node_form($node);
/*
** Display the node form extensions:
*/
$output .= implode("\n", module_invoke_all('node_link', $node));
return $output;
}
function node_admin_nodes() {
$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(
array(t('Approve the selected posts'), 'UPDATE {node} SET status = 1, moderate = 0 WHERE nid = %d'),
array(t('Promote the selected posts'), 'UPDATE {node} SET status = 1, promote = 1 WHERE nid = %d'),
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'),
array(t('Unpublish the selected posts'), 'UPDATE {node} SET status = 0 WHERE nid = %d')
);
/*
** Handle operations:
*/
if (empty($_SESSION['node_overview_filter'])) {
$_SESSION['node_overview_filter'] = 0;
}
if (isset($_POST['edit']['filter'])) {
$_SESSION['node_overview_filter'] = $_POST['edit']['filter'];
}
if (isset($_POST['edit']['operation'])) {
$operation = $operations[$_POST['edit']['operation']][1];
foreach ($_POST['edit']['status'] as $nid => $value) {
if ($value) {
db_query($operation, $nid);
}
}
drupal_set_message(t('the update has been performed.'));
}
$filter = $_SESSION['node_overview_filter'];
/*
** Render filter form:
*/
$options = array();
foreach ($filters as $key => $value) {
$options[] = $value[0];
}
$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:
*/
$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);
$header = array(NULL, t('title'), t('type'), t('author'), t('status'), array('data' => t('operations'), 'colspan' => 2));
while ($node = db_fetch_object($result)) {
$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() : ''), node_invoke($node, '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"));
}
if ($pager = theme('pager', NULL, 50, 0)) {
$rows[] = array(array('data' => $pager, 'colspan' => 7));
}
$output .= '<h3>'. $filters[$filter][0] .'</h3>';
$output .= theme('table', $header, $rows);
return form($output);
}
function node_admin_settings($edit) {
$op = $_POST['op'];
if ($op == t('Save configuration')) {
/*
** Save the configuration options:
*/
foreach ($edit as $name => $value) {
variable_set($name, $value);
}
drupal_set_message(t('the content settings have been saved.'));
}
if ($op == t('Reset to defaults')) {
/*
** Reset the configuration options to their default value:
*/
foreach ($edit as $name => $value) {
variable_del($name);
}
drupal_set_message(t('the content settings have been reset to their default values.'));
}
$header = array_merge(array(t('type')), array_keys(node_invoke_nodeapi($node, 'settings')));
foreach (node_list() as $type) {
$node->type = $type;
$cols = array();
foreach (node_invoke_nodeapi($node, 'settings') as $setting) {
$cols[] = array('data' => $setting, 'align' => 'center', 'width' => 55);
}
$rows[] = array_merge(array(node_invoke($node, 'node_name')), $cols);
}
$output .= theme('table', $header, $rows);
/* This is an idea for the future.
foreach (node_list() as $type) {
$node->type = $type;
// Create theme('table', ) data:
$header = array_keys(node_invoke_nodeapi($node, 'settings'));
$cols = array();
foreach (node_invoke_nodeapi($node, 'settings') as $setting) {
$cols[] = array('data' => $setting, 'align' => 'center', 'width' => 75);
}
$output .= '<h2>'. node_invoke($node, 'node_name') .'</h2>';
$output .= theme('table', $header, array($cols));
$output .= '<br /><br />';
}
}
*/
$output .= form_submit(t('Save configuration'));
$output .= form_submit(t('Reset to defaults'));
return form($output);
}
function node_revision_overview($nid) {
if (user_access('administer nodes')) {
$node = node_load(array('nid' => $nid));
if ($node->revisions) {
$header = array(t('older revisions'), array('colspan' => '3', 'data' => t('operations')));
foreach ($node->revisions as $key => $revision) {
$rows[] = array(t('revision #%r revised by %u on %d', array('%r' => $key, '%u' => format_name(user_load(array('uid' => $revision['uid']))), '%d' => format_date($revision['timestamp'], 'small'))) . ($revision['history'] ? '<br /><small>'. $revision['history'] .'</small>' : ''), l(t('view'), "node/view/$node->nid", array(), "revision=$key"), l(t('rollback'), "node/rollback-revision/$node->nid/$key"), l(t('delete'), "node/delete-revision/$node->nid/$key"));
}
$output .= theme('table', $header, $rows);
}
}
return $output;
}
/*
** 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.
*/
function node_revision_create($node) {
global $user;
/*
** 'revision' is the name of the field used to indicicate that we
** have to create a new revision of a node.
*/
if ($node->nid && $node->revision) {
$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);
}
return $node;
}
/*
** Roll-back to the revision with the specified revision number.
*/
function node_revision_rollback($nid, $revision) {
global $user;
if (user_access('administer nodes')) {
$node = node_load(array('nid' => $nid));
/*
** 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);
drupal_set_message(t("rollbacked to revision #%revision of '%title'", array('%revision' => $revision, '%title' => $node->title)));
}
}
/*
** Delete the revision with specified revision number.
*/
function node_revision_delete($nid, $revision) {
if (user_access('administer nodes')) {
$node = node_load(array('nid' => $nid));
unset($node->revisions[$revision]);
node_save($node, array('nid', 'revisions'));
drupal_set_message(t("deleted revision #%revision of '%title'", array('%revision' => $revision, '%title' => $node->title)));
}
}
/*
** 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();
}
}
function node_admin() {
$op = $_POST['op'];
$edit = $_POST['edit'];
if (user_access('administer nodes')) {
if (empty($op)) {
$op = arg(2);
}
/*
** Compile a list of the administrative links:
*/
switch ($op) {
case 'search':
$output = search_type('node', url('admin/node/search'), $_POST['keys']);
break;
case 'edit':
$output = node_admin_edit(arg(3));
break;
case 'delete':
$output = node_delete(array('nid' => arg(3)));
break;
case t('Preview'):
$edit = node_validate($edit, $error);
$output = node_preview($edit, $error);
break;
case t('Submit'):
$output = node_submit($edit);
break;
case t('Delete'):
$output = node_delete($edit);
break;
case t('Save configuration'):
case t('Reset to defaults'):
case 'settings':
$output = node_admin_settings($edit);
break;
default:
$output = node_admin_nodes();
}
print theme('page', $output);
}
else {
print theme('page', message_access());
}
}
function node_block($op = 'list', $delta = 0) {
if ($op == 'list') {
$blocks[0]['info'] = t('Syndicate');
return $blocks;
}
else {
$block['subject'] = t('Syndicate');
$block['content'] = theme('xml_icon', url('node/feed'));
return $block;
}
}
function node_feed($nodes = 0, $channel = array()) {
global $base_url, $languages;
/*
** 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.
*/
if (!$nodes) {
$nodes = db_query_range('SELECT nid FROM {node} WHERE promote = 1 AND status = 1 ORDER BY created DESC', 0, 15);
}
while ($node = db_fetch_object($nodes)) {
/*
** Load the specified node:
*/
$item = node_load(array('nid' => $node->nid));
$link = url("node/view/$node->nid");
$items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body), array('pubDate' => date('r', $item->changed)));
}
$output .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
$output .= "<!DOCTYPE rss [<!ENTITY % HTMLlat1 PUBLIC \"-//W3C//ENTITIES Latin 1 for XHTML//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent\">]>\n";
if (!$channel['version']) $channel['version'] = '0.92';
if (!$channel['title']) $channel['title'] = variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', '');
if (!$channel['link']) $channel['link'] = $base_url;
if (!$channel['description']) $channel['description'] = variable_get('site_mission', '');
foreach ($languages as $key => $value) break;
if (!$channel['language']) $channel['language'] = $key ? $key : 'en';
$output .= "<rss version=\"". $channel["version"] . "\">\n";
$output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
$output .= "</rss>\n";
drupal_set_header('Content-Type: text/xml; charset=utf-8');
print $output;
}
function node_validate($node, &$error) {
global $user;
$error = array();
/*
** Convert the node to an object if necessary:
*/
$node = array2object($node);
/*
** Validate the title field:
*/
if (isset($node->title)) {
$node->title = strip_tags($node->title);
if (!$node->title) {
$error['title'] = theme('error', t('You have to specify a valid title.'));
}
}
/*
** Common default values:
*/
$node->teaser = node_teaser($node->body);
/*
** Create a new revision when required:
*/
$node = node_revision_create($node);
if (user_access('administer nodes')) {
/*
** Setup default values if required:
*/
if (!$node->created) {
$node->created = time();
}
if (!$node->date) {
$node->date = date('M j, Y g:i a', $node->created);
}
if (!is_numeric($node->status)) {
$node->status = 1;
}
/*
** Validate the 'authored by'-field:
*/
if (empty($node->name) || empty($node->uid)){
/*
** 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))) {
$node->uid = $account->uid;
}
else {
$error['name'] = theme('error', t("The name '%u' does not exist.", array ('%u' => $node->name)));
}
/*
** Validate the 'authored on'-field:
*/
if (strtotime($node->date) > 1000) {
$node->created = strtotime($node->date);
}
else {
$error['date'] = theme('error', t('You have to specifiy a valid date.'));
}
}
else {
// Validate for normal users:
$node->uid = $user->uid ? $user->uid : 0;
// Force defaults in case people modify the form:
$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);
unset($node->created);
}
/*
** Do node type specific validation checks.
*/
$result = node_invoke($node, 'validate');
$error = $error + (is_array($result) ? $result : array()) + node_invoke_nodeapi($node, 'validate');
return $node;
}
function node_form($edit, $error = NULL) {
/*
** Validate the node:
*/
if ($error === NULL) {
/* Only validate if we don't already know the errors. */
$edit = node_validate($edit, $error);
}
// Prepend extra node form:
$form = implode('', node_invoke_nodeapi($edit, 'form pre', $error));
// Get the node specific bits:
// Can't use node_invoke:
// $error and $param must be passed by reference.
$function = node_get_module_name($edit) .'_form';
if (function_exists($function)) {
$form .= $function($edit, $error, $param);
}
// Append extra node form:
$form .= implode('', node_invoke_nodeapi($edit, 'form post', $error));
$output .= "<div class=\"node-form\">";
/*
** Add the admin specific parts:
*/
if (user_access('administer nodes')) {
$output .= "<div class=\"admin\">";
$author = form_textfield(t('Authored by'), 'name', $edit->name, 20, 60, $error['name']);
$author .= form_textfield(t('Authored on'), 'date', $edit->date, 20, 25, $error['date']);
$output .= "<div class=\"authored\">";
$output .= form_group(t('Authoring information'), $author);
$output .= "</div>\n";
$options .= form_checkbox(t('Published'), 'status', 1, isset($edit->status) ? $edit->status : variable_get('node_status_$edit->type', 1));
$options .= form_checkbox(t('In moderation queue'), 'moderate', 1, isset($edit->moderate) ? $edit->moderate : variable_get("node_moderate_$edit->type", 0));
$options .= form_checkbox(t('Promoted to front page'), 'promote', 1, isset($edit->promote) ? $edit->promote : variable_get("node_promote_$edit->type", 1));
$options .= form_checkbox(t('Static on front page'), 'static', 1, isset($edit->static) ? $edit->static : variable_get("node_static_$edit->type", 0));
$options .= form_checkbox(t('Create new revision'), 'revision', 1, isset($edit->revision) ? $edit->revision : variable_get("node_revision_$edit->type", 0));
$output .= "<div class=\"options\">";
$output .= form_group(t('Options'), $options);
$output .= "</div>\n";
$extras .= implode("</div><div class=\"extra\">", node_invoke_nodeapi($edit, 'form admin'));
$output .= $extras ? "<div class=\"extra\">$extras</div></div>" : "</div>";
}
/*
** Add the default fields:
*/
$output .= "<div class=\"standard\">";
$output .= form_textfield(t('Title'), 'title', $edit->title, 60, 128, $error['title']);
/*
** Add the node specific fields:
*/
$output .= $form;
/*
** Add the hidden fields:
*/
if ($edit->nid) {
$output .= form_hidden('nid', $edit->nid);
}
if (isset($edit->uid)) {
/*
** The use of isset() is mandatory in the context of user IDs as uid
** 0 denotes the anonymous user.
*/
$output .= form_hidden('uid', $edit->uid);
}
if ($edit->created) {
$output .= form_hidden('created', $edit->created);
}
$output .= form_hidden('type', $edit->type);
/*
** Add the buttons:
*/
$output .= form_submit(t('Preview'));
if (!$error) {
if ($edit->title && $edit->type) {
$output .= form_submit(t('Submit'));
}
elseif (!variable_get('node_preview', 0)) {
$output .= form_submit(t('Submit'));
}
}
if ($edit->nid && node_access('delete', $edit)) {
$output .= form_submit(t('Delete'));
}
$output .= "</div></div>";
return form($output, ($param['method'] ? $param['method'] : 'post'), $param['action'], array_merge($param['options'], array('id' => 'node-form')));
}
function node_add($type) {
global $user;
$edit = $_POST['edit'];
/*
** If a node type has been specified, validate it existence. If no
** (valid) node type has been provied, display a node type overview.
*/
if ($type && node_access('create', $type)) {
// Initialize settings:
$node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type);
/*
** Allow the following fields to be initialized via $_GET (eg. for use
** with a 'blog it' bookmarklet):
*/
foreach (array('title', 'teaser', 'body') as $field) {
if ($_GET['edit'][$field]) {
$node[$field] = $_GET['edit'][$field];
}
}
$output = node_form($node);
drupal_set_title(t('Submit %name', array('%name' => node_invoke($node, 'node_name'))));
drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('create content'), 'node/add')));
}
else {
/*
** Compile a list with the different node types and their explanation:
*/
foreach (node_list() as $type) {
if (node_access('create', $type)) {
$output .= '<li>';
$output .= ' '. l(node_invoke($type, 'node_name'), "node/add/$type", array('title' => t('Add a new %s.', array('%s' => node_invoke($type, 'node_name')))));
$output .= " <div style=\"margin-left: 20px;\">". implode("\n", module_invoke_all('help', 'node/add#'. $type)) .'</div>';
$output .= '</li>';
}
}
$output = t('Choose the appropriate item from the list:') ."<ul>$output</ul>";
drupal_set_breadcrumb(array(l(t('Home'), NULL)));
}
return $output;
}
function node_edit($id) {
global $user;
$node = node_load(array('nid' => $id));
drupal_set_title(t('Edit %name', array('%name' => node_invoke($node, 'node_name'))));
if (node_access('update', $node)) {
$output = node_form($node);
}
else {
$output = message_access();
}
return $output;
}
function node_preview($node, $error = NULL) {
/*
** Convert the array to an object:
*/
$node = array2object($node);
if (node_access('create', $node) || node_access('update', $node)) {
/*
** Load the user's name when needed:
*/
if (isset($node->name)) {
/*
** The use of isset() is mandatory in the context of user IDs as uid
** 0 denotes the anonymous user.
*/
if ($user = user_load(array('name' => $node->name))) {
$node->uid = $user->uid;
}
else {
$node->uid = 0; // anonymous user
}
}
else if ($node->uid) {
$user = user_load(array('uid' => $node->uid));
$node->name = $user->name;
}
/*
** Set the created time when needed:
*/
if (empty($node->created)) {
$node->created = time();
}
$node->changed = time();
/*
** Extract a teaser:
*/
$node->teaser = node_teaser($node->body);
/*
** Display a preview of the node:
*/
if ($node->teaser && $node->teaser != $node->body) {
$output = '<h3>'. t('Preview trimmed version') .'</h3>';
$output .= node_view($node, 1);
$output .= '<p><i>'. t("The trimmed version of your post shows how your post looks like when promoted to the main page or when exported for syndication. You can insert a delimiter '&lt;!--break--&gt' (without the quotes) to fine-tune where your post gets split.") .'</i></p>';
$output .= '<h3>'. t('Preview full version') .'</h3>';
$output .= node_view($node, 0);
}
else {
$output .= node_view($node, 0);
}
$output .= node_form($node, $error);
$name = node_invoke($node, 'node_name');
drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('create content'), 'node/add'), l(t('Submit %name', array('%name' => $name)), "node/add/$node->type")));
return $output;
}
}
function node_submit($node) {
global $user;
/*
** Fixup the node when required:
*/
$node = node_validate($node, $error);
/*
** If something went wrong, go back to the preview form:
*/
if ($error) {
return node_preview($node, $error);
}
/*
** Prepare the node's body:
*/
if ($node->nid) {
/*
** Check whether the current user has the proper access rights to
** perform this operation:
*/
if (node_access('update', $node)) {
$node->nid = node_save($node);
watchdog('special', "$node->type: updated '$node->title'", l(t('view post'), "node/view/$node->nid"));
$msg = t('the %name was updated.', array ('%name' => node_invoke($node, 'node_name')));
}
}
else {
/*
** Check whether the current user has the proper access rights to
** perform this operation:
*/
if (node_access('create', $node)) {
/*
** Verify a user's submission rate and avoid duplicate nodes being
** inserted:
*/
throttle('node', variable_get('max_node_rate', 900));
$node->nid = node_save($node);
watchdog('special', "$node->type: added '$node->title'", l(t('view post'), "node/view/$node->nid"));
$msg = t('your %name was created.', array ('%name' => node_invoke($node, 'node_name')));
}
}
$node = node_load(array('nid' => $node->nid));
drupal_set_message($msg);
drupal_set_title($node->title);
return node_show($node, NULL);
}
function node_delete($edit) {
$node = node_load(array('nid' => $edit['nid']));
if (node_access('delete', $node)) {
if ($edit['confirm']) {
/*
** Delete the specified node:
*/
db_query("DELETE FROM {node} WHERE nid = '$node->nid'");
/*
** Call the node specific callback (if any):
*/
node_invoke($node, 'delete');
node_invoke_nodeapi($node, 'delete');
/*
** Clear the cache so an anonymous poster can see the node being
** deleted.
*/
cache_clear_all();
watchdog('special', "$node->type: deleted '$node->title'");
$output = t('The node has been deleted.');
}
else {
$output .= form_item(t('Confirm deletion'), $node->title);
$output .= form_hidden('nid', $node->nid);
$output .= form_hidden('confirm', 1);
$output .= form_submit(t('Delete'));
$output = form($output);
}
}
return $output;
}
function node_page() {
if (user_access('access content')) {
$op = $_POST['op'] ? $_POST['op'] : arg(1);
$edit = $_POST['edit'];
switch ($op) {
case 'feed':
node_feed();
return;
case 'add':
// When a module defines multiple node types, the URL is of the form 'foo/bar':
print theme('page', node_add(arg(3) ? arg(2) ."/". arg(3) : arg(2)));
break;
case 'edit':
print theme('page', node_edit(arg(2)));
break;
case 'view':
if ($node = node_load(array('nid' => arg(2)), $_GET['revision'])) {
print theme('page', node_show($node, arg(3)), $node->title);
}
else {
drupal_not_found();
}
break;
case 'revisions':
print theme('page', node_revision_overview(arg(2)), t('Revisions'));
break;
case 'rollback-revision':
node_revision_rollback(arg(2), arg(3));
print theme('page', node_revision_overview(arg(2)), t('Revisions'));
break;
case 'delete-revision':
node_revision_delete(arg(2), arg(3));
print theme('page', node_revision_overview(arg(2)), t('Revisions'));
break;
case t('Preview'):
$edit = node_validate($edit, $error);
print theme('page', node_preview($edit, $error), t('Preview %name', array('%name' => $name)));
break;
case t('Submit'):
drupal_set_title(t('Submit %name', array('%name' => $name)));
print theme('page', node_submit($edit));
break;
case t('Delete'):
print theme('page', node_delete($edit), t('Delete %name', array('%name' => $name)));
break;
default:
$output = '';
$result = pager_query('SELECT nid, type FROM {node} WHERE promote = 1 AND status = 1 ORDER BY static DESC, created DESC', variable_get('default_nodes_main', 10));
while ($node = db_fetch_object($result)) {
$output .= node_view(node_load(array('nid' => $node->nid, 'type' => $node->type)), 1);
}
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS" href="'. url('node/feed') .'" />');
print theme('page', $output, '');
}
}
else {
print theme('page', message_access());
}
}
function node_update_index() {
// Return an array of values to dictate how to update the search index
// for this particular type of node.
//
// "last_update"'s value is used with variable_set to set the
// last time this node type had an index update run.
//
// "node_type"'s value is used to identify the node type in the search
// index.
//
// "select"'s value is used to select the node id and text fields from
// the table we are indexing. In this case, we also check against the
// last run date for the nodes update.
return array('last_update' => 'node_cron_last',
'node_type' => 'node',
'select' => "SELECT n.nid as lno, n.title as text1, n.body as text2 FROM {node} n WHERE n.status = 1 AND moderate = 0 and (created > " . variable_get('node_cron_last', 1) . " or changed > " . variable_get('node_cron_last', 1) . ")");
}
function node_nodeapi(&$node, $op, $arg = 0) {
switch ($op) {
case 'settings':
$output[t('publish')] = form_checkbox('', "node_status_$node->type", 1, variable_get("node_status_$node->type", 1));
$output[t('promote')] = form_checkbox('', "node_promote_$node->type", 1, variable_get("node_promote_$node->type", 1));
$output[t('moderate')] = form_checkbox('', "node_moderate_$node->type", 1, variable_get("node_moderate_$node->type", 0));
$output[t('static')] = form_checkbox('', "node_static_$node->type", 1, variable_get("node_static_$node->type", 0));
$output[t('revision')] = form_checkbox('', "node_revision_$node->type", 1, variable_get("node_revision_$node->type", 0));
return $output;
case 'fields':
return array('nid', 'uid', 'type', 'title', 'teaser', 'body', 'revisions', 'status', 'promote', 'moderate', 'static', 'created', 'changed');
}
}
?>