From 8759ca7368fbe6014286b320cf25185fc1e1a602 Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Wed, 16 Jul 2003 20:14:26 +0000
Subject: [PATCH] - Commited the URL aliasing patch.  Thanks Matt.

  This update requires you to run update.php!
---
 CHANGELOG                            |   1 +
 database/database.mssql              |   1 +
 database/database.mysql              |   1 +
 database/database.pgsql              |   1 +
 includes/common.inc                  |  15 ++++
 index.php                            |   5 +-
 modules/forum.module                 |   8 ++-
 modules/forum/forum.module           |   8 ++-
 modules/node.module                  | 104 ++++++++++++++++++++++-----
 modules/node/node.module             | 104 ++++++++++++++++++++++-----
 modules/page.module                  |   4 +-
 modules/page/page.module             |   4 +-
 modules/statistics.module            |   5 +-
 modules/statistics/statistics.module |   5 +-
 modules/title.module                 |   2 +-
 modules/tracker.module               |   2 +-
 modules/tracker/tracker.module       |   2 +-
 update.php                           |   7 +-
 18 files changed, 224 insertions(+), 55 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 2e49f016ff8c..19c503c72284 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 Drupal x.x.x, xxxx-xx-xx (to be released)
 ------------------------
 
+- added support for configurable URLs.
 - database backend:
     * added support for database table prefxing.
 - performance improvements:
