Commit be8e898d authored by Dries's avatar Dries

- Uhm.  Rewrote the module system: less code clutter, less run-time
  overhead, and a lot better (simpler) module API.  I had to edit a
  LOT of files to get this refactored but I'm sure it was worth the
  effort.

  For module writers / maintainers:

  None of the hooks changed, so 95% of the old modules should still
  work.  You can remove some code instead as "$module = array(...)"
  just became obsolete.  Also - and let's thank God for this - the
  global variable "$repository" has been eliminated to avoid modules
  relying on, and poking in drupal's internal data structures.  Take
  a look at include/module.inc to investigate the details/changes.

- Improved design of the content modules "story", "book" and "node"
  (to aid smooth integration of permisions + moderate.module).  I'm
  still working on the permissions but I got side tracked for which
  I "Oops!".
parent 16818777
......@@ -186,13 +186,6 @@ function account_content_save($edit) {
function account_user($uname) {
global $user, $status, $theme;
function module($name, $module, $username) {
global $theme;
if ($module[account] && $block = $module[account]($username, "account", "view")) {
if ($block[content]) $theme->box($block[subject], $block[content]);
}
}
if ($user->id && $user->userid == $uname) {
$output .= "<TABLE BORDER=\"0\" CELLPADDING=\"2\" CELLSPACING=\"2\">\n";
$output .= " <TR><TD ALIGN=\"right\"><B>". t("Username") .":</B></TD><TD>$user->userid</TD></TR>\n";
......@@ -208,17 +201,16 @@ function module($name, $module, $username) {
$theme->footer();
}
elseif ($uname && $account = account_get_user($uname)) {
$block1 .= "<TABLE BORDER=\"0\" CELLPADDING=\"1\" CELLSPACING=\"1\">\n";
$block1 .= " <TR><TD ALIGN=\"right\"><B>". t("Username") .":</B></TD><TD>$account->userid</TD></TR>\n";
$block1 .= " <TR><TD ALIGN=\"right\"><B>". t("E-mail") .":</B></TD><TD>". format_email($account->fake_email) ."</TD></TR>\n";
$block1 .= " <TR><TD ALIGN=\"right\"><B>". t("Homepage") .":</B></TD><TD>". format_url($account->url) ."</TD></TR>\n";
$block1 .= " <TR><TD ALIGN=\"right\"><B>". t("Bio") .":</B></TD><TD>". check_output($account->bio) ."</TD></TR>\n";
$block1 .= "</TABLE>\n";
$output .= "<TABLE BORDER=\"0\" CELLPADDING=\"1\" CELLSPACING=\"1\">\n";
$output .= " <TR><TD ALIGN=\"right\"><B>". t("Username") .":</B></TD><TD>$account->userid</TD></TR>\n";
$output .= " <TR><TD ALIGN=\"right\"><B>". t("E-mail") .":</B></TD><TD>". format_email($account->fake_email) ."</TD></TR>\n";
$output .= " <TR><TD ALIGN=\"right\"><B>". t("Homepage") .":</B></TD><TD>". format_url($account->url) ."</TD></TR>\n";
$output .= " <TR><TD ALIGN=\"right\"><B>". t("Bio") .":</B></TD><TD>". check_output($account->bio) ."</TD></TR>\n";
$output .= "</TABLE>\n";
// Display account information:
$theme->header();
if ($block1) $theme->box(strtr(t("%a's user information"), array("%a" => $uname)), $block1);
module_iterate("module", $uname);
$theme->box(strtr(t("%a's user information"), array("%a" => $uname)), $output);
$theme->footer();
}
else {
......
......@@ -10,11 +10,11 @@ function status($message) {
}
function admin_page($mod) {
global $repository, $menu, $modules, $user;
global $menu, $user;
function module($name, $module) {
global $menu, $modules, $user;
if ($module["admin"] && user_access($user, $name)) $output .= "<A HREF=\"admin.php?mod=$name\">$name</A> | ";
function module($name) {
global $menu, $user;
if (function_exists($name. "_admin") && user_access($user, $name)) $output .= "<A HREF=\"admin.php?mod=$name\">$name</A> | ";
$menu .= $output;
}
......@@ -32,18 +32,9 @@ function module($name, $module) {
</STYLE>
<BODY BGCOLOR="#FFFFFF" LINK="#005599" VLINK="#004499" ALINK="#FF0000">
<H1>Administration</H1>
<?php
ksort($repository);
module_iterate("module");
?>
<?php module_iterate("module"); ?>
<HR><?php echo $menu; ?><A HREF="index.php">home</A><HR>
<?php
if (user_access($user, $mod)) module_execute($mod, "admin");
?>
<?php if (user_access($user, $mod)) module_invoke($mod, "admin"); ?>
</BODY>
</HTML>
<?php
......
......@@ -3,16 +3,9 @@
include_once "includes/common.inc";
function cron_run() {
global $repository;
$time = time();
$result = db_query("SELECT * FROM crons WHERE $time - timestamp > scheduled");
while ($task = db_fetch_object($result)) {
if ($repository[$task->module]["cron"]) $repository[$task->module]["cron"]();
}
while ($task = db_fetch_object($result)) module_invoke($task->module, "cron");
db_query("UPDATE crons SET timestamp = $time WHERE $time - timestamp > scheduled");
}
......
......@@ -2,9 +2,9 @@
include_once "includes/common.inc";
function export($name, $module) {
function export($name) {
global $REQUEST_URI;
module_execute($name, "export", explode("/", $REQUEST_URI));
module_invoke($name, "export", explode("/", $REQUEST_URI));
}
module_iterate("export");
......
......@@ -24,7 +24,7 @@ function throttle($type, $rate) {
if ($throttle = db_fetch_object(db_query("SELECT * FROM watchdog WHERE type = '$type' AND hostname = '". getenv("REMOTE_ADDR") ."' AND ". time() ." - timestamp < $rate"))) {
watchdog("warning", "throttle: '". getenv("REMOTE_ADDR") ."' exceeded submission rate - $throttle->type");
header("Location: error.php?op=throttle");
exit();
die("submission rate exceeded");
}
else {
watchdog($type, "throttle control");
......@@ -196,6 +196,7 @@ function form_submit($value) {
include_once "includes/node.inc";
user_init();
module_init();
$locale = locale_init();
$conf = variable_init();
$theme = theme_init();
......
<?php
// applies function $function to every known module:
// initialize modules:
function module_init() {
module_list();
}
// apply function $function to every known module:
function module_iterate($function, $argument = "") {
global $repository;
foreach ($repository as $name=>$module) {
$function($name, $module, $argument);
foreach (module_list() as $name) $function($name, $argument);
}
// invoke hook $hook of module $name with optional arguments:
function module_invoke($name, $hook, $argument = "") {
$function = $name ."_". $hook;
if (function_exists($function)) return $function($argument);
}
// return true if module $name supports hook $hook, and false otherwise:
function module_is_hook($name, $hook) {
return function_exists($name ."_". $hook);
}
// return an array of module names (includes lazy module loading):
function module_list() {
static $list;
if (!$list) {
$handle = opendir("modules");
$list = array();
while ($file = readdir($handle)) {
if (".module" == substr($file, -7)) {
$filename = substr($file, 0, -7);
include "modules/$filename.module";
$list[$filename] = $filename;
}
}
closedir($handle);
}
return $list;
}
// executes hook $hook of module $module with optional arguments:
function module_execute($module, $hook, $argument = "") {
global $repository;
return ($repository[$module][$hook]) ? $repository[$module][$hook]($argument) : "";
// return 1 if module $name exists, 0 otherwise:
function module_exist($name) {
$list = module_list();
return ($list[$name]) ? 1 : 0;
}
// returns true if module $module supports hook $hook, and false otherwise:
function module_hook($module, $hook) {
global $repository;
return $repository[$module][$hook];
// return 1 if module $name implements hook $hook, 0 otherwise:
function module_hook($name, $hook) {
return function_exists($name ."_". $hook);
}
// rehashes the crons:
function module_rehash_crons($name, $module) {
if ($module["cron"]) {
// rehash module-exported crons:
function module_rehash_crons($name) {
if (module_hook($name, "cron")) {
if (!db_fetch_object(db_query("SELECT * FROM crons WHERE module = '$name'"))) {
db_query("INSERT INTO crons (module, scheduled, timestamp) VALUES ('$name', '172800', '0')");
}
......@@ -32,10 +64,10 @@ function module_rehash_crons($name, $module) {
}
}
// rehashes the blocks:
function module_rehash_blocks($name, $module) {
// rehash module-exported blocks:
function module_rehash_blocks($name) {
db_query("UPDATE blocks SET remove = '1' WHERE module = '$name'");
if ($module["block"] && $blocks = $module["block"]()) {
if ($blocks = module_invoke($name, "block")) {
foreach ($blocks as $offset=>$block) {
foreach ($block as $item=>$data) {
$block[$item] = addslashes($data);
......@@ -51,22 +83,20 @@ function module_rehash_blocks($name, $module) {
db_query("DELETE FROM blocks WHERE module = '$name' AND remove = '1'");
}
// rehashes a module:
// rehash a module:
function module_rehash($name) {
global $repository;
if ($module = $repository[$name]) {
if (module_exist($name)) {
$result = db_query("SELECT * FROM modules WHERE name = '$name'");
if (!$object = db_fetch_object($result)) {
db_query("INSERT INTO modules (name) VALUES ('$name')");
}
// rehash crons (if necessary):
module_rehash_crons($name, $module);
// rehash module-exported crons (if necessary):
module_rehash_crons($name);
// rehash blocks (if necessary):
module_rehash_blocks($name, $module);
// rehash module-exported blocks (if necessary):
module_rehash_blocks($name);
}
else {
// remove all reference to module:
......@@ -76,16 +106,4 @@ function module_rehash($name) {
}
}
// load modules into repository:
$handle = opendir("modules");
$repository = array();
while ($file = readdir($handle)) {
if (".module" == substr($file, -7)) {
$filename = substr($file, 0, -7);
include "modules/$filename.module";
$repository[$filename] = $module;
}
}
closedir($handle);
?>
?>
\ No newline at end of file
......@@ -11,7 +11,7 @@ function search_form($keys) {
function search_data($keys, $type) {
if ($keys && $type) {
$result = module_execute($type, "find", check_input($keys));
$result = module_invoke($type, "find", check_input($keys));
foreach ($result as $entry) {
$output .= "<P>\n";
$output .= " <B><U><A HREF=\"$entry[link]\">$entry[title]</A></U></B><BR>";
......
......@@ -13,31 +13,22 @@ function theme_init() {
}
function theme_link($separator = " | ") {
global $repository;
$links[] = "<A HREF=\"index.php\">". t("home") ."</A>";
$links[] = "<A HREF=\"search.php\">". t("search") ."</A>";
$links[] = "<A HREF=\"submit.php\">". t("submit") ."</A>";
if ($repository[forum]) $links[] = "<A HREF=\"module.php?mod=forum\">".t("forum") ."</A>";
if ($repository[diary]) $links[] = "<A HREF=\"module.php?mod=diary\">". t("diary") ."</A>";
$links[] = "<A HREF=\"account.php\">". t("account") ."</A>";
if ($repository[book]) $links[] = "<A HREF=\"module.php?mod=book\">". t("handbook") ."</A>";
if (module_exist("forum")) $links[] = "<A HREF=\"module.php?mod=forum\">".t("forum") ."</A>";
if (module_exist("diary")) $links[] = "<A HREF=\"module.php?mod=diary\">". t("diary") ."</A>";
if (module_exist("book")) $links[] = "<A HREF=\"module.php?mod=book\">". t("handbook") ."</A>";
return implode($separator, $links);
}
function theme_menu($name, $module) {
global $menu;
if ($module["menu"]) $menu = ($menu) ? array_merge($menu, $module["menu"]()) : $module["menu"]();
}
function theme_account($theme) {
global $user, $links, $menu;
global $user;
if ($user->id) {
module_iterate("theme_menu");
// Display account settings:
$content .= "<LI><A HREF=\"account.php?op=track&topic=comments\">". t("track your comments") ."</A></LI>\n";
$content .= "<LI><A HREF=\"account.php?op=track&topic=nodes\">". t("track your nodes") ."</A></LI>\n";
......@@ -53,10 +44,12 @@ function theme_account($theme) {
$content .= "<P>\n";
}
if ($menu) {
foreach ($menu as $link) $content .= "<LI>$link</LI>\n";
$content .= "<P>\n";
foreach (module_list() as $name) {
if ($links = module_invoke($name, "menu")) {
foreach ($links as $link) $content .= "<LI>$link</LI>\n";
}
}
if ($link) $content .= "<P>\n";
$content .= "<LI><A HREF=\"account.php?op=logout\">". t("logout") ."</A></LI>\n";
......@@ -92,7 +85,7 @@ function theme_blocks($region, $theme) {
if ($user->id) $result = db_query("SELECT * FROM blocks b LEFT JOIN layout l ON b.name = l.block WHERE (b.status = 2 OR (b.status = 1 AND l.user = '$user->id'))". (($region == "left" || $region == "right") ? ($region == "left" ? " AND b.region = 0" : " AND b.region = 1") : "") ." ORDER BY weight");
else $result = db_query("SELECT * FROM blocks WHERE status = 2". (($region == "left" || $region == "right") ? ($region == "left" ? " AND region = 0" : " AND region = 1") : "") ." ORDER BY weight");
while ($block = db_fetch_object($result)) {
$blocks = module_execute($block->module, "block");
$blocks = module_invoke($block->module, "block");
$theme->box(t($blocks[$block->offset]["subject"]), $blocks[$block->offset]["content"]);
}
break;
......
......@@ -2,7 +2,7 @@
include_once "includes/common.inc";
if (variable_get(dev_timing, 0)) timer_start();
module_execute($mod, "page");
module_invoke($mod, "page");
if (variable_get(dev_timing, 0)) timer_print();
?>
<?php
$module = array("help" => "account_help",
"find" => "account_find",
"admin" => "account_admin");
function account_help() {
?>
<P>The account-module is responsible for maintaining the user database. It automatically handles tasks like registration, authentication, access control, password retrieval, user settings and much more.</P>
......@@ -153,9 +149,9 @@ function account_edit_save($name, $edit) {
function account_edit($name) {
global $access, $account;
function access($name, $module) {
function access($name) {
global $access, $account;
if ($module["admin"]) $access .= "<OPTION VALUE=\"$name\"". (user_access($account, $name) ? " SELECTED" : "") .">$name</OPTION>";
if (module_hook($name, "admin")) $access .= "<OPTION VALUE=\"$name\"". (user_access($account, $name) ? " SELECTED" : "") .">$name</OPTION>";
}
$status = array("blocked", "not confirmed", "open");
......
<?php
$module = array("page" => "block_page",
"help" => "block_help",
"admin" => "block_admin");
function block_help() {
?>
<P>Blocks are the boxes visible in the side bars on the left- and right-hand side of the website. They are either exported by the engine or by any of the active modules. To really get your teeth into a drupal website, you are going to have to deal with blocks and administering blocks in a fairly sophisticated fashion. This means you will need to understand how the block placement strategy works.</P>
......@@ -23,7 +19,7 @@ function block_page() {
while ($block = db_fetch_object($result)) {
if ($state % 3 == 0) print " <TR>\n";
print " <TD ALIGN=\"center\" VALIGN=\"top\" WIDTH=\"33%\">\n";
$blocks = module_execute($block->module, "block");
$blocks = module_invoke($block->module, "block");
$theme->box($blocks[$block->offset]["subject"], $blocks[$block->offset]["content"]);
print " </TD>\n";
if ($state % 3 == 2) print " </TR>\n";
......@@ -40,8 +36,6 @@ function block_admin_save($edit) {
}
function block_admin_display() {
global $repository;
$result = db_query("SELECT * FROM blocks ORDER BY module");
// Generate output:
......@@ -50,7 +44,7 @@ function block_admin_display() {
$output .= " <TR><TH>block</TH><TH>module</TH><TH>status</TH><TH>weight</TH><TH>region</TH></TR>\n";
while ($block = db_fetch_object($result)) {
$module = ($repository[$block->module]["admin"]) ? "<A HREF=\"admin.php?mod=$block->module\">$block->module</A>" : $block->module;
$module = module_hook($block->module, "admin") ? "<A HREF=\"admin.php?mod=$block->module\">$block->module</A>" : $block->module;
$status .= "<SELECT NAME=\"edit[$block->name][status]\">\n";
$status .= " <OPTION VALUE=\"2\"". (($block->status == 2) ? " SELECTED" : "") .">enabled: always</OPTION>\n";
......
<?php
$module = array("page" => "block_page",
"help" => "block_help",
"admin" => "block_admin");
function block_help() {
?>
<P>Blocks are the boxes visible in the side bars on the left- and right-hand side of the website. They are either exported by the engine or by any of the active modules. To really get your teeth into a drupal website, you are going to have to deal with blocks and administering blocks in a fairly sophisticated fashion. This means you will need to understand how the block placement strategy works.</P>
......@@ -23,7 +19,7 @@ function block_page() {
while ($block = db_fetch_object($result)) {
if ($state % 3 == 0) print " <TR>\n";
print " <TD ALIGN=\"center\" VALIGN=\"top\" WIDTH=\"33%\">\n";
$blocks = module_execute($block->module, "block");
$blocks = module_invoke($block->module, "block");
$theme->box($blocks[$block->offset]["subject"], $blocks[$block->offset]["content"]);
print " </TD>\n";
if ($state % 3 == 2) print " </TR>\n";
......@@ -40,8 +36,6 @@ function block_admin_save($edit) {
}
function block_admin_display() {
global $repository;
$result = db_query("SELECT * FROM blocks ORDER BY module");
// Generate output:
......@@ -50,7 +44,7 @@ function block_admin_display() {
$output .= " <TR><TH>block</TH><TH>module</TH><TH>status</TH><TH>weight</TH><TH>region</TH></TR>\n";
while ($block = db_fetch_object($result)) {
$module = ($repository[$block->module]["admin"]) ? "<A HREF=\"admin.php?mod=$block->module\">$block->module</A>" : $block->module;
$module = module_hook($block->module, "admin") ? "<A HREF=\"admin.php?mod=$block->module\">$block->module</A>" : $block->module;
$status .= "<SELECT NAME=\"edit[$block->name][status]\">\n";
$status .= " <OPTION VALUE=\"2\"". (($block->status == 2) ? " SELECTED" : "") .">enabled: always</OPTION>\n";
......
<?php
$module = array("find" => "book_find",
"page" => "book_page",
"user" => "book_user",
"admin" => "book_admin",
"export" => "book_export");
class Book {
function Book($nid, $userid, $title, $body, $parent, $weight, $timestamp) {
$this->nid = $nid;
$this->userid = $userid;
$this->title = $title;
$this->body = $body;
$this->parent = $parent;
$this->weight = $weight;
$this->timestamp = $timestamp;
function Book($book) {
$this = new Node($book);
$this->body = $book[body];
$this->parent = $book[parent];
$this->weight = $book[weight];
}
}
......@@ -184,7 +175,7 @@ function book_admin() {
print book_tree();
break;
case t("Preview"):
book_view(new Book(($edit[nid] ? $edit[nid] : -1), ($edit[userid] ? $edit[userid] : $user->userid), $edit[title], $edit[body], $edit[parent], $edit[weight], ($edit[timestamp] ? $edit[timestamp] : time())));
book_view(new Book($edit));
print book_form($edit);
break;
case t("Submit"):
......@@ -235,7 +226,7 @@ function book_user() {
$theme->box($title, book_update($id));
break;
case t("Preview"):
book_view(new Book(($edit[nid] ? $edit[nid] : -1), $user->userid, $edit[title], $edit[body], $edit[parent], $edit[weight], ($edit[timestamp] ? $edit[timestamp] : time())));
book_view(new Book($edit));
$theme->box($title, book_form($edit));
break;
case t("Submit"):
......
<?php
$module = array("find" => "book_find",
"page" => "book_page",
"user" => "book_user",
"admin" => "book_admin",
"export" => "book_export");
class Book {
function Book($nid, $userid, $title, $body, $parent, $weight, $timestamp) {
$this->nid = $nid;
$this->userid = $userid;
$this->title = $title;
$this->body = $body;
$this->parent = $parent;
$this->weight = $weight;
$this->timestamp = $timestamp;
function Book($book) {
$this = new Node($book);
$this->body = $book[body];
$this->parent = $book[parent];
$this->weight = $book[weight];
}
}
......@@ -184,7 +175,7 @@ function book_admin() {
print book_tree();
break;
case t("Preview"):
book_view(new Book(($edit[nid] ? $edit[nid] : -1), ($edit[userid] ? $edit[userid] : $user->userid), $edit[title], $edit[body], $edit[parent], $edit[weight], ($edit[timestamp] ? $edit[timestamp] : time())));
book_view(new Book($edit));
print book_form($edit);
break;
case t("Submit"):
......@@ -235,7 +226,7 @@ function book_user() {
$theme->box($title, book_update($id));
break;
case t("Preview"):
book_view(new Book(($edit[nid] ? $edit[nid] : -1), $user->userid, $edit[title], $edit[body], $edit[parent], $edit[weight], ($edit[timestamp] ? $edit[timestamp] : time())));
book_view(new Book($edit));
$theme->box($title, book_form($edit));
break;
case t("Submit"):
......
<?php
$module = array("help" => "box_help",
"block" => "box_block",
"admin" => "box_admin");
function box_help() {
?>
<P>The content of the site can be almost entirely altered through <I>boxes</I>. Simply put, boxes are small bits of text, HTML or PHP code which will get plugged into the site just like any other block. Boxes are typically used to add custom blocks to the site.</P>
......@@ -96,8 +91,7 @@ function box_admin_delete($id) {
}
function box_admin_rehash() {
global $repository;
module_rehash_blocks("box", $repository["box"]);
module_rehash_blocks("box");
}
function box_admin_edit($id) {
......
<?php
$module = array();
class Calendar {
var $date;
......
<?php
$module = array("block" => "calendar_block");
function calendar_block() {
global $date;
......
<?php
$module = array("find" => "comment_find",
"admin" => "comment_admin");
function comment_find($keys) {
global $user;
$find = array();
......
<?php
$module = array("find" => "comment_find",
"admin" => "comment_admin");
function comment_find($keys) {
global $user;
$find = array();
......
<?php
$module = array("help" => "cron_help",
"admin" => "cron_admin");
function cron_help() {
?>
<P>Cron (which stands for chronograph) is a periodic command scheduler: it executes commands at intervals specified in seconds. It can be used to control the execution of daily, weekly and monthly jobs (or anything with a period of <i>n</i> seconds). Automating tasks is one of the best ways to keep a system running smoothly, and if most of your administration does not require your direct involvement, cron is an ideal solution.</P>
......@@ -18,9 +15,8 @@ function cron_save($edit) {
}
function cron_execute($name) {
global $repository;
watchdog("message", "cron: executed '". $name ."_cron()'");
$repository[$name]["cron"]();
module_invoke($name, "cron");
db_query("UPDATE crons SET timestamp = ". time() ." WHERE module = '$name'");
}
......
<?php
$module = array("cron" => "cvs_cron",
"conf" => "cvs_conf",
"page" => "cvs_page");
function cvs_cron() {
$result = db_query("SELECT * FROM cvs WHERE status = '0' ORDER BY timestamp DESC LIMIT 50");
......
<?php
$module = array("find" => "diary_find",
"help" => "diary_help",
"menu" => "diary_menu",
"page" => "diary_page",
"admin" => "diary_admin",
"block" => "diary_block",
"export" => "diary_export");
include_once "includes/common.inc";
function diary_find($keys) {
global $user;
$find = array();
......
<?php
$module = array("page" => "drupal_page");
function drupal_page() {
global $theme, $user;
......
<?php
$module = array("page" => "drupal_page");
function drupal_page() {
global $theme, $user;
......
<?php
$module = array("page" => "forum_page",
"admin" => "forum_admin");
$format = array(0 => HTML, 1 => PHP, 2 => text);
function forum_status() {
......
<?php
$module = array("page" => "forum_page",
"admin" => "forum_admin");
$format = array(0 => HTML, 1 => PHP, 2 => text);
function forum_status() {
......
<?php
$module = array("page" => "headline_page",
"cron" => "headline_cron",
"help" => "headline_help",
"block" => "headline_block",
"admin" => "headline_admin",
"export" => "headline_export");
include_once "includes/common.inc";
include_once "modules/backend.class";
function headline_blocks() {
......@@ -151,8 +143,7 @@ function headline_admin_add() {
}
function headline_admin_rehash() {
global $repository;
module_rehash_blocks("headline", $repository["headline"]);
module_rehash_blocks("headline");
}
function headline_admin() {
......
<?php
// RSS 1.0 headline exporter (v0.1)
//
// Kristjan Jansen -- kika@sloleht.ee
$module = array("export" => "headlineRSS10_export");
include_once "includes/common.inc";
include_once "modules/backend.class";
function headlineRSS10_export($uri) {
......
<?php
$module = array("admin" => "help_admin");
function help_module($name, $module) {
print "<H2><A NAME=\"$name\">". ucfirst($name) ." module</A></H2>";
if ($module["help"]) $module["help"]();