Commit e3d2c46f authored by Kjartan's avatar Kjartan

- applied search patch.

- added who is online block.
- made weblog module more configurable.
- users may now delete their own accounts (Feature #8)
- users may now request a password using email address *or* username.
  formerly required both items to match an account which was onerous.
- the link to request a new password is now presented whenever a user
  fails login.
- there is now a confirmation message after submitting edits to your
  user information.
- error messages in user.module may now be stylized by themes.
- <hook>_form has a $param setting you can fill with form parameters.
- improved wording for a few config settings.
- fixed various non-coding standard things.
parent f8372fa3
......@@ -144,22 +144,36 @@ function variable_del($name) {
unset($conf[$name]);
}
/*
** Format a single result entry of a search query:
*/
/**
* Format a single result entry of a search query:
*
* @param $item a single search result as returned by <module>_search of type
* array("count" => ..., "link" => ..., "title" => ...,
* "user" => ..., "date" => ..., "keywords" => ...)
* @param $type module type of this item
*/
function search_item($item, $type) {
$output .= " <b><u><a href=\"". $item["link"] ."\">". $item["title"] ."</a></u></b><br />";
$output .= " <b>". $item["count"] . "&nbsp;&nbsp;<u><a href=\"". $item["link"] . "\">". $item["title"] ."</a></u></b><br />";
$output .= " <small>$type ". ($item["user"] ? " - ". $item["user"] : "") ."". ($item["date"] ? " - ". format_date($item["date"], "small") : "") ."</small>";
$output .= "<br /><br />";
return $output;
}
/*
** Render a generic search form:
*/
/**
* Render a generic search form.
*
* "Generic" means "universal usable" - that is, usable not only from
* module.php?mod=search, but also as a simple seach box (without
* "Restrict search to", help text, etc) from theme's header etc.
* This means: provide options to only conditionally render certain
* parts of this form.
*
* @param $action Form action. Defaults to module.php?mod=search.
* @param $query Query string. Defaults to global $keys.
* @param $options != 0: Render additional form fields/text
* ("Restrict search to", help text, etc).
*/
function search_form($action = 0, $query = 0, $options = 0) {
global $keys;
......@@ -171,8 +185,8 @@ function search_form($action = 0, $query = 0, $options = 0) {
$query = $keys;
}
$output .= " <input type=\"text\" size=\"50\" value=\"". check_form($keys) ."\" name=\"keys\">";
$output .= " <input type=\"submit\" value=\"". t("Search") ."\">\n";
$output .= " <br /><input type=\"text\" size=\"50\" value=\"". check_form($keys) ."\" name=\"keys\" />";
$output .= " <input type=\"submit\" value=\"". t("Search") ."\" />\n";
if ($options != 0) {
$output .= "<br />";
......@@ -180,18 +194,21 @@ function search_form($action = 0, $query = 0, $options = 0) {
foreach (module_list() as $name) {
if (module_hook($name, "search")) {
$output .= " <input type=\"checkbox\" name=\"edit[type][$name]\" ". ($edit["type"][$name] ? " checked=\"checked\"" : "") ."/> ". t($name);
$output .= " <input type=\"checkbox\" name=\"edit[type][$name]\" ". ($edit["type"][$name] ? " checked=\"checked\"" : "") ." /> ". t($name);
}
}
// TODO: (link to) search hints
}
$form .= "<br />";
return form($output, "post", $action);
}
/*
** Collect the search results:
*/
* Collect the search results:
*/
function search_data() {
global $keys, $edit;
......@@ -200,12 +217,18 @@ function search_data() {
if ($keys) {
foreach (module_list() as $name) {
if (module_hook($name, "search") && (!$edit["type"] || $edit["type"][$name]) && ($result = module_invoke($name, "search", check_query($keys)))) {
if ($name == "node" || $name == "comment") {
$output .= "<b>Matching ". $name ."s ranked in order of relevance</b><br />";
}
else {
$output .= "<b>Matching ". $name ."s</b><br />";
}
foreach ($result as $entry) {
$output .= search_item($entry, $name);
}
}
}
if(!$output) {
if (!$output) {
$output .= t("Your search yielded no results.");
}
}
......@@ -213,10 +236,16 @@ function search_data() {
return $output;
}
/*
** Display the search form and the resulting data:
*/
/**
* Display the search form and the resulting data.
*
* @param $type If set, search only nodes of this type.
* Otherwise, search all types.
* @param $action Form action. Defaults to module.php?mod=search.
* @param $query Query string. Defaults to global $keys.
* @param $options != 0: Render additional form fields/text
* ("Restrict search to", help text, etc).
*/
function search_type($type = 0, $action = 0, $query = 0, $options = 0) {
global $edit;
......@@ -304,7 +333,9 @@ function check_query($text) {
function filter($text) {
foreach (module_list() as $name) {
if (module_hook($name, "filter")) $text = module_invoke($name, "filter", $text);
if (module_hook($name, "filter")) {
$text = module_invoke($name, "filter", $text);
}
}
return $text;
......@@ -327,10 +358,6 @@ function check_file($filename) {
}
}
function file_encode($filename) {
$edit[filedata] = base64_encode(fread($fd, filesize($edit[upload_file])));
}
function format_info($body, $block) {
return "<table><tr><td><table align=\"right\" border=\"1\" width=\"180\"><tr><td>$block</td></tr></table>$body</td></tr></table>\n";
}
......@@ -552,7 +579,9 @@ function field_get($string, $name) {
function field_set($string, $name, $value) {
$rval = ereg_replace(",$name=[^,]+", "", ",$string");
if ($value) $rval .= ($rval == "," ? "" : ",") ."$name=$value";
if ($value) {
$rval .= ($rval == "," ? "" : ",") ."$name=$value";
}
return substr($rval, 1);
}
......
......@@ -191,6 +191,15 @@ function comment_preview($edit) {
comment_view($comment, t("reply to this comment"));
$theme->box(t("Reply"), comment_form($edit));
if ($edit["pid"]) {
$comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$edit[pid]'"));
comment_view($comment, t("reply to this comment"));
}
else {
node_view(node_load(array("nid" => $edit["nid"])));
$edit["pid"] = 0;
}
}
function comment_post($edit) {
......@@ -531,10 +540,32 @@ function comment_render($nid, $cid) {
function comment_search($keys) {
global $PHP_SELF;
$result = db_query("SELECT c.*, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.subject LIKE '%$keys%' OR c.comment LIKE '%$keys%' ORDER BY c.timestamp DESC LIMIT 20");
while ($comment = db_fetch_object($result)) {
$find[$i++] = array("title" => check_output($comment->subject), "link" => (strstr($PHP_SELF, "admin.php") ? "admin.php?mod=comment&op=edit&id=$comment->cid" : "node.php?id=$comment->nid&cid=$comment->cid"), "user" => $comment->name, "date" => $comment->timestamp);
}
// 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
//
// The select statement may optionally provide "nid", which is a secondary
// identifier which is currently used byt the comment module.
//
$find = do_search(array("keys" => $keys,
"type" => "comment",
"select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND s.word like '%'"));
return $find;
}
......@@ -715,4 +746,23 @@ function comment_admin() {
}
}
function comment_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 (comment) had an index update run.
//
// "node_type"'s value is used to identify the node type in the search
// index (commentt in this case).
//
// "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 comments update.
return array("last_update" => "comment_cron_last",
"node_type" => "comment",
"select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE timestamp > ". variable_get("comment_cron_last", 1));
}
?>
......@@ -191,6 +191,15 @@ function comment_preview($edit) {
comment_view($comment, t("reply to this comment"));
$theme->box(t("Reply"), comment_form($edit));
if ($edit["pid"]) {
$comment = db_fetch_object(db_query("SELECT c.*, u.uid, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.cid = '$edit[pid]'"));
comment_view($comment, t("reply to this comment"));
}
else {
node_view(node_load(array("nid" => $edit["nid"])));
$edit["pid"] = 0;
}
}
function comment_post($edit) {
......@@ -531,10 +540,32 @@ function comment_render($nid, $cid) {
function comment_search($keys) {
global $PHP_SELF;
$result = db_query("SELECT c.*, u.name FROM comments c LEFT JOIN users u ON c.uid = u.uid WHERE c.subject LIKE '%$keys%' OR c.comment LIKE '%$keys%' ORDER BY c.timestamp DESC LIMIT 20");
while ($comment = db_fetch_object($result)) {
$find[$i++] = array("title" => check_output($comment->subject), "link" => (strstr($PHP_SELF, "admin.php") ? "admin.php?mod=comment&op=edit&id=$comment->cid" : "node.php?id=$comment->nid&cid=$comment->cid"), "user" => $comment->name, "date" => $comment->timestamp);
}
// 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
//
// The select statement may optionally provide "nid", which is a secondary
// identifier which is currently used byt the comment module.
//
$find = do_search(array("keys" => $keys,
"type" => "comment",
"select" => "select s.lno as lno, c.nid as nid, c.subject as title, c.timestamp as created, u.uid as uid, u.name as name, s.count as count FROM search_index s, comments c LEFT JOIN users u ON c.uid = u.uid WHERE s.lno = c.cid AND s.type = 'comment' AND s.word like '%'"));
return $find;
}
......@@ -715,4 +746,23 @@ function comment_admin() {
}
}
function comment_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 (comment) had an index update run.
//
// "node_type"'s value is used to identify the node type in the search
// index (commentt in this case).
//
// "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 comments update.
return array("last_update" => "comment_cron_last",
"node_type" => "comment",
"select" => "SELECT c.cid as lno, c.subject as text1, c.comment as text2 FROM comments c WHERE timestamp > ". variable_get("comment_cron_last", 1));
}
?>
......@@ -48,7 +48,7 @@ function node_teaser($body) {
function node_invoke($node, $name, $arg = 0) {
if (is_array($node)) {
$function = $node[type] ."_$name";
$function = $node["type"] ."_$name";
}
else if (is_object($node)) {
$function = $node->type ."_$name";
......@@ -261,11 +261,28 @@ function node_perm() {
function node_search($keys) {
global $PHP_SELF;
$result = db_query("SELECT n.nid, n.title, n.created, u.uid, u.name FROM node n LEFT JOIN users u ON n.uid = u.uid WHERE n.status = 1 AND (n.title LIKE '%$keys%' OR n.teaser LIKE '%$keys%' OR n.body LIKE '%$keys%') ORDER BY n.created DESC LIMIT 20");
while ($node = db_fetch_object($result)) {
$find[$i++] = array("title" => check_output($node->title), "link" => (strstr($PHP_SELF, "admin.php") ? "admin.php?mod=node&type=node&op=edit&id=$node->nid" : "node.php?id=$node->nid"), "user" => $node->name, "date" => $node->created);
}
// 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 LEFT 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 $find;
}
......@@ -316,7 +333,7 @@ function node_filter_line($text) {
** Replace '<br>', '<br />', '<p>' and '<p />' by '\n':
*/
$text = eregi_replace("<br>", "\n", $text);
$text = eregi_replace("<br />", "\n", $text);
$text = eregi_replace("<br />", "\n", $text);
$text = eregi_replace("<p>", "\n", $text);
$text = eregi_replace("<p />", "\n", $text);
......@@ -772,7 +789,7 @@ function node_form($edit) {
$function = $edit->type ."_form";
if (function_exists($function)) {
$form .= $function($edit, $help, $error);
$form .= $function($edit, $help, $error, $param);
}
/*
......@@ -857,7 +874,7 @@ function node_form($edit) {
$output .= " </tr>";
$output .= "</table>";
return form($output);
return form($output, ($param["method"] ? $param["method"] : "post"), $param["action"], $param["options"]);
}
function node_add($type) {
......@@ -1176,4 +1193,23 @@ function node_page() {
$theme->footer();
}
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) . ")");
}
?>
......@@ -48,7 +48,7 @@ function node_teaser($body) {
function node_invoke($node, $name, $arg = 0) {
if (is_array($node)) {
$function = $node[type] ."_$name";
$function = $node["type"] ."_$name";
}
else if (is_object($node)) {
$function = $node->type ."_$name";
......@@ -261,11 +261,28 @@ function node_perm() {
function node_search($keys) {
global $PHP_SELF;
$result = db_query("SELECT n.nid, n.title, n.created, u.uid, u.name FROM node n LEFT JOIN users u ON n.uid = u.uid WHERE n.status = 1 AND (n.title LIKE '%$keys%' OR n.teaser LIKE '%$keys%' OR n.body LIKE '%$keys%') ORDER BY n.created DESC LIMIT 20");
while ($node = db_fetch_object($result)) {
$find[$i++] = array("title" => check_output($node->title), "link" => (strstr($PHP_SELF, "admin.php") ? "admin.php?mod=node&type=node&op=edit&id=$node->nid" : "node.php?id=$node->nid"), "user" => $node->name, "date" => $node->created);
}
// 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 LEFT 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 $find;
}
......@@ -316,7 +333,7 @@ function node_filter_line($text) {
** Replace '<br>', '<br />', '<p>' and '<p />' by '\n':
*/
$text = eregi_replace("<br>", "\n", $text);
$text = eregi_replace("<br />", "\n", $text);
$text = eregi_replace("<br />", "\n", $text);
$text = eregi_replace("<p>", "\n", $text);
$text = eregi_replace("<p />", "\n", $text);
......@@ -772,7 +789,7 @@ function node_form($edit) {
$function = $edit->type ."_form";
if (function_exists($function)) {
$form .= $function($edit, $help, $error);
$form .= $function($edit, $help, $error, $param);
}
/*
......@@ -857,7 +874,7 @@ function node_form($edit) {
$output .= " </tr>";
$output .= "</table>";
return form($output);
return form($output, ($param["method"] ? $param["method"] : "post"), $param["action"], $param["options"]);
}
function node_add($type) {
......@@ -1176,4 +1193,23 @@ function node_page() {
$theme->footer();
}
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) . ")");
}
?>
<?php
// $Id$
function search_help() {
$output = "<b>". t("Search hints") ."</b>";
$output .= "<p>". t("The search allows you to search for words in the website's content. You can specify multiple words, and they will all be searched for, and the page that provides the highest hit count returned.") ."</p>";
$output .= "<p>". t("As this website provides multiple content types, the results are grouped by content type as well. If you only wish to search through certain types of content, you can modify the behaviour of this search using the 'Restrict search to' checkboxes below.") ."</p>";
$output .= "<p>". t("To specify that a word is <b>required</b> in the pages that are returned, place a '+' in front of it like this '+walk'.") ."</p>";
$output .= "<p>". t("You can also use wildcards, so 'walk*' will match 'walk', 'walking', 'walker', 'walkable' and 'walkability'... Alright you got me, I made the last ones up.") ."</p>";
$output .= "<p>". t("Searches are not case sensitive, regardless of how you type them all letters will be searched for in lower case") ."</p>";
$output .= "<b>". t("Words excluded from the search") ."</b>";
$output .= "<p>". t("Some words which commonly occur are filtered out by the searching process, these are commonly called 'noisewords'. Examples are 'a, at, and, are, as, ask', and the list goes on. Words shorter than ". variable_get("minimum_word_size", 2) ." letters are also filtered from the search index.");
$output .= "<p>". t("These words will never be matched when specified, even if they appear in the node you are searching for.");
return $output;
}
/**
* Return an array of valid search access permissions
*/
function search_perm() {
return array("search content");
return array("search content", "administer search");
}
/**
* Return an array of links to be displayed
*
* @param $type The type of page requesting the link
*
*/
function search_link($type) {
if ($type == "page" && user_access("search content")) {
$links[] = "<a href=\"module.php?mod=search\" title=\"". t("Search for older content.") ."\">". t("search") ."</a>";
}
if ($type == "admin" && user_access("administer search")) {
$links[] = "<a href=\"admin.php?mod=search\">". t("search") ."</a>";
}
return $links ? $links : array();
}
/*
function search_item($item, $type) {
$output .= "<p>";
$output .= " <b><u><a href=\"". $item["link"] ."\">". $item["title"] ."</a></u></b><br />";
$output .= " <small>$type ". ($item["user"] ? " - ". $item["user"] : "") ."". ($item["date"] ? " - ". format_date($item["date"], "small") : "") ."</small>";
$output .= "</p>";
/**
* search engine administration actions
*
*/
function search_admin() {
global $op, $id, $edit;
// Only allow people with sufficient access.
if (user_access("administer search")) {
switch ($op) {
case "Submit":
print status(search_save($edit));
break;
case "reindex":
search_invalidate();
print t("index invalidated") ."<br />\n";
search_cron();
print t("index recreated") ."<br /><hr />\n";
break;
default:
}
print search_display(array("noisewords" => variable_get("noisewords", ""), "minimum_word_size" => variable_get("minimum_word_size", 2), "help_pos" => variable_get("help_pos", 1), "remove_short" => variable_get("remove_short", "0")));
}
return;
}
/**
* perform a regularly run action across all modules that have the
* <module>_update_index function in them.
*
*/
function search_cron() {
foreach (module_list() as $module) {
$module_array = module_invoke($module, "update_index");
if ($module_array) {
update_index($module_array);
}
$module_array = null;
}
return;
}
/**
* Perform a search on a word(s)
*
* Search function called by each node that supports the indexed search
*
* @param $search_array an array as returned from <module>_search
* of type array("keys" => ...,
* "type" => ..., "select" => ...)
* see node_search in node.module for an
* explanation of array items
*/
function do_search($search_array) {
global $PHP_SELF;
$keys = strtolower($search_array["keys"]);
$type = $search_array["type"];
$select = $search_array["select"];
// Replace wildcards with mysql wildcards
$keys = str_replace("*", "%", $keys);
// Split the words entered into an array
$words = explode(" ", $keys);
foreach ($words as $word) {
// If the word is too short, and we've got it set to skip them,
// loop
if (strlen($word) < variable_get("remove_short", 0)) {
continue;
}
// If the word is preceeded by a "+", then this word is required, and
// pages that match other words, but not this one will be removed
if (substr($word, 0, 1) == "+") {
$word = substr($word, 1);
$required = 1;
$reqcount++;
$remove_rest = 1;
}
else {
$required = 0;
}
// Put the next search word into the query and do the query
$query = preg_replace("'\%'", $word, $select);
$result = db_query($query);
// If we got any results
if (db_num_rows($result) != 0) {
$found = 1;
// Create an in memory array of the results,
while ($row = db_fetch_array($result)) {
$lno = $row["lno"];
$nid = $row["nid"];
$title = $row["title"];
$created = $row["created"];
$uid = $row["uid"];
$name = $row["name"];
$count = $row["count"];