diff --git a/database/database.mssql b/database/database.mssql
index f4b60db6960a..a5f1a698e865 100644
--- a/database/database.mssql
+++ b/database/database.mssql
@@ -186,6 +186,7 @@ CREATE TABLE [dbo].[node] (
   [nid] [numeric](10, 0) NULL ,
   [type] [varchar] (16) NOT NULL ,
   [title] [varchar] (128) NOT NULL ,
+  [path] [varchar] (250) NULL ,
   [score] [int] NOT NULL ,
   [votes] [int] NOT NULL ,
   [uid] [int] NOT NULL ,
diff --git a/database/database.mysql b/database/database.mysql
index 1862f66d8c2c..dda4cc8543ac 100644
--- a/database/database.mysql
+++ b/database/database.mysql
@@ -262,6 +262,7 @@ CREATE TABLE node (
   nid int(10) unsigned NOT NULL auto_increment,
   type varchar(16) NOT NULL default '',
   title varchar(128) NOT NULL default '',
+  path varchar(250) NULL default '',
   score int(11) NOT NULL default '0',
   votes int(11) NOT NULL default '0',
   uid int(10) NOT NULL default '0',
diff --git a/database/database.pgsql b/database/database.pgsql
index 061cfb136319..2e26a9f4f249 100644
--- a/database/database.pgsql
+++ b/database/database.pgsql
@@ -261,6 +261,7 @@ CREATE TABLE node (
   nid SERIAL,
   type varchar(16) NOT NULL default '',
   title varchar(128) NOT NULL default '',
+  path varchar(250) NULL default '',
   score integer NOT NULL default '0',
   votes integer NOT NULL default '0',
   uid integer NOT NULL default '0',
diff --git a/includes/common.inc b/includes/common.inc
index 145cb07a886c..e846f8640120 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -298,6 +298,21 @@ function valid_email_address($mail) {
   }
 }
 
+/**
+ * Verify the syntax of the given URL.
+ *
+ * @param $url  an URL
+ */
+function valid_url($url) {
+
+  if (preg_match("/^[a-zA-z0-9\/:_\-_\.]+$/", $url)) {
+    return 1;
+  }
+  else {
+    return 0;
+  }
+}
+
 /**
  * Format a single result entry of a search query:
  *
diff --git a/index.php b/index.php
index 3d149eab22b7..5da9d3cc160d 100644
--- a/index.php
+++ b/index.php
@@ -4,7 +4,10 @@
 include_once "includes/common.inc";
 
 if (isset($_GET["q"])) {
-  $mod = arg(0);
+  if (module_exist("node") && $path = node_get_alias($_GET["q"])) {
+    $_GET["q"] = $path;
+  }
+   $mod = arg(0);
 }
 else {
   $_GET["q"] = variable_get("site_frontpage", "node");
diff --git a/modules/forum.module b/modules/forum.module
index e4a637ce9488..6dd7af988d92 100644
--- a/modules/forum.module
+++ b/modules/forum.module
@@ -121,13 +121,14 @@ function forum_link($type, $node = 0, $main = 0) {
   if (!$main && $type == "node" && $node->type == "forum") {
     // get previous and next topic
 
-    $result = db_query("SELECT n.nid, n.title, n.body, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM {node} n INNER JOIN {forum} f ON n.nid = f.nid INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = f.nid AND f.tid = %d AND n.status = 1 GROUP BY n.nid, n.title, n.body, n.created ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)), $node->tid);
+    $result = db_query("SELECT n.nid, n.title, n.body, n.path, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM {node} n INNER JOIN {forum} f ON n.nid = f.nid INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = f.nid AND f.tid = %d AND n.status = 1 GROUP BY n.nid, n.title, n.body, n.created ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)), $node->tid);
 
     while ($topic = db_fetch_object($result)) {
       if ($stop == 1) {
         $next->nid = $topic->nid;
         $next->title = $topic->title;
         $next->body = $topic->body;
+        $next->path = $topic->path;
         break;
       }
       if ($topic->nid == $node->nid) {
@@ -137,15 +138,16 @@ function forum_link($type, $node = 0, $main = 0) {
         $prev->nid = $topic->nid;
         $prev->title = $topic->title;
         $prev->body = $topic->body;
+        $prev->path = $topic->path;
       }
     }
 
     if ($prev) {
-      $links[] = l(t("previous forum topic"), "node/view/$prev->nid", array("title" => $prev->title .": ". substr(strip_tags($prev->body), 0, 100)."..."));
+      $links[] = l(t("previous forum topic"), node_url($prev), array("title" => $prev->title .": ". substr(strip_tags($prev->body), 0, 100)."..."));
     }
 
     if ($next) {
-      $links[] = l(t("next forum topic"), "node/view/$next->nid", array("title" => $next->title .": ". substr(strip_tags($next->body), 0, 100)."..."));
+      $links[] = l(t("next forum topic"), node_url($next), array("title" => $next->title .": ". substr(strip_tags($next->body), 0, 100)."..."));
     }
   }
 
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index e4a637ce9488..6dd7af988d92 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -121,13 +121,14 @@ function forum_link($type, $node = 0, $main = 0) {
   if (!$main && $type == "node" && $node->type == "forum") {
     // get previous and next topic
 
-    $result = db_query("SELECT n.nid, n.title, n.body, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM {node} n INNER JOIN {forum} f ON n.nid = f.nid INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = f.nid AND f.tid = %d AND n.status = 1 GROUP BY n.nid, n.title, n.body, n.created ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)), $node->tid);
+    $result = db_query("SELECT n.nid, n.title, n.body, n.path, GREATEST(n.created, MAX(c.timestamp)) AS date_sort, COUNT(c.nid) AS num_comments FROM {node} n INNER JOIN {forum} f ON n.nid = f.nid INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = f.nid AND f.tid = %d AND n.status = 1 GROUP BY n.nid, n.title, n.body, n.created ORDER BY ". _forum_get_topic_order(isset($user->sortby) ? $user->sortby : variable_get("forum_order",1)), $node->tid);
 
     while ($topic = db_fetch_object($result)) {
       if ($stop == 1) {
         $next->nid = $topic->nid;
         $next->title = $topic->title;
         $next->body = $topic->body;
+        $next->path = $topic->path;
         break;
       }
       if ($topic->nid == $node->nid) {
@@ -137,15 +138,16 @@ function forum_link($type, $node = 0, $main = 0) {
         $prev->nid = $topic->nid;
         $prev->title = $topic->title;
         $prev->body = $topic->body;
+        $prev->path = $topic->path;
       }
     }
 
     if ($prev) {
-      $links[] = l(t("previous forum topic"), "node/view/$prev->nid", array("title" => $prev->title .": ". substr(strip_tags($prev->body), 0, 100)."..."));
+      $links[] = l(t("previous forum topic"), node_url($prev), array("title" => $prev->title .": ". substr(strip_tags($prev->body), 0, 100)."..."));
     }
 
     if ($next) {
-      $links[] = l(t("next forum topic"), "node/view/$next->nid", array("title" => $next->title .": ". substr(strip_tags($next->body), 0, 100)."..."));
+      $links[] = l(t("next forum topic"), node_url($next), array("title" => $next->title .": ". substr(strip_tags($next->body), 0, 100)."..."));
     }
   }
 
diff --git a/modules/node.module b/modules/node.module
index dfdff21ec9c8..e1b93cdd31c9 100644
--- a/modules/node.module
+++ b/modules/node.module
@@ -49,7 +49,7 @@ function node_system($field){
 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")));
+    $items[] = l($node->title, node_url($node), array("title" => format_plural($number, "%count comment", "%count comments")));
   }
 
   return theme("theme_node_list", $items, $title);
@@ -437,7 +437,7 @@ function node_access($op, $node = 0) {
 }
 
 function node_perm() {
-  return array("administer nodes", "access content");
+  return array("administer nodes", "access content", "create custom URLs");
 }
 
 function node_search($keys) {
@@ -469,6 +469,7 @@ 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_select(t("Preview post"), "node_preview", variable_get("node_preview", 0), array(t("Optional"), t("Required")), t("Must users preview posts before submitting?"));
+
   return $output;
 }
 
@@ -524,7 +525,7 @@ function node_link($type, $node = 0, $main = 0) {
     }
 
     if ($main == 1 && $node->teaser && $node->teaser != $node->body) {
-      $links[] = l(t("read more"), "node/view/$node->nid", array("title" => t("Read the rest of this posting.")));
+      $links[] = l(t("read more"), node_url($node), array("title" => t("Read the rest of this posting.")));
     }
 
     if (user_access("administer nodes")) {
@@ -576,7 +577,7 @@ function node_admin_edit($node) {
     $output .= "<table border=\"1\" cellpadding=\"2\" cellspacing=\"2\">";
     $output .= " <tr><th>". t("older revisions") ."</th><th colspan=\"3\">". t("operations") ."</th></tr>";
     foreach ($node->revisions as $key => $revision) {
-      $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>";
+      $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_url($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>";
     }
     $output .= "</table>";
   }
@@ -603,7 +604,7 @@ function node_admin_nodes() {
   $header = array(t("title"), t("type"), t("author"), t("status"), array("data" => t("operations"), "colspan" => 2));
 
   while ($node = db_fetch_object($result)) {
-    $rows[] = array(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"));
+    $rows[] = array(l($node->title, node_url($node)) ." ". (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"));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
@@ -851,6 +852,23 @@ function node_block($op = "list", $delta = 0) {
   }
 }
 
+function node_get_alias($path) {
+
+  $result = db_query("SELECT nid FROM {node} WHERE path = '%s'", trim($path, "/"));
+  if ($node = db_fetch_object($result)) {
+    return "node/view/$node->nid";
+  }
+}
+
+function node_url($node) {
+  if ($node->path) {
+    return $node->path;
+  }
+  else {
+    return "node/view/$node->nid";
+  }
+}
+
 function node_feed($nodes = 0, $channel = array()) {
   global $base_url, $languages;
 
@@ -863,12 +881,12 @@ function node_feed($nodes = 0, $channel = array()) {
   */
 
   if (!$nodes) {
-    $nodes = db_query_range("SELECT nid FROM {node} WHERE promote = '1' AND status = '1' ORDER BY created DESC", 0, 15);
+    $nodes = db_query_range("SELECT nid, path FROM {node} WHERE promote = '1' AND status = '1' ORDER BY created DESC", 0, 15);
   }
 
   while ($node = db_fetch_object($nodes)) {
     $item = node_load(array("nid" => $node->nid));
-    $link = url("node/view/$item->nid");
+    $link = url(node_url($node));
     $items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body));
   }
 
@@ -909,6 +927,19 @@ function node_validate($node, &$error) {
     }
   }
 
+  /*
+  ** Clean the path field:
+  */
+
+  if ($node->path) {
+    if (!valid_url($node->path)) {
+      $error["path"] = theme("theme_error", t("The specified path is not valid."));
+    }
+    else if (db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE nid != %d AND path = '%s'", $node->nid, $node->path))) {
+      $error["path"] = theme("theme_error", t("The specified path is already in use."));
+    }
+  }
+
   /*
   ** Common default values:
   */
@@ -992,6 +1023,41 @@ function node_validate($node, &$error) {
 }
 
 
+function node_clean_path($path) {
+/*
+** Clean the node path
+*/
+  global $base_url;
+
+  /*
+  ** Replace absolute URL for this site with relative URL.
+  */
+  $path = str_replace($base_url, "", $path);
+
+  /*
+  ** Only allow alpha numeric characters and slashes.
+  */
+  $path = preg_replace("'[^a-zA-Z0-9/.]'", " ", $path);
+
+  /*
+  ** Remove all whitespace.
+  */
+  $path = str_replace(" ", "", $path);
+
+  /*
+  ** Replace two or more sequential slashes with only one slashes.
+  */
+  $path = preg_replace("'//*'","/",$path);
+
+  /*
+  ** Remove beginning and trailing slashes.
+  */
+  $path = trim($path, "/");
+
+  return $path;
+}
+
+
 function node_form($edit, $error = NULL) {
 
   /*
@@ -1066,6 +1132,10 @@ function node_form($edit, $error = NULL) {
   $output .= "<div class=\"standard\">";
   $output .= form_textfield(t("Title"), "title", $edit->title, 60, 128, $error["title"]);
 
+  if (user_access("create custom URLs")) {
+    $output .= form_textfield(t("Path alias"), "path", ($edit->path == "node/view/$edit->nid") ? "" : $edit->path, 60, 250, $error["path"] ? $error["path"] : t("Optionally specify an alternative URL by which this node can be accessed.  For example, type 'about/' when writing an about page."));
+  }
+
   /*
   ** Add the node specific fields:
   */
@@ -1100,11 +1170,13 @@ function node_form($edit, $error = NULL) {
 
   $output .= form_submit(t("Preview"));
 
-  if ($edit->title && $edit->type && !$error) {
-    $output .= form_submit(t("Submit"));
-  }
-  elseif (!variable_get("node_preview", 0)) {
-    $output .= form_submit(t("Submit"));
+  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)) {
@@ -1285,7 +1357,7 @@ function node_submit($node) {
 
     if (node_access("update", $node)) {
       $nid = node_save($node);
-      watchdog("special", "$node->type: updated '$node->title'", l("view post", "node/view/$node->nid"));
+      watchdog("special", "$node->type: updated '$node->title'", l("view post", node_url($node)));
       $output = t("The %name has been updated.", array ("%name" => module_invoke($node->type, "node", "name")));
     }
   }
@@ -1306,7 +1378,7 @@ function node_submit($node) {
       throttle("node", variable_get("max_node_rate", 900));
 
       $nid = node_save($node);
-      watchdog("special", "$node->type: added '$node->title'", l("view post", "node/view/$nid"));
+      watchdog("special", "$node->type: added '$node->title'", l("view post", node_url($node)));
       $output = t("Thanks for your submission.");
     }
   }
@@ -1327,7 +1399,7 @@ function node_submit($node) {
   }
 
   if ($nid && node_access("view", $node)) {
-    $links[] = l(t("view"), "node/view/$nid");
+    $links[] = l(t("view"), node_url($node));
   }
 
   if ($nid && node_access("update", $node)) {
@@ -1474,7 +1546,7 @@ function node_nodeapi(&$node, $op, $arg = 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");
+      return array("nid", "uid", "type", "title", "path", "teaser", "body", "revisions", "status", "promote", "moderate", "static", "created", "changed");
   }
 }
 
diff --git a/modules/node/node.module b/modules/node/node.module
index dfdff21ec9c8..e1b93cdd31c9 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -49,7 +49,7 @@ function node_system($field){
 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")));
+    $items[] = l($node->title, node_url($node), array("title" => format_plural($number, "%count comment", "%count comments")));
   }
 
   return theme("theme_node_list", $items, $title);
@@ -437,7 +437,7 @@ function node_access($op, $node = 0) {
 }
 
 function node_perm() {
-  return array("administer nodes", "access content");
+  return array("administer nodes", "access content", "create custom URLs");
 }
 
 function node_search($keys) {
@@ -469,6 +469,7 @@ 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_select(t("Preview post"), "node_preview", variable_get("node_preview", 0), array(t("Optional"), t("Required")), t("Must users preview posts before submitting?"));
+
   return $output;
 }
 
@@ -524,7 +525,7 @@ function node_link($type, $node = 0, $main = 0) {
     }
 
     if ($main == 1 && $node->teaser && $node->teaser != $node->body) {
-      $links[] = l(t("read more"), "node/view/$node->nid", array("title" => t("Read the rest of this posting.")));
+      $links[] = l(t("read more"), node_url($node), array("title" => t("Read the rest of this posting.")));
     }
 
     if (user_access("administer nodes")) {
@@ -576,7 +577,7 @@ function node_admin_edit($node) {
     $output .= "<table border=\"1\" cellpadding=\"2\" cellspacing=\"2\">";
     $output .= " <tr><th>". t("older revisions") ."</th><th colspan=\"3\">". t("operations") ."</th></tr>";
     foreach ($node->revisions as $key => $revision) {
-      $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>";
+      $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_url($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>";
     }
     $output .= "</table>";
   }
@@ -603,7 +604,7 @@ function node_admin_nodes() {
   $header = array(t("title"), t("type"), t("author"), t("status"), array("data" => t("operations"), "colspan" => 2));
 
   while ($node = db_fetch_object($result)) {
-    $rows[] = array(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"));
+    $rows[] = array(l($node->title, node_url($node)) ." ". (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"));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
@@ -851,6 +852,23 @@ function node_block($op = "list", $delta = 0) {
   }
 }
 
+function node_get_alias($path) {
+
+  $result = db_query("SELECT nid FROM {node} WHERE path = '%s'", trim($path, "/"));
+  if ($node = db_fetch_object($result)) {
+    return "node/view/$node->nid";
+  }
+}
+
+function node_url($node) {
+  if ($node->path) {
+    return $node->path;
+  }
+  else {
+    return "node/view/$node->nid";
+  }
+}
+
 function node_feed($nodes = 0, $channel = array()) {
   global $base_url, $languages;
 
@@ -863,12 +881,12 @@ function node_feed($nodes = 0, $channel = array()) {
   */
 
   if (!$nodes) {
-    $nodes = db_query_range("SELECT nid FROM {node} WHERE promote = '1' AND status = '1' ORDER BY created DESC", 0, 15);
+    $nodes = db_query_range("SELECT nid, path FROM {node} WHERE promote = '1' AND status = '1' ORDER BY created DESC", 0, 15);
   }
 
   while ($node = db_fetch_object($nodes)) {
     $item = node_load(array("nid" => $node->nid));
-    $link = url("node/view/$item->nid");
+    $link = url(node_url($node));
     $items .= format_rss_item($item->title, $link, ($item->teaser ? $item->teaser : $item->body));
   }
 
@@ -909,6 +927,19 @@ function node_validate($node, &$error) {
     }
   }
 
+  /*
+  ** Clean the path field:
+  */
+
+  if ($node->path) {
+    if (!valid_url($node->path)) {
+      $error["path"] = theme("theme_error", t("The specified path is not valid."));
+    }
+    else if (db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE nid != %d AND path = '%s'", $node->nid, $node->path))) {
+      $error["path"] = theme("theme_error", t("The specified path is already in use."));
+    }
+  }
+
   /*
   ** Common default values:
   */
@@ -992,6 +1023,41 @@ function node_validate($node, &$error) {
 }
 
 
+function node_clean_path($path) {
+/*
+** Clean the node path
+*/
+  global $base_url;
+
+  /*
+  ** Replace absolute URL for this site with relative URL.
+  */
+  $path = str_replace($base_url, "", $path);
+
+  /*
+  ** Only allow alpha numeric characters and slashes.
+  */
+  $path = preg_replace("'[^a-zA-Z0-9/.]'", " ", $path);
+
+  /*
+  ** Remove all whitespace.
+  */
+  $path = str_replace(" ", "", $path);
+
+  /*
+  ** Replace two or more sequential slashes with only one slashes.
+  */
+  $path = preg_replace("'//*'","/",$path);
+
+  /*
+  ** Remove beginning and trailing slashes.
+  */
+  $path = trim($path, "/");
+
+  return $path;
+}
+
+
 function node_form($edit, $error = NULL) {
 
   /*
@@ -1066,6 +1132,10 @@ function node_form($edit, $error = NULL) {
   $output .= "<div class=\"standard\">";
   $output .= form_textfield(t("Title"), "title", $edit->title, 60, 128, $error["title"]);
 
+  if (user_access("create custom URLs")) {
+    $output .= form_textfield(t("Path alias"), "path", ($edit->path == "node/view/$edit->nid") ? "" : $edit->path, 60, 250, $error["path"] ? $error["path"] : t("Optionally specify an alternative URL by which this node can be accessed.  For example, type 'about/' when writing an about page."));
+  }
+
   /*
   ** Add the node specific fields:
   */
@@ -1100,11 +1170,13 @@ function node_form($edit, $error = NULL) {
 
   $output .= form_submit(t("Preview"));
 
-  if ($edit->title && $edit->type && !$error) {
-    $output .= form_submit(t("Submit"));
-  }
-  elseif (!variable_get("node_preview", 0)) {
-    $output .= form_submit(t("Submit"));
+  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)) {
@@ -1285,7 +1357,7 @@ function node_submit($node) {
 
     if (node_access("update", $node)) {
       $nid = node_save($node);
-      watchdog("special", "$node->type: updated '$node->title'", l("view post", "node/view/$node->nid"));
+      watchdog("special", "$node->type: updated '$node->title'", l("view post", node_url($node)));
       $output = t("The %name has been updated.", array ("%name" => module_invoke($node->type, "node", "name")));
     }
   }
@@ -1306,7 +1378,7 @@ function node_submit($node) {
       throttle("node", variable_get("max_node_rate", 900));
 
       $nid = node_save($node);
-      watchdog("special", "$node->type: added '$node->title'", l("view post", "node/view/$nid"));
+      watchdog("special", "$node->type: added '$node->title'", l("view post", node_url($node)));
       $output = t("Thanks for your submission.");
     }
   }
@@ -1327,7 +1399,7 @@ function node_submit($node) {
   }
 
   if ($nid && node_access("view", $node)) {
-    $links[] = l(t("view"), "node/view/$nid");
+    $links[] = l(t("view"), node_url($node));
   }
 
   if ($nid && node_access("update", $node)) {
@@ -1474,7 +1546,7 @@ function node_nodeapi(&$node, $op, $arg = 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");
+      return array("nid", "uid", "type", "title", "path", "teaser", "body", "revisions", "status", "promote", "moderate", "static", "created", "changed");
   }
 }
 
diff --git a/modules/page.module b/modules/page.module
index 2dea4392a214..8406cc70ac95 100644
--- a/modules/page.module
+++ b/modules/page.module
@@ -75,9 +75,9 @@ function page_link($type) {
   $links = array();
 
   if ($type == "page" && user_access("access content")) {
-    $result = db_query("SELECT n.nid, n.title, p.link, p.description FROM {page} p INNER JOIN {node} n ON p.nid = n.nid WHERE n.status = '1' AND p.link != '' ORDER BY p.link");
+    $result = db_query("SELECT n.nid, n.title, n.path, p.link, p.description FROM {page} p INNER JOIN {node} n ON p.nid = n.nid WHERE n.status = '1' AND p.link != '' ORDER BY p.link");
     while ($page = db_fetch_object($result)) {
-      $links[] = l($page->link, "node/view/$page->nid", array("title" => $page->description));
+      $links[] = l($page->link, node_url($page), array("title" => $page->description));
     }
   }
 
diff --git a/modules/page/page.module b/modules/page/page.module
index 2dea4392a214..8406cc70ac95 100644
--- a/modules/page/page.module
+++ b/modules/page/page.module
@@ -75,9 +75,9 @@ function page_link($type) {
   $links = array();
 
   if ($type == "page" && user_access("access content")) {
-    $result = db_query("SELECT n.nid, n.title, p.link, p.description FROM {page} p INNER JOIN {node} n ON p.nid = n.nid WHERE n.status = '1' AND p.link != '' ORDER BY p.link");
+    $result = db_query("SELECT n.nid, n.title, n.path, p.link, p.description FROM {page} p INNER JOIN {node} n ON p.nid = n.nid WHERE n.status = '1' AND p.link != '' ORDER BY p.link");
     while ($page = db_fetch_object($result)) {
-      $links[] = l($page->link, "node/view/$page->nid", array("title" => $page->description));
+      $links[] = l($page->link, node_url($page), array("title" => $page->description));
     }
   }
 
diff --git a/modules/statistics.module b/modules/statistics.module
index 5881b902eb79..c8901874b890 100644
--- a/modules/statistics.module
+++ b/modules/statistics.module
@@ -302,9 +302,6 @@ function statistics_admin_accesslog_table($type, $id) {
   $header = array(t("timestamp"), t("post"), t("user"), t("hostname"), t("referrer"), array("data" => t("operations"), "colspan" => "3"));
 
   while ($log = db_fetch_object($result)) {
-    if (!$node = node_load(array("nid" => $log->nid))) {
-      $node->nid = 0;
-    }
     $user = user_load(array("uid" => $log->uid));
 
     if ($log->url) {
@@ -314,7 +311,7 @@ function statistics_admin_accesslog_table($type, $id) {
       $url = message_na();
     }
 
-    $rows[] = array(array("data" => format_date($log->timestamp, "small"), "nowrap" => "nowrap"), ($node->nid ? l($node->title, "node/view/$node->nid") : message_na()), format_name($user), $log->hostname ? $log->hostname : message_na(), $url, ($log->nid ? l(t("track node"), "admin/statistics/log/node/$log->nid") : ""), ($user->uid ? l(t("track user"), "admin/statistics/log/user/$user->uid") : ""), ($log->hostname ? l(t("track host"), "admin/statistics/log/host/$log->hostname") : ""));
+    $rows[] = array(array("data" => format_date($log->timestamp, "small"), "nowrap" => "nowrap"), ($node->nid ? l($node->title, node_url($node)) : message_na()), format_name($user), $log->hostname ? $log->hostname : message_na(), $url, ($log->nid ? l(t("track node"), "admin/statistics/log/node/$log->nid") : ""), ($user->uid ? l(t("track user"), "admin/statistics/log/user/$user->uid") : ""), ($log->hostname ? l(t("track host"), "admin/statistics/log/host/$log->hostname") : ""));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module
index 5881b902eb79..c8901874b890 100644
--- a/modules/statistics/statistics.module
+++ b/modules/statistics/statistics.module
@@ -302,9 +302,6 @@ function statistics_admin_accesslog_table($type, $id) {
   $header = array(t("timestamp"), t("post"), t("user"), t("hostname"), t("referrer"), array("data" => t("operations"), "colspan" => "3"));
 
   while ($log = db_fetch_object($result)) {
-    if (!$node = node_load(array("nid" => $log->nid))) {
-      $node->nid = 0;
-    }
     $user = user_load(array("uid" => $log->uid));
 
     if ($log->url) {
@@ -314,7 +311,7 @@ function statistics_admin_accesslog_table($type, $id) {
       $url = message_na();
     }
 
-    $rows[] = array(array("data" => format_date($log->timestamp, "small"), "nowrap" => "nowrap"), ($node->nid ? l($node->title, "node/view/$node->nid") : message_na()), format_name($user), $log->hostname ? $log->hostname : message_na(), $url, ($log->nid ? l(t("track node"), "admin/statistics/log/node/$log->nid") : ""), ($user->uid ? l(t("track user"), "admin/statistics/log/user/$user->uid") : ""), ($log->hostname ? l(t("track host"), "admin/statistics/log/host/$log->hostname") : ""));
+    $rows[] = array(array("data" => format_date($log->timestamp, "small"), "nowrap" => "nowrap"), ($node->nid ? l($node->title, node_url($node)) : message_na()), format_name($user), $log->hostname ? $log->hostname : message_na(), $url, ($log->nid ? l(t("track node"), "admin/statistics/log/node/$log->nid") : ""), ($user->uid ? l(t("track user"), "admin/statistics/log/user/$user->uid") : ""), ($log->hostname ? l(t("track host"), "admin/statistics/log/host/$log->hostname") : ""));
   }
 
   if ($pager = pager_display(NULL, 50, 0, "admin")) {
diff --git a/modules/title.module b/modules/title.module
index b1281a7522f9..9044e03df82b 100644
--- a/modules/title.module
+++ b/modules/title.module
@@ -32,7 +32,7 @@ function title_page() {
       $header = array(t("Type"), t("Title"), t("Author"));
       while ($node = db_fetch_object($result)) {
         $type = ucfirst(module_invoke($node->type, "node", "name"));
-        $title = l($node->title, "node/view/$node->nid");
+        $title = l($node->title, node_url($node));
         $author = format_name($node);
         $rows[] = array(array("data" => $type, "class" => "type"), array("data" => $title, "class" => "content"), array("data" => $author, "class" => "author"));
       }
diff --git a/modules/tracker.module b/modules/tracker.module
index c6fa4bdc4529..5563d4ec0b3e 100644
--- a/modules/tracker.module
+++ b/modules/tracker.module
@@ -42,7 +42,7 @@ function tracker_posts($id = 0) {
     }
 
     $type = ucfirst(module_invoke($node->type, "node", "name"));
-    $title = l($node->title, "node/view/$node->nid") ." ". (node_is_new($node->nid, $node->changed) ? theme("theme_mark") : "");
+    $title = l($node->title, node_url($node)) ." ". (node_is_new($node->nid, $node->changed) ? theme("theme_mark") : "");
     $author = format_name($node);
 
     $comments = array();
diff --git a/modules/tracker/tracker.module b/modules/tracker/tracker.module
index c6fa4bdc4529..5563d4ec0b3e 100644
--- a/modules/tracker/tracker.module
+++ b/modules/tracker/tracker.module
@@ -42,7 +42,7 @@ function tracker_posts($id = 0) {
     }
 
     $type = ucfirst(module_invoke($node->type, "node", "name"));
-    $title = l($node->title, "node/view/$node->nid") ." ". (node_is_new($node->nid, $node->changed) ? theme("theme_mark") : "");
+    $title = l($node->title, node_url($node)) ." ". (node_is_new($node->nid, $node->changed) ? theme("theme_mark") : "");
     $author = format_name($node);
 
     $comments = array();
diff --git a/update.php b/update.php
index e52e78b0b958..9cc1ee5ed88c 100644
--- a/update.php
+++ b/update.php
@@ -38,7 +38,8 @@
   "2003-05-24" => "update_54",
   "2003-05-31" => "update_55",
   "2003-06-04" => "update_56",
-  "2003-06-08" => "update_57"
+  "2003-06-08" => "update_57",
+  "2003-06-08: first update since Drupal 4.2.0 release" => "update_58"
 );
 
 function update_32() {
@@ -265,6 +266,10 @@ function update_57() {
   update_sql("DELETE FROM variable WHERE name = 'site_charset'");
 }
 
+function update_58() {
+  update_sql("ALTER TABLE node ADD path varchar(250) NULL default ''");
+}
+
 /*
 ** System functions
 */
-- 
GitLab