changeset 1:c1f4ac30525a 6.0

Drupal 6.0
author Franck Deroche <webmaster@defr.org>
date Tue, 23 Dec 2008 14:28:28 +0100
parents 5a113a1c4740
children 85b5b336180c
files .htaccess CHANGELOG.txt COPYRIGHT.txt INSTALL.mysql.txt INSTALL.pgsql.txt INSTALL.txt LICENSE.txt MAINTAINERS.txt UPGRADE.txt cron.php includes/actions.inc includes/batch.inc includes/bootstrap.inc includes/cache-install.inc includes/cache.inc includes/common.inc includes/database.inc includes/database.mysql-common.inc includes/database.mysql.inc includes/database.mysqli.inc includes/database.pgsql.inc includes/file.inc includes/form.inc includes/image.gd.inc includes/image.inc includes/install.inc includes/install.mysql.inc includes/install.mysqli.inc includes/install.pgsql.inc includes/language.inc includes/locale.inc includes/mail.inc includes/menu.inc includes/module.inc includes/pager.inc includes/path.inc includes/session.inc includes/tablesort.inc includes/theme.inc includes/theme.maintenance.inc includes/unicode.inc includes/xmlrpc.inc includes/xmlrpcs.inc index.php install.php misc/ahah.js misc/arrow-asc.png misc/arrow-desc.png misc/autocomplete.js misc/batch.js misc/blog.png misc/collapse.js misc/draggable.png misc/drupal.js misc/druplicon.png misc/farbtastic/farbtastic.css misc/farbtastic/farbtastic.js misc/farbtastic/marker.png misc/farbtastic/mask.png misc/farbtastic/wheel.png misc/favicon.ico misc/feed.png misc/form.js misc/forum-closed.png misc/forum-default.png misc/forum-hot-new.png misc/forum-hot.png misc/forum-new.png misc/forum-sticky.png misc/grippie.png misc/jquery.form.js misc/jquery.js misc/menu-collapsed-rtl.png misc/menu-collapsed.png misc/menu-expanded.png misc/menu-leaf.png misc/powered-black-135x42.png misc/powered-black-80x15.png misc/powered-black-88x31.png misc/powered-blue-135x42.png misc/powered-blue-80x15.png misc/powered-blue-88x31.png misc/powered-gray-135x42.png misc/powered-gray-80x15.png misc/powered-gray-88x31.png misc/print-rtl.css misc/print.css misc/progress.gif misc/progress.js misc/tabledrag.js misc/tableheader.js misc/tableselect.js misc/teaser.js misc/textarea.js misc/throbber.gif misc/tree-bottom.png misc/tree.png misc/watchdog-error.png misc/watchdog-ok.png misc/watchdog-warning.png misc/xml.png modules/README.txt modules/aggregator/aggregator-feed-source.tpl.php modules/aggregator/aggregator-item.tpl.php modules/aggregator/aggregator-rtl.css modules/aggregator/aggregator-summary-item.tpl.php modules/aggregator/aggregator-summary-items.tpl.php modules/aggregator/aggregator-wrapper.tpl.php modules/aggregator/aggregator.admin.inc modules/aggregator/aggregator.css modules/aggregator/aggregator.info modules/aggregator/aggregator.install modules/aggregator/aggregator.module modules/aggregator/aggregator.pages.inc modules/block/block-admin-display-form.tpl.php modules/block/block.admin.inc modules/block/block.css modules/block/block.info modules/block/block.install modules/block/block.js modules/block/block.module modules/blog/blog.info modules/blog/blog.module modules/blog/blog.pages.inc modules/blogapi/blogapi.info modules/blogapi/blogapi.install modules/blogapi/blogapi.module modules/book/book-all-books-block.tpl.php modules/book/book-export-html.tpl.php modules/book/book-navigation.tpl.php modules/book/book-node-export-html.tpl.php modules/book/book-rtl.css modules/book/book.admin.inc modules/book/book.css modules/book/book.info modules/book/book.install modules/book/book.module modules/book/book.pages.inc modules/color/color-rtl.css modules/color/color.css modules/color/color.info modules/color/color.install modules/color/color.js modules/color/color.module modules/color/images/hook-rtl.png modules/color/images/hook.png modules/color/images/lock.png modules/comment/comment-folded.tpl.php modules/comment/comment-rtl.css modules/comment/comment-wrapper.tpl.php modules/comment/comment.admin.inc modules/comment/comment.css modules/comment/comment.info modules/comment/comment.install modules/comment/comment.js modules/comment/comment.module modules/comment/comment.pages.inc modules/comment/comment.tpl.php modules/contact/contact.admin.inc modules/contact/contact.info modules/contact/contact.install modules/contact/contact.module modules/contact/contact.pages.inc modules/dblog/dblog-rtl.css modules/dblog/dblog.admin.inc modules/dblog/dblog.css modules/dblog/dblog.info modules/dblog/dblog.install modules/dblog/dblog.module modules/filter/filter.admin.inc modules/filter/filter.info modules/filter/filter.install modules/filter/filter.module modules/filter/filter.pages.inc modules/forum/forum-icon.tpl.php modules/forum/forum-list.tpl.php modules/forum/forum-rtl.css modules/forum/forum-submitted.tpl.php modules/forum/forum-topic-list.tpl.php modules/forum/forum-topic-navigation.tpl.php modules/forum/forum.admin.inc modules/forum/forum.css modules/forum/forum.info modules/forum/forum.install modules/forum/forum.module modules/forum/forum.pages.inc modules/forum/forums.tpl.php modules/help/help-rtl.css modules/help/help.admin.inc modules/help/help.css modules/help/help.info modules/help/help.module modules/locale/locale.css modules/locale/locale.info modules/locale/locale.install modules/locale/locale.module modules/menu/menu.admin.inc modules/menu/menu.info modules/menu/menu.install modules/menu/menu.module modules/node/content_types.inc modules/node/node-rtl.css modules/node/node.admin.inc modules/node/node.css modules/node/node.info modules/node/node.install modules/node/node.module modules/node/node.pages.inc modules/node/node.tpl.php modules/openid/login-bg.png modules/openid/openid.css modules/openid/openid.inc modules/openid/openid.info modules/openid/openid.install modules/openid/openid.js modules/openid/openid.module modules/openid/openid.pages.inc modules/openid/xrds.inc modules/path/path.admin.inc modules/path/path.info modules/path/path.module modules/php/php.info modules/php/php.install modules/php/php.module modules/ping/ping.info modules/ping/ping.module modules/poll/poll-bar-block.tpl.php modules/poll/poll-bar.tpl.php modules/poll/poll-results-block.tpl.php modules/poll/poll-results.tpl.php modules/poll/poll-rtl.css modules/poll/poll-vote.tpl.php modules/poll/poll.css modules/poll/poll.info modules/poll/poll.install modules/poll/poll.module modules/poll/poll.pages.inc modules/profile/profile-block.tpl.php modules/profile/profile-listing.tpl.php modules/profile/profile-wrapper.tpl.php modules/profile/profile.admin.inc modules/profile/profile.css modules/profile/profile.info modules/profile/profile.install modules/profile/profile.js modules/profile/profile.module modules/profile/profile.pages.inc modules/search/search-block-form.tpl.php modules/search/search-result.tpl.php modules/search/search-results.tpl.php modules/search/search-rtl.css modules/search/search-theme-form.tpl.php modules/search/search.admin.inc modules/search/search.css modules/search/search.info modules/search/search.install modules/search/search.module modules/search/search.pages.inc modules/statistics/statistics.admin.inc modules/statistics/statistics.info modules/statistics/statistics.install modules/statistics/statistics.module modules/statistics/statistics.pages.inc modules/syslog/syslog.info modules/syslog/syslog.module modules/system/admin-rtl.css modules/system/admin.css modules/system/block.tpl.php modules/system/box.tpl.php modules/system/defaults-rtl.css modules/system/defaults.css modules/system/maintenance-page.tpl.php modules/system/maintenance.css modules/system/page.tpl.php modules/system/system-menus-rtl.css modules/system/system-menus.css modules/system/system-rtl.css modules/system/system.admin.inc modules/system/system.css modules/system/system.info modules/system/system.install modules/system/system.js modules/system/system.module modules/taxonomy/taxonomy.admin.inc modules/taxonomy/taxonomy.css modules/taxonomy/taxonomy.info modules/taxonomy/taxonomy.install modules/taxonomy/taxonomy.js modules/taxonomy/taxonomy.module modules/taxonomy/taxonomy.pages.inc modules/throttle/throttle.admin.inc modules/throttle/throttle.info modules/throttle/throttle.module modules/tracker/tracker.css modules/tracker/tracker.info modules/tracker/tracker.module modules/tracker/tracker.pages.inc modules/translation/translation.info modules/translation/translation.module modules/translation/translation.pages.inc modules/trigger/trigger.admin.inc modules/trigger/trigger.info modules/trigger/trigger.install modules/trigger/trigger.module modules/update/update-rtl.css modules/update/update.compare.inc modules/update/update.css modules/update/update.fetch.inc modules/update/update.info modules/update/update.install modules/update/update.module modules/update/update.report.inc modules/update/update.settings.inc modules/upload/upload.admin.inc modules/upload/upload.info modules/upload/upload.install modules/upload/upload.module modules/user/user-picture.tpl.php modules/user/user-profile-category.tpl.php modules/user/user-profile-item.tpl.php modules/user/user-profile.tpl.php modules/user/user-rtl.css modules/user/user.admin.inc modules/user/user.css modules/user/user.info modules/user/user.install modules/user/user.js modules/user/user.module modules/user/user.pages.inc profiles/default/default.profile robots.txt scripts/code-clean.sh scripts/code-style.pl scripts/cron-curl.sh scripts/cron-lynx.sh scripts/drupal.sh sites/all/README.txt sites/default/default.settings.php themes/README.txt themes/bluemarine/block.tpl.php themes/bluemarine/bluemarine.info themes/bluemarine/box.tpl.php themes/bluemarine/comment.tpl.php themes/bluemarine/logo.png themes/bluemarine/node.tpl.php themes/bluemarine/page.tpl.php themes/bluemarine/screenshot.png themes/bluemarine/style-rtl.css themes/bluemarine/style.css themes/chameleon/background.png themes/chameleon/chameleon.info themes/chameleon/chameleon.theme themes/chameleon/common-rtl.css themes/chameleon/common.css themes/chameleon/logo.png themes/chameleon/marvin/bullet.png themes/chameleon/marvin/druplicon-watermark-rtl.png themes/chameleon/marvin/druplicon-watermark.png themes/chameleon/marvin/logo.png themes/chameleon/marvin/marvin.info themes/chameleon/marvin/screenshot.png themes/chameleon/marvin/style-rtl.css themes/chameleon/marvin/style.css themes/chameleon/screenshot.png themes/chameleon/style-rtl.css themes/chameleon/style.css themes/engines/phptemplate/phptemplate.engine themes/garland/block.tpl.php themes/garland/color/base.png themes/garland/color/color.inc themes/garland/color/preview.css themes/garland/color/preview.png themes/garland/comment.tpl.php themes/garland/fix-ie-rtl.css themes/garland/fix-ie.css themes/garland/garland.info themes/garland/images/bg-bar-white.png themes/garland/images/bg-bar.png themes/garland/images/bg-content-left.png themes/garland/images/bg-content-right.png themes/garland/images/bg-content.png themes/garland/images/bg-navigation-item-hover.png themes/garland/images/bg-navigation-item.png themes/garland/images/bg-navigation.png themes/garland/images/bg-tab.png themes/garland/images/body.png themes/garland/images/gradient-inner.png themes/garland/images/menu-collapsed-rtl.gif themes/garland/images/menu-collapsed.gif themes/garland/images/menu-expanded.gif themes/garland/images/menu-leaf.gif themes/garland/images/task-list.png themes/garland/logo.png themes/garland/maintenance-page.tpl.php themes/garland/minnelli/color/base.png themes/garland/minnelli/color/color.inc themes/garland/minnelli/color/preview.png themes/garland/minnelli/logo.png themes/garland/minnelli/minnelli.css themes/garland/minnelli/minnelli.info themes/garland/minnelli/screenshot.png themes/garland/node.tpl.php themes/garland/page.tpl.php themes/garland/print.css themes/garland/screenshot.png themes/garland/style-rtl.css themes/garland/style.css themes/garland/template.php themes/pushbutton/arrow-next-hover-rtl.png themes/pushbutton/arrow-next-hover.png themes/pushbutton/arrow-next-rtl.png themes/pushbutton/arrow-next-visited-rtl.png themes/pushbutton/arrow-next-visited.png themes/pushbutton/arrow-next.png themes/pushbutton/arrow-prev-hover-rtl.png themes/pushbutton/arrow-prev-hover.png themes/pushbutton/arrow-prev-rtl.png themes/pushbutton/arrow-prev-visited-rtl.png themes/pushbutton/arrow-prev-visited.png themes/pushbutton/arrow-prev.png themes/pushbutton/arrow-up-hover.png themes/pushbutton/arrow-up-visited.png themes/pushbutton/arrow-up.png themes/pushbutton/background.png themes/pushbutton/block.tpl.php themes/pushbutton/box.tpl.php themes/pushbutton/comment.tpl.php themes/pushbutton/forum-container-rtl.jpg themes/pushbutton/forum-container.jpg themes/pushbutton/forum-link-rtl.png themes/pushbutton/forum-link.png themes/pushbutton/header-a.jpg themes/pushbutton/header-b-rtl.jpg themes/pushbutton/header-b.jpg themes/pushbutton/header-c.png themes/pushbutton/icon-block-rtl.png themes/pushbutton/icon-block.png themes/pushbutton/icon-comment-rtl.png themes/pushbutton/icon-comment.png themes/pushbutton/logo-active-rtl.jpg themes/pushbutton/logo-active.jpg themes/pushbutton/logo-background-rtl.jpg themes/pushbutton/logo-background.jpg themes/pushbutton/logo-hover-rtl.jpg themes/pushbutton/logo-hover.jpg themes/pushbutton/logo.png themes/pushbutton/node.tpl.php themes/pushbutton/page.tpl.php themes/pushbutton/pushbutton.info themes/pushbutton/screenshot.png themes/pushbutton/style-rtl.css themes/pushbutton/style.css themes/pushbutton/tabs-off-rtl.png themes/pushbutton/tabs-off.png themes/pushbutton/tabs-on-rtl.png themes/pushbutton/tabs-on.png themes/pushbutton/tabs-option-hover-rtl.png themes/pushbutton/tabs-option-hover.png themes/pushbutton/tabs-option-off-rtl.png themes/pushbutton/tabs-option-off.png themes/pushbutton/tabs-option-on.png update.php xmlrpc.php
diffstat 463 files changed, 85479 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.htaccess	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,103 @@
+#
+# Apache/PHP/Drupal settings:
+#
+
+# Protect files and directories from prying eyes.
+<FilesMatch "\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template)$">
+  Order allow,deny
+</FilesMatch>
+
+# Don't show directory listings for URLs which map to a directory.
+Options -Indexes
+
+# Follow symbolic links in this directory.
+Options +FollowSymLinks
+
+# Customized error messages.
+ErrorDocument 404 /index.php
+
+# Set the default handler.
+DirectoryIndex index.php
+
+# Override PHP settings. More in sites/default/settings.php
+# but the following cannot be changed at runtime.
+
+# PHP 4, Apache 1.
+<IfModule mod_php4.c>
+  php_value magic_quotes_gpc                0
+  php_value register_globals                0
+  php_value session.auto_start              0
+  php_value mbstring.http_input             pass
+  php_value mbstring.http_output            pass
+  php_value mbstring.encoding_translation   0
+</IfModule>
+
+# PHP 4, Apache 2.
+<IfModule sapi_apache2.c>
+  php_value magic_quotes_gpc                0
+  php_value register_globals                0
+  php_value session.auto_start              0
+  php_value mbstring.http_input             pass
+  php_value mbstring.http_output            pass
+  php_value mbstring.encoding_translation   0
+</IfModule>
+
+# PHP 5, Apache 1 and 2.
+<IfModule mod_php5.c>
+  php_value magic_quotes_gpc                0
+  php_value register_globals                0
+  php_value session.auto_start              0
+  php_value mbstring.http_input             pass
+  php_value mbstring.http_output            pass
+  php_value mbstring.encoding_translation   0
+</IfModule>
+
+# Requires mod_expires to be enabled.
+<IfModule mod_expires.c>
+  # Enable expirations.
+  ExpiresActive On
+
+  # Cache all files for 2 weeks after access (A).
+  ExpiresDefault A1209600
+
+  # Do not cache dynamically generated pages.
+  ExpiresByType text/html A1
+</IfModule>
+
+# Various rewrite rules.
+<IfModule mod_rewrite.c>
+  RewriteEngine on
+
+  # If your site can be accessed both with and without the 'www.' prefix, you
+  # can use one of the following settings to redirect users to your preferred
+  # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
+  #
+  # To redirect all users to access the site WITH the 'www.' prefix,
+  # (http://example.com/... will be redirected to http://www.example.com/...)
+  # adapt and uncomment the following:
+  # RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
+  # RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
+  #
+  # To redirect all users to access the site WITHOUT the 'www.' prefix,
+  # (http://www.example.com/... will be redirected to http://example.com/...)
+  # uncomment and adapt the following:
+  # RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
+  # RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
+
+  # Modify the RewriteBase if you are using Drupal in a subdirectory or in a
+  # VirtualDocumentRoot and the rewrite rules are not working properly.
+  # For example if your site is at http://example.com/drupal uncomment and
+  # modify the following line:
+  # RewriteBase /drupal
+  #
+  # If your site is running in a VirtualDocumentRoot at http://example.com/,
+  # uncomment the following line:
+  # RewriteBase /
+
+  # Rewrite URLs of the form 'index.php?q=x'.
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+</IfModule>
+
+# $Id: .htaccess,v 1.90 2007/10/05 14:43:23 dries Exp $
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CHANGELOG.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,825 @@
+// $Id: CHANGELOG.txt,v 1.253.2.3 2008/02/13 14:25:42 goba Exp $
+
+Drupal 6.0, 2008-02-13
+----------------------
+- New, faster and better menu system.
+- New watchdog as a hook functionality.
+   * New hook_watchdog that can be implemented by any module to route log
+     messages to various destinations.
+   * Expands the severity levels from 3 (Error, Warning, Notice) to the 8
+     levels defined in RFC 3164.
+   * The watchdog module is now called dblog, and is optional, but enabled by
+     default in the default install profile.
+   * Extended the database log module so log messages can be filtered.
+   * Added syslog module: useful for monitoring large Drupal installations.
+- Added optional e-mail notifications when users are approved, blocked, or
+  deleted.
+- Drupal works with error reporting set to E_ALL.
+- Added scripts/drupal.sh to execute Drupal code from the command line. Useful
+  to use Drupal as a framework to build command-line tools.
+- Made signature support optional and made it possible to theme signatures.
+- Made it possible to filter the URL aliases on the URL alias administration
+  screen.
+- Language system improvements:
+    * Support for right to left languages.
+    * Language detection based on parts of the URL.
+    * Browser based language detection.
+    * Made it possible to specify a node's language.
+    * Support for translating posts on the site to different languages.
+    * Language dependent path aliases.
+    * Automatically import translations when adding a new language.
+    * JavaScript interface translation.
+    * Automatically import a module's translation upon enabling that module.
+- Moved "PHP input filter" to a standalone module so it can be deleted for
+  security reasons.
+- Usability:
+    * Improved handling of teasers in posts.
+    * Added sticky table headers.
+    * Check for clean URL support automatically with JavaScript.
+    * Removed default/settings.php. Instead the installer will create it from
+      default.settings.php.
+    * Made it possible to configure your own date formats.
+    * Remember anonymous comment posters.
+    * Only allow modules and themes to be enabled that have explicitly been
+      ported to the correct core API version.
+    * Can now specify the minimum PHP version required for a module within the
+      .info file.
+    * Dynamically check password strength and confirmation.
+    * Refactored poll administration.
+    * Implemented drag-and-drop positioning for blocks, menu items, taxonomy
+      vocabularies and terms, forums, profile fields, and input format filters.
+- Theme system:
+    * Added .info files to themes and made it easier to specify regions and
+      features.
+    * Added theme registry: modules can directly provide .tpl.php files for
+      their themes without having to create theme_ functions.
+    * Used the Garland theme for the installation and maintenance pages.
+    * Added theme preprocess functions for themes that are templates.
+    * Added support for themeable functions in JavaScript.
+- Refactored update.php to a generic batch API to be able to run time-consuming
+  operations in multiple subsequent HTTP requests.
+- Installer:
+    * Themed the installer with the Garland theme.
+    * Added form to provide initial site information during installation.
+    * Added ability to provide extra installation steps programmatically.
+    * Made it possible to import interface translations at install time.
+- Added the HTML corrector filter:
+    * Fixes faulty and chopped off HTML in postings.
+    * Tags are now automatically closed at the end of the teaser.
+- Performance:
+    * Made it easier to conditionally load .include files and split up many core
+      modules.
+    * Added a JavaScript aggregator.
+    * Added block-level caching, improving performance for both authenticated
+      and anonymous users.
+    * Made Drupal work correctly when running behind a reverse proxy like
+      Squid or Pound.
+- File handling improvements:
+    * Entries in the files table are now keyed to a user instead of a node.
+    * Added reusable validation functions to check for uploaded file sizes,
+      extensions, and image resolution.
+    * Added ability to create and remove temporary files during a cron job.
+- Forum improvements:
+    * Any node type may now be posted in a forum.
+- Taxonomy improvements:
+    * Descriptions for terms are now shown on taxonomy/term pages as well
+      as RSS feeds.
+    * Added versioning support to categories by associating them with node
+      revisions.
+- Added support for OpenID.
+- Added support for triggering configurable actions.
+- Added the Update status module to automatically check for available updates
+  and warn sites if they are missing security updates or newer versions.
+  Sites deploying from CVS should use http://drupal.org/project/cvs_deploy.
+  Advanced settings provided by http://drupal.org/project/update_advanced.
+- Upgraded the core JavaScript library to jQuery version 1.2.3.
+- Added a new Schema API, which provides built-in support for core and
+  contributed modules to work with databases other than MySQL.
+- Removed drupal.module. The functionality lives on as the Site network
+  contributed module (http://drupal.org/project/site_network).
+- Removed old system updates. Updates from Drupal versions prior to 5.x will
+  require upgrading to 5.x before upgrading to 6.x.
+
+Drupal 5.7, 2008-01-28
+----------------------
+- fixed the input format configuration page.
+- fixed a variety of small bugs.
+
+Drupal 5.6, 2008-01-10
+----------------------
+- fixed a variety of small bugs.
+- fixed a security issue (Cross site request forgery), see SA-2008-005
+- fixed a security issue (Cross site scripting, UTF8), see SA-2008-006
+- fixed a security issue (Cross site scripting, register_globals), see SA-2008-007
+
+Drupal 5.5, 2007-12-06
+----------------------
+- fixed missing missing brackets in a query in the user module.
+- fixed taxonomy feed bug introduced by SA-2007-031
+
+Drupal 5.4, 2007-12-05
+----------------------
+- fixed a variety of small bugs.
+- fixed a security issue (SQL injection), see SA-2007-031
+
+Drupal 5.3, 2007-10-17
+----------------------
+- fixed a variety of small bugs.
+- fixed a security issue (HTTP response splitting), see SA-2007-024
+- fixed a security issue (Arbitrary code execution via installer), see SA-2007-025
+- fixed a security issue (Cross site scripting via uploads), see SA-2007-026
+- fixed a security issue (User deletion cross site request forgery), see SA-2007-029
+- fixed a security issue (API handling of unpublished comment), see SA-2007-030
+
+Drupal 5.2, 2007-07-26
+----------------------
+- changed hook_link() $teaser argument to match documentation.
+- fixed a variety of small bugs.
+- fixed a security issue (cross-site request forgery), see SA-2007-017
+- fixed a security issue (cross-site scripting), see SA-2007-018
+
+Drupal 5.1, 2007-01-29
+----------------------
+- fixed security issue (code execution), see SA-2007-005
+- fixed a variety of small bugs.
+
+Drupal 5.0, 2007-01-15
+----------------------
+- Completely retooled the administration page
+    * /Admin now contains an administration page which may be themed
+    * Reorganised administration menu items by task and by module
+    * Added a status report page with detailed PHP/MySQL/Drupal information
+- Added web-based installer which can:
+    * Check installation and run-time requirements
+    * Automatically generate the database configuration file
+    * Install pre-made 'install profiles' or distributions
+    * Import the database structure with automatic table prefixing
+    * Be localized
+- Added new default Garland theme
+- Added color module to change some themes' color schemes
+- Included the jQuery JavaScript library 1.0.4 and converted all core JavaScript to use it
+- Introduced the ability to alter mail sent from system
+- Module system:
+    * Added .info files for module meta-data
+    * Added support for module dependencies
+    * Improved module installation screen
+    * Moved core modules to their own directories
+    * Added support for module uninstalling
+- Added support for different cache backends
+- Added support for a generic "sites/all" directory.
+- Usability:
+    * Added support for auto-complete forms (AJAX) to user profiles.
+    * Made it possible to instantly assign roles to newly created user accounts.
+    * Improved configurability of the contact forms.
+    * Reorganized the settings pages.
+    * Made it easy to investigate popular search terms.
+    * Added a 'select all' checkbox and a range select feature to administration tables.
+    * Simplified the 'break' tag to split teasers from body.
+    * Use proper capitalization for titles, menu items and operations.
+- Integrated urlfilter.module into filter.module
+- Block system:
+    * Extended the block visibility settings with a role specific setting.
+    * Made it possible to customize all block titles.
+- Poll module:
+    * Optionally allow people to inspect all votes.
+    * Optionally allow people to cancel their vote.
+- Distributed authentication:
+    * Added default server option.
+- Added default robots.txt to control crawlers.
+- Database API:
+    * Added db_table_exists().
+- Blogapi module:
+    * 'Blogapi new' and 'blogapi edit' nodeapi operations.
+- User module:
+    * Added hook_profile_alter().
+    * E-mail verification is made optional.
+    * Added mass editing and filtering on admin/user/user.
+- PHP Template engine:
+    * Add the ability to look for a series of suggested templates.
+    * Look for page templates based upon the path.
+    * Look for block templates based upon the region, module, and delta.
+- Content system:
+    * Made it easier for node access modules to work well with each other.
+    * Added configurable content types.
+    * Changed node rendering to work with structured arrays.
+- Performance:
+    * Improved session handling: reduces database overhead.
+    * Improved access checking: reduces database overhead.
+    * Made it possible to do memcached based session management.
+    * Omit sidebars when serving a '404 - Page not found': saves CPU cycles and bandwidth.
+    * Added an 'aggressive' caching policy.
+    * Added a CSS aggregator and compressor (up to 40% faster page loads).
+- Removed the archive module.
+- Upgrade system:
+    * Created space for update branches.
+- Forms API:
+    * Made it possible to programmatically submit forms.
+    * Improved api for multistep forms.
+- Theme system:
+    * Split up and removed drupal.css.
+    * Added nested lists generation.
+    * Added a self-clearing block class.
+
+Drupal 4.7.11, 2008-01-10
+-------------------------
+- fixed a security issue (Cross site request forgery), see SA-2008-005
+- fixed a security issue (Cross site scripting, UTF8), see SA-2008-006
+- fixed a security issue (Cross site scripting, register_globals), see SA-2008-007
+
+Drupal 4.7.10, 2007-12-06
+-------------------------
+- fixed taxonomy feed bug introduced by SA-2007-031
+
+Drupal 4.7.9, 2007-12-05
+------------------------
+- fixed a security issue (SQL injection), see SA-2007-031
+
+Drupal 4.7.8, 2007-10-17
+----------------------
+- fixed a security issue (HTTP response splitting), see SA-2007-024
+- fixed a security issue (Cross site scripting via uploads), see SA-2007-026
+- fixed a security issue (API handling of unpublished comment), see SA-2007-030
+
+Drupal 4.7.7, 2007-07-26
+------------------------
+- fixed security issue (XSS), see SA-2007-018
+
+Drupal 4.7.6, 2007-01-29
+------------------------
+- fixed security issue (code execution), see SA-2007-005
+
+Drupal 4.7.5, 2007-01-05
+------------------------
+- Fixed security issue (XSS), see SA-2007-001
+- Fixed security issue (DoS), see SA-2007-002
+
+Drupal 4.7.4, 2006-10-18
+------------------------
+- Fixed security issue (XSS), see SA-2006-024
+- Fixed security issue (CSRF), see SA-2006-025
+- Fixed security issue (Form action attribute injection), see SA-2006-026
+
+Drupal 4.7.3, 2006-08-02
+------------------------
+- Fixed security issue (XSS), see SA-2006-011
+
+Drupal 4.7.2, 2006-06-01
+------------------------
+- Fixed critical upload issue, see SA-2006-007
+- Fixed taxonomy XSS issue, see SA-2006-008
+- Fixed a variety of small bugs.
+
+Drupal 4.7.1, 2006-05-24
+------------------------
+- Fixed critical SQL issue, see SA-2006-005
+- Fixed a serious upgrade related bug.
+- Fixed a variety of small bugs.
+
+Drupal 4.7.0, 2006-05-01
+------------------------
+- Added free tagging support.
+- Added a site-wide contact form.
+- Theme system:
+    * Added the PHPTemplate theme engine and removed the Xtemplate engine.
+    * Converted the bluemarine theme from XTemplate to PHPTemplate.
+    * Converted the pushbutton theme from XTemplate to PHPTemplate.
+- Usability:
+    * Reworked the 'request new password' functionality.
+    * Reworked the node and comment edit forms.
+    * Made it easy to add nodes to the navigation menu.
+    * Added site 'offline for maintenance' feature.
+    * Added support for auto-complete forms (AJAX).
+    * Added support for collapsible page sections (JS).
+    * Added support for resizable text fields (JS).
+    * Improved file upload functionality (AJAX).
+    * Reorganized some settings pages.
+    * Added friendly database error screens.
+    * Improved styling of update.php.
+- Refactored the forms API.
+    * Made it possible to alter, extend or theme forms.
+- Comment system:
+    * Added support for "mass comment operations" to ease repetitive tasks.
+    * Comment moderation has been removed.
+- Node system:
+    * Reworked the revision functionality.
+    * Removed the bookmarklet code. Third-party modules can now handle
+      This.
+- Upgrade system:
+    * Allows contributed modules to plug into the upgrade system.
+- Profiles:
+    * Added a block to display author information along with posts.
+    * Added support for private profile fields.
+- Statistics module:
+    * Added the ability to track page generation times.
+    * Made it possible to block certain IPs/hostnames.
+- Block system:
+    * Added support for theme-specific block regions.
+- Syndication:
+    * Made the aggregator module parse Atom feeds.
+    * Made the aggregator generate RSS feeds.
+    * Added RSS feed settings.
+- XML-RPC:
+    * Replaced the XML-RPC library by a better one.
+- Performance:
+    * Added 'loose caching' option for high-traffic sites.
+    * Improved performance of path aliasing.
+    * Added the ability to track page generation times.
+- Internationalization:
+    * Improved Unicode string handling API.
+    * Added support for PHP's multibyte string module.
+- Added support for PHP5's 'mysqli' extension.
+- Search module:
+    * Made indexer smarter and more robust.
+    * Added advanced search operators (e.g. phrase, node type, ...).
+    * Added customizable result ranking.
+- PostgreSQL support:
+    * Removed dependency on PL/pgSQL procedural language.
+- Menu system:
+    * Added support for external URLs.
+- Queue module:
+    * Removed from core.
+- HTTP handling:
+    * Added support for a tolerant Base URL.
+    * Output URIs relative to the root, without a base tag.
+
+Drupal 4.6.11, 2007-01-05
+-------------------------
+- Fixed security issue (XSS), see SA-2007-001
+- Fixed security issue (DoS), see SA-2007-002
+
+Drupal 4.6.10, 2006-10-18
+------------------------
+- Fixed security issue (XSS), see SA-2006-024
+- Fixed security issue (CSRF), see SA-2006-025
+- Fixed security issue (Form action attribute injection), see SA-2006-026
+
+Drupal 4.6.9, 2006-08-02
+------------------------
+- Fixed security issue (XSS), see SA-2006-011
+
+Drupal 4.6.8, 2006-06-01
+------------------------
+- Fixed critical upload issue, see SA-2006-007
+- Fixed taxonomy XSS issue, see SA-2006-008
+
+Drupal 4.6.7, 2006-05-24
+------------------------
+- Fixed critical SQL issue, see SA-2006-005
+
+Drupal 4.6.6, 2006-03-13
+------------------------
+- Fixed bugs, including 4 security vulnerabilities.
+
+Drupal 4.6.5, 2005-12-12
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.6.4, 2005-11-30
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.6.3, 2005-08-15
+------------------------
+- Fixed bugs, including a critical "arbitrary PHP code execution" bug.
+
+Drupal 4.6.2, 2005-06-29
+------------------------
+- Fixed bugs, including two critical "arbitrary PHP code execution" bugs.
+
+Drupal 4.6.1, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.6.0, 2005-04-15
+------------------------
+- PHP5 compliance
+- Search:
+    * Added UTF-8 support to make it work with all languages.
+    * Improved search indexing algorithm.
+    * Improved search output.
+    * Impose a throttle on indexing of large sites.
+    * Added search block.
+- Syndication:
+    * Made the ping module ping pingomatic.com which, in turn, will ping all the major ping services.
+    * Made Drupal generate RSS 2.0 feeds.
+    * Made RSS feeds extensible.
+    * Added categories to RSS feeds.
+    * Added enclosures to RSS feeds.
+- Flood control mechanism:
+    * Added a mechanism to throttle certain operations.
+- Usability:
+    * Refactored the block configuration pages.
+    * Refactored the statistics pages.
+    * Refactored the watchdog pages.
+    * Refactored the throttle module configuration.
+    * Refactored the access rules page.
+    * Refactored the content administration page.
+    * Introduced forum configuration pages.
+    * Added a 'add child page' link to book pages.
+- Contact module:
+    * Added a simple contact module that allows users to contact each other using e-mail.
+- Multi-site configuration:
+    * Made it possible to run multiple sites from a single code base.
+- Added an image API: enables better image handling.
+- Block system:
+    * Extended the block visibility settings.
+- Theme system:
+    * Added new theme functions.
+- Database backend:
+    * The PEAR database backend is no longer supported.
+- Performance:
+    * Improved performance of the forum topics block.
+    * Improved performance of the tracker module.
+    * Improved performance of the node pages.
+- Documentation:
+    * Improved and extended PHPDoc/Doxygen comments.
+
+Drupal 4.5.8, 2006-03-13
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.5.7, 2005-12-12
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.5.6, 2005-11-30
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.5.5, 2005-08-15
+------------------------
+- Fixed bugs, including a critical "arbitrary PHP code execution" bug.
+
+Drupal 4.5.4, 2005-06-29
+------------------------
+- Fixed bugs, including two critical "arbitrary PHP code execution" bugs.
+
+Drupal 4.5.3, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.5.2, 2005-01-15
+------------------------
+- Fixed bugs: a cross-site scripting (XSS) vulnerability has been fixed.
+
+Drupal 4.5.1, 2004-12-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.5.0, 2004-10-18
+------------------------
+- Navigation:
+    * Made it possible to add, delete, rename and move menu items.
+    * Introduced tabs and subtabs for local tasks.
+    * Reorganized the navigation menus.
+- User management:
+    * Added support for multiple roles per user.
+    * Made it possible to add custom profile fields.
+    * Made it possible to browse user profiles by field.
+- Node system:
+    * Added support for node-level permissions.
+- Comment module:
+    * Made it possible to leave contact information without having to register.
+- Upload module:
+    * Added support for uploading documents (includes images).
+- Forum module:
+    * Added support for sticky forum topics.
+    * Made it possible to track forum topics.
+- Syndication:
+    * Added support for RSS ping-notifications of http://technorati.com/.
+    * Refactored the categorization of syndicated news items.
+    * Added an URL alias for 'rss.xml'.
+    * Improved date parsing.
+- Database backend:
+    * Added support for multiple database connections.
+    * The PostgreSQL backend does no longer require PEAR.
+- Theme system:
+    * Changed all GIFs to PNGs.
+    * Reorganised the handling of themes, template engines, templates and styles.
+    * Unified and extended the available theme settings.
+    * Added theme screenshots.
+- Blocks:
+    * Added 'recent comments' block.
+    * Added 'categories' block.
+- Blogger API:
+    * Added support for auto-discovery of blogger API via RSD.
+- Performance:
+    * Added support for sending gzip compressed pages.
+    * Improved performance of the forum module.
+- Accessibility:
+    * Improved the accessibility of the archive module's calendar.
+    * Improved form handling and error reporting.
+    * Added HTTP redirects to prevent submitting twice when refreshing right after a form submission.
+- Refactored 403 (forbidden) handling and added support for custom 403 pages.
+- Documentation:
+    * Added PHPDoc/Doxygen comments.
+- Filter system:
+    * Added support for using multiple input formats on the site
+    * Expanded the embedded PHP-code feature so it can be used everywhere
+    * Added support for role-dependant filtering, through input formats
+- UI translation:
+    * Managing translations is now completely done through the administration interface
+    * Added support for importing/exporting gettext .po files
+
+Drupal 4.4.3, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.4.2, 2004-07-04
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.4.1, 2004-05-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.4.0, 2004-04-01
+------------------------
+- Added support for the MetaWeblog API and MovableType extensions.
+- Added a file API: enables better document management.
+- Improved the watchdog and search module to log search keys.
+- News aggregator:
+    * Added support for conditional GET.
+    * Added OPML feed subscription list.
+    * Added support for <image>, <pubDate>, <dc:date>, <dcterms:created>, <dcterms:issued> and <dcterms:modified>.
+- Comment module:
+    * Made it possible to disable the "comment viewing controls".
+- Performance:
+    * Improved module loading when serving cached pages.
+    * Made it possible to automatically disable modules when under heavy load.
+    * Made it possible to automatically disable blocks when under heavy load.
+    * Improved performance and memory footprint of the locale module.
+- Theme system:
+    * Made all theme functions start with 'theme_'.
+    * Made all theme functions return their output.
+    * Migrated away from using the BaseTheme class.
+    * Added many new theme functions and refactored existing theme functions.
+    * Added avatar support to 'Xtemplate'.
+    * Replaced theme 'UnConeD' by 'Chameleon'.
+    * Replaced theme 'Marvin' by 'Pushbutton'.
+- Usability:
+    * Added breadcrumb navigation to all pages.
+    * Made it possible to add context-sensitive help to all pages.
+    * Replaced drop-down menus by radio buttons where appropriate.
+    * Removed the 'magic_quotes_gpc = 0' requirement.
+    * Added a 'book navigation' block.
+- Accessibility:
+    * Made themes degrade gracefully in absence of CSS.
+    * Grouped form elements using '<fieldset>' and '<legend>' tags.
+    * Added '<label>' tags to form elements.
+- Refactored 404 (file not found) handling and added support for custom 404 pages.
+- Improved the filter system to prevent conflicts between filters:
+    * Made it possible to change the order in which filters are applied.
+- Documentation:
+    * Added PHPDoc/Doxygen comments.
+
+Drupal 4.3.2, 2004-01-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.3.1, 2003-12-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.3.0, 2003-11-01
+------------------------
+- Added support for configurable URLs.
+- Added support for sortable table columns.
+- Database backend:
+    * Added support for selective database table prefixing.
+- Performance:
+    * Optimized many SQL queries for speed by converting left joins to inner joins.
+- Comment module:
+    * Rewrote the comment housekeeping code to be much more efficient and scalable.
+    * Changed the comment module to use the standard pager.
+- User module:
+    * Added support for multiple sessions per user.
+    * Added support for anonymous user sessions.
+- Forum module:
+    * Improved the forum views and the themability thereof.
+- Book module:
+    * Improved integration of non-book nodes in the book outline.
+- Usability:
+    * Added support for "mass node operations" to ease repetitive tasks.
+    * Added support for breadcrumb navigation to several modules' user pages.
+    * Integrated the administration pages with the normal user pages.
+
+Drupal 4.2.0, 2003-08-01
+------------------------
+- Added support for clean URLs.
+- Added textarea hook and support for onload attributes: enables integration of WYSIWYG editors.
+- Rewrote the RSS/RDF parser:
+    * It will now use PHP's built-in XML parser to parse news feeds.
+- Rewrote the administration pages:
+    * Improved the navigational elements and added breadcrumb navigation.
+    * Improved the look and feel.
+    * Added context-sensitive help.
+- Database backend:
+    * Fixed numerous SQL queries to make Drupal ANSI compliant.
+    * Added MSSQL database scheme.
+- Search module:
+    * Changed the search module to use implicit AND'ing instead of implicit OR'ing.
+- Node system:
+    * Replaced the "post content" permission by more fine-grained permissions.
+    * Improved content submission:
+        + Improved teasers: teasers are now optional, teaser length can be configured, teaser and body are edited in a single textarea, users will no longer be bothered with teasers when the post is too short for one.
+        + Added the ability to preview both the short and the full version of your posts.
+    * Extended the node API which allows for better integration.
+    * Added default node settings to control the behavior for promotion, moderation and other options.
+- Themes:
+    * Replaced theme "Goofy" by "Xtemplate", a template driven theme.
+- Removed the 'register_globals = on' requirement.
+- Added better installation instructions.
+
+Drupal 4.1.0, 2003-02-01
+------------------------
+- Collaboratively revised and expanded the Drupal documentation.
+- Rewrote comment.module:
+    * Reintroduced comment rating/moderation.
+    * Added support for comment paging.
+    * Performance improvements: improved comment caching, faster SQL queries, etc.
+- Rewrote block.module:
+    * Performance improvements: blocks are no longer rendered when not displayed.
+- Rewrote forum.module:
+    * Added a lot of features one can find in stand-alone forum software including but not limited to support for topic paging, added support for icons, rewrote the statistics module, etc.
+- Rewrote statistics.module:
+    * Collects access counts for each node, referrer logs, number of users/guests.
+    * Export blocks displaying top viewed nodes over last 24 hour period, top viewed nodes over all time, last nodes viewed, how many users/guest online.
+- Added throttle.module:
+    * Auto-throttle congestion control mechanism: Drupal can adapt itself based on the server load.
+- Added profile.module:
+    * Enables to extend the user and registration page.
+- Added pager support to the main page.
+- Replaced weblogs.module by ping.module:
+    * Added support for normal and RSS notifications of http://blo.gs/.
+    * Added support for RSS ping-notifications of http://weblogs.com/.
+- Removed the rating module
+- Themes:
+    * Removed a significant portion of hard-coded mark-up.
+
+Drupal 4.0.0, 2002-06-15
+------------------------
+- Added tracker.module:
+    * Replaces the previous "your [site]" links (recent comments and nodes).
+- Added weblogs.module:
+    * This will ping weblogs.com when new content is promoted.
+- Added taxonomy module which replaces the meta module.
+    * Supports relations, hierarchies and synonyms.
+- Added a caching system:
+    * Speeds up pages for anonymous users and reduces system load.
+- Added support for external SMTP libraries.
+- Added an archive extension to the calendar.
+- Added support for the Blogger API.
+- Themes:
+    * Cleaned up the theme system.
+    * Moved themes that are not maintained to contributions CVS repository.
+- Database backend:
+    * Changed to PEAR database abstraction layer.
+    * Using ANSI SQL queries to be more portable.
+- Rewrote the user system:
+    * Added support for Drupal authentication through XML-RPC and through a Jabber server.
+    * Added support for modules to add more user data.
+    * Users may delete their own account.
+    * Added who's new and who's online blocks.
+- Changed block system:
+    * Various hard coded blocks are now dynamic.
+    * Blocks can now be enabled and/or be set by the user.
+    * Blocks can be set to only show up on some pages.
+    * Merged box module with block module.
+- Node system:
+    * Blogs can be updated.
+    * Teasers (abstracts) on all node types.
+    * Improved error checking.
+    * Content versioning support.
+    * Usability improvements.
+- Improved book module to support text, HTML and PHP pages.
+- Improved comment module to mark new comments.
+- Added a general outliner which will let any node type be linked to a book.
+- Added an update script that lets you upgrade from previous releases or on a day to day basis when using the development tree.
+- Search module:
+    * Improved the search system by making it context sensitive.
+    * Added indexing.
+- Various updates:
+    * Changed output to valid XHTML.
+    * Improved multiple sites using the same Drupal database support.
+    * Added support for session IDs in URLs instead of cookies.
+    * Made the type of content on the front page configurable.
+    * Made each cloud site have its own settings.
+    * Modules and themes can now be enabled/disabled using the administration pages.
+    * Added URL abstraction for links.
+    * Usability changes (renamed links, better UI, etc).
+- Collaboratively revised and expanded the Drupal documentation.
+
+Drupal 3.0.1, 2001-10-15
+------------------------
+- Various updates:
+    * Added missing translations
+    * Updated the themes: tidied up some HTML code and added new Drupal logos.
+
+Drupal 3.0.0, 2001-09-15
+------------------------
+- Major overhaul of the entire underlying design:
+    * Everything is based on nodes: nodes are a conceptual "black box" to couple and manage different types of content and that promotes reusing existing code, thus reducing the complexity and size of Drupal as well as improving long-term stability.
+- Rewrote submission/moderation queue and renamed it to queue.module.
+- Removed FAQ and documentation module and merged them into a "book module".
+- Removed ban module and integrated it into account.module as "access control":
+    * Access control is based on much more powerful regular expressions (regex) now rather than on MySQL pattern matching.
+- Rewrote watchdog and submission throttle:
+    * Improved watchdog messages and added watchdog filter.
+- Rewrote headline code and renamed it to import.module and export.module:
+    * Added various improvements, including a better parser, bundles and better control over individual feeds.
+- Rewrote section code and renamed it to meta.module:
+    * Supports unlimited amount of nested topics. Topics can be nested to create a multi-level hierarchy.
+- Rewrote configuration file resolving:
+    * Drupal tries to locate a configuration file that matches your domain name or uses conf.php if the former failed. Note also that the configuration files got renamed from .conf to .php for security's sake on mal-configured Drupal sites.
+- Added caching support which makes Drupal extremely scalable.
+- Added access.module:
+    * Allows you to set up 'roles' (groups) and to bind a set of permissions to each group.
+- Added blog.module.
+- Added poll.module.
+- Added system.module:
+    * Moved most of the configuration options from hostname.conf to the new administration section.
+    * Added support for custom "filters".
+- Added statistics.module
+- Added moderate.module:
+    * Allows to assign users editorial/moderator rights to certain nodes or topics.
+- Added page.module:
+    * Allows creation of static (and dynamic) pages through the administration interface.
+- Added help.module:
+    * Groups all available module documentation on a single page.
+- Added forum.module:
+    * Added an integrated forum.
+- Added cvs.module and cvs-to-sql.pl:
+    * Allows to display and mail CVS log messages as daily digests.
+- Added book.module:
+    * Allows collaborative handbook writing: primary used for Drupal documentation.
+- Removed cron.module and integrated it into conf.module.
+- Removed module.module as it was no longer needed.
+- Various updates:
+    * Added "auto-post new submissions" feature versus "moderate new submissions".
+    * Introduced links/Drupal tags: [[link]]
+    * Added preview functionality when submitting new content (such as a story) from the administration pages.
+    * Made the administration section only show those links a user has access to.
+    * Made all modules use specific form_* functions to guarantee a rock-solid forms and more consistent layout.
+    * Improved scheduler:
+        + Content can be scheduled to be 'posted', 'queued' and 'hidden'.
+    * Improved account module:
+        + Added "access control" to allow/deny certain usernames/e-mail addresses/hostnames.
+    * Improved locale module:
+        + Added new overview to easy the translation process.
+    * Improved comment module:
+        + Made it possible to permanently delete comments.
+    * Improved rating module
+    * Improved story module:
+        + Added preview functionality for administrators.
+        + Made it possible to permanently delete stories.
+    * Improved themes:
+        + W3C validation on a best effort basis.
+        + Removed $theme->control() from themes.
+        + Added theme "goofy".
+- Collaboratively revised and expanded the Drupal documentation.
+
+Drupal 2.0.0, 2001-03-15
+------------------------
+- Rewrote the comment/discussion code:
+    * Comment navigation should be less confusing now.
+    * Additional/alternative display and order methods have been added.
+    * Modules can be extended with a "comment system": modules can embed the existing comment system without having to write their own, duplicate comment system.
+- Added sections and section manager:
+    * Story sections can be maintained from the administration pages.
+    * Story sections make the open submission more adaptive in that you can set individual post, dump and expiration thresholds for each section according to the story type and urgency level: stories in certain sections do not "expire" and might stay interesting and active as time passes by, whereas news-related stories are only considered "hot" over a short period of time.
+- Multiple vhosts + multiple directories:
+    * You can set up multiple Drupal sites on top of the same physical source tree either by using vhosts or sub-directories.
+- Added "user ratings" similar to SlashCode's Karma or Scoop's Mojo:
+    * All rating logic is packed into a module to ease experimenting with different rating heuristics/algorithms.
+- Added "search infrastructure":
+    * Improved search page and integrated search functionality in the administration pages.
+- Added translation / localization / internationalization support:
+    * Because many people would love to see their website showing a lot less of English, and far more of their own language, Drupal provides a framework to set up a multi-lingual website or to overwrite the default English text in English.
+- Added fine-grained user permission (or group) system:
+    * Users can be granted access to specific administration sections. Example: a FAQ maintainer can be given access to maintain the FAQ and translators can be given access to the translation pages.
+- Added FAQ module
+- Changed the "open submission queue" into a (optional) module.
+- Various updates:
+    * Improved account module:
+        + User accounts can be deleted.
+        + Added fine-grained permission support.
+    * Improved block module
+    * Improved diary module:
+        + Diary entries can be deleted
+    * Improved headline module:
+        + Improved parser to support more "generic" RDF/RSS/XML backend.
+    * Improved module module
+    * Improved watchdog module
+    * Improved database abstraction layer
+    * Improved themes:
+        + W3C validation on a best effort basis
+        + Added theme "example" (alias "Stone Age")
+    * Added new scripts to directory "scripts"
+    * Added directory "misc"
+    * Added CREDITS file
+- Revised documentation
+
+Drupal 1.0.0, 2001-01-15
+------------------------
+- Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYRIGHT.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,25 @@
+// $Id: COPYRIGHT.txt,v 1.2.2.1 2008/02/06 12:45:55 goba Exp $
+
+All Drupal code is Copyright 2001 - 2008 by the original authors.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program as the file LICENSE.txt; if not, please see
+http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+
+Drupal is a registered trademark of Dries Buytaert.
+
+Drupal includes works under other copyright notices and distributed
+according to the terms of the GNU General Public License or a compatible
+license, including:
+
+  jQuery - Copyright (c) 2008 John Resig
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/INSTALL.mysql.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,40 @@
+// $Id: INSTALL.mysql.txt,v 1.10 2007/11/19 19:53:51 goba Exp $
+
+CREATE THE MySQL DATABASE
+--------------------------
+
+This step is only necessary if you don't already have a database set-up (e.g. by
+your host). In the following examples, 'username' is an example MySQL user which
+has the CREATE and GRANT privileges. Use the appropriate user name for your
+system.
+
+First, you must create a new database for your Drupal site (here, 'databasename'
+is the name of the new database):
+
+  mysqladmin -u username -p create databasename
+
+MySQL will prompt for the 'username' database password and then create the
+initial database files. Next you must login and set the access database rights:
+
+  mysql -u username -p
+
+Again, you will be asked for the 'username' database password. At the MySQL
+prompt, enter following command:
+
+  GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
+  ON databasename.*
+  TO 'username'@'localhost' IDENTIFIED BY 'password';
+
+where
+
+ 'databasename' is the name of your database
+ 'username@localhost' is the username of your MySQL account
+ 'password' is the password required for that username
+
+Note: Unless your database user has all of the privileges listed above, you will
+not be able to run Drupal.
+
+If successful, MySQL will reply with:
+
+  Query OK, 0 rows affected
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/INSTALL.pgsql.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,28 @@
+// $Id: INSTALL.pgsql.txt,v 1.7 2007/11/26 16:36:42 dries Exp $
+
+CREATE THE PostgreSQL DATABASE
+------------------------------
+
+Note that the database must be created with UTF-8 (Unicode) encoding.
+
+1. CREATE DATABASE USER
+
+   This step is only necessary if you don't already have a user set up (e.g.
+   by your host) or you want to create new user for use with Drupal only. The
+   following command creates a new user named "username" and asks for a
+   password for that user:
+
+     createuser --pwprompt --encrypted --no-adduser --no-createdb username
+
+   If everything works correctly, you'll see a "CREATE USER" notice.
+
+2. CREATE THE DRUPAL DATABASE
+
+   This step is only necessary if you don't already have a database set up (e.g.
+   by your host) or you want to create new database for use with Drupal only.
+   The following command creates a new database named "databasename", which is
+   owned by previously created "username":
+
+     createdb --encoding=UNICODE --owner=username databasename
+
+   If everything works correctly, you'll see a "CREATE DATABASE" notice.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/INSTALL.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,330 @@
+// $Id: INSTALL.txt,v 1.61.2.2 2008/02/07 20:46:56 goba Exp $
+
+CONTENTS OF THIS FILE
+---------------------
+
+ * Requirements
+ * Optional requirements
+ * Installation
+ * Drupal administration
+ * Customizing your theme(s)
+ * Multisite Configuration
+ * More Information
+
+REQUIREMENTS
+------------
+
+Drupal requires a web server, PHP 4 (4.3.5 or greater) or PHP 5
+(http://www.php.net/) and either MySQL (http://www.mysql.com/) or PostgreSQL
+(http://www.postgresql.org/). The Apache web server and MySQL database are
+recommended; other web server and database combinations such as IIS and
+PostgreSQL have been tested to a lesser extent. When using MySQL, version 4.1.1
+or greater is recommended to assure you can safely transfer the database.
+
+For more detailed information about Drupal requirements, see "Requirements"
+(http://drupal.org/requirements) in the Drupal handbook.
+
+For detailed information on how to configure a test server environment using
+a variety of operating systems and web servers, see "Local server setup"
+(http://drupal.org/node/157602) in the Drupal handbook.
+
+OPTIONAL REQUIREMENTS
+---------------------
+
+- To use XML-based services such as the Blogger API and RSS syndication,
+  you will need PHP's XML extension. This extension is enabled by default.
+
+- To use Drupal's "Clean URLs" feature on an Apache web server, you will need
+  the mod_rewrite module and the ability to use local .htaccess files. For
+  Clean URLs support on IIS, see "Using Clean URLs with IIS"
+  (http://drupal.org/node/3854) in the Drupal handbook.
+
+- Various Drupal features require that the web server process (for
+  example, httpd) be able to initiate outbound connections. This is usually
+  possible, but some hosting providers or server configurations forbid such
+  connections. The features that depend on this functionality include the
+  integrated "Update status" module (which downloads information about
+  available updates of Drupal core and any installed contributed modules and
+  themes), the ability to log in via OpenID, fetching aggregator feeds, or
+  other network-dependent services.
+
+
+INSTALLATION
+------------
+
+1. DOWNLOAD DRUPAL AND OPTIONALLY A TRANSLATION
+
+   You can obtain the latest Drupal release from http://drupal.org/. The files
+   are in .tar.gz format and can be extracted using most compression tools. On a
+   typical Unix command line, use:
+
+     wget http://drupal.org/files/projects/drupal-x.x.tar.gz
+     tar -zxvf drupal-x.x.tar.gz
+
+   This will create a new directory drupal-x.x/ containing all Drupal files
+   and directories. Move the contents of that directory into a directory within
+   your web server's document root or your public HTML directory:
+
+     mv drupal-x.x/* drupal-x.x/.htaccess /var/www/html
+
+   If you would like to have the default English interface translated to a
+   different language, we have good news. You can install and use Drupal in
+   other languages from the start. Check whether a released package of the
+   language desired is available for this Drupal version at
+   http://drupal.org/project/translations and download the package. Extract
+   the contents to the same directory where you extracted Drupal into.
+
+2. GRANT WRITE PERMISSIONS ON CONFIGURATION FILE
+
+   Drupal comes with a default.settings.php file in the sites/default
+   directory. The installer will create a copy of this file filled with
+   the details you provide through the install process, in the same
+   directory. Give the web server write privileges to the sites/default
+   directory with the command (from the installation directory):
+
+     chmod o+w sites/default
+
+3. CREATE THE DRUPAL DATABASE
+
+   Drupal requires access to a database in order to be installed. Your database
+   user will need sufficient privileges to run Drupal. Additional information
+   about privileges, and instructions to create a database using the command
+   line are available in INSTALL.mysql.txt (for MySQL) or INSTALL.pgsql.txt
+   (for PostgreSQL).
+
+   To create a database using PHPMyAdmin or a web-based control panel consult
+   the documentation or ask your webhost service provider.
+
+   Take note of the username, password, database name and hostname as you
+   create the database. You will enter these items in the install script.
+
+4. RUN THE INSTALL SCRIPT
+
+   To run the install script point your browser to the base URL of your website
+   (e.g., http://www.example.com).
+
+   You will be guided through several screens to set up the database,
+   create tables, add the first user account and provide basic web
+   site settings.
+
+   The install script will attempt to create a files storage directory
+   in the default location at sites/default/files (the location of the
+   files directory may be changed after Drupal is installed). In some
+   cases, you may need to create the directory and modify its permissions
+   manually. Use the following commands (from the installation directory)
+   to create the files directory and grant the web server write privileges to it:
+
+     mkdir sites/default/files
+     chmod o+w sites/default/files
+
+   The install script will attempt to write-protect the sites/default
+   directory after creating the settings.php file. If you make manual
+   changes to that file later, be sure to protect it again after making
+   your modifications. Failure to remove write permissions to that file
+   is a security risk. Although the default location for the settings.php
+   file is at sites/default/settings.php, it may be in another location
+   if you use the multi-site setup, as explained below.
+
+5. CONFIGURE DRUPAL
+
+   When the install script succeeds, you will be directed to the "Welcome"
+   page, and you will be logged in as the administrator already. Proceed with
+   the initial configuration steps suggested on the "Welcome" page.
+
+   If the default Drupal theme is not displaying properly and links on the page
+   result in "Page Not Found" errors, try manually setting the $base_url variable
+   in the settings.php file if not already set. It's currently known that servers
+   running FastCGI can run into problems if the $base_url variable is left
+   commented out (see http://bugs.php.net/bug.php?id=19656).
+
+6. REVIEW FILE SYSTEM STORAGE SETTINGS AND FILE PERMISSIONS
+
+   The files directory created in step 4 is the default file system path used
+   to store all uploaded files, as well as some temporary files created by Drupal.
+   After installation, the settings for the file system path may be modified
+   to store uploaded files in a different location.
+
+   It is not necessary to modify this path, but you may wish to change it if:
+
+     * your site runs multiple Drupal installations from a single codebase
+       (modify the file system path of each installation to a different
+       directory so that uploads do not overlap between installations); or,
+
+     * your site runs a number of web server front-ends behind a load
+       balancer or reverse proxy (modify the file system path on each
+       server to point to a shared file repository).
+
+   To modify the file system path:
+
+     * Ensure that the new location for the path exists or create it if
+       necessary. To create a new directory named uploads, for example,
+       use the following command from a shell or system prompt (while in
+       the installation directory):
+
+           mkdir uploads
+
+     * Ensure that the new location for the path is writable by the web
+       server process. To grant write permissions for a directory named
+       uploads, you may need to use the following command from a shell
+       or system prompt (while in the installation directory):
+
+           chmod o+w uploads
+
+     * Access the file system path settings in Drupal by selecting these
+       menu items from the Navigation menu:
+
+           Administer > Site configuration > File system
+
+       Enter the path to the new location (e.g.: uploads) at the File
+       System Path prompt.
+
+   Changing the file system path after files have been uploaded may cause
+   unexpected problems on an existing site. If you modify the file system path
+   on an existing site, remember to copy all files from the original location
+   to the new location.
+   
+   Some administrators suggest making the documentation files, especially
+   CHANGELOG.txt, non-readable so that the exact version of Drupal you are
+   running is slightly more difficult to determine. If you wish to implement
+   this optional security measure, use the following command from a shell or
+   system prompt (while in the installation directory):
+
+          chmod a-r CHANGELOG.txt
+
+   Note that the example only affects CHANGELOG.txt. To completely hide
+   all documentation files from public view, repeat this command for each of
+   the Drupal documentation files in the installation directory, substituting the
+   name of each file for CHANGELOG.txt in the example.
+
+   For more information on setting file permissions, see "Modifying Linux, Unix,
+   and Mac file permissions" (http://drupal.org/node/202483) or "Modifying
+   Windows file permissions" (http://drupal.org/node/202491) in the online
+   handbook.
+
+7. CRON MAINTENANCE TASKS
+
+   Many Drupal modules have periodic tasks that must be triggered by a cron
+   maintenance task, including search module (to build and update the index
+   used for keyword searching), aggregator module (to retrieve feeds from other
+   sites), ping module (to notify other sites about new or updated content), and
+   system module (to perform routine maintenance and pruning on system tables).
+   To activate these tasks, call the cron page by visiting
+   http://www.example.com/cron.php, which, in turn, executes tasks on behalf
+   of installed modules.
+
+   Most systems support the crontab utility for scheduling tasks like this. The
+   following example crontab line will activate the cron tasks automatically on
+   the hour:
+
+   0   *   *   *   *   wget -O - -q -t 1 http://www.example.com/cron.php
+
+   More information about cron maintenance tasks are available in the help pages
+   and in Drupal's online handbook at http://drupal.org/cron. Example scripts can
+   be found in the scripts/ directory.
+
+DRUPAL ADMINISTRATION
+---------------------
+
+A new installation of Drupal defaults to a very basic configuration with only a
+few active modules and minimal user access rights.
+
+Use your administration panel to enable and configure services. For example:
+
+General Settings       Administer > Site configuration > Site information
+Enable Modules         Administer > Site building > Modules
+Configure Themes       Administer > Site building > Themes
+Set User Permissions   Administer > User management > Permissions
+
+For more information on configuration options, read the instructions which
+accompany the different configuration settings and consult the various help
+pages available in the administration panel.
+
+Community-contributed modules and themes are available at http://drupal.org/.
+
+CUSTOMIZING YOUR THEME(S)
+-------------------------
+
+Now that your installation is running, you will want to customize the look of
+your site. Several sample themes are included and more can be downloaded from
+drupal.org.
+
+Simple customization of your theme can be done using only CSS. Further changes
+require understanding the phptemplate engine that is part of Drupal. See
+http://drupal.org/handbook/customization to find out more.
+
+MULTISITE CONFIGURATION
+-----------------------
+
+A single Drupal installation can host several Drupal-powered sites, each with
+its own individual configuration.
+
+Additional site configurations are created in subdirectories within the 'sites'
+directory. Each subdirectory must have a 'settings.php' file which specifies the
+configuration settings. The easiest way to create additional sites is to copy
+the 'default' directory and modify the 'settings.php' file as appropriate. The
+new directory name is constructed from the site's URL. The configuration for
+www.example.com could be in 'sites/example.com/settings.php' (note that 'www.'
+should be omitted if users can access your site at http://example.com/).
+
+Sites do not have to have a different domain. You can also use subdomains and
+subdirectories for Drupal sites. For example, example.com, sub.example.com,
+and sub.example.com/site3 can all be defined as independent Drupal sites. The
+setup for a configuration such as this would look like the following:
+
+  sites/default/settings.php
+  sites/example.com/settings.php
+  sites/sub.example.com/settings.php
+  sites/sub.example.com.site3/settings.php
+
+When searching for a site configuration (for example www.sub.example.com/site3),
+Drupal will search for configuration files in the following order, using the
+first configuration it finds:
+
+  sites/www.sub.example.com.site3/settings.php
+  sites/sub.example.com.site3/settings.php
+  sites/example.com.site3/settings.php
+  sites/www.sub.example.com/settings.php
+  sites/sub.example.com/settings.php
+  sites/example.com/settings.php
+  sites/default/settings.php
+
+If you are installing on a non-standard port, the port number is treated as the
+deepest subdomain. For example: http://www.example.com:8080/ could be loaded
+from sites/8080.www.example.com/. The port number will be removed according to
+the pattern above if no port-specific configuration is found, just like a real
+subdomain.
+
+Each site configuration can have its own site-specific modules and themes in
+addition to those installed in the standard 'modules' and 'themes' directories.
+To use site-specific modules or themes, simply create a 'modules' or 'themes'
+directory within the site configuration directory. For example, if
+sub.example.com has a custom theme and a custom module that should not be
+accessible to other sites, the setup would look like this:
+
+  sites/sub.example.com/:
+  settings.php
+  themes/custom_theme
+  modules/custom_module
+
+NOTE: for more information about multiple virtual hosts or the configuration
+settings, consult the Drupal handbook at drupal.org.
+
+For more information on configuring Drupal's file system path in a multi-site
+configuration, see step 6 above.
+
+MORE INFORMATION
+----------------
+
+- For additional documentation, see the online Drupal handbook at
+  http://drupal.org/handbook.
+
+- For a list of security announcements, see the "Security announcements" page
+  at http://drupal.org/security (available as an RSS feed). This page also
+  describes how to subscribe to these announcements via e-mail.
+
+- For information about the Drupal security process, or to find out how to report
+  a potential security issue to the Drupal security team, see the "Security team"
+  page at http://drupal.org/security-team.
+
+- For information about the wide range of available support options, see the
+  "Support" page at http://drupal.org/support.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,342 @@
+// $Id: LICENSE.txt,v 1.5 2006/07/09 11:33:06 dries Exp $
+
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+      How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAINTAINERS.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,88 @@
+// $Id: MAINTAINERS.txt,v 1.19 2008/02/04 10:26:49 goba Exp $
+
+List of maintainers
+--------------------------------------------------------------------------------
+
+LEGEND
+======
+
+- M: the maintainer
+- S: status:
+      "supported"     : someone is actually paid to look after this.
+      "maintained"    : someone actually looks after it.
+      "fixes/patches" : it has a maintainer but they don't have time to
+                        do much other than throw the odd patch in.
+      "orphan"        : no current maintainer, but maybe you could take
+                        the role as you write new code?
+- W: website with status or information
+
+--------------------------------------------------------------------------------
+
+BLOG API
+M: James Walker <walkah@walkah.net>
+S: maintained
+
+DISTRIBUTED AUTHENTICATION MODULES
+M: Moshe Weitzman <weitzman@tejasa.com>
+S: maintained
+
+DOCUMENTATION COORDINATOR
+M: Steven Peck <speck@blkmtn.org>
+S: maintained
+
+FILTER SYSTEM
+M: Steven Wittens <unconed@drupal.org>
+S: maintained
+
+FORM SYSTEM
+M: Károly Négyesi <chx@mail.tvnet.hu>
+S: maintained
+
+LOCALE MODULE
+M: Gabor Hojtsy <goba@php.net>
+S: maintained
+
+LOGGING
+M: Khalid Baheyeldin <drupal@2bits.com>
+S: maintained
+
+MENU SYSTEM
+M: Károly Négyesi <chx@mail.tvnet.hu>
+S: maintained
+
+PATH MODULE
+M: Matt Westgate <drupal@asitis.org>
+S: maintained
+
+POSTGRESQL
+M: Sammy Spets <sammys-drupal@synerger.com>
+S: maintained
+
+SECURITY COORDINATOR
+M: Heine Deelstra <hdeelstra@gmail.com>
+S: maintained
+
+STATISTICS MODULE
+M: Jeremy Andrews <jeremy@kerneltrap.com>
+S: maintained
+
+THEME SYSTEM
+M: Earl Miles <merlin@logrus.com>
+   Joon Park <joon@dvessel.com>
+S: maintained
+
+UPDATE MODULE
+M: Derek Wright <http://drupal.org/user/46549/contact>
+   Earl Miles <http://drupal.org/user/26979/contact>
+S: maintained
+
+XML-RPC SERVER/CLIENT
+M: John VanDyk <http://drupal.org/user/2375/contact>
+S: maintained
+
+TRANSLATIONS COORDINATOR
+M: Gerhard Killesreiter <gerhard@killesreiter.de>
+S: maintained
+
+THE REST:
+M: Dries <dries@drupal.org>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UPGRADE.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,110 @@
+// $Id: UPGRADE.txt,v 1.12 2008/01/04 16:15:58 goba Exp $
+
+UPGRADING
+---------
+
+Prior to upgrading, you should ensure that:
+
+ * Your system meets or exceeds Drupal's minimum requirements as shown at
+   http://drupal.org/requirements.
+ * You have a backup of all your relevant data (#1).
+ * Custom and contributed modules have been checked for compatibility (#11).
+ * Custom and contributed themes have been checked for compatibility (#11).
+ * You have read through this entire document.
+
+Let's begin!
+
+1.  Backup your database and Drupal directory - especially your "sites"
+    directory which contains your configuration file and added modules and
+    themes, any contributed or custom modules in your "modules" directory,
+    and your "files" directory which contains uploaded files. If other files
+    have modifications, such as .htaccess or robots.txt, those should be
+    backed up as well.
+
+    Note: for a single site setup, the configuration file is the "settings.php"
+    file located at sites/default/settings.php. The default.settings.php file
+    contains a clean copy for restoration purposes, if required.
+
+    For multisite configurations, the configuration file is located in a
+    structure like the following:
+
+      sites/default/settings.php
+      sites/example.com/settings.php
+      sites/sub.example.com/settings.php
+      sites/sub.example.com.path/settings.php
+
+    More information on multisite configuration is located in INSTALL.txt.
+
+2.  If possible, log on as the user with user ID 1, which is the first account
+    created and the main administrator account. User ID 1 will be able to
+    automatically access update.php in step #10. There are special instructions
+    in step #10 if you are unable to log on as user ID 1. Do not close your
+    browser until the final step is complete.
+
+3.  Place the site in "Off-line" mode, to let the database updates run without
+    interruption and avoid displaying errors to end users of the site. This
+    option is at http://www.example.com/?q=admin/settings/site-maintenance
+    (replace www.example.com with your installation's domain name and path).
+
+4.  If using a custom or contributed theme, switch
+    to a core theme, such as Garland or Bluemarine.
+
+5.  Disable all custom and contributed modules.
+
+6.  Remove all old files and directories from the Drupal installation directory.
+
+7.  Unpack the new files and directories into the Drupal installation directory.
+
+8.  Copy your backed up "files" and "sites" directories to the Drupal
+    installation directory. If other system files such as .htaccess or
+    robots.txt were customized, re-create the modifications in the new
+    versions of the files using the backups taken in step #1.
+
+9.  Verify the new configuration file to make sure it has correct information.
+
+10. Run update.php by visiting http://www.example.com/update.php (replace
+    www.example.com with your Drupal installation's domain name and path). This
+    step will update the core database tables to the new Drupal installation.
+
+    Note: if you are unable to access update.php do the following:
+
+      - Open your settings.php with a text editor.
+
+      - There is a line that says $update_free_access = FALSE;
+        Change it to $update_free_access = TRUE;
+
+      - Once update.php is done, you must change the settings.php file
+        back to its original form with $update_free_access = FALSE;
+
+11. Ensure that the versions of all custom and contributed modules match the
+    new Drupal version to which you have updated. For a major update, such as
+    from 5.x to 6.x, modules from previous versions will not be compatible
+    and updated versions will be required.
+
+      - For contributed modules, check http://drupal.org/project/modules
+        for the version of a module matching your version of Drupal.
+
+      - For custom modules, review http://drupal.org/update/modules to
+        ensure that a custom module is compatible with the current version.
+
+12. Re-enable custom and contributed modules and re-run update.php
+    to update custom and contributed database tables.
+
+13. Return the site to its original theme (if you switched to a core
+    theme like Garland or Bluemarine in step #4). If your site uses a
+    custom or contributed theme, make sure it is compatible with your
+    version of Drupal.
+
+      - For contributed themes, check http://drupal.org/project/themes
+        for the version of a theme matching your version of Drupal.
+
+      - For custom themes, review http://drupal.org/update/theme to ensure
+        that a custom theme is compatible with the current version.
+
+14. Finally, return your site to "Online" mode so your visitors may resume
+    browsing. As in step #3, this option is available in your administration
+    screens at http://www.example.com/?q=admin/settings/site-maintenance
+    (replace www.example.com with your installation's domain name and path).
+
+For more information on upgrading visit
+the Drupal handbook at http://drupal.org/upgrade
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cron.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+<?php
+// $Id: cron.php,v 1.36 2006/08/09 07:42:55 dries Exp $
+
+/**
+ * @file
+ * Handles incoming requests to fire off regularly-scheduled tasks (cron jobs).
+ */
+
+include_once './includes/bootstrap.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+drupal_cron_run();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/actions.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,365 @@
+<?php
+// $Id: actions.inc,v 1.8 2007/12/31 14:51:04 goba Exp $
+
+/**
+ * @file
+ * This is the actions engine for executing stored actions.
+ */
+
+/**
+ * Perform a given list of actions by executing their callback functions.
+ *
+ * Given the IDs of actions to perform, find out what the callbacks
+ * for the actions are by querying the database. Then call each callback
+ * using the function call $function($object, $context, $a1, $2)
+ * where $function is the name of a function written in compliance with
+ * the action specification; that is, foo($object, $context).
+ *
+ * @param $action_ids
+ *   The ID of the action to perform. Can be a single action ID or an array
+ *   of IDs. IDs of instances will be numeric; IDs of singletons will be
+ *   function names.
+ * @param $object
+ *   Parameter that will be passed along to the callback. Typically the
+ *   object that the action will act on; a node, user or comment object.
+ *   If the action does not act on an object, pass a dummy object. This
+ *   is necessary to support PHP 4 object referencing.
+ * @param $context
+ *   Parameter that will be passed along to the callback. $context is a
+ *   keyed array containing extra information about what is currently
+ *   happening at the time of the call. Typically $context['hook'] and
+ *   $context['op'] will tell which hook-op combination resulted in this
+ *   call to actions_do().
+ * @param $a1
+ *   Parameter that will be passed along to the callback.
+ * @param $a2
+ *   Parameter that will be passed along to the callback.
+ *
+ * @return
+ *   An associative array containing the result of the function that
+ *   performs the action, keyed on action ID.
+ */
+function actions_do($action_ids, &$object, $context = NULL, $a1 = NULL, $a2 = NULL) {
+  static $stack;
+  $stack++;
+  if ($stack > variable_get('actions_max_stack', 35)) {
+    watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', WATCHDOG_ERROR);
+    return;
+  }
+  $actions = array();
+  $available_actions = actions_list();
+  $result = array();
+  if (is_array($action_ids)) {
+    $where = array();
+    $where_values = array();
+    foreach ($action_ids as $action_id) {
+      if (is_numeric($action_id)) {
+        $where[] = 'OR aid = %d';
+        $where_values[] = $action_id;
+      }
+      elseif (isset($available_actions[$action_id])) {
+        $actions[$action_id] = $available_actions[$action_id];
+      }
+    }
+
+    // When we have action instances we must go to the database to
+    // retrieve instance data.
+    if ($where) {
+      $where_clause = implode(' ', $where);
+      // Strip off leading 'OR '.
+      $where_clause = '('. strstr($where_clause, " ") .')';
+      $result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
+      while ($action = db_fetch_object($result_db)) {
+        $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
+        $actions[$action->aid]['callback'] = $action->callback;
+        $actions[$action->aid]['type'] = $action->type;
+      }
+    }
+
+    // Fire actions, in no particular order.
+    foreach ($actions as $action_id => $params) {
+      if (is_numeric($action_id)) { // Configurable actions need parameters.
+        $function = $params['callback'];
+        $context = array_merge($context, $params);
+        $result[$action_id] = $function($object, $context, $a1, $a2);
+      }
+      // Singleton action; $action_id is the function name.
+      else {
+        $result[$action_id] = $action_id($object, $context, $a1, $a2);
+      }
+    }
+  }
+  // Optimized execution of single action.
+  else {
+    // If it's a configurable action, retrieve stored parameters.
+    if (is_numeric($action_ids)) {
+      $action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids));
+      $function = $action->callback;
+      $context = array_merge($context, unserialize($action->parameters));
+      $result[$action_ids] = $function($object, $context, $a1, $a2);
+    }
+    // Singleton action; $action_ids is the function name.
+    else {
+      $result[$action_ids] = $action_ids($object, $context, $a1, $a2);
+    }
+  }
+  return $result;
+}
+
+
+/**
+ * Discover all action functions by invoking hook_action_info().
+ *
+ * mymodule_action_info() {
+ *   return array(
+ *     'mymodule_functiondescription_action' => array(
+ *       'type' => 'node',
+ *       'description' => t('Save node'),
+ *       'configurable' => FALSE,
+ *       'hooks' => array(
+ *         'nodeapi' => array('delete', 'insert', 'update', 'view'),
+ *         'comment' => array('delete', 'insert', 'update', 'view'),
+ *       )
+ *     )
+ *   );
+ * }
+ *
+ * The description is used in presenting possible actions to the user for
+ * configuration. The type is used to present these actions in a logical
+ * grouping and to denote context. Some types are 'node', 'user', 'comment',
+ * and 'system'. If an action is configurable it will provide form,
+ * validation and submission functions. The hooks the action supports
+ * are declared in the 'hooks' array.
+ *
+ * @param $reset
+ *   Reset the action info static cache.
+ *
+ * @return
+ *   An associative array keyed on function name. The value of each key is
+ *   an array containing information about the action, such as type of
+ *   action and description of the action, e.g.,
+ *
+ *   @code
+ *   $actions['node_publish_action'] = array(
+ *     'type' => 'node',
+ *     'description' => t('Publish post'),
+ *     'configurable' => FALSE,
+ *     'hooks' => array(
+ *       'nodeapi' => array('presave', 'insert', 'update', 'view'),
+ *       'comment' => array('delete', 'insert', 'update', 'view'),
+ *     ),
+ *   );
+ *   @endcode
+ */
+function actions_list($reset = FALSE) {
+  static $actions;
+  if (!isset($actions) || $reset) {
+    $actions = module_invoke_all('action_info');
+    drupal_alter('action_info', $actions);
+  }
+
+  // See module_implements for explanations of this cast.
+  return (array)$actions;
+}
+
+/**
+ * Retrieve all action instances from the database.
+ *
+ * Compare with actions_list() which gathers actions by
+ * invoking hook_action_info(). The two are synchronized
+ * by visiting /admin/build/actions (when actions.module is
+ * enabled) which runs actions_synchronize().
+ *
+ * @return
+ *   Associative array keyed by action ID. Each value is
+ *   an associative array with keys 'callback', 'description',
+ *   'type' and 'configurable'.
+ */
+function actions_get_all_actions() {
+  $actions = array();
+  $result = db_query("SELECT * FROM {actions}");
+  while ($action = db_fetch_object($result)) {
+    $actions[$action->aid] = array(
+      'callback' => $action->callback,
+      'description' => $action->description,
+      'type' => $action->type,
+      'configurable' => (bool) $action->parameters,
+    );
+  }
+  return $actions;
+}
+
+/**
+ * Create an associative array keyed by md5 hashes of function names.
+ *
+ * Hashes are used to prevent actual function names from going out into
+ * HTML forms and coming back.
+ *
+ * @param $actions
+ *   An associative array with function names as keys and associative
+ *   arrays with keys 'description', 'type', etc. as values. Generally
+ *   the output of actions_list() or actions_get_all_actions() is given
+ *   as input to this function.
+ *
+ * @return
+ *   An associative array keyed on md5 hash of function name. The value of
+ *   each key is an associative array of function, description, and type
+ *   for the action.
+ */
+function actions_actions_map($actions) {
+  $actions_map = array();
+  foreach ($actions as $callback => $array) {
+    $key = md5($callback);
+    $actions_map[$key]['callback']     = isset($array['callback']) ? $array['callback'] : $callback;
+    $actions_map[$key]['description']  = $array['description'];
+    $actions_map[$key]['type']         = $array['type'];
+    $actions_map[$key]['configurable'] = $array['configurable'];
+  }
+  return $actions_map;
+}
+
+/**
+ * Given an md5 hash of a function name, return the function name.
+ *
+ * Faster than actions_actions_map() when you only need the function name.
+ *
+ * @param $hash
+ *   MD5 hash of a function name
+ *
+ * @return
+ *   Function name
+ */
+function actions_function_lookup($hash) {
+  $actions_list = actions_list();
+  foreach ($actions_list as $function => $array) {
+    if (md5($function) == $hash) {
+      return $function;
+    }
+  }
+
+  // Must be an instance; must check database.
+  $aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters != ''", $hash));
+  return $aid;
+}
+
+/**
+ * Synchronize actions that are provided by modules.
+ *
+ * They are synchronized with actions that are stored in the actions table.
+ * This is necessary so that actions that do not require configuration can
+ * receive action IDs. This is not necessarily the best approach,
+ * but it is the most straightforward.
+ */
+function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
+  if (!$actions_in_code) {
+    $actions_in_code = actions_list();
+  }
+  $actions_in_db = array();
+  $result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
+  while ($action = db_fetch_object($result)) {
+    $actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
+  }
+
+  // Go through all the actions provided by modules.
+  foreach ($actions_in_code as $callback => $array) {
+    // Ignore configurable actions since their instances get put in
+    // when the user adds the action.
+    if (!$array['configurable']) {
+      // If we already have an action ID for this action, no need to assign aid.
+      if (array_key_exists($callback, $actions_in_db)) {
+        unset($actions_in_db[$callback]);
+      }
+      else {
+        // This is a new singleton that we don't have an aid for; assign one.
+        db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
+        watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description'])));
+      }
+    }
+  }
+
+  // Any actions that we have left in $actions_in_db are orphaned.
+  if ($actions_in_db) {
+    $orphaned = array();
+    $placeholder = array();
+
+    foreach ($actions_in_db as $callback => $array) {
+      $orphaned[] = $callback;
+      $placeholder[] = "'%s'";
+    }
+
+    $orphans = implode(', ', $orphaned);
+
+    if ($delete_orphans) {
+      $placeholders = implode(', ', $placeholder);
+      $results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
+      while ($action = db_fetch_object($results)) {
+        actions_delete($action->aid);
+        watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description)));
+      }
+    }
+    else {
+      $link = l(t('Remove orphaned actions'), 'admin/build/actions/orphan');
+      $count = count($actions_in_db);
+      watchdog('actions', format_plural($count, 'One orphaned action (%orphans) exists in the actions table. !link', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), 'warning'));
+    }
+  }
+}
+
+/**
+ * Save an action and its associated user-supplied parameter values to the database.
+ *
+ * @param $function
+ *   The name of the function to be called when this action is performed.
+ * @param $params
+ *   An associative array with parameter names as keys and parameter values
+ *   as values.
+ * @param $desc
+ *   A user-supplied description of this particular action, e.g., 'Send
+ *   e-mail to Jim'.
+ * @param $aid
+ *   The ID of this action. If omitted, a new action is created.
+ *
+ * @return
+ *   The ID of the action.
+ */
+function actions_save($function, $type, $params, $desc, $aid = NULL) {
+  $serialized = serialize($params);
+  if ($aid) {
+    db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = %d", $function, $type, $serialized, $desc, $aid);
+    watchdog('actions', 'Action %action saved.', array('%action' => $desc));
+  }
+  else {
+    // aid is the callback for singleton actions so we need to keep a
+    // separate table for numeric aids.
+    db_query('INSERT INTO {actions_aid} VALUES (default)');
+    $aid = db_last_insert_id('actions_aid', 'aid');
+    db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES (%d, '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
+    watchdog('actions', 'Action %action created.', array('%action' => $desc));
+  }
+
+  return $aid;
+}
+
+/**
+ * Retrieve a single action from the database.
+ *
+ * @param $aid
+ *   integer The ID of the action to retrieve.
+ *
+ * @return
+ *   The appropriate action row from the database as an object.
+ */
+function actions_load($aid) {
+  return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $aid));
+}
+
+/**
+ * Delete a single action from the database.
+ *
+ * @param $aid
+ *   integer The ID of the action to delete.
+ */
+function actions_delete($aid) {
+  db_query("DELETE FROM {actions} WHERE aid = %d", $aid);
+  module_invoke_all('actions_delete', $aid);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/batch.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,354 @@
+<?php
+// $Id: batch.inc,v 1.14 2007/12/20 11:57:20 goba Exp $
+
+/**
+ * @file Batch processing API for processes to run in multiple HTTP requests.
+ */
+
+/**
+ * State-based dispatcher for the batch processing page.
+ */
+function _batch_page() {
+  $batch =& batch_get();
+
+  // Retrieve the current state of batch from db.
+  if (isset($_REQUEST['id']) && $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d AND token = '%s'", $_REQUEST['id'], drupal_get_token($_REQUEST['id'])))) {
+    $batch = unserialize($data);
+  }
+  else {
+    return FALSE;
+  }
+
+  // Register database update for end of processing.
+  register_shutdown_function('_batch_shutdown');
+
+  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
+  $output = NULL;
+  switch ($op) {
+    case 'start':
+      $output = _batch_start();
+      break;
+
+    case 'do':
+      // JS-version AJAX callback.
+      _batch_do();
+      break;
+
+    case 'do_nojs':
+      // Non-JS progress page.
+      $output = _batch_progress_page_nojs();
+      break;
+
+    case 'finished':
+      $output = _batch_finished();
+      break;
+  }
+
+  return $output;
+}
+
+/**
+ * Initiate the batch processing
+ */
+function _batch_start() {
+  // Choose between the JS and non-JS version.
+  // JS-enabled users are identified through the 'has_js' cookie set in drupal.js.
+  // If the user did not visit any JS enabled page during his browser session,
+  // he gets the non-JS version...
+  if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
+    return _batch_progress_page_js();
+  }
+  else {
+    return _batch_progress_page_nojs();
+  }
+}
+
+/**
+ * Batch processing page with JavaScript support.
+ */
+function _batch_progress_page_js() {
+  $batch = batch_get();
+
+  // The first batch set gets to set the page title
+  // and the initialization and error messages.
+  $current_set = _batch_current_set();
+  drupal_set_title($current_set['title']);
+  drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
+
+  $url = url($batch['url'], array('query' => array('id' => $batch['id'])));
+  $js_setting = array(
+    'batch' => array(
+      'errorMessage' => $current_set['error_message'] .'<br/>'. $batch['error_message'],
+      'initMessage' => $current_set['init_message'],
+      'uri' => $url,
+    ),
+  );
+  drupal_add_js($js_setting, 'setting');
+  drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
+
+  $output = '<div id="progress"></div>';
+  return $output;
+}
+
+/**
+ * Do one pass of execution and inform back the browser about progression
+ * (used for JavaScript-mode only).
+ */
+function _batch_do() {
+  // HTTP POST required
+  if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+    drupal_set_message(t('HTTP POST is required.'), 'error');
+    drupal_set_title(t('Error'));
+    return '';
+  }
+
+  // Perform actual processing.
+  list($percentage, $message) = _batch_process();
+
+  drupal_json(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
+}
+
+/**
+ * Batch processing page without JavaScript support.
+ */
+function _batch_progress_page_nojs() {
+  $batch =& batch_get();
+  $current_set = _batch_current_set();
+
+  drupal_set_title($current_set['title']);
+
+  $new_op = 'do_nojs';
+
+  if (!isset($batch['running'])) {
+    // This is the first page so we return some output immediately.
+    $percentage = 0;
+    $message = $current_set['init_message'];
+    $batch['running'] = TRUE;
+  }
+  else {
+    // This is one of the later requests: do some processing first.
+
+    // Error handling: if PHP dies due to a fatal error (e.g. non-existant
+    // function), it will output whatever is in the output buffer,
+    // followed by the error message.
+    ob_start();
+    $fallback = $current_set['error_message'] .'<br/>'. $batch['error_message'];
+    $fallback = theme('maintenance_page', $fallback, FALSE, FALSE);
+
+    // We strip the end of the page using a marker in the template, so any
+    // additional HTML output by PHP shows up inside the page rather than
+    // below it. While this causes invalid HTML, the same would be true if
+    // we didn't, as content is not allowed to appear after </html> anyway.
+    list($fallback) = explode('<!--partial-->', $fallback);
+    print $fallback;
+
+    // Perform actual processing.
+    list($percentage, $message) = _batch_process($batch);
+    if ($percentage == 100) {
+      $new_op = 'finished';
+    }
+
+    // PHP did not die : remove the fallback output.
+    ob_end_clean();
+  }
+
+  $url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op)));
+  drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL='. $url .'">');
+  $output = theme('progress_bar', $percentage, $message);
+  return $output;
+}
+
+/**
+ * Advance batch processing for 1 second (or process the whole batch if it
+ * was not set for progressive execution - e.g forms submitted by drupal_execute).
+ */
+function _batch_process() {
+  $batch =& batch_get();
+  $current_set =& _batch_current_set();
+  $set_changed = TRUE;
+
+  if ($batch['progressive']) {
+    timer_start('batch_processing');
+  }
+
+  while (!$current_set['success']) {
+    // If this is the first time we iterate this batch set in the current
+    // request, we check if it requires an additional file for functions
+    // definitions.
+    if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
+      include_once($current_set['file']);
+    }
+
+    $finished = 1;
+    $task_message = '';
+    if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
+      // Build the 'context' array, execute the function call,
+      // and retrieve the user message.
+      $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
+      // Process the current operation.
+      call_user_func_array($function, array_merge($args, array(&$batch_context)));
+    }
+
+    if ($finished == 1) {
+      // Make sure this step isn't counted double when computing $current.
+      $finished = 0;
+      // Remove the operation and clear the sandbox.
+      array_shift($current_set['operations']);
+      $current_set['sandbox'] = array();
+    }
+
+    // If the batch set is completed, browse through the remaining sets,
+    // executing 'control sets' (stored form submit handlers) along the way -
+    // this might in turn insert new batch sets.
+    // Stop when we find a set that actually has operations.
+    $set_changed = FALSE;
+    $old_set = $current_set;
+    while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
+      $current_set =& _batch_current_set();
+      $set_changed = TRUE;
+    }
+    // At this point, either $current_set is a 'real' batch set (has operations),
+    // or all sets have been completed.
+
+    // If we're in progressive mode, stop after 1 second.
+    if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
+      break;
+    }
+  }
+
+  if ($batch['progressive']) {
+    // Gather progress information.
+
+    // Reporting 100% progress will cause the whole batch to be considered
+    // processed. If processing was paused right after moving to a new set,
+    // we have to use the info from the new (unprocessed) one.
+    if ($set_changed && isset($current_set['operations'])) {
+      // Processing will continue with a fresh batch set.
+      $remaining = count($current_set['operations']);
+      $total = $current_set['total'];
+      $progress_message = $current_set['init_message'];
+      $task_message = '';
+    }
+    else {
+      $remaining = count($old_set['operations']);
+      $total = $old_set['total'];
+      $progress_message = $old_set['progress_message'];
+    }
+
+    $current    = $total - $remaining + $finished;
+    $percentage = $total ? floor($current / $total * 100) : 100;
+    $values = array(
+      '@remaining'  => $remaining,
+      '@total'      => $total,
+      '@current'    => floor($current),
+      '@percentage' => $percentage,
+      );
+    $message = strtr($progress_message, $values) .'<br/>';
+    $message .= $task_message ? $task_message : '&nbsp';
+
+    return array($percentage, $message);
+  }
+  else {
+    // If we're not in progressive mode, the whole batch has been processed by now.
+    return _batch_finished();
+  }
+
+}
+
+/**
+ * Retrieve the batch set being currently processed.
+ */
+function &_batch_current_set() {
+  $batch =& batch_get();
+  return $batch['sets'][$batch['current_set']];
+}
+
+/**
+ * Move execution to the next batch set if any, executing the stored
+ * form _submit handlers along the way (thus possibly inserting
+ * additional batch sets).
+ */
+function _batch_next_set() {
+  $batch =& batch_get();
+  if (isset($batch['sets'][$batch['current_set'] + 1])) {
+    $batch['current_set']++;
+    $current_set =& _batch_current_set();
+    if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) {
+      // We use our stored copies of $form and $form_state, to account for
+      // possible alteration by the submit handlers.
+      $function($batch['form'], $batch['form_state']);
+    }
+    return TRUE;
+  }
+}
+
+/**
+ * End the batch processing:
+ * Call the 'finished' callbacks to allow custom handling of results,
+ * and resolve page redirection.
+ */
+function _batch_finished() {
+  $batch =& batch_get();
+
+  // Execute the 'finished' callbacks for each batch set.
+  foreach ($batch['sets'] as $key => $batch_set) {
+    if (isset($batch_set['finished'])) {
+      // Check if the set requires an additional file for functions definitions.
+      if (isset($batch_set['file']) && is_file($batch_set['file'])) {
+        include_once($batch_set['file']);
+      }
+      if (function_exists($batch_set['finished'])) {
+        $batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']);
+      }
+    }
+  }
+
+  // Cleanup the batch table and unset the global $batch variable.
+  if ($batch['progressive']) {
+    db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
+  }
+  $_batch = $batch;
+  $batch = NULL;
+
+  // Redirect if needed.
+  if ($_batch['progressive']) {
+    // Put back the 'destination' that was saved in batch_process().
+    if (isset($_batch['destination'])) {
+      $_REQUEST['destination'] = $_batch['destination'];
+    }
+
+    // Use $_batch['form_state']['redirect'], or $_batch['redirect'],
+    // or $_batch['source_page'].
+    if (isset($_batch['form_state']['redirect'])) {
+      $redirect = $_batch['form_state']['redirect'];
+    }
+    elseif (isset($_batch['redirect'])) {
+      $redirect = $_batch['redirect'];
+    }
+    else {
+      $redirect = $_batch['source_page'];
+    }
+
+    // Let drupal_redirect_form handle redirection logic.
+    $form = isset($batch['form']) ? $batch['form'] : array();
+    if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) {
+      drupal_redirect_form($form, $redirect);
+    }
+
+    // We get here if $form['#redirect'] was FALSE, or if the form is a
+    // multi-step form. We save the final $form_state value to be retrieved
+    // by drupal_get_form, and we redirect to the originating page.
+    $_SESSION['batch_form_state'] = $_batch['form_state'];
+    drupal_goto($_batch['source_page']);
+  }
+}
+
+/**
+ * Shutdown function: store the batch data for next request,
+ * or clear the table if the batch is finished.
+ */
+function _batch_shutdown() {
+  if ($batch = batch_get()) {
+    db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/bootstrap.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1146 @@
+<?php
+// $Id: bootstrap.inc,v 1.206.2.2 2008/02/11 14:36:21 goba Exp $
+
+/**
+ * @file
+ * Functions that need to be loaded on every Drupal request.
+ */
+
+/**
+ * Indicates that the item should never be removed unless explicitly told to
+ * using cache_clear_all() with a cache ID.
+ */
+define('CACHE_PERMANENT', 0);
+
+/**
+ * Indicates that the item should be removed at the next general cache wipe.
+ */
+define('CACHE_TEMPORARY', -1);
+
+/**
+ * Indicates that page caching is disabled.
+ */
+define('CACHE_DISABLED', 0);
+
+/**
+ * Indicates that page caching is enabled, using "normal" mode.
+ */
+define('CACHE_NORMAL', 1);
+
+/**
+ * Indicates that page caching is using "aggressive" mode. This bypasses
+ * loading any modules for additional speed, which may break functionality in
+ * modules that expect to be run on each page load.
+ */
+define('CACHE_AGGRESSIVE', 2);
+
+/**
+ *
+ * Severity levels, as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html
+ * @see watchdog()
+ * @see watchdog_severity_levels()
+ */
+define('WATCHDOG_EMERG',    0); // Emergency: system is unusable
+define('WATCHDOG_ALERT',    1); // Alert: action must be taken immediately
+define('WATCHDOG_CRITICAL', 2); // Critical: critical conditions
+define('WATCHDOG_ERROR',    3); // Error: error conditions
+define('WATCHDOG_WARNING',  4); // Warning: warning conditions
+define('WATCHDOG_NOTICE',   5); // Notice: normal but significant condition
+define('WATCHDOG_INFO',     6); // Informational: informational messages
+define('WATCHDOG_DEBUG',    7); // Debug: debug-level messages
+
+/**
+ * First bootstrap phase: initialize configuration.
+ */
+define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
+
+/**
+ * Second bootstrap phase: try to call a non-database cache
+ * fetch routine.
+ */
+define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1);
+
+/**
+ * Third bootstrap phase: initialize database layer.
+ */
+define('DRUPAL_BOOTSTRAP_DATABASE', 2);
+
+/**
+ * Fourth bootstrap phase: identify and reject banned hosts.
+ */
+define('DRUPAL_BOOTSTRAP_ACCESS', 3);
+
+/**
+ * Fifth bootstrap phase: initialize session handling.
+ */
+define('DRUPAL_BOOTSTRAP_SESSION', 4);
+
+/**
+ * Sixth bootstrap phase: load bootstrap.inc and module.inc, start
+ * the variable system and try to serve a page from the cache.
+ */
+define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5);
+
+/**
+ * Seventh bootstrap phase: find out language of the page.
+ */
+define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);
+
+/**
+ * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request.
+ */
+define('DRUPAL_BOOTSTRAP_PATH', 7);
+
+/**
+ * Final bootstrap phase: Drupal is fully loaded; validate and fix
+ * input data.
+ */
+define('DRUPAL_BOOTSTRAP_FULL', 8);
+
+/**
+ * Role ID for anonymous users; should match what's in the "role" table.
+ */
+define('DRUPAL_ANONYMOUS_RID', 1);
+
+/**
+ * Role ID for authenticated users; should match what's in the "role" table.
+ */
+define('DRUPAL_AUTHENTICATED_RID', 2);
+
+/**
+ * No language negotiation. The default language is used.
+ */
+define('LANGUAGE_NEGOTIATION_NONE', 0);
+
+/**
+ * Path based negotiation with fallback to default language
+ * if no defined path prefix identified.
+ */
+define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
+
+/**
+ * Path based negotiation with fallback to user preferences
+ * and browser language detection if no defined path prefix
+ * identified.
+ */
+define('LANGUAGE_NEGOTIATION_PATH', 2);
+
+/**
+ * Domain based negotiation with fallback to default language
+ * if no language identified by domain.
+ */
+define('LANGUAGE_NEGOTIATION_DOMAIN', 3);
+
+/**
+ * Start the timer with the specified name. If you start and stop
+ * the same timer multiple times, the measured intervals will be
+ * accumulated.
+ *
+ * @param name
+ *   The name of the timer.
+ */
+function timer_start($name) {
+  global $timers;
+
+  list($usec, $sec) = explode(' ', microtime());
+  $timers[$name]['start'] = (float)$usec + (float)$sec;
+  $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1;
+}
+
+/**
+ * Read the current timer value without stopping the timer.
+ *
+ * @param name
+ *   The name of the timer.
+ * @return
+ *   The current timer value in ms.
+ */
+function timer_read($name) {
+  global $timers;
+
+  if (isset($timers[$name]['start'])) {
+    list($usec, $sec) = explode(' ', microtime());
+    $stop = (float)$usec + (float)$sec;
+    $diff = round(($stop - $timers[$name]['start']) * 1000, 2);
+
+    if (isset($timers[$name]['time'])) {
+      $diff += $timers[$name]['time'];
+    }
+    return $diff;
+  }
+}
+
+/**
+ * Stop the timer with the specified name.
+ *
+ * @param name
+ *   The name of the timer.
+ * @return
+ *   A timer array. The array contains the number of times the
+ *   timer has been started and stopped (count) and the accumulated
+ *   timer value in ms (time).
+ */
+function timer_stop($name) {
+  global $timers;
+
+  $timers[$name]['time'] = timer_read($name);
+  unset($timers[$name]['start']);
+
+  return $timers[$name];
+}
+
+/**
+ * Find the appropriate configuration directory.
+ *
+ * Try finding a matching configuration directory by stripping the website's
+ * hostname from left to right and pathname from right to left. The first
+ * configuration file found will be used; the remaining will ignored. If no
+ * configuration file is found, return a default value '$confdir/default'.
+ *
+ * Example for a fictitious site installed at
+ * http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in
+ * the following directories:
+ *
+ *  1. $confdir/8080.www.drupal.org.mysite.test
+ *  2. $confdir/www.drupal.org.mysite.test
+ *  3. $confdir/drupal.org.mysite.test
+ *  4. $confdir/org.mysite.test
+ *
+ *  5. $confdir/8080.www.drupal.org.mysite
+ *  6. $confdir/www.drupal.org.mysite
+ *  7. $confdir/drupal.org.mysite
+ *  8. $confdir/org.mysite
+ *
+ *  9. $confdir/8080.www.drupal.org
+ * 10. $confdir/www.drupal.org
+ * 11. $confdir/drupal.org
+ * 12. $confdir/org
+ *
+ * 13. $confdir/default
+ *
+ * @param $require_settings
+ *   Only configuration directories with an existing settings.php file
+ *   will be recognized. Defaults to TRUE. During initial installation,
+ *   this is set to FALSE so that Drupal can detect a matching directory,
+ *   then create a new settings.php file in it.
+ * @param reset
+ *   Force a full search for matching directories even if one had been
+ *   found previously.
+ * @return
+ *   The path of the matching directory.
+ */
+function conf_path($require_settings = TRUE, $reset = FALSE) {
+  static $conf = '';
+
+  if ($conf && !$reset) {
+    return $conf;
+  }
+
+  $confdir = 'sites';
+  $uri = explode('/', $_SERVER['SCRIPT_NAME'] ? $_SERVER['SCRIPT_NAME'] : $_SERVER['SCRIPT_FILENAME']);
+  $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
+  for ($i = count($uri) - 1; $i > 0; $i--) {
+    for ($j = count($server); $j > 0; $j--) {
+      $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
+      if (file_exists("$confdir/$dir/settings.php") || (!$require_settings && file_exists("$confdir/$dir"))) {
+        $conf = "$confdir/$dir";
+        return $conf;
+      }
+    }
+  }
+  $conf = "$confdir/default";
+  return $conf;
+}
+
+/**
+ * Unsets all disallowed global variables. See $allowed for what's allowed.
+ */
+function drupal_unset_globals() {
+  if (ini_get('register_globals')) {
+    $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'GLOBALS' => 1);
+    foreach ($GLOBALS as $key => $value) {
+      if (!isset($allowed[$key])) {
+        unset($GLOBALS[$key]);
+      }
+    }
+  }
+}
+
+/**
+ * Loads the configuration and sets the base URL, cookie domain, and
+ * session name correctly.
+ */
+function conf_init() {
+  global $base_url, $base_path, $base_root;
+
+  // Export the following settings.php variables to the global namespace
+  global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access;
+  $conf = array();
+
+  if (file_exists('./'. conf_path() .'/settings.php')) {
+    include_once './'. conf_path() .'/settings.php';
+  }
+
+  if (isset($base_url)) {
+    // Parse fixed base URL from settings.php.
+    $parts = parse_url($base_url);
+    if (!isset($parts['path'])) {
+      $parts['path'] = '';
+    }
+    $base_path = $parts['path'] .'/';
+    // Build $base_root (everything until first slash after "scheme://").
+    $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
+  }
+  else {
+    // Create base URL
+    $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
+
+    // As $_SERVER['HTTP_HOST'] is user input, ensure it only contains
+    // characters allowed in hostnames.
+    $base_url = $base_root .= '://'. preg_replace('/[^a-z0-9-:._]/i', '', $_SERVER['HTTP_HOST']);
+
+    // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
+    // be modified by a visitor.
+    if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
+      $base_path = "/$dir";
+      $base_url .= $base_path;
+      $base_path .= '/';
+    }
+    else {
+      $base_path = '/';
+    }
+  }
+
+  if ($cookie_domain) {
+    // If the user specifies the cookie domain, also use it for session name.
+    $session_name = $cookie_domain;
+  }
+  else {
+    // Otherwise use $base_url as session name, without the protocol
+    // to use the same session identifiers across http and https.
+    list( , $session_name) = explode('://', $base_url, 2);
+    // We escape the hostname because it can be modified by a visitor.
+    if (!empty($_SERVER['HTTP_HOST'])) {
+      $cookie_domain = check_plain($_SERVER['HTTP_HOST']);
+    }
+  }
+  // Strip leading periods, www., and port numbers from cookie domain.
+  $cookie_domain = ltrim($cookie_domain, '.');
+  if (strpos($cookie_domain, 'www.') === 0) {
+    $cookie_domain = substr($cookie_domain, 4);
+  }
+  $cookie_domain = explode(':', $cookie_domain);
+  $cookie_domain = '.'. $cookie_domain[0];
+  // Per RFC 2109, cookie domains must contain at least one dot other than the
+  // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
+  if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
+    ini_set('session.cookie_domain', $cookie_domain);
+  }
+  session_name('SESS'. md5($session_name));
+}
+
+/**
+ * Returns and optionally sets the filename for a system item (module,
+ * theme, etc.). The filename, whether provided, cached, or retrieved
+ * from the database, is only returned if the file exists.
+ *
+ * This function plays a key role in allowing Drupal's resources (modules
+ * and themes) to be located in different places depending on a site's
+ * configuration. For example, a module 'foo' may legally be be located
+ * in any of these three places:
+ *
+ * modules/foo/foo.module
+ * sites/all/modules/foo/foo.module
+ * sites/example.com/modules/foo/foo.module
+ *
+ * Calling drupal_get_filename('module', 'foo') will give you one of
+ * the above, depending on where the module is located.
+ *
+ * @param $type
+ *   The type of the item (i.e. theme, theme_engine, module).
+ * @param $name
+ *   The name of the item for which the filename is requested.
+ * @param $filename
+ *   The filename of the item if it is to be set explicitly rather
+ *   than by consulting the database.
+ *
+ * @return
+ *   The filename of the requested item.
+ */
+function drupal_get_filename($type, $name, $filename = NULL) {
+  static $files = array();
+
+  if (!isset($files[$type])) {
+    $files[$type] = array();
+  }
+
+  if (!empty($filename) && file_exists($filename)) {
+    $files[$type][$name] = $filename;
+  }
+  elseif (isset($files[$type][$name])) {
+    // nothing
+  }
+  // Verify that we have an active database connection, before querying
+  // the database.  This is required because this function is called both
+  // before we have a database connection (i.e. during installation) and
+  // when a database connection fails.
+  elseif (db_is_active() && (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file))) {
+    $files[$type][$name] = $file;
+  }
+  else {
+    // Fallback to searching the filesystem if the database connection is
+    // not established or the requested file is not found.
+    $config = conf_path();
+    $dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
+    $file = (($type == 'theme_engine') ? "$name.engine" : "$name.$type");
+
+    foreach (array("$config/$dir/$file", "$config/$dir/$name/$file", "$dir/$file", "$dir/$name/$file") as $file) {
+      if (file_exists($file)) {
+        $files[$type][$name] = $file;
+        break;
+      }
+    }
+  }
+
+  if (isset($files[$type][$name])) {
+    return $files[$type][$name];
+  }
+}
+
+/**
+ * Load the persistent variable table.
+ *
+ * The variable table is composed of values that have been saved in the table
+ * with variable_set() as well as those explicitly specified in the configuration
+ * file.
+ */
+function variable_init($conf = array()) {
+  // NOTE: caching the variables improves performance by 20% when serving cached pages.
+  if ($cached = cache_get('variables', 'cache')) {
+    $variables = $cached->data;
+  }
+  else {
+    $result = db_query('SELECT * FROM {variable}');
+    while ($variable = db_fetch_object($result)) {
+      $variables[$variable->name] = unserialize($variable->value);
+    }
+    cache_set('variables', $variables);
+  }
+
+  foreach ($conf as $name => $value) {
+    $variables[$name] = $value;
+  }
+
+  return $variables;
+}
+
+/**
+ * Return a persistent variable.
+ *
+ * @param $name
+ *   The name of the variable to return.
+ * @param $default
+ *   The default value to use if this variable has never been set.
+ * @return
+ *   The value of the variable.
+ */
+function variable_get($name, $default) {
+  global $conf;
+
+  return isset($conf[$name]) ? $conf[$name] : $default;
+}
+
+/**
+ * Set a persistent variable.
+ *
+ * @param $name
+ *   The name of the variable to set.
+ * @param $value
+ *   The value to set. This can be any PHP data type; these functions take care
+ *   of serialization as necessary.
+ */
+function variable_set($name, $value) {
+  global $conf;
+
+  $serialized_value = serialize($value);
+  db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", $serialized_value, $name);
+  if (!db_affected_rows()) {
+    @db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, $serialized_value);
+  }
+
+  cache_clear_all('variables', 'cache');
+
+  $conf[$name] = $value;
+}
+
+/**
+ * Unset a persistent variable.
+ *
+ * @param $name
+ *   The name of the variable to undefine.
+ */
+function variable_del($name) {
+  global $conf;
+
+  db_query("DELETE FROM {variable} WHERE name = '%s'", $name);
+  cache_clear_all('variables', 'cache');
+
+  unset($conf[$name]);
+}
+
+
+/**
+ * Retrieve the current page from the cache.
+ *
+ * Note: we do not serve cached pages when status messages are waiting (from
+ * a redirected form submission which was completed).
+ *
+ * @param $status_only
+ *   When set to TRUE, retrieve the status of the page cache only
+ *   (whether it was started in this request or not).
+ */
+function page_get_cache($status_only = FALSE) {
+  static $status = FALSE;
+  global $user, $base_root;
+
+  if ($status_only) {
+    return $status;
+  }
+  $cache = NULL;
+
+  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) {
+    $cache = cache_get($base_root . request_uri(), 'cache_page');
+
+    if (empty($cache)) {
+      ob_start();
+      $status = TRUE;
+    }
+  }
+
+  return $cache;
+}
+
+/**
+ * Call all init or exit hooks without including all modules.
+ *
+ * @param $hook
+ *   The name of the bootstrap hook we wish to invoke.
+ */
+function bootstrap_invoke_all($hook) {
+  foreach (module_list(TRUE, TRUE) as $module) {
+    drupal_load('module', $module);
+    module_invoke($module, $hook);
+  }
+}
+
+/**
+ * Includes a file with the provided type and name. This prevents
+ * including a theme, engine, module, etc., more than once.
+ *
+ * @param $type
+ *   The type of item to load (i.e. theme, theme_engine, module).
+ * @param $name
+ *   The name of the item to load.
+ *
+ * @return
+ *   TRUE if the item is loaded or has already been loaded.
+ */
+function drupal_load($type, $name) {
+  static $files = array();
+
+  if (isset($files[$type][$name])) {
+    return TRUE;
+  }
+
+  $filename = drupal_get_filename($type, $name);
+
+  if ($filename) {
+    include_once "./$filename";
+    $files[$type][$name] = TRUE;
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Set HTTP headers in preparation for a page response.
+ *
+ * Authenticated users are always given a 'no-cache' header, and will
+ * fetch a fresh page on every request.  This prevents authenticated
+ * users seeing locally cached pages that show them as logged out.
+ *
+ * @see page_set_cache()
+ */
+function drupal_page_header() {
+  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
+  header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
+  header("Cache-Control: store, no-cache, must-revalidate");
+  header("Cache-Control: post-check=0, pre-check=0", FALSE);
+}
+
+/**
+ * Set HTTP headers in preparation for a cached page response.
+ *
+ * The general approach here is that anonymous users can keep a local
+ * cache of the page, but must revalidate it on every request.  Then,
+ * they are given a '304 Not Modified' response as long as they stay
+ * logged out and the page has not been modified.
+ *
+ */
+function drupal_page_cache_header($cache) {
+  // Set default values:
+  $last_modified = gmdate('D, d M Y H:i:s', $cache->created) .' GMT';
+  $etag = '"'. md5($last_modified) .'"';
+
+  // See if the client has provided the required HTTP headers:
+  $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
+  $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
+
+  if ($if_modified_since && $if_none_match
+      && $if_none_match == $etag // etag must match
+      && $if_modified_since == $last_modified) {  // if-modified-since must match
+    header('HTTP/1.1 304 Not Modified');
+    // All 304 responses must send an etag if the 200 response for the same object contained an etag
+    header("Etag: $etag");
+    exit();
+  }
+
+  // Send appropriate response:
+  header("Last-Modified: $last_modified");
+  header("ETag: $etag");
+
+  // The following headers force validation of cache:
+  header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
+  header("Cache-Control: must-revalidate");
+
+  if (variable_get('page_compression', TRUE)) {
+    // Determine if the browser accepts gzipped data.
+    if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE && function_exists('gzencode')) {
+      // Strip the gzip header and run uncompress.
+      $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
+    }
+    elseif (function_exists('gzencode')) {
+      header('Content-Encoding: gzip');
+    }
+  }
+
+  // Send the original request's headers. We send them one after
+  // another so PHP's header() function can deal with duplicate
+  // headers.
+  $headers = explode("\n", $cache->headers);
+  foreach ($headers as $header) {
+    header($header);
+  }
+
+  print $cache->data;
+}
+
+/**
+ * Define the critical hooks that force modules to always be loaded.
+ */
+function bootstrap_hooks() {
+  return array('boot', 'exit');
+}
+
+/**
+ * Unserializes and appends elements from a serialized string.
+ *
+ * @param $obj
+ *   The object to which the elements are appended.
+ * @param $field
+ *   The attribute of $obj whose value should be unserialized.
+ */
+function drupal_unpack($obj, $field = 'data') {
+  if ($obj->$field && $data = unserialize($obj->$field)) {
+    foreach ($data as $key => $value) {
+      if (!isset($obj->$key)) {
+        $obj->$key = $value;
+      }
+    }
+  }
+  return $obj;
+}
+
+/**
+ * Return the URI of the referring page.
+ */
+function referer_uri() {
+  if (isset($_SERVER['HTTP_REFERER'])) {
+    return $_SERVER['HTTP_REFERER'];
+  }
+}
+
+/**
+ * Encode special characters in a plain-text string for display as HTML.
+ *
+ * Uses drupal_validate_utf8 to prevent cross site scripting attacks on
+ * Internet Explorer 6.
+ */
+function check_plain($text) {
+  return drupal_validate_utf8($text) ? htmlspecialchars($text, ENT_QUOTES) : '';
+}
+
+/**
+ * Checks whether a string is valid UTF-8.
+ *
+ * All functions designed to filter input should use drupal_validate_utf8
+ * to ensure they operate on valid UTF-8 strings to prevent bypass of the
+ * filter.
+ *
+ * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented
+ * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent
+ * bytes. When these subsequent bytes are HTML control characters such as
+ * quotes or angle brackets, parts of the text that were deemed safe by filters
+ * end up in locations that are potentially unsafe; An onerror attribute that
+ * is outside of a tag, and thus deemed safe by a filter, can be interpreted
+ * by the browser as if it were inside the tag.
+ *
+ * This function exploits preg_match behaviour (since PHP 4.3.5) when used
+ * with the u modifier, as a fast way to find invalid UTF-8. When the matched
+ * string contains an invalid byte sequence, it will fail silently.
+ *
+ * preg_match may not fail on 4 and 5 octet sequences, even though they
+ * are not supported by the specification.
+ *
+ * The specific preg_match behaviour is present since PHP 4.3.5.
+ *
+ * @param $text
+ *   The text to check.
+ * @return
+ *   TRUE if the text is valid UTF-8, FALSE if not.
+ */
+function drupal_validate_utf8($text) {
+  if (strlen($text) == 0) {
+    return TRUE;
+  }
+  return (preg_match('/^./us', $text) == 1);
+}
+
+/**
+ * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
+ * generate an equivalent using other environment variables.
+ */
+function request_uri() {
+
+  if (isset($_SERVER['REQUEST_URI'])) {
+    $uri = $_SERVER['REQUEST_URI'];
+  }
+  else {
+    if (isset($_SERVER['argv'])) {
+      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['argv'][0];
+    }
+    elseif (isset($_SERVER['QUERY_STRING'])) {
+      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
+    }
+    else {
+      $uri = $_SERVER['SCRIPT_NAME'];
+    }
+  }
+
+  return $uri;
+}
+
+/**
+ * Log a system message.
+ *
+ * @param $type
+ *   The category to which this message belongs.
+ * @param $message
+ *   The message to store in the log. See t() for documentation
+ *   on how $message and $variables interact. Keep $message
+ *   translatable by not concatenating dynamic values into it!
+ * @param $variables
+ *   Array of variables to replace in the message on display or
+ *   NULL if message is already translated or not possible to
+ *   translate.
+ * @param $severity
+ *   The severity of the message, as per RFC 3164
+ * @param $link
+ *   A link to associate with the message.
+ *
+ * @see watchdog_severity_levels()
+ */
+function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
+  global $user, $base_root;
+
+  // Prepare the fields to be logged
+  $log_message = array(
+    'type'        => $type,
+    'message'     => $message,
+    'variables'   => $variables,
+    'severity'    => $severity,
+    'link'        => $link,
+    'user'        => $user,
+    'request_uri' => $base_root . request_uri(),
+    'referer'     => referer_uri(),
+    'ip'          => ip_address(),
+    'timestamp'   => time(),
+    );
+
+  // Call the logging hooks to log/process the message
+  foreach (module_implements('watchdog', TRUE) as $module) {
+    module_invoke($module, 'watchdog', $log_message);
+  }
+}
+
+/**
+ * Set a message which reflects the status of the performed operation.
+ *
+ * If the function is called with no arguments, this function returns all set
+ * messages without clearing them.
+ *
+ * @param $message
+ *   The message should begin with a capital letter and always ends with a
+ *   period '.'.
+ * @param $type
+ *   The type of the message. One of the following values are possible:
+ *   - 'status'
+ *   - 'warning'
+ *   - 'error'
+ * @param $repeat
+ *   If this is FALSE and the message is already set, then the message won't
+ *   be repeated.
+ */
+function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
+  if ($message) {
+    if (!isset($_SESSION['messages'])) {
+      $_SESSION['messages'] = array();
+    }
+
+    if (!isset($_SESSION['messages'][$type])) {
+      $_SESSION['messages'][$type] = array();
+    }
+
+    if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
+      $_SESSION['messages'][$type][] = $message;
+    }
+  }
+
+  // messages not set when DB connection fails
+  return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;
+}
+
+/**
+ * Return all messages that have been set.
+ *
+ * @param $type
+ *   (optional) Only return messages of this type.
+ * @param $clear_queue
+ *   (optional) Set to FALSE if you do not want to clear the messages queue
+ * @return
+ *   An associative array, the key is the message type, the value an array
+ *   of messages. If the $type parameter is passed, you get only that type,
+ *   or an empty array if there are no such messages. If $type is not passed,
+ *   all message types are returned, or an empty array if none exist.
+ */
+function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
+  if ($messages = drupal_set_message()) {
+    if ($type) {
+      if ($clear_queue) {
+        unset($_SESSION['messages'][$type]);
+      }
+      if (isset($messages[$type])) {
+        return array($type => $messages[$type]);
+      }
+    }
+    else {
+      if ($clear_queue) {
+        unset($_SESSION['messages']);
+      }
+      return $messages;
+    }
+  }
+  return array();
+}
+
+/**
+ * Perform an access check for a given mask and rule type. Rules are usually
+ * created via admin/user/rules page.
+ *
+ * If any allow rule matches, access is allowed. Otherwise, if any deny rule
+ * matches, access is denied.  If no rule matches, access is allowed.
+ *
+ * @param $type string
+ *   Type of access to check: Allowed values are:
+ *     - 'host': host name or IP address
+ *     - 'mail': e-mail address
+ *     - 'user': username
+ * @param $mask string
+ *   String or mask to test: '_' matches any character, '%' matches any
+ *   number of characters.
+ * @return bool
+ *   TRUE if access is denied, FALSE if access is allowed.
+ */
+function drupal_is_denied($type, $mask) {
+  // Because this function is called for every page request, both cached
+  // and non-cached pages, we tried to optimize it as much as possible.
+  // We deny access if the only matching records in the {access} table have
+  // status 0 (deny). If any have status 1 (allow), or if there are no
+  // matching records, we allow access.
+  $sql = "SELECT 1 FROM {access} WHERE type = '%s' AND LOWER('%s') LIKE LOWER(mask) AND status = %d";
+  return db_result(db_query_range($sql, $type, $mask, 0, 0, 1)) && !db_result(db_query_range($sql, $type, $mask, 1, 0, 1));
+}
+
+/**
+ * Generates a default anonymous $user object.
+ *
+ * @return Object - the user object.
+ */
+function drupal_anonymous_user($session = '') {
+  $user = new stdClass();
+  $user->uid = 0;
+  $user->hostname = ip_address();
+  $user->roles = array();
+  $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
+  $user->session = $session;
+  $user->cache = 0;
+  return $user;
+}
+
+/**
+ * A string describing a phase of Drupal to load. Each phase adds to the
+ * previous one, so invoking a later phase automatically runs the earlier
+ * phases too. The most important usage is that if you want to access the
+ * Drupal database from a script without loading anything else, you can
+ * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
+ *
+ * @param $phase
+ *   A constant. Allowed values are:
+ *     DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration.
+ *     DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine.
+ *     DRUPAL_BOOTSTRAP_DATABASE: initialize database layer.
+ *     DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts.
+ *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
+ *     DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
+ *       the variable system and try to serve a page from the cache.
+ *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
+ *     DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
+ *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
+ */
+function drupal_bootstrap($phase) {
+  static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $phase_index = 0;
+
+  while ($phase >= $phase_index && isset($phases[$phase_index])) {
+    $current_phase = $phases[$phase_index];
+    unset($phases[$phase_index++]);
+    _drupal_bootstrap($current_phase);
+  }
+}
+
+function _drupal_bootstrap($phase) {
+  global $conf;
+
+  switch ($phase) {
+
+    case DRUPAL_BOOTSTRAP_CONFIGURATION:
+      drupal_unset_globals();
+      // Start a page timer:
+      timer_start('page');
+      // Initialize the configuration
+      conf_init();
+      break;
+
+    case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
+      // Allow specifying special cache handlers in settings.php, like
+      // using memcached or files for storing cache information.
+      require_once variable_get('cache_inc', './includes/cache.inc');
+      // If the page_cache_fastpath is set to TRUE in settings.php and
+      // page_cache_fastpath (implemented in the special implementation of
+      // cache.inc) printed the page and indicated this with a returned TRUE
+      // then we are done.
+      if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) {
+        exit;
+      }
+      break;
+
+    case DRUPAL_BOOTSTRAP_DATABASE:
+      // Initialize the default database.
+      require_once './includes/database.inc';
+      db_set_active();
+      break;
+
+    case DRUPAL_BOOTSTRAP_ACCESS:
+      // Deny access to hosts which were banned - t() is not yet available.
+      if (drupal_is_denied('host', ip_address())) {
+        header('HTTP/1.1 403 Forbidden');
+        print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
+        exit();
+      }
+      break;
+
+    case DRUPAL_BOOTSTRAP_SESSION:
+      require_once variable_get('session_inc', './includes/session.inc');
+      session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
+      session_start();
+      break;
+
+    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
+      // Initialize configuration variables, using values from settings.php if available.
+      $conf = variable_init(isset($conf) ? $conf : array());
+      // Load module handling.
+      require_once './includes/module.inc';
+      $cache_mode = variable_get('cache', CACHE_DISABLED);
+      // Get the page from the cache.
+      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
+      // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
+      if ($cache_mode != CACHE_AGGRESSIVE) {
+        bootstrap_invoke_all('boot');
+      }
+      // If there is a cached page, display it.
+      if ($cache) {
+        drupal_page_cache_header($cache);
+        // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
+        if ($cache_mode != CACHE_AGGRESSIVE) {
+          bootstrap_invoke_all('exit');
+        }
+        // We are done.
+        exit;
+      }
+      // Prepare for non-cached page workflow.
+      drupal_page_header();
+      break;
+
+    case DRUPAL_BOOTSTRAP_LANGUAGE:
+      drupal_init_language();
+      break;
+
+    case DRUPAL_BOOTSTRAP_PATH:
+      require_once './includes/path.inc';
+      // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
+      drupal_init_path();
+      break;
+
+    case DRUPAL_BOOTSTRAP_FULL:
+      require_once './includes/common.inc';
+      _drupal_bootstrap_full();
+      break;
+  }
+}
+
+/**
+ * Enables use of the theme system without requiring database access.
+ *
+ * Loads and initializes the theme system for site installs, updates and when
+ * the site is in off-line mode. This also applies when the database fails.
+ *
+ * @see _drupal_maintenance_theme()
+ */
+function drupal_maintenance_theme() {
+  require_once './includes/theme.maintenance.inc';
+  _drupal_maintenance_theme();
+}
+
+/**
+ * Return the name of the localisation function. Use in code that needs to
+ * run both during installation and normal operation.
+ */
+function get_t() {
+  static $t;
+  if (is_null($t)) {
+    $t = function_exists('install_main') ? 'st' : 't';
+  }
+  return $t;
+}
+
+/**
+ *  Choose a language for the current page, based on site and user preferences.
+ */
+function drupal_init_language() {
+  global $language, $user;
+
+  // Ensure the language is correctly returned, even without multilanguage support.
+  // Useful for eg. XML/HTML 'lang' attributes.
+  if (variable_get('language_count', 1) == 1) {
+    $language = language_default();
+  }
+  else {
+    include_once './includes/language.inc';
+    $language = language_initialize();
+  }
+}
+
+/**
+ * Get a list of languages set up indexed by the specified key
+ *
+ * @param $field The field to index the list with.
+ * @param $reset Boolean to request a reset of the list.
+ */
+function language_list($field = 'language', $reset = FALSE) {
+  static $languages = NULL;
+
+  // Reset language list
+  if ($reset) {
+    $languages = NULL;
+  }
+
+  // Init language list
+  if (!isset($languages)) {
+    if (variable_get('language_count', 1) > 1 || module_exists('locale')) {
+      $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC');
+      while ($row = db_fetch_object($result)) {
+        $languages['language'][$row->language] = $row;
+      }
+    }
+    else {
+      // No locale module, so use the default language only.
+      $default = language_default();
+      $languages['language'][$default->language] = $default;
+    }
+  }
+
+  // Return the array indexed by the right field
+  if (!isset($languages[$field])) {
+    $languages[$field] = array();
+    foreach ($languages['language'] as $lang) {
+      // Some values should be collected into an array
+      if (in_array($field, array('enabled', 'weight'))) {
+        $languages[$field][$lang->$field][$lang->language] = $lang;
+      }
+      else {
+        $languages[$field][$lang->$field] = $lang;
+      }
+    }
+  }
+  return $languages[$field];
+}
+
+/**
+ * Default language used on the site
+ *
+ * @param $property
+ *   Optional property of the language object to return
+ */
+function language_default($property = NULL) {
+  $language = variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => ''));
+  return $property ? $language->$property : $language;
+}
+
+/**
+ * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
+ * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address
+ * of the proxy server, and not the client's.
+ *
+ * @return
+ *   IP address of client machine, adjusted for reverse proxy.
+ */
+function ip_address() {
+  static $ip_address = NULL;
+
+  if (!isset($ip_address)) {
+    $ip_address = $_SERVER['REMOTE_ADDR'];
+    if (variable_get('reverse_proxy', 0) && array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
+      // If an array of known reverse proxy IPs is provided, then trust
+      // the XFF header if request really comes from one of them.
+      $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array());
+      if (!empty($reverse_proxy_addresses) && in_array($ip_address, $reverse_proxy_addresses, TRUE)) {
+        // If there are several arguments, we need to check the most
+        // recently added one, i.e. the last one.
+        $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
+      }
+    }
+  }
+
+  return $ip_address;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/cache-install.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,24 @@
+<?php
+// $Id: cache-install.inc,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * A stub cache implementation to be used during the installation
+ * process when database access is not yet available. Because Drupal's
+ * caching system never requires that cached data be present, these
+ * stub functions can short-circuit the process and sidestep the
+ * need for any persistent storage. Obviously, using this cache
+ * implementation during normal operations would have a negative impact
+ * on performance.
+ */
+
+function cache_get($key, $table = 'cache') {
+  return FALSE;
+}
+
+function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
+  return;
+}
+
+function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
+  return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/cache.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,183 @@
+<?php
+// $Id: cache.inc,v 1.17 2008/01/29 11:36:06 goba Exp $
+
+/**
+ * Return data from the persistent cache. Data may be stored as either plain text or as serialized data.
+ * cache_get will automatically return unserialized objects and arrays.
+ *
+ * @param $cid
+ *   The cache ID of the data to retrieve.
+ * @param $table
+ *   The table $table to store the data in. Valid core values are 'cache_filter',
+ *   'cache_menu', 'cache_page', or 'cache' for the default cache.
+ */
+function cache_get($cid, $table = 'cache') {
+  global $user;
+
+  // Garbage collection necessary when enforcing a minimum cache lifetime
+  $cache_flush = variable_get('cache_flush', 0);
+  if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) {
+    // Reset the variable immediately to prevent a meltdown in heavy load situations.
+    variable_set('cache_flush', 0);
+    // Time to flush old cache data
+    db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush);
+  }
+
+  $cache = db_fetch_object(db_query("SELECT data, created, headers, expire, serialized FROM {". $table ."} WHERE cid = '%s'", $cid));
+  if (isset($cache->data)) {
+    // If the data is permanent or we're not enforcing a minimum cache lifetime
+    // always return the cached data.
+    if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) {
+      $cache->data = db_decode_blob($cache->data);
+      if ($cache->serialized) {
+        $cache->data = unserialize($cache->data);
+      }
+    }
+    // If enforcing a minimum cache lifetime, validate that the data is
+    // currently valid for this user before we return it by making sure the
+    // cache entry was created before the timestamp in the current session's
+    // cache timer. The cache variable is loaded into the $user object by
+    // sess_read() in session.inc.
+    else {
+      if ($user->cache > $cache->created) {
+        // This cache data is too old and thus not valid for us, ignore it.
+        return 0;
+      }
+      else {
+        $cache->data = db_decode_blob($cache->data);
+        if ($cache->serialized) {
+          $cache->data = unserialize($cache->data);
+        }
+      }
+    }
+    return $cache;
+  }
+  return 0;
+}
+
+/**
+ * Store data in the persistent cache.
+ *
+ * The persistent cache is split up into four database
+ * tables. Contributed modules can add additional tables.
+ *
+ * 'cache_page': This table stores generated pages for anonymous
+ * users. This is the only table affected by the page cache setting on
+ * the administrator panel.
+ *
+ * 'cache_menu': Stores the cachable part of the users' menus.
+ *
+ * 'cache_filter': Stores filtered pieces of content. This table is
+ * periodically cleared of stale entries by cron.
+ *
+ * 'cache': Generic cache storage table.
+ *
+ * The reasons for having several tables are as follows:
+ *
+ * - smaller tables allow for faster selects and inserts
+ * - we try to put fast changing cache items and rather static
+ *   ones into different tables. The effect is that only the fast
+ *   changing tables will need a lot of writes to disk. The more
+ *   static tables will also be better cachable with MySQL's query cache
+ *
+ * @param $cid
+ *   The cache ID of the data to store.
+ * @param $data
+ *   The data to store in the cache. Complex data types will be automatically serialized before insertion.
+ *   Strings will be stored as plain text and not serialized.
+ * @param $table
+ *   The table $table to store the data in. Valid core values are 'cache_filter',
+ *   'cache_menu', 'cache_page', or 'cache'.
+ * @param $expire
+ *   One of the following values:
+ *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
+ *     explicitly told to using cache_clear_all() with a cache ID.
+ *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
+ *     general cache wipe.
+ *   - A Unix timestamp: Indicates that the item should be kept at least until
+ *     the given time, after which it behaves like CACHE_TEMPORARY.
+ * @param $headers
+ *   A string containing HTTP header information for cached pages.
+ */
+function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
+  $serialized = 0;
+  if (is_object($data) || is_array($data)) {
+    $data = serialize($data);
+    $serialized = 1;
+  }
+  $created = time();
+  db_query("UPDATE {". $table ."} SET data = %b, created = %d, expire = %d, headers = '%s', serialized = %d WHERE cid = '%s'", $data, $created, $expire, $headers, $serialized, $cid);
+  if (!db_affected_rows()) {
+    @db_query("INSERT INTO {". $table ."} (cid, data, created, expire, headers, serialized) VALUES ('%s', %b, %d, %d, '%s', %d)", $cid, $data, $created, $expire, $headers, $serialized);
+  }
+}
+
+/**
+ *
+ * Expire data from the cache. If called without arguments, expirable
+ * entries will be cleared from the cache_page and cache_block tables.
+ *
+ * @param $cid
+ *   If set, the cache ID to delete. Otherwise, all cache entries that can
+ *   expire are deleted.
+ *
+ * @param $table
+ *   If set, the table $table to delete from. Mandatory
+ *   argument if $cid is set.
+ *
+ * @param $wildcard
+ *   If set to TRUE, the $cid is treated as a substring
+ *   to match rather than a complete ID. The match is a right hand
+ *   match. If '*' is given as $cid, the table $table will be emptied.
+ */
+function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
+  global $user;
+
+  if (!isset($cid) && !isset($table)) {
+    // Clear the block cache first, so stale data will
+    // not end up in the page cache.
+    cache_clear_all(NULL, 'cache_block');
+    cache_clear_all(NULL, 'cache_page');
+    return;
+  }
+
+  if (empty($cid)) {
+    if (variable_get('cache_lifetime', 0)) {
+      // We store the time in the current user's $user->cache variable which
+      // will be saved into the sessions table by sess_write(). We then
+      // simulate that the cache was flushed for this user by not returning
+      // cached data that was cached before the timestamp.
+      $user->cache = time();
+
+      $cache_flush = variable_get('cache_flush', 0);
+      if ($cache_flush == 0) {
+        // This is the first request to clear the cache, start a timer.
+        variable_set('cache_flush', time());
+      }
+      else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) {
+        // Clear the cache for everyone, cache_flush_delay seconds have
+        // passed since the first request to clear the cache.
+        db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
+        variable_set('cache_flush', 0);
+      }
+    }
+    else {
+      // No minimum cache lifetime, flush all temporary cache entries now.
+      db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
+    }
+  }
+  else {
+    if ($wildcard) {
+      if ($cid == '*') {
+        db_query("DELETE FROM {". $table ."}");
+      }
+      else {
+        db_query("DELETE FROM {". $table ."} WHERE cid LIKE '%s%%'", $cid);
+      }
+    }
+    else {
+      db_query("DELETE FROM {". $table ."} WHERE cid = '%s'", $cid);
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/common.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,3558 @@
+<?php
+// $Id: common.inc,v 1.756.2.4 2008/02/13 14:25:42 goba Exp $
+
+/**
+ * @file
+ * Common functions that many Drupal modules will need to reference.
+ *
+ * The functions that are critical and need to be available even when serving
+ * a cached page are instead located in bootstrap.inc.
+ */
+
+/**
+ * Return status for saving which involved creating a new item.
+ */
+define('SAVED_NEW', 1);
+
+/**
+ * Return status for saving which involved an update to an existing item.
+ */
+define('SAVED_UPDATED', 2);
+
+/**
+ * Return status for saving which deleted an existing item.
+ */
+define('SAVED_DELETED', 3);
+
+/**
+ * Set content for a specified region.
+ *
+ * @param $region
+ *   Page region the content is assigned to.
+ * @param $data
+ *   Content to be set.
+ */
+function drupal_set_content($region = NULL, $data = NULL) {
+  static $content = array();
+
+  if (!is_null($region) && !is_null($data)) {
+    $content[$region][] = $data;
+  }
+  return $content;
+}
+
+/**
+ * Get assigned content.
+ *
+ * @param $region
+ *   A specified region to fetch content for. If NULL, all regions will be
+ *   returned.
+ * @param $delimiter
+ *   Content to be inserted between exploded array elements.
+ */
+function drupal_get_content($region = NULL, $delimiter = ' ') {
+  $content = drupal_set_content();
+  if (isset($region)) {
+    if (isset($content[$region]) && is_array($content[$region])) {
+      return implode($delimiter, $content[$region]);
+    }
+  }
+  else {
+    foreach (array_keys($content) as $region) {
+      if (is_array($content[$region])) {
+        $content[$region] = implode($delimiter, $content[$region]);
+      }
+    }
+    return $content;
+  }
+}
+
+/**
+ * Set the breadcrumb trail for the current page.
+ *
+ * @param $breadcrumb
+ *   Array of links, starting with "home" and proceeding up to but not including
+ *   the current page.
+ */
+function drupal_set_breadcrumb($breadcrumb = NULL) {
+  static $stored_breadcrumb;
+
+  if (!is_null($breadcrumb)) {
+    $stored_breadcrumb = $breadcrumb;
+  }
+  return $stored_breadcrumb;
+}
+
+/**
+ * Get the breadcrumb trail for the current page.
+ */
+function drupal_get_breadcrumb() {
+  $breadcrumb = drupal_set_breadcrumb();
+
+  if (is_null($breadcrumb)) {
+    $breadcrumb = menu_get_active_breadcrumb();
+  }
+
+  return $breadcrumb;
+}
+
+/**
+ * Add output to the head tag of the HTML page.
+ *
+ * This function can be called as long the headers aren't sent.
+ */
+function drupal_set_html_head($data = NULL) {
+  static $stored_head = '';
+
+  if (!is_null($data)) {
+    $stored_head .= $data ."\n";
+  }
+  return $stored_head;
+}
+
+/**
+ * Retrieve output to be displayed in the head tag of the HTML page.
+ */
+function drupal_get_html_head() {
+  $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
+  return $output . drupal_set_html_head();
+}
+
+/**
+ * Reset the static variable which holds the aliases mapped for this request.
+ */
+function drupal_clear_path_cache() {
+  drupal_lookup_path('wipe');
+}
+
+/**
+ * Set an HTTP response header for the current page.
+ *
+ * Note: When sending a Content-Type header, always include a 'charset' type,
+ * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
+ */
+function drupal_set_header($header = NULL) {
+  // We use an array to guarantee there are no leading or trailing delimiters.
+  // Otherwise, header('') could get called when serving the page later, which
+  // ends HTTP headers prematurely on some PHP versions.
+  static $stored_headers = array();
+
+  if (strlen($header)) {
+    header($header);
+    $stored_headers[] = $header;
+  }
+  return implode("\n", $stored_headers);
+}
+
+/**
+ * Get the HTTP response headers for the current page.
+ */
+function drupal_get_headers() {
+  return drupal_set_header();
+}
+
+/**
+ * Add a feed URL for the current page.
+ *
+ * @param $url
+ *   A url for the feed.
+ * @param $title
+ *   The title of the feed.
+ */
+function drupal_add_feed($url = NULL, $title = '') {
+  static $stored_feed_links = array();
+
+  if (!is_null($url) && !isset($stored_feed_links[$url])) {
+    $stored_feed_links[$url] = theme('feed_icon', $url, $title);
+
+    drupal_add_link(array('rel' => 'alternate',
+                          'type' => 'application/rss+xml',
+                          'title' => $title,
+                          'href' => $url));
+  }
+  return $stored_feed_links;
+}
+
+/**
+ * Get the feed URLs for the current page.
+ *
+ * @param $delimiter
+ *   A delimiter to split feeds by.
+ */
+function drupal_get_feeds($delimiter = "\n") {
+  $feeds = drupal_add_feed();
+  return implode($feeds, $delimiter);
+}
+
+/**
+ * @name HTTP handling
+ * @{
+ * Functions to properly handle HTTP responses.
+ */
+
+/**
+ * Parse an array into a valid urlencoded query string.
+ *
+ * @param $query
+ *   The array to be processed e.g. $_GET.
+ * @param $exclude
+ *   The array filled with keys to be excluded. Use parent[child] to exclude
+ *   nested items.
+ * @param $parent
+ *   Should not be passed, only used in recursive calls.
+ * @return
+ *   An urlencoded string which can be appended to/as the URL query string.
+ */
+function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
+  $params = array();
+
+  foreach ($query as $key => $value) {
+    $key = drupal_urlencode($key);
+    if ($parent) {
+      $key = $parent .'['. $key .']';
+    }
+
+    if (in_array($key, $exclude)) {
+      continue;
+    }
+
+    if (is_array($value)) {
+      $params[] = drupal_query_string_encode($value, $exclude, $key);
+    }
+    else {
+      $params[] = $key .'='. drupal_urlencode($value);
+    }
+  }
+
+  return implode('&', $params);
+}
+
+/**
+ * Prepare a destination query string for use in combination with drupal_goto().
+ *
+ * Used to direct the user back to the referring page after completing a form.
+ * By default the current URL is returned. If a destination exists in the
+ * previous request, that destination is returned. As such, a destination can
+ * persist across multiple pages.
+ *
+ * @see drupal_goto()
+ */
+function drupal_get_destination() {
+  if (isset($_REQUEST['destination'])) {
+    return 'destination='. urlencode($_REQUEST['destination']);
+  }
+  else {
+    // Use $_GET here to retrieve the original path in source form.
+    $path = isset($_GET['q']) ? $_GET['q'] : '';
+    $query = drupal_query_string_encode($_GET, array('q'));
+    if ($query != '') {
+      $path .= '?'. $query;
+    }
+    return 'destination='. urlencode($path);
+  }
+}
+
+/**
+ * Send the user to a different Drupal page.
+ *
+ * This issues an on-site HTTP redirect. The function makes sure the redirected
+ * URL is formatted correctly.
+ *
+ * Usually the redirected URL is constructed from this function's input
+ * parameters. However you may override that behavior by setting a
+ * <em>destination</em> in either the $_REQUEST-array (i.e. by using
+ * the query string of an URI) or the $_REQUEST['edit']-array (i.e. by
+ * using a hidden form field). This is used to direct the user back to
+ * the proper page after completing a form. For example, after editing
+ * a post on the 'admin/content/node'-page or after having logged on using the
+ * 'user login'-block in a sidebar. The function drupal_get_destination()
+ * can be used to help set the destination URL.
+ *
+ * Drupal will ensure that messages set by drupal_set_message() and other
+ * session data are written to the database before the user is redirected.
+ *
+ * This function ends the request; use it rather than a print theme('page')
+ * statement in your menu callback.
+ *
+ * @param $path
+ *   A Drupal path or a full URL.
+ * @param $query
+ *   A query string component, if any.
+ * @param $fragment
+ *   A destination fragment identifier (named anchor).
+ * @param $http_response_code
+ *   Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
+ *   - 301 Moved Permanently (the recommended value for most redirects)
+ *   - 302 Found (default in Drupal and PHP, sometimes used for spamming search
+ *         engines)
+ *   - 303 See Other
+ *   - 304 Not Modified
+ *   - 305 Use Proxy
+ *   - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
+ *   Note: Other values are defined by RFC 2616, but are rarely used and poorly
+ *   supported.
+ * @see drupal_get_destination()
+ */
+function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
+
+  if (isset($_REQUEST['destination'])) {
+    extract(parse_url(urldecode($_REQUEST['destination'])));
+  }
+  else if (isset($_REQUEST['edit']['destination'])) {
+    extract(parse_url(urldecode($_REQUEST['edit']['destination'])));
+  }
+
+  $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
+  // Remove newlines from the URL to avoid header injection attacks.
+  $url = str_replace(array("\n", "\r"), '', $url);
+
+  // Allow modules to react to the end of the page request before redirecting.
+  // We do not want this while running update.php.
+  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
+    module_invoke_all('exit', $url);
+  }
+
+  // Even though session_write_close() is registered as a shutdown function, we
+  // need all session data written to the database before redirecting.
+  session_write_close();
+
+  header('Location: '. $url, TRUE, $http_response_code);
+
+  // The "Location" header sends a redirect status code to the HTTP daemon. In
+  // some cases this can be wrong, so we make sure none of the code below the
+  // drupal_goto() call gets executed upon redirection.
+  exit();
+}
+
+/**
+ * Generates a site off-line message.
+ */
+function drupal_site_offline() {
+  drupal_maintenance_theme();
+  drupal_set_header('HTTP/1.1 503 Service unavailable');
+  drupal_set_title(t('Site off-line'));
+  print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
+    t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
+}
+
+/**
+ * Generates a 404 error if the request can not be handled.
+ */
+function drupal_not_found() {
+  drupal_set_header('HTTP/1.1 404 Not Found');
+
+  watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+  // Keep old path for reference.
+  if (!isset($_REQUEST['destination'])) {
+    $_REQUEST['destination'] = $_GET['q'];
+  }
+
+  $path = drupal_get_normal_path(variable_get('site_404', ''));
+  if ($path && $path != $_GET['q']) {
+    // Set the active item in case there are tabs to display, or other
+    // dependencies on the path.
+    menu_set_active_item($path);
+    $return = menu_execute_active_handler($path);
+  }
+
+  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    drupal_set_title(t('Page not found'));
+    $return = t('The requested page could not be found.');
+  }
+
+  // To conserve CPU and bandwidth, omit the blocks.
+  print theme('page', $return, FALSE);
+}
+
+/**
+ * Generates a 403 error if the request is not allowed.
+ */
+function drupal_access_denied() {
+  drupal_set_header('HTTP/1.1 403 Forbidden');
+  watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+  // Keep old path for reference.
+  if (!isset($_REQUEST['destination'])) {
+    $_REQUEST['destination'] = $_GET['q'];
+  }
+
+  $path = drupal_get_normal_path(variable_get('site_403', ''));
+  if ($path && $path != $_GET['q']) {
+    // Set the active item in case there are tabs to display or other
+    // dependencies on the path.
+    menu_set_active_item($path);
+    $return = menu_execute_active_handler($path);
+  }
+
+  if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    drupal_set_title(t('Access denied'));
+    $return = t('You are not authorized to access this page.');
+  }
+  print theme('page', $return);
+}
+
+/**
+ * Perform an HTTP request.
+ *
+ * This is a flexible and powerful HTTP client implementation. Correctly handles
+ * GET, POST, PUT or any other HTTP requests. Handles redirects.
+ *
+ * @param $url
+ *   A string containing a fully qualified URI.
+ * @param $headers
+ *   An array containing an HTTP header => value pair.
+ * @param $method
+ *   A string defining the HTTP request to use.
+ * @param $data
+ *   A string containing data to include in the request.
+ * @param $retry
+ *   An integer representing how many times to retry the request in case of a
+ *   redirect.
+ * @return
+ *   An object containing the HTTP request headers, response code, headers,
+ *   data and redirect status.
+ */
+function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
+  static $self_test = FALSE;
+  $result = new stdClass();
+  // Try to clear the drupal_http_request_fails variable if it's set. We
+  // can't tie this call to any error because there is no surefire way to
+  // tell whether a request has failed, so we add the check to places where
+  // some parsing has failed.
+  if (!$self_test && variable_get('drupal_http_request_fails', FALSE)) {
+    $self_test = TRUE;
+    $works = module_invoke('system', 'check_http_request');
+    $self_test = FALSE;
+    if (!$works) {
+      // Do not bother with further operations if we already know that we
+      // have no chance.
+      $result->error = t("The server can't issue HTTP requests");
+      return $result;
+    }
+  }
+
+  // Parse the URL and make sure we can handle the schema.
+  $uri = parse_url($url);
+
+  switch ($uri['scheme']) {
+    case 'http':
+      $port = isset($uri['port']) ? $uri['port'] : 80;
+      $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
+      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
+      break;
+    case 'https':
+      // Note: Only works for PHP 4.3 compiled with OpenSSL.
+      $port = isset($uri['port']) ? $uri['port'] : 443;
+      $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
+      $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
+      break;
+    default:
+      $result->error = 'invalid schema '. $uri['scheme'];
+      return $result;
+  }
+
+  // Make sure the socket opened properly.
+  if (!$fp) {
+    // When a network error occurs, we use a negative number so it does not
+    // clash with the HTTP status codes.
+    $result->code = -$errno;
+    $result->error = trim($errstr);
+    return $result;
+  }
+
+  // Construct the path to act on.
+  $path = isset($uri['path']) ? $uri['path'] : '/';
+  if (isset($uri['query'])) {
+    $path .= '?'. $uri['query'];
+  }
+
+  // Create HTTP request.
+  $defaults = array(
+    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
+    // We don't add the port to prevent from breaking rewrite rules checking the
+    // host that do not take into account the port number.
+    'Host' => "Host: $host",
+    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
+    'Content-Length' => 'Content-Length: '. strlen($data)
+  );
+
+  // If the server url has a user then attempt to use basic authentication
+  if (isset($uri['user'])) {
+    $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
+  }
+
+  foreach ($headers as $header => $value) {
+    $defaults[$header] = $header .': '. $value;
+  }
+
+  $request = $method .' '. $path ." HTTP/1.0\r\n";
+  $request .= implode("\r\n", $defaults);
+  $request .= "\r\n\r\n";
+  if ($data) {
+    $request .= $data ."\r\n";
+  }
+  $result->request = $request;
+
+  fwrite($fp, $request);
+
+  // Fetch response.
+  $response = '';
+  while (!feof($fp) && $chunk = fread($fp, 1024)) {
+    $response .= $chunk;
+  }
+  fclose($fp);
+
+  // Parse response.
+  list($split, $result->data) = explode("\r\n\r\n", $response, 2);
+  $split = preg_split("/\r\n|\n|\r/", $split);
+
+  list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
+  $result->headers = array();
+
+  // Parse headers.
+  while ($line = trim(array_shift($split))) {
+    list($header, $value) = explode(':', $line, 2);
+    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
+      // RFC 2109: the Set-Cookie response header comprises the token Set-
+      // Cookie:, followed by a comma-separated list of one or more cookies.
+      $result->headers[$header] .= ','. trim($value);
+    }
+    else {
+      $result->headers[$header] = trim($value);
+    }
+  }
+
+  $responses = array(
+    100 => 'Continue', 101 => 'Switching Protocols',
+    200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
+    300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
+    400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
+    500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
+  );
+  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
+  // base code in their class.
+  if (!isset($responses[$code])) {
+    $code = floor($code / 100) * 100;
+  }
+
+  switch ($code) {
+    case 200: // OK
+    case 304: // Not modified
+      break;
+    case 301: // Moved permanently
+    case 302: // Moved temporarily
+    case 307: // Moved temporarily
+      $location = $result->headers['Location'];
+
+      if ($retry) {
+        $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
+        $result->redirect_code = $result->code;
+      }
+      $result->redirect_url = $location;
+
+      break;
+    default:
+      $result->error = $text;
+  }
+
+  $result->code = $code;
+  return $result;
+}
+/**
+ * @} End of "HTTP handling".
+ */
+
+/**
+ * Log errors as defined by administrator.
+ *
+ * Error levels:
+ * - 0 = Log errors to database.
+ * - 1 = Log errors to database and to screen.
+ */
+function drupal_error_handler($errno, $message, $filename, $line, $context) {
+  // If the @ error suppression operator was used, error_reporting is
+  // temporarily set to 0.
+  if (error_reporting() == 0) {
+    return;
+  }
+
+  if ($errno & (E_ALL ^ E_NOTICE)) {
+    $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
+
+    // For database errors, we want the line number/file name of the place that
+    // the query was originally called, not _db_query().
+    if (isset($context[DB_ERROR])) {
+      $backtrace = array_reverse(debug_backtrace());
+
+      // List of functions where SQL queries can originate.
+      $query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
+
+      // Determine where query function was called, and adjust line/file
+      // accordingly.
+      foreach ($backtrace as $index => $function) {
+        if (in_array($function['function'], $query_functions)) {
+          $line = $backtrace[$index]['line'];
+          $filename = $backtrace[$index]['file'];
+          break;
+        }
+      }
+    }
+
+    $entry = $types[$errno] .': '. $message .' in '. $filename .' on line '. $line .'.';
+
+    // Force display of error messages in update.php.
+    if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
+      drupal_set_message($entry, 'error');
+    }
+
+    watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
+  }
+}
+
+function _fix_gpc_magic(&$item) {
+  if (is_array($item)) {
+    array_walk($item, '_fix_gpc_magic');
+  }
+  else {
+    $item = stripslashes($item);
+  }
+}
+
+/**
+ * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
+ * since PHP generates single backslashes for file paths on Windows systems.
+ *
+ * tmp_name does not have backslashes added see
+ * http://php.net/manual/en/features.file-upload.php#42280
+ */
+function _fix_gpc_magic_files(&$item, $key) {
+  if ($key != 'tmp_name') {
+    if (is_array($item)) {
+      array_walk($item, '_fix_gpc_magic_files');
+    }
+    else {
+      $item = stripslashes($item);
+    }
+  }
+}
+
+/**
+ * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
+ */
+function fix_gpc_magic() {
+  static $fixed = FALSE;
+  if (!$fixed && ini_get('magic_quotes_gpc')) {
+    array_walk($_GET, '_fix_gpc_magic');
+    array_walk($_POST, '_fix_gpc_magic');
+    array_walk($_COOKIE, '_fix_gpc_magic');
+    array_walk($_REQUEST, '_fix_gpc_magic');
+    array_walk($_FILES, '_fix_gpc_magic_files');
+    $fixed = TRUE;
+  }
+}
+
+/**
+ * Translate strings to the page language or a given language.
+ *
+ * All human-readable text that will be displayed somewhere within a page should
+ * be run through the t() function.
+ *
+ * Examples:
+ * @code
+ *   if (!$info || !$info['extension']) {
+ *     form_set_error('picture_upload', t('The uploaded file was not an image.'));
+ *   }
+ *
+ *   $form['submit'] = array(
+ *     '#type' => 'submit',
+ *     '#value' => t('Log in'),
+ *   );
+ * @endcode
+ *
+ * Any text within t() can be extracted by translators and changed into
+ * the equivalent text in their native language.
+ *
+ * Special variables called "placeholders" are used to signal dynamic
+ * information in a string which should not be translated. Placeholders
+ * can also be used for text that may change from time to time
+ * (such as link paths) to be changed without requiring updates to translations.
+ *
+ * For example:
+ * @code
+ *   $output = t('There are currently %members and %visitors online.', array(
+ *     '%members' => format_plural($total_users, '1 user', '@count users'),
+ *     '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
+ * @endcode
+ *
+ * There are three styles of placeholders:
+ * - !variable, which indicates that the text should be inserted as-is. This is
+ *   useful for inserting variables into things like e-mail.
+ *   @code
+ *     $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE))));
+ *   @endcode
+ *
+ * - @variable, which indicates that the text should be run through check_plain,
+ *   to escape HTML characters. Use this for any output that's displayed within
+ *   a Drupal page.
+ *   @code
+ *     drupal_set_title($title = t("@name's blog", array('@name' => $account->name)));
+ *   @endcode
+ *
+ * - %variable, which indicates that the string should be HTML escaped and
+ *   highlighted with theme_placeholder() which shows up by default as
+ *   <em>emphasized</em>.
+ *   @code
+ *     $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
+ *   @endcode
+ *
+ * When using t(), try to put entire sentences and strings in one t() call.
+ * This makes it easier for translators, as it provides context as to what each
+ * word refers to. HTML markup within translation strings is allowed, but should
+ * be avoided if possible. The exception are embedded links; link titles add a
+ * context for translators, so should be kept in the main string.
+ *
+ * Here is an example of incorrect usage of t():
+ * @code
+ *   $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
+ * @endcode
+ *
+ * Here is an example of t() used correctly:
+ * @code
+ *   $output .= '<p>'. t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) .'</p>';
+ * @endcode
+ *
+ * Also avoid escaping quotation marks wherever possible.
+ *
+ * Incorrect:
+ * @code
+ *   $output .= t('Don\'t click me.');
+ * @endcode
+ *
+ * Correct:
+ * @code
+ *   $output .= t("Don't click me.");
+ * @endcode
+ *
+ * @param $string
+ *   A string containing the English string to translate.
+ * @param $args
+ *   An associative array of replacements to make after translation. Incidences
+ *   of any key in this array are replaced with the corresponding value.
+ *   Based on the first character of the key, the value is escaped and/or themed:
+ *    - !variable: inserted as is
+ *    - @variable: escape plain text to HTML (check_plain)
+ *    - %variable: escape text and theme as a placeholder for user-submitted
+ *      content (check_plain + theme_placeholder)
+ * @param $langcode
+ *   Optional language code to translate to a language other than what is used
+ *   to display the page.
+ * @return
+ *   The translated string.
+ */
+function t($string, $args = array(), $langcode = NULL) {
+  global $language;
+  static $custom_strings;
+
+  $langcode = isset($langcode) ? $langcode : $language->language;
+
+  // First, check for an array of customized strings. If present, use the array
+  // *instead of* database lookups. This is a high performance way to provide a
+  // handful of string replacements. See settings.php for examples.
+  // Cache the $custom_strings variable to improve performance.
+  if (!isset($custom_strings[$langcode])) {
+    $custom_strings[$langcode] = variable_get('locale_custom_strings_'. $langcode, array());
+  }
+  // Custom strings work for English too, even if locale module is disabled.
+  if (isset($custom_strings[$langcode][$string])) {
+    $string = $custom_strings[$langcode][$string];
+  }
+  // Translate with locale module if enabled.
+  elseif (function_exists('locale') && $langcode != 'en') {
+    $string = locale($string, $langcode);
+  }
+  if (empty($args)) {
+    return $string;
+  }
+  else {
+    // Transform arguments before inserting them.
+    foreach ($args as $key => $value) {
+      switch ($key[0]) {
+        case '@':
+          // Escaped only.
+          $args[$key] = check_plain($value);
+          break;
+
+        case '%':
+        default:
+          // Escaped and placeholder.
+          $args[$key] = theme('placeholder', $value);
+          break;
+
+        case '!':
+          // Pass-through.
+      }
+    }
+    return strtr($string, $args);
+  }
+}
+
+/**
+ * @defgroup validation Input validation
+ * @{
+ * Functions to validate user input.
+ */
+
+/**
+ * Verify the syntax of the given e-mail address.
+ *
+ * Empty e-mail addresses are allowed. See RFC 2822 for details.
+ *
+ * @param $mail
+ *   A string containing an e-mail address.
+ * @return
+ *   TRUE if the address is in a valid format.
+ */
+function valid_email_address($mail) {
+  $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+';
+  $domain = '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.?)+';
+  $ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}';
+  $ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}';
+
+  return preg_match("/^$user@($domain|(\[($ipv4|$ipv6)\]))$/", $mail);
+}
+
+/**
+ * Verify the syntax of the given URL.
+ *
+ * This function should only be used on actual URLs. It should not be used for
+ * Drupal menu paths, which can contain arbitrary characters.
+ *
+ * @param $url
+ *   The URL to verify.
+ * @param $absolute
+ *   Whether the URL is absolute (beginning with a scheme such as "http:").
+ * @return
+ *   TRUE if the URL is in a valid format.
+ */
+function valid_url($url, $absolute = FALSE) {
+  $allowed_characters = '[a-z0-9\/:_\-_\.\?\$,;~=#&%\+]';
+  if ($absolute) {
+    return preg_match("/^(http|https|ftp):\/\/". $allowed_characters ."+$/i", $url);
+  }
+  else {
+    return preg_match("/^". $allowed_characters ."+$/i", $url);
+  }
+}
+
+/**
+ * @} End of "defgroup validation".
+ */
+
+/**
+ * Register an event for the current visitor (hostname/IP) to the flood control mechanism.
+ *
+ * @param $name
+ *   The name of an event.
+ */
+function flood_register_event($name) {
+  db_query("INSERT INTO {flood} (event, hostname, timestamp) VALUES ('%s', '%s', %d)", $name, ip_address(), time());
+}
+
+/**
+ * Check if the current visitor (hostname/IP) is allowed to proceed with the specified event.
+ *
+ * The user is allowed to proceed if he did not trigger the specified event more
+ * than $threshold times per hour.
+ *
+ * @param $name
+ *   The name of the event.
+ * @param $number
+ *   The maximum number of the specified event per hour (per visitor).
+ * @return
+ *   True if the user did not exceed the hourly threshold. False otherwise.
+ */
+function flood_is_allowed($name, $threshold) {
+  $number = db_result(db_query("SELECT COUNT(*) FROM {flood} WHERE event = '%s' AND hostname = '%s' AND timestamp > %d", $name, ip_address(), time() - 3600));
+  return ($number < $threshold ? TRUE : FALSE);
+}
+
+function check_file($filename) {
+  return is_uploaded_file($filename);
+}
+
+/**
+ * Prepare a URL for use in an HTML attribute. Strips harmful protocols.
+ */
+function check_url($uri) {
+  return filter_xss_bad_protocol($uri, FALSE);
+}
+
+/**
+ * @defgroup format Formatting
+ * @{
+ * Functions to format numbers, strings, dates, etc.
+ */
+
+/**
+ * Formats an RSS channel.
+ *
+ * Arbitrary elements may be added using the $args associative array.
+ */
+function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
+  global $language;
+  $langcode = $langcode ? $langcode : $language->language;
+
+  $output = "<channel>\n";
+  $output .= ' <title>'. check_plain($title) ."</title>\n";
+  $output .= ' <link>'. check_url($link) ."</link>\n";
+
+  // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
+  // We strip all HTML tags, but need to prevent double encoding from properly
+  // escaped source data (such as &amp becoming &amp;amp;).
+  $output .= ' <description>'. check_plain(decode_entities(strip_tags($description))) ."</description>\n";
+  $output .= ' <language>'. check_plain($langcode) ."</language>\n";
+  $output .= format_xml_elements($args);
+  $output .= $items;
+  $output .= "</channel>\n";
+
+  return $output;
+}
+
+/**
+ * Format a single RSS item.
+ *
+ * Arbitrary elements may be added using the $args associative array.
+ */
+function format_rss_item($title, $link, $description, $args = array()) {
+  $output = "<item>\n";
+  $output .= ' <title>'. check_plain($title) ."</title>\n";
+  $output .= ' <link>'. check_url($link) ."</link>\n";
+  $output .= ' <description>'. check_plain($description) ."</description>\n";
+  $output .= format_xml_elements($args);
+  $output .= "</item>\n";
+
+  return $output;
+}
+
+/**
+ * Format XML elements.
+ *
+ * @param $array
+ *   An array where each item represent an element and is either a:
+ *   - (key => value) pair (<key>value</key>)
+ *   - Associative array with fields:
+ *     - 'key': element name
+ *     - 'value': element contents
+ *     - 'attributes': associative array of element attributes
+ *
+ * In both cases, 'value' can be a simple string, or it can be another array
+ * with the same format as $array itself for nesting.
+ */
+function format_xml_elements($array) {
+  $output = '';
+  foreach ($array as $key => $value) {
+    if (is_numeric($key)) {
+      if ($value['key']) {
+        $output .= ' <'. $value['key'];
+        if (isset($value['attributes']) && is_array($value['attributes'])) {
+          $output .= drupal_attributes($value['attributes']);
+        }
+
+        if ($value['value'] != '') {
+          $output .= '>'. (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) .'</'. $value['key'] .">\n";
+        }
+        else {
+          $output .= " />\n";
+        }
+      }
+    }
+    else {
+      $output .= ' <'. $key .'>'. (is_array($value) ? format_xml_elements($value) : check_plain($value)) ."</$key>\n";
+    }
+  }
+  return $output;
+}
+
+/**
+ * Format a string containing a count of items.
+ *
+ * This function ensures that the string is pluralized correctly. Since t() is
+ * called by this function, make sure not to pass already-localized strings to
+ * it.
+ *
+ * For example:
+ * @code
+ *   $output = format_plural($node->comment_count, '1 comment', '@count comments');
+ * @endcode
+ *
+ * Example with additional replacements:
+ * @code
+ *   $output = format_plural($update_count,
+ *     'Changed the content type of 1 post from %old-type to %new-type.',
+ *     'Changed the content type of @count posts from %old-type to %new-type.',
+ *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type)));
+ * @endcode
+ *
+ * @param $count
+ *   The item count to display.
+ * @param $singular
+ *   The string for the singular case. Please make sure it is clear this is
+ *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
+ *   Do not use @count in the singular string.
+ * @param $plural
+ *   The string for the plural case. Please make sure it is clear this is plural,
+ *   to ease translation. Use @count in place of the item count, as in "@count
+ *   new comments".
+ * @param $args
+ *   An associative array of replacements to make after translation. Incidences
+ *   of any key in this array are replaced with the corresponding value.
+ *   Based on the first character of the key, the value is escaped and/or themed:
+ *    - !variable: inserted as is
+ *    - @variable: escape plain text to HTML (check_plain)
+ *    - %variable: escape text and theme as a placeholder for user-submitted
+ *      content (check_plain + theme_placeholder)
+ *   Note that you do not need to include @count in this array.
+ *   This replacement is done automatically for the plural case.
+ * @param $langcode
+ *   Optional language code to translate to a language other than
+ *   what is used to display the page.
+ * @return
+ *   A translated string.
+ */
+function format_plural($count, $singular, $plural, $args = array(), $langcode = NULL) {
+  $args['@count'] = $count;
+  if ($count == 1) {
+    return t($singular, $args, $langcode);
+  }
+
+  // Get the plural index through the gettext formula.
+  $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, $langcode) : -1;
+  // Backwards compatibility.
+  if ($index < 0) {
+    return t($plural, $args, $langcode);
+  }
+  else {
+    switch ($index) {
+      case "0":
+        return t($singular, $args, $langcode);
+      case "1":
+        return t($plural, $args, $langcode);
+      default:
+        unset($args['@count']);
+        $args['@count['. $index .']'] = $count;
+        return t(strtr($plural, array('@count' => '@count['. $index .']')), $args, $langcode);
+    }
+  }
+}
+
+/**
+ * Parse a given byte count.
+ *
+ * @param $size
+ *   A size expressed as a number of bytes with optional SI size and unit
+ *   suffix (e.g. 2, 3K, 5MB, 10G).
+ * @return
+ *   An integer representation of the size.
+ */
+function parse_size($size) {
+  $suffixes = array(
+    '' => 1,
+    'k' => 1024,
+    'm' => 1048576, // 1024 * 1024
+    'g' => 1073741824, // 1024 * 1024 * 1024
+  );
+  if (preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', $size, $match)) {
+    return $match[1] * $suffixes[drupal_strtolower($match[2])];
+  }
+}
+
+/**
+ * Generate a string representation for the given byte count.
+ *
+ * @param $size
+ *   A size in bytes.
+ * @param $langcode
+ *   Optional language code to translate to a language other than what is used
+ *   to display the page.
+ * @return
+ *   A translated string representation of the size.
+ */
+function format_size($size, $langcode = NULL) {
+  if ($size < 1024) {
+    return format_plural($size, '1 byte', '@count bytes', array(), $langcode);
+  }
+  else {
+    $size = round($size / 1024, 2);
+    $suffix = t('KB', array(), $langcode);
+    if ($size >= 1024) {
+      $size = round($size / 1024, 2);
+      $suffix = t('MB', array(), $langcode);
+    }
+    return t('@size @suffix', array('@size' => $size, '@suffix' => $suffix), $langcode);
+  }
+}
+
+/**
+ * Format a time interval with the requested granularity.
+ *
+ * @param $timestamp
+ *   The length of the interval in seconds.
+ * @param $granularity
+ *   How many different units to display in the string.
+ * @param $langcode
+ *   Optional language code to translate to a language other than
+ *   what is used to display the page.
+ * @return
+ *   A translated string representation of the interval.
+ */
+function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
+  $units = array('1 year|@count years' => 31536000, '1 week|@count weeks' => 604800, '1 day|@count days' => 86400, '1 hour|@count hours' => 3600, '1 min|@count min' => 60, '1 sec|@count sec' => 1);
+  $output = '';
+  foreach ($units as $key => $value) {
+    $key = explode('|', $key);
+    if ($timestamp >= $value) {
+      $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), $langcode);
+      $timestamp %= $value;
+      $granularity--;
+    }
+
+    if ($granularity == 0) {
+      break;
+    }
+  }
+  return $output ? $output : t('0 sec', array(), $langcode);
+}
+
+/**
+ * Format a date with the given configured format or a custom format string.
+ *
+ * Drupal allows administrators to select formatting strings for 'small',
+ * 'medium' and 'large' date formats. This function can handle these formats,
+ * as well as any custom format.
+ *
+ * @param $timestamp
+ *   The exact date to format, as a UNIX timestamp.
+ * @param $type
+ *   The format to use. Can be "small", "medium" or "large" for the preconfigured
+ *   date formats. If "custom" is specified, then $format is required as well.
+ * @param $format
+ *   A PHP date format string as required by date(). A backslash should be used
+ *   before a character to avoid interpreting the character as part of a date
+ *   format.
+ * @param $timezone
+ *   Time zone offset in seconds; if omitted, the user's time zone is used.
+ * @param $langcode
+ *   Optional language code to translate to a language other than what is used
+ *   to display the page.
+ * @return
+ *   A translated date string in the requested format.
+ */
+function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
+  if (!isset($timezone)) {
+    global $user;
+    if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+      $timezone = $user->timezone;
+    }
+    else {
+      $timezone = variable_get('date_default_timezone', 0);
+    }
+  }
+
+  $timestamp += $timezone;
+
+  switch ($type) {
+    case 'small':
+      $format = variable_get('date_format_short', 'm/d/Y - H:i');
+      break;
+    case 'large':
+      $format = variable_get('date_format_long', 'l, F j, Y - H:i');
+      break;
+    case 'custom':
+      // No change to format.
+      break;
+    case 'medium':
+    default:
+      $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
+  }
+
+  $max = strlen($format);
+  $date = '';
+  for ($i = 0; $i < $max; $i++) {
+    $c = $format[$i];
+    if (strpos('AaDlM', $c) !== FALSE) {
+      $date .= t(gmdate($c, $timestamp), array(), $langcode);
+    }
+    else if ($c == 'F') {
+      // Special treatment for long month names: May is both an abbreviation
+      // and a full month name in English, but other languages have
+      // different abbreviations.
+      $date .= trim(t('!long-month-name '. gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode));
+    }
+    else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
+      $date .= gmdate($c, $timestamp);
+    }
+    else if ($c == 'r') {
+      $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode);
+    }
+    else if ($c == 'O') {
+      $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60);
+    }
+    else if ($c == 'Z') {
+      $date .= $timezone;
+    }
+    else if ($c == '\\') {
+      $date .= $format[++$i];
+    }
+    else {
+      $date .= $c;
+    }
+  }
+
+  return $date;
+}
+
+/**
+ * @} End of "defgroup format".
+ */
+
+/**
+ * Generate a URL from a Drupal menu path. Will also pass-through existing URLs.
+ *
+ * @param $path
+ *   The Drupal path being linked to, such as "admin/content/node", or an
+ *   existing URL like "http://drupal.org/".  The special path
+ *   '<front>' may also be given and will generate the site's base URL.
+ * @param $options
+ *   An associative array of additional options, with the following keys:
+ *     'query'
+ *       A query string to append to the link, or an array of query key/value
+ *       properties.
+ *     'fragment'
+ *       A fragment identifier (or named anchor) to append to the link.
+ *       Do not include the '#' character.
+ *     'absolute' (default FALSE)
+ *       Whether to force the output to be an absolute link (beginning with
+ *       http:). Useful for links that will be displayed outside the site, such
+ *       as in an RSS feed.
+ *     'alias' (default FALSE)
+ *       Whether the given path is an alias already.
+ *     'external'
+ *       Whether the given path is an external URL.
+ *     'language'
+ *       An optional language object. Used to build the URL to link to and
+ *       look up the proper alias for the link.
+ *     'base_url'
+ *       Only used internally, to modify the base URL when a language dependent
+ *       URL requires so.
+ *     'prefix'
+ *       Only used internally, to modify the path when a language dependent URL
+ *       requires so.
+ * @return
+ *   A string containing a URL to the given path.
+ *
+ * When creating links in modules, consider whether l() could be a better
+ * alternative than url().
+ */
+function url($path = NULL, $options = array()) {
+  // Merge in defaults.
+  $options += array(
+    'fragment' => '',
+    'query' => '',
+    'absolute' => FALSE,
+    'alias' => FALSE,
+    'prefix' => ''
+  );
+  if (!isset($options['external'])) {
+    // Return an external link if $path contains an allowed absolute URL.
+    // Only call the slow filter_xss_bad_protocol if $path contains a ':' before
+    // any / ? or #.
+    $colonpos = strpos($path, ':');
+    $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
+  }
+
+  // May need language dependent rewriting if language.inc is present.
+  if (function_exists('language_url_rewrite')) {
+    language_url_rewrite($path, $options);
+  }
+  if ($options['fragment']) {
+    $options['fragment'] = '#'. $options['fragment'];
+  }
+  if (is_array($options['query'])) {
+    $options['query'] = drupal_query_string_encode($options['query']);
+  }
+
+  if ($options['external']) {
+    // Split off the fragment.
+    if (strpos($path, '#') !== FALSE) {
+      list($path, $old_fragment) = explode('#', $path, 2);
+      if (isset($old_fragment) && !$options['fragment']) {
+        $options['fragment'] = '#'. $old_fragment;
+      }
+    }
+    // Append the query.
+    if ($options['query']) {
+      $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query'];
+    }
+    // Reassemble.
+    return $path . $options['fragment'];
+  }
+
+  global $base_url;
+  static $script;
+  static $clean_url;
+
+  if (!isset($script)) {
+    // On some web servers, such as IIS, we can't omit "index.php". So, we
+    // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
+    // Apache.
+    $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : '';
+  }
+
+  // Cache the clean_url variable to improve performance.
+  if (!isset($clean_url)) {
+    $clean_url = (bool)variable_get('clean_url', '0');
+  }
+
+  if (!isset($options['base_url'])) {
+    // The base_url might be rewritten from the language rewrite in domain mode.
+    $options['base_url'] = $base_url;
+  }
+
+  // Preserve the original path before aliasing.
+  $original_path = $path;
+
+  // The special path '<front>' links to the default front page.
+  if ($path == '<front>') {
+    $path = '';
+  }
+  elseif (!empty($path) && !$options['alias']) {
+    $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : '');
+  }
+
+  if (function_exists('custom_url_rewrite_outbound')) {
+    // Modules may alter outbound links by reference.
+    custom_url_rewrite_outbound($path, $options, $original_path);
+  }
+
+  $base = $options['absolute'] ? $options['base_url'] .'/' : base_path();
+  $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
+  $path = drupal_urlencode($prefix . $path);
+
+  if ($clean_url) {
+    // With Clean URLs.
+    if ($options['query']) {
+      return $base . $path .'?'. $options['query'] . $options['fragment'];
+    }
+    else {
+      return $base . $path . $options['fragment'];
+    }
+  }
+  else {
+    // Without Clean URLs.
+    $variables = array();
+    if (!empty($path)) {
+      $variables[] = 'q='. $path;
+    }
+    if (!empty($options['query'])) {
+      $variables[] = $options['query'];
+    }
+    if ($query = join('&', $variables)) {
+      return $base . $script .'?'. $query . $options['fragment'];
+    }
+    else {
+      return $base . $options['fragment'];
+    }
+  }
+}
+
+/**
+ * Format an attribute string to insert in a tag.
+ *
+ * @param $attributes
+ *   An associative array of HTML attributes.
+ * @return
+ *   An HTML string ready for insertion in a tag.
+ */
+function drupal_attributes($attributes = array()) {
+  if (is_array($attributes)) {
+    $t = '';
+    foreach ($attributes as $key => $value) {
+      $t .= " $key=".'"'. check_plain($value) .'"';
+    }
+    return $t;
+  }
+}
+
+/**
+ * Format an internal Drupal link.
+ *
+ * This function correctly handles aliased paths, and allows themes to highlight
+ * links to the current page correctly, so all internal links output by modules
+ * should be generated by this function if possible.
+ *
+ * @param $text
+ *   The text to be enclosed with the anchor tag.
+ * @param $path
+ *   The Drupal path being linked to, such as "admin/content/node". Can be an
+ *   external or internal URL.
+ *     - If you provide the full URL, it will be considered an external URL.
+ *     - If you provide only the path (e.g. "admin/content/node"), it is
+ *       considered an internal link. In this case, it must be a system URL
+ *       as the url() function will generate the alias.
+ *     - If you provide '<front>', it generates a link to the site's
+ *       base URL (again via the url() function).
+ *     - If you provide a path, and 'alias' is set to TRUE (see below), it is
+ *       used as is.
+ * @param $options
+ *   An associative array of additional options, with the following keys:
+ *     'attributes'
+ *       An associative array of HTML attributes to apply to the anchor tag.
+ *     'query'
+ *       A query string to append to the link, or an array of query key/value
+ *       properties.
+ *     'fragment'
+ *       A fragment identifier (named anchor) to append to the link.
+ *       Do not include the '#' character.
+ *     'absolute' (default FALSE)
+ *       Whether to force the output to be an absolute link (beginning with
+ *       http:). Useful for links that will be displayed outside the site, such
+ *       as in an RSS feed.
+ *     'html' (default FALSE)
+ *       Whether the title is HTML, or just plain-text. For example for making
+ *       an image a link, this must be set to TRUE, or else you will see the
+ *       escaped HTML.
+ *     'alias' (default FALSE)
+ *       Whether the given path is an alias already.
+ * @return
+ *   an HTML string containing a link to the given path.
+ */
+function l($text, $path, $options = array()) {
+  // Merge in defaults.
+  $options += array(
+      'attributes' => array(),
+      'html' => FALSE,
+    );
+
+  // Append active class.
+  if ($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) {
+    if (isset($options['attributes']['class'])) {
+      $options['attributes']['class'] .= ' active';
+    }
+    else {
+      $options['attributes']['class'] = 'active';
+    }
+  }
+
+  // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
+  // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
+  if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
+    $options['attributes']['title'] = strip_tags($options['attributes']['title']);
+  }
+
+  return '<a href="'. check_url(url($path, $options)) .'"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</a>';
+}
+
+/**
+ * Perform end-of-request tasks.
+ *
+ * This function sets the page cache if appropriate, and allows modules to
+ * react to the closing of the page by calling hook_exit().
+ */
+function drupal_page_footer() {
+  if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
+    page_set_cache();
+  }
+
+  module_invoke_all('exit');
+}
+
+/**
+ * Form an associative array from a linear array.
+ *
+ * This function walks through the provided array and constructs an associative
+ * array out of it. The keys of the resulting array will be the values of the
+ * input array. The values will be the same as the keys unless a function is
+ * specified, in which case the output of the function is used for the values
+ * instead.
+ *
+ * @param $array
+ *   A linear array.
+ * @param $function
+ *   A name of a function to apply to all values before output.
+ * @result
+ *   An associative array.
+ */
+function drupal_map_assoc($array, $function = NULL) {
+  if (!isset($function)) {
+    $result = array();
+    foreach ($array as $value) {
+      $result[$value] = $value;
+    }
+    return $result;
+  }
+  elseif (function_exists($function)) {
+    $result = array();
+    foreach ($array as $value) {
+      $result[$value] = $function($value);
+    }
+    return $result;
+  }
+}
+
+/**
+ * Evaluate a string of PHP code.
+ *
+ * This is a wrapper around PHP's eval(). It uses output buffering to capture both
+ * returned and printed text. Unlike eval(), we require code to be surrounded by
+ * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone
+ * PHP file.
+ *
+ * Using this wrapper also ensures that the PHP code which is evaluated can not
+ * overwrite any variables in the calling code, unlike a regular eval() call.
+ *
+ * @param $code
+ *   The code to evaluate.
+ * @return
+ *   A string containing the printed output of the code, followed by the returned
+ *   output of the code.
+ */
+function drupal_eval($code) {
+  global $theme_path, $theme_info, $conf;
+
+  // Store current theme path.
+  $old_theme_path = $theme_path;
+
+  // Restore theme_path to the theme, as long as drupal_eval() executes,
+  // so code evaluted will not see the caller module as the current theme.
+  // If theme info is not initialized get the path from theme_default.
+  if (!isset($theme_info)) {
+    $theme_path = drupal_get_path('theme', $conf['theme_default']);
+  }
+  else {
+    $theme_path = dirname($theme_info->filename);
+  }
+
+  ob_start();
+  print eval('?>'. $code);
+  $output = ob_get_contents();
+  ob_end_clean();
+
+  // Recover original theme path.
+  $theme_path = $old_theme_path;
+
+  return $output;
+}
+
+/**
+ * Returns the path to a system item (module, theme, etc.).
+ *
+ * @param $type
+ *   The type of the item (i.e. theme, theme_engine, module).
+ * @param $name
+ *   The name of the item for which the path is requested.
+ *
+ * @return
+ *   The path to the requested item.
+ */
+function drupal_get_path($type, $name) {
+  return dirname(drupal_get_filename($type, $name));
+}
+
+/**
+ * Returns the base URL path of the Drupal installation.
+ * At the very least, this will always default to /.
+ */
+function base_path() {
+  return $GLOBALS['base_path'];
+}
+
+/**
+ * Provide a substitute clone() function for PHP4.
+ */
+function drupal_clone($object) {
+  return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object);
+}
+
+/**
+ * Add a <link> tag to the page's HEAD.
+ */
+function drupal_add_link($attributes) {
+  drupal_set_html_head('<link'. drupal_attributes($attributes) ." />\n");
+}
+
+/**
+ * Adds a CSS file to the stylesheet queue.
+ *
+ * Themes may replace module-defined CSS files by adding a stylesheet with the
+ * same filename. For example, themes/garland/system-menus.css would replace
+ * modules/system/system-menus.css. This allows themes to override complete
+ * CSS files, rather than specific selectors, when necessary.
+ *
+ * @param $path
+ *   (optional) The path to the CSS file relative to the base_path(), e.g.,
+ *   /modules/devel/devel.css.
+ *
+ *   Modules should always prefix the names of their CSS files with the module
+ *   name, for example: system-menus.css rather than simply menus.css. Themes
+ *   can override module-supplied CSS files based on their filenames, and this
+ *   prefixing helps prevent confusing name collisions for theme developers.
+ *
+ *   If the direction of the current language is right-to-left (Hebrew,
+ *   Arabic, etc.), the function will also look for an RTL CSS file and append
+ *   it to the list. The name of this file should have an '-rtl.css' suffix.
+ *   For example a CSS file called 'name.css' will have a 'name-rtl.css'
+ *   file added to the list, if exists in the same directory. This CSS file
+ *   should contain overrides for properties which should be reversed or
+ *   otherwise different in a right-to-left display.
+ *
+ *   If the original CSS file is being overridden by a theme, the theme is
+ *   responsible for supplying an accompanying RTL CSS file to replace the
+ *   module's.
+ * @param $type
+ *   (optional) The type of stylesheet that is being added. Types are: module
+ *   or theme.
+ * @param $media
+ *   (optional) The media type for the stylesheet, e.g., all, print, screen.
+ * @param $preprocess
+ *   (optional) Should this CSS file be aggregated and compressed if this
+ *   feature has been turned on under the performance section?
+ *
+ *   What does this actually mean?
+ *   CSS preprocessing is the process of aggregating a bunch of separate CSS
+ *   files into one file that is then compressed by removing all extraneous
+ *   white space.
+ *
+ *   The reason for merging the CSS files is outlined quite thoroughly here:
+ *   http://www.die.net/musings/page_load_time/
+ *   "Load fewer external objects. Due to request overhead, one bigger file
+ *   just loads faster than two smaller ones half its size."
+ *
+ *   However, you should *not* preprocess every file as this can lead to
+ *   redundant caches. You should set $preprocess = FALSE when:
+ *
+ *     - Your styles are only used rarely on the site. This could be a special
+ *       admin page, the homepage, or a handful of pages that does not represent
+ *       the majority of the pages on your site.
+ *
+ *   Typical candidates for caching are for example styles for nodes across
+ *   the site, or used in the theme.
+ * @return
+ *   An array of CSS files.
+ */
+function drupal_add_css($path = NULL, $type = 'module', $media = 'all', $preprocess = TRUE) {
+  static $css = array();
+  global $language;
+
+  // Create an array of CSS files for each media type first, since each type needs to be served
+  // to the browser differently.
+  if (isset($path)) {
+    // This check is necessary to ensure proper cascading of styles and is faster than an asort().
+    if (!isset($css[$media])) {
+      $css[$media] = array('module' => array(), 'theme' => array());
+    }
+
+    // If a theme is adding the current stylesheet, check for any existing CSS files
+    // with the same name. If they exist, remove them and allow the theme's own CSS
+    // file to replace it.
+    if ($type == 'theme') {
+      foreach ($css[$media]['module'] as $old_path => $old_preprocess) {
+        // Match by style sheet name.
+        if (basename($path) == basename($old_path)) {
+          unset($css[$media]['module'][$old_path]);
+
+          // If the current language is RTL and the CSS file had an RTL variant,
+          // pull out the original. The theme must provide its own RTL style.
+          if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
+            $rtl_old_path = str_replace('.css', '-rtl.css', $old_path);
+            if (isset($css[$media]['module'][$rtl_old_path])) {
+              unset($css[$media]['module'][$rtl_old_path]);
+            }
+          }
+          // Set the preprocess state of the current module, then exit the search loop.
+          $preprocess = $old_preprocess;
+          break;
+        }
+      }
+    }
+    $css[$media][$type][$path] = $preprocess;
+
+    // If the current language is RTL, add the CSS file with RTL overrides.
+    if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
+      $rtl_path = str_replace('.css', '-rtl.css', $path);
+      if (file_exists($rtl_path)) {
+        $css[$media][$type][$rtl_path] = $preprocess;
+      }
+    }
+  }
+
+  return $css;
+}
+
+/**
+ * Returns a themed representation of all stylesheets that should be attached to the page.
+ *
+ * It loads the CSS in order, with 'core' CSS first, then 'module' CSS, then
+ * 'theme' CSS files. This ensures proper cascading of styles for easy
+ * overriding in modules and themes.
+ *
+ * @param $css
+ *   (optional) An array of CSS files. If no array is provided, the default
+ *   stylesheets array is used instead.
+ * @return
+ *   A string of XHTML CSS tags.
+ */
+function drupal_get_css($css = NULL) {
+  $output = '';
+  if (!isset($css)) {
+    $css = drupal_add_css();
+  }
+  $no_module_preprocess = '';
+  $no_theme_preprocess = '';
+
+  $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  $directory = file_directory_path();
+  $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
+
+  // A dummy query-string is added to filenames, to gain control over
+  // browser-caching. The string changes on every update or full cache
+  // flush, forcing browsers to load a new copy of the files, as the
+  // URL changed.
+  $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
+
+  foreach ($css as $media => $types) {
+    // If CSS preprocessing is off, we still need to output the styles.
+    // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones.
+    foreach ($types as $type => $files) {
+      foreach ($types[$type] as $file => $preprocess) {
+        if (!$preprocess || !($is_writable && $preprocess_css)) {
+          // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
+          // regardless of whether preprocessing is on or off.
+          if (!$preprocess && $type == 'module') {
+            $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+          }
+          // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
+          // regardless of whether preprocessing is on or off.
+          else if (!$preprocess && $type == 'theme') {
+            $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+          }
+          else {
+            $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+          }
+        }
+      }
+    }
+
+    if ($is_writable && $preprocess_css) {
+      $filename = md5(serialize($types) . $query_string) .'.css';
+      $preprocess_file = drupal_build_css_cache($types, $filename);
+      $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $preprocess_file .'" />'."\n";
+    }
+  }
+
+  return $no_module_preprocess . $output . $no_theme_preprocess;
+}
+
+/**
+ * Aggregate and optimize CSS files, putting them in the files directory.
+ *
+ * @param $types
+ *   An array of types of CSS files (e.g., screen, print) to aggregate and
+ *   compress into one file.
+ * @param $filename
+ *   The name of the aggregate CSS file.
+ * @return
+ *   The name of the CSS file.
+ */
+function drupal_build_css_cache($types, $filename) {
+  $data = '';
+
+  // Create the css/ within the files folder.
+  $csspath = file_create_path('css');
+  file_check_directory($csspath, FILE_CREATE_DIRECTORY);
+
+  if (!file_exists($csspath .'/'. $filename)) {
+    // Build aggregate CSS file.
+    foreach ($types as $type) {
+      foreach ($type as $file => $cache) {
+        if ($cache) {
+          $contents = drupal_load_stylesheet($file, TRUE);
+          // Return the path to where this CSS file originated from.
+          $base = base_path() . dirname($file) .'/';
+          _drupal_build_css_path(NULL, $base);
+          // Prefix all paths within this CSS file, ignoring external and absolute paths.
+          $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
+        }
+      }
+    }
+
+    // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
+    // @import rules must proceed any other style, so we move those to the top.
+    $regexp = '/@import[^;]+;/i';
+    preg_match_all($regexp, $data, $matches);
+    $data = preg_replace($regexp, '', $data);
+    $data = implode('', $matches[0]) . $data;
+
+    // Create the CSS file.
+    file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE);
+  }
+  return $csspath .'/'. $filename;
+}
+
+/**
+ * Helper function for drupal_build_css_cache().
+ *
+ * This function will prefix all paths within a CSS file.
+ */
+function _drupal_build_css_path($matches, $base = NULL) {
+  static $_base;
+  // Store base path for preg_replace_callback.
+  if (isset($base)) {
+    $_base = $base;
+  }
+
+  // Prefix with base and remove '../' segments where possible.
+  $path = $_base . $matches[1];
+  $last = '';
+  while ($path != $last) {
+    $last = $path;
+    $path = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $path);
+  }
+  return 'url('. $path .')';
+}
+
+/**
+ * Loads the stylesheet and resolves all @import commands.
+ *
+ * Loads a stylesheet and replaces @import commands with the contents of the
+ * imported file. Use this instead of file_get_contents when processing
+ * stylesheets.
+ *
+ * The returned contents are compressed removing white space and comments only
+ * when CSS aggregation is enabled. This optimization will not apply for
+ * color.module enabled themes with CSS aggregation turned off.
+ *
+ * @param $file
+ *   Name of the stylesheet to be processed.
+ * @param $optimize
+ *   Defines if CSS contents should be compressed or not.
+ * @return
+ *   Contents of the stylesheet including the imported stylesheets.
+ */
+function drupal_load_stylesheet($file, $optimize = NULL) {
+  static $_optimize;
+  // Store optimization parameter for preg_replace_callback with nested @import loops.
+  if (isset($optimize)) {
+    $_optimize = $optimize;
+  }
+
+  $contents = '';
+  if (file_exists($file)) {
+    // Load the local CSS stylesheet.
+    $contents = file_get_contents($file);
+
+    // Change to the current stylesheet's directory.
+    $cwd = getcwd();
+    chdir(dirname($file));
+
+    // Replaces @import commands with the actual stylesheet content.
+    // This happens recursively but omits external files.
+    $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents);
+    // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
+    $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
+
+    if ($_optimize) {
+      // Perform some safe CSS optimizations.
+      $contents = preg_replace('<
+        \s*([@{}:;,]|\)\s|\s\()\s* |  # Remove whitespace around separators, but keep space around parentheses.
+        /\*([^*\\\\]|\*(?!/))+\*/ |   # Remove comments that are not CSS hacks.
+        [\n\r]                        # Remove line breaks.
+        >x', '\1', $contents);
+    }
+
+    // Change back directory.
+    chdir($cwd);
+  }
+
+  return $contents;
+}
+
+/**
+ * Loads stylesheets recursively and returns contents with corrected paths.
+ *
+ * This function is used for recursive loading of stylesheets and
+ * returns the stylesheet content with all url() paths corrected.
+ */
+function _drupal_load_stylesheet($matches) {
+  $filename = $matches[1];
+  // Load the imported stylesheet and replace @import commands in there as well.
+  $file = drupal_load_stylesheet($filename);
+  // Alter all url() paths, but not external.
+  return preg_replace('/url\(([\'"]?)(?![a-z]+:)([^\'")]+)[\'"]?\)?;/i', 'url(\1'. dirname($filename) .'/', $file);
+}
+
+/**
+ * Delete all cached CSS files.
+ */
+function drupal_clear_css_cache() {
+  file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+}
+
+/**
+ * Add a JavaScript file, setting or inline code to the page.
+ *
+ * The behavior of this function depends on the parameters it is called with.
+ * Generally, it handles the addition of JavaScript to the page, either as
+ * reference to an existing file or as inline code. The following actions can be
+ * performed using this function:
+ *
+ * - Add a file ('core', 'module' and 'theme'):
+ *   Adds a reference to a JavaScript file to the page. JavaScript files
+ *   are placed in a certain order, from 'core' first, to 'module' and finally
+ *   'theme' so that files, that are added later, can override previously added
+ *   files with ease.
+ *
+ * - Add inline JavaScript code ('inline'):
+ *   Executes a piece of JavaScript code on the current page by placing the code
+ *   directly in the page. This can, for example, be useful to tell the user that
+ *   a new message arrived, by opening a pop up, alert box etc.
+ *
+ * - Add settings ('setting'):
+ *   Adds a setting to Drupal's global storage of JavaScript settings. Per-page
+ *   settings are required by some modules to function properly. The settings
+ *   will be accessible at Drupal.settings.
+ *
+ * @param $data
+ *   (optional) If given, the value depends on the $type parameter:
+ *   - 'core', 'module' or 'theme': Path to the file relative to base_path().
+ *   - 'inline': The JavaScript code that should be placed in the given scope.
+ *   - 'setting': An array with configuration options as associative array. The
+ *       array is directly placed in Drupal.settings. You might want to wrap your
+ *       actual configuration settings in another variable to prevent the pollution
+ *       of the Drupal.settings namespace.
+ * @param $type
+ *   (optional) The type of JavaScript that should be added to the page. Allowed
+ *   values are 'core', 'module', 'theme', 'inline' and 'setting'. You
+ *   can, however, specify any value. It is treated as a reference to a JavaScript
+ *   file. Defaults to 'module'.
+ * @param $scope
+ *   (optional) The location in which you want to place the script. Possible
+ *   values are 'header' and 'footer' by default. If your theme implements
+ *   different locations, however, you can also use these.
+ * @param $defer
+ *   (optional) If set to TRUE, the defer attribute is set on the <script> tag.
+ *   Defaults to FALSE. This parameter is not used with $type == 'setting'.
+ * @param $cache
+ *   (optional) If set to FALSE, the JavaScript file is loaded anew on every page
+ *   call, that means, it is not cached. Defaults to TRUE. Used only when $type
+ *   references a JavaScript file.
+ * @param $preprocess
+ *   (optional) Should this JS file be aggregated if this
+ *   feature has been turned on under the performance section?
+ * @return
+ *   If the first parameter is NULL, the JavaScript array that has been built so
+ *   far for $scope is returned. If the first three parameters are NULL,
+ *   an array with all scopes is returned.
+ */
+function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) {
+  static $javascript = array();
+
+  if (isset($data)) {
+
+    // Add jquery.js and drupal.js, as well as the basePath setting, the
+    // first time a Javascript file is added.
+    if (empty($javascript)) {
+      $javascript['header'] = array(
+        'core' => array(
+          'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
+          'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
+        ),
+        'module' => array(),
+        'theme' => array(),
+        'setting' => array(
+          array('basePath' => base_path()),
+        ),
+        'inline' => array(),
+      );
+    }
+
+    if (isset($scope) && !isset($javascript[$scope])) {
+      $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array());
+    }
+
+    if (isset($type) && isset($scope) && !isset($javascript[$scope][$type])) {
+      $javascript[$scope][$type] = array();
+    }
+
+    switch ($type) {
+      case 'setting':
+        $javascript[$scope][$type][] = $data;
+        break;
+      case 'inline':
+        $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer);
+        break;
+      default:
+        // If cache is FALSE, don't preprocess the JS file.
+        $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess));
+    }
+  }
+
+  if (isset($scope)) {
+
+    if (isset($javascript[$scope])) {
+      return $javascript[$scope];
+    }
+    else {
+      return array();
+    }
+  }
+  else {
+    return $javascript;
+  }
+}
+
+/**
+ * Returns a themed presentation of all JavaScript code for the current page.
+ *
+ * References to JavaScript files are placed in a certain order: first, all
+ * 'core' files, then all 'module' and finally all 'theme' JavaScript files
+ * are added to the page. Then, all settings are output, followed by 'inline'
+ * JavaScript code. If running update.php, all preprocessing is disabled.
+ *
+ * @parameter $scope
+ *   (optional) The scope for which the JavaScript rules should be returned.
+ *   Defaults to 'header'.
+ * @parameter $javascript
+ *   (optional) An array with all JavaScript code. Defaults to the default
+ *   JavaScript array for the given scope.
+ * @return
+ *   All JavaScript code segments and includes for the scope as HTML tags.
+ */
+function drupal_get_js($scope = 'header', $javascript = NULL) {
+  if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
+    locale_update_js_files();
+  }
+
+  if (!isset($javascript)) {
+    $javascript = drupal_add_js(NULL, NULL, $scope);
+  }
+
+  if (empty($javascript)) {
+    return '';
+  }
+
+  $output = '';
+  $preprocessed = '';
+  $no_preprocess = array('core' => '', 'module' => '', 'theme' => '');
+  $files = array();
+  $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  $directory = file_directory_path();
+  $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
+
+  // A dummy query-string is added to filenames, to gain control over
+  // browser-caching. The string changes on every update or full cache
+  // flush, forcing browsers to load a new copy of the files, as the
+  // URL changed. Files that should not be cached (see drupal_add_js())
+  // get time() as query-string instead, to enforce reload on every
+  // page request.
+  $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
+
+  foreach ($javascript as $type => $data) {
+
+    if (!$data) continue;
+
+    switch ($type) {
+      case 'setting':
+        $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $data)) .");</script>\n";
+        break;
+      case 'inline':
+        foreach ($data as $info) {
+          $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .'>'. $info['code'] ."</script>\n";
+        }
+        break;
+      default:
+        // If JS preprocessing is off, we still need to output the scripts.
+        // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
+        foreach ($data as $path => $info) {
+          if (!$info['preprocess'] || !$is_writable || !$preprocess_js) {
+            $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? $query_string : '?'. time()) ."\"></script>\n";
+          }
+          else {
+            $files[$path] = $info;
+          }
+        }
+    }
+  }
+
+  // Aggregate any remaining JS files that haven't already been output.
+  if ($is_writable && $preprocess_js && count($files) > 0) {
+    $filename = md5(serialize($files) . $query_string) .'.js';
+    $preprocess_file = drupal_build_js_cache($files, $filename);
+    $preprocessed .= '<script type="text/javascript" src="'. base_path() . $preprocess_file .'"></script>'."\n";
+  }
+
+  // Keep the order of JS files consistent as some are preprocessed and others are not.
+  // Make sure any inline or JS setting variables appear last after libraries have loaded.
+  $output = $preprocessed . implode('', $no_preprocess) . $output;
+
+  return $output;
+}
+
+/**
+ * Assist in adding the tableDrag JavaScript behavior to a themed table.
+ *
+ * Draggable tables should be used wherever an outline or list of sortable items
+ * needs to be arranged by an end-user. Draggable tables are very flexible and
+ * can manipulate the value of form elements placed within individual columns.
+ *
+ * To set up a table to use drag and drop in place of weight select-lists or
+ * in place of a form that contains parent relationships, the form must be
+ * themed into a table. The table must have an id attribute set. If using
+ * theme_table(), the id may be set as such:
+ * @code
+ * $output = theme('table', $header, $rows, array('id' => 'my-module-table'));
+ * return $output;
+ * @endcode
+ *
+ * In the theme function for the form, a special class must be added to each
+ * form element within the same column, "grouping" them together.
+ *
+ * In a situation where a single weight column is being sorted in the table, the
+ * classes could be added like this (in the theme function):
+ * @code
+ * $form['my_elements'][$delta]['weight']['#attributes']['class'] = "my-elements-weight";
+ * @endcode
+ *
+ * Each row of the table must also have a class of "draggable" in order to enable the
+ * drag handles:
+ * @code
+ * $row = array(...);
+ * $rows[] = array(
+ *   'data' => $row,
+ *   'class' => 'draggable',
+ * );
+ * @endcode
+ *
+ * When tree relationships are present, the two additional classes
+ * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
+ * - Rows with the 'tabledrag-leaf' class cannot have child rows.
+ * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
+ *
+ * Calling drupal_add_tabledrag() would then be written as such:
+ * @code
+ * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight');
+ * @endcode
+ *
+ * In a more complex case where there are several groups in one column (such as
+ * the block regions on the admin/build/block page), a separate subgroup class
+ * must also be added to differentiate the groups.
+ * @code
+ * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
+ * @endcode
+ *
+ * $group is still 'my-element-weight', and the additional $subgroup variable
+ * will be passed in as 'my-elements-weight-'. $region. This also means that
+ * you'll need to call drupal_add_tabledrag() once for every region added.
+ *
+ * @code
+ * foreach ($regions as $region) {
+ *   drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
+ * }
+ * @endcode
+ *
+ * In a situation where tree relationships are present, adding multiple
+ * subgroups is not necessary, because the table will contain indentations that
+ * provide enough information about the sibling and parent relationships.
+ * See theme_menu_overview_form() for an example creating a table containing
+ * parent relationships.
+ *
+ * Please note that this function should be called from the theme layer, such as
+ * in a .tpl.php file, theme_ function, or in a template_preprocess function,
+ * not in a form declartion. Though the same JavaScript could be added to the
+ * page using drupal_add_js() directly, this function helps keep template files
+ * clean and readable. It also prevents tabledrag.js from being added twice
+ * accidentally.
+ *
+ * @param $table_id
+ *   String containing the target table's id attribute. If the table does not
+ *   have an id, one will need to be set, such as <table id="my-module-table">.
+ * @param $action
+ *   String describing the action to be done on the form item. Either 'match'
+ *   'depth', or 'order'. Match is typically used for parent relationships.
+ *   Order is typically used to set weights on other form elements with the same
+ *   group. Depth updates the target element with the current indentation.
+ * @param $relationship
+ *   String describing where the $action variable should be performed. Either
+ *   'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
+ *   up the tree. Sibling will look for fields in the same group in rows above
+ *   and below it. Self affects the dragged row itself. Group affects the
+ *   dragged row, plus any children below it (the entire dragged group).
+ * @param $group
+ *   A class name applied on all related form elements for this action.
+ * @param $subgroup
+ *   (optional) If the group has several subgroups within it, this string should
+ *   contain the class name identifying fields in the same subgroup.
+ * @param $source
+ *   (optional) If the $action is 'match', this string should contain the class
+ *   name identifying what field will be used as the source value when matching
+ *   the value in $subgroup.
+ * @param $hidden
+ *   (optional) The column containing the field elements may be entirely hidden
+ *   from view dynamically when the JavaScript is loaded. Set to FALSE if the
+ *   column should not be hidden.
+ * @param $limit
+ *   (optional) Limit the maximum amount of parenting in this table.
+ * @see block-admin-display-form.tpl.php
+ * @see theme_menu_overview_form()
+ */
+function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
+  static $js_added = FALSE;
+  if (!$js_added) {
+    drupal_add_js('misc/tabledrag.js', 'core');
+    $js_added = TRUE;
+  }
+
+  // If a subgroup or source isn't set, assume it is the same as the group.
+  $target = isset($subgroup) ? $subgroup : $group;
+  $source = isset($source) ? $source : $target;
+  $settings['tableDrag'][$table_id][$group][] = array(
+    'target' => $target,
+    'source' => $source,
+    'relationship' => $relationship,
+    'action' => $action,
+    'hidden' => $hidden,
+    'limit' => $limit,
+  );
+  drupal_add_js($settings, 'setting');
+}
+
+/**
+ * Aggregate JS files, putting them in the files directory.
+ *
+ * @param $files
+ *   An array of JS files to aggregate and compress into one file.
+ * @param $filename
+ *   The name of the aggregate JS file.
+ * @return
+ *   The name of the JS file.
+ */
+function drupal_build_js_cache($files, $filename) {
+  $contents = '';
+
+  // Create the js/ within the files folder.
+  $jspath = file_create_path('js');
+  file_check_directory($jspath, FILE_CREATE_DIRECTORY);
+
+  if (!file_exists($jspath .'/'. $filename)) {
+    // Build aggregate JS file.
+    foreach ($files as $path => $info) {
+      if ($info['preprocess']) {
+        // Append a ';' after each JS file to prevent them from running together.
+        $contents .= file_get_contents($path) .';';
+      }
+    }
+
+    // Create the JS file.
+    file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE);
+  }
+
+  return $jspath .'/'. $filename;
+}
+
+/**
+ * Delete all cached JS files.
+ */
+function drupal_clear_js_cache() {
+  file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+  variable_set('javascript_parsed', array());
+}
+
+/**
+ * Converts a PHP variable into its Javascript equivalent.
+ *
+ * We use HTML-safe strings, i.e. with <, > and & escaped.
+ */
+function drupal_to_js($var) {
+  switch (gettype($var)) {
+    case 'boolean':
+      return $var ? 'true' : 'false'; // Lowercase necessary!
+    case 'integer':
+    case 'double':
+      return $var;
+    case 'resource':
+    case 'string':
+      return '"'. str_replace(array("\r", "\n", "<", ">", "&"),
+                              array('\r', '\n', '\x3c', '\x3e', '\x26'),
+                              addslashes($var)) .'"';
+    case 'array':
+      // Arrays in JSON can't be associative. If the array is empty or if it
+      // has sequential whole number keys starting with 0, it's not associative
+      // so we can go ahead and convert it as an array.
+      if (empty ($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
+        $output = array();
+        foreach ($var as $v) {
+          $output[] = drupal_to_js($v);
+        }
+        return '[ '. implode(', ', $output) .' ]';
+      }
+      // Otherwise, fall through to convert the array as an object.
+    case 'object':
+      $output = array();
+      foreach ($var as $k => $v) {
+        $output[] = drupal_to_js(strval($k)) .': '. drupal_to_js($v);
+      }
+      return '{ '. implode(', ', $output) .' }';
+    default:
+      return 'null';
+  }
+}
+
+/**
+ * Return data in JSON format.
+ *
+ * This function should be used for JavaScript callback functions returning
+ * data in JSON format. It sets the header for JavaScript output.
+ *
+ * @param $var
+ *   (optional) If set, the variable will be converted to JSON and output.
+ */
+function drupal_json($var = NULL) {
+  // We are returning JavaScript, so tell the browser.
+  drupal_set_header('Content-Type: text/javascript; charset=utf-8');
+
+  if (isset($var)) {
+    echo drupal_to_js($var);
+  }
+}
+
+/**
+ * Wrapper around urlencode() which avoids Apache quirks.
+ *
+ * Should be used when placing arbitrary data in an URL. Note that Drupal paths
+ * are urlencoded() when passed through url() and do not require urlencoding()
+ * of individual components.
+ *
+ * Notes:
+ * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
+ *   in Apache where it 404s on any path containing '%2F'.
+ * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
+ *   URLs are used, which are interpreted as delimiters by PHP. These
+ *   characters are double escaped so PHP will still see the encoded version.
+ * - With clean URLs, Apache changes '//' to '/', so every second slash is
+ *   double escaped.
+ *
+ * @param $text
+ *   String to encode
+ */
+function drupal_urlencode($text) {
+  if (variable_get('clean_url', '0')) {
+    return str_replace(array('%2F', '%26', '%23', '//'),
+                       array('/', '%2526', '%2523', '/%252F'),
+                       rawurlencode($text));
+  }
+  else {
+    return str_replace('%2F', '/', rawurlencode($text));
+  }
+}
+
+/**
+ * Ensure the private key variable used to generate tokens is set.
+ *
+ * @return
+ *   The private key.
+ */
+function drupal_get_private_key() {
+  if (!($key = variable_get('drupal_private_key', 0))) {
+    $key = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true));
+    variable_set('drupal_private_key', $key);
+  }
+  return $key;
+}
+
+/**
+ * Generate a token based on $value, the current user session and private key.
+ *
+ * @param $value
+ *   An additional value to base the token on.
+ */
+function drupal_get_token($value = '') {
+  $private_key = drupal_get_private_key();
+  return md5(session_id() . $value . $private_key);
+}
+
+/**
+ * Validate a token based on $value, the current user session and private key.
+ *
+ * @param $token
+ *   The token to be validated.
+ * @param $value
+ *   An additional value to base the token on.
+ * @param $skip_anonymous
+ *   Set to true to skip token validation for anonymous users.
+ * @return
+ *   True for a valid token, false for an invalid token. When $skip_anonymous
+ *   is true, the return value will always be true for anonymous users.
+ */
+function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
+  global $user;
+  return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', ''))));
+}
+
+/**
+ * Performs one or more XML-RPC request(s).
+ *
+ * @param $url
+ *   An absolute URL of the XML-RPC endpoint.
+ *     Example:
+ *     http://www.example.com/xmlrpc.php
+ * @param ...
+ *   For one request:
+ *     The method name followed by a variable number of arguments to the method.
+ *   For multiple requests (system.multicall):
+ *     An array of call arrays. Each call array follows the pattern of the single
+ *     request: method name followed by the arguments to the method.
+ * @return
+ *   For one request:
+ *     Either the return value of the method on success, or FALSE.
+ *     If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg().
+ *   For multiple requests:
+ *     An array of results. Each result will either be the result
+ *     returned by the method called, or an xmlrpc_error object if the call
+ *     failed. See xmlrpc_error().
+ */
+function xmlrpc($url) {
+  require_once './includes/xmlrpc.inc';
+  $args = func_get_args();
+  return call_user_func_array('_xmlrpc', $args);
+}
+
+function _drupal_bootstrap_full() {
+  static $called;
+
+  if ($called) {
+    return;
+  }
+  $called = 1;
+  require_once './includes/theme.inc';
+  require_once './includes/pager.inc';
+  require_once './includes/menu.inc';
+  require_once './includes/tablesort.inc';
+  require_once './includes/file.inc';
+  require_once './includes/unicode.inc';
+  require_once './includes/image.inc';
+  require_once './includes/form.inc';
+  require_once './includes/mail.inc';
+  require_once './includes/actions.inc';
+  // Set the Drupal custom error handler.
+  set_error_handler('drupal_error_handler');
+  // Emit the correct charset HTTP header.
+  drupal_set_header('Content-Type: text/html; charset=utf-8');
+  // Detect string handling method
+  unicode_check();
+  // Undo magic quotes
+  fix_gpc_magic();
+  // Load all enabled modules
+  module_load_all();
+  // Let all modules take action before menu system handles the request
+  // We do not want this while running update.php.
+  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
+    module_invoke_all('init');
+  }
+}
+
+/**
+ * Store the current page in the cache.
+ *
+ * We try to store a gzipped version of the cache. This requires the
+ * PHP zlib extension (http://php.net/manual/en/ref.zlib.php).
+ * Presence of the extension is checked by testing for the function
+ * gzencode. There are two compression algorithms: gzip and deflate.
+ * The majority of all modern browsers support gzip or both of them.
+ * We thus only deal with the gzip variant and unzip the cache in case
+ * the browser does not accept gzip encoding.
+ *
+ * @see drupal_page_header
+ */
+function page_set_cache() {
+  global $user, $base_root;
+
+  if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && page_get_cache(TRUE)) {
+    // This will fail in some cases, see page_get_cache() for the explanation.
+    if ($data = ob_get_contents()) {
+      $cache = TRUE;
+      if (variable_get('page_compression', TRUE) && function_exists('gzencode')) {
+        // We do not store the data in case the zlib mode is deflate.
+        // This should be rarely happening.
+        if (zlib_get_coding_type() == 'deflate') {
+          $cache = FALSE;
+        }
+        else if (zlib_get_coding_type() == FALSE) {
+          $data = gzencode($data, 9, FORCE_GZIP);
+        }
+        // The remaining case is 'gzip' which means the data is
+        // already compressed and nothing left to do but to store it.
+      }
+      ob_end_flush();
+      if ($cache && $data) {
+        cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
+      }
+    }
+  }
+}
+
+/**
+ * Executes a cron run when called
+ * @return
+ * Returns TRUE if ran successfully
+ */
+function drupal_cron_run() {
+  // If not in 'safe mode', increase the maximum execution time:
+  if (!ini_get('safe_mode')) {
+    set_time_limit(240);
+  }
+
+  // Fetch the cron semaphore
+  $semaphore = variable_get('cron_semaphore', FALSE);
+
+  if ($semaphore) {
+    if (time() - $semaphore > 3600) {
+      // Either cron has been running for more than an hour or the semaphore
+      // was not reset due to a database error.
+      watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR);
+
+      // Release cron semaphore
+      variable_del('cron_semaphore');
+    }
+    else {
+      // Cron is still running normally.
+      watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING);
+    }
+  }
+  else {
+    // Register shutdown callback
+    register_shutdown_function('drupal_cron_cleanup');
+
+    // Lock cron semaphore
+    variable_set('cron_semaphore', time());
+
+    // Iterate through the modules calling their cron handlers (if any):
+    module_invoke_all('cron');
+
+    // Record cron time
+    variable_set('cron_last', time());
+    watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
+
+    // Release cron semaphore
+    variable_del('cron_semaphore');
+
+    // Return TRUE so other functions can check if it did run successfully
+    return TRUE;
+  }
+}
+
+/**
+ * Shutdown function for cron cleanup.
+ */
+function drupal_cron_cleanup() {
+  // See if the semaphore is still locked.
+  if (variable_get('cron_semaphore', FALSE)) {
+    watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING);
+
+    // Release cron semaphore
+    variable_del('cron_semaphore');
+  }
+}
+
+/**
+ * Return an array of system file objects.
+ *
+ * Returns an array of file objects of the given type from the site-wide
+ * directory (i.e. modules/), the all-sites directory (i.e.
+ * sites/all/modules/), the profiles directory, and site-specific directory
+ * (i.e. sites/somesite/modules/). The returned array will be keyed using the
+ * key specified (name, basename, filename). Using name or basename will cause
+ * site-specific files to be prioritized over similar files in the default
+ * directories. That is, if a file with the same name appears in both the
+ * site-wide directory and site-specific directory, only the site-specific
+ * version will be included.
+ *
+ * @param $mask
+ *   The regular expression of the files to find.
+ * @param $directory
+ *   The subdirectory name in which the files are found. For example,
+ *   'modules' will search in both modules/ and
+ *   sites/somesite/modules/.
+ * @param $key
+ *   The key to be passed to file_scan_directory().
+ * @param $min_depth
+ *   Minimum depth of directories to return files from.
+ *
+ * @return
+ *   An array of file objects of the specified type.
+ */
+function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
+  global $profile;
+  $config = conf_path();
+
+  // When this function is called during Drupal's initial installation process,
+  // the name of the profile that's about to be installed is stored in the global
+  // $profile variable. At all other times, the standard Drupal systems variable
+  // table contains the name of the current profile, and we can call variable_get()
+  // to determine what one is active.
+  if (!isset($profile)) {
+    $profile = variable_get('install_profile', 'default');
+  }
+  $searchdir = array($directory);
+  $files = array();
+
+  // Always search sites/all/* as well as the global directories
+  $searchdir[] = 'sites/all/'. $directory;
+
+  // The 'profiles' directory contains pristine collections of modules and
+  // themes as organized by a distribution.  It is pristine in the same way
+  // that /modules is pristine for core; users should avoid changing anything
+  // there in favor of sites/all or sites/<domain> directories.
+  if (file_exists("profiles/$profile/$directory")) {
+    $searchdir[] = "profiles/$profile/$directory";
+  }
+
+  if (file_exists("$config/$directory")) {
+    $searchdir[] = "$config/$directory";
+  }
+
+  // Get current list of items
+  foreach ($searchdir as $dir) {
+    $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth));
+  }
+
+  return $files;
+}
+
+
+/**
+ * This dispatch function hands off structured Drupal arrays to type-specific
+ * *_alter implementations. It ensures a consistent interface for all altering
+ * operations.
+ *
+ * @param $type
+ *   The data type of the structured array. 'form', 'links',
+ *   'node_content', and so on are several examples.
+ * @param $data
+ *   The structured array to be altered.
+ * @param ...
+ *   Any additional params will be passed on to the called
+ *   hook_$type_alter functions.
+ */
+function drupal_alter($type, &$data) {
+  // PHP's func_get_args() always returns copies of params, not references, so
+  // drupal_alter() can only manipulate data that comes in via the required first
+  // param. For the edge case functions that must pass in an arbitrary number of
+  // alterable parameters (hook_form_alter() being the best example), an array of
+  // those params can be placed in the __drupal_alter_by_ref key of the $data
+  // array. This is somewhat ugly, but is an unavoidable consequence of a flexible
+  // drupal_alter() function, and the limitations of func_get_args().
+  // @todo: Remove this in Drupal 7.
+  if (is_array($data) && isset($data['__drupal_alter_by_ref'])) {
+    $by_ref_parameters = $data['__drupal_alter_by_ref'];
+    unset($data['__drupal_alter_by_ref']);
+  }
+
+  // Hang onto a reference to the data array so that it isn't blown away later.
+  // Also, merge in any parameters that need to be passed by reference.
+  $args = array(&$data);
+  if (isset($by_ref_parameters)) {
+    $args = array_merge($args, $by_ref_parameters);
+  }
+
+  // Now, use func_get_args() to pull in any additional parameters passed into
+  // the drupal_alter() call.
+  $additional_args = func_get_args();
+  array_shift($additional_args);
+  array_shift($additional_args);
+  $args = array_merge($args, $additional_args);
+
+  foreach (module_implements($type .'_alter') as $module) {
+    $function = $module .'_'. $type .'_alter';
+    call_user_func_array($function, $args);
+  }
+}
+
+
+/**
+ * Renders HTML given a structured array tree.
+ *
+ * Recursively iterates over each of the array elements, generating HTML code.
+ * This function is usually called from within a another function, like
+ * drupal_get_form() or node_view().
+ *
+ * @param $elements
+ *   The structured array describing the data to be rendered.
+ * @return
+ *   The rendered HTML.
+ */
+function drupal_render(&$elements) {
+  if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) {
+    return NULL;
+  }
+
+  // If the default values for this element haven't been loaded yet, populate
+  // them.
+  if (!isset($elements['#defaults_loaded']) || !$elements['#defaults_loaded']) {
+    if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) {
+      $elements += $info;
+    }
+  }
+
+  // Make any final changes to the element before it is rendered. This means
+  // that the $element or the children can be altered or corrected before the
+  // element is rendered into the final text.
+  if (isset($elements['#pre_render'])) {
+    foreach ($elements['#pre_render'] as $function) {
+      if (function_exists($function)) {
+        $elements = $function($elements);
+      }
+    }
+  }
+
+  $content = '';
+  // Either the elements did not go through form_builder or one of the children
+  // has a #weight.
+  if (!isset($elements['#sorted'])) {
+    uasort($elements, "element_sort");
+  }
+  $elements += array('#title' => NULL, '#description' => NULL);
+  if (!isset($elements['#children'])) {
+    $children = element_children($elements);
+    /* Render all the children that use a theme function */
+    if (isset($elements['#theme']) && empty($elements['#theme_used'])) {
+      $elements['#theme_used'] = TRUE;
+
+      $previous = array();
+      foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
+        $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL;
+      }
+      // If we rendered a single element, then we will skip the renderer.
+      if (empty($children)) {
+        $elements['#printed'] = TRUE;
+      }
+      else {
+        $elements['#value'] = '';
+      }
+      $elements['#type'] = 'markup';
+
+      unset($elements['#prefix'], $elements['#suffix']);
+      $content = theme($elements['#theme'], $elements);
+
+      foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
+        $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL;
+      }
+    }
+    /* render each of the children using drupal_render and concatenate them */
+    if (!isset($content) || $content === '') {
+      foreach ($children as $key) {
+        $content .= drupal_render($elements[$key]);
+      }
+    }
+  }
+  if (isset($content) && $content !== '') {
+    $elements['#children'] = $content;
+  }
+
+  // Until now, we rendered the children, here we render the element itself
+  if (!isset($elements['#printed'])) {
+    $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements);
+    $elements['#printed'] = TRUE;
+  }
+
+  if (isset($content) && $content !== '') {
+    // Filter the outputted content and make any last changes before the
+    // content is sent to the browser. The changes are made on $content
+    // which allows the output'ed text to be filtered.
+    if (isset($elements['#post_render'])) {
+      foreach ($elements['#post_render'] as $function) {
+        if (function_exists($function)) {
+          $content = $function($content, $elements);
+        }
+      }
+    }
+    $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
+    $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
+    return $prefix . $content . $suffix;
+  }
+}
+
+/**
+ * Function used by uasort to sort structured arrays by weight.
+ */
+function element_sort($a, $b) {
+  $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
+  $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
+  if ($a_weight == $b_weight) {
+    return 0;
+  }
+  return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+/**
+ * Check if the key is a property.
+ */
+function element_property($key) {
+  return $key[0] == '#';
+}
+
+/**
+ * Get properties of a structured array element. Properties begin with '#'.
+ */
+function element_properties($element) {
+  return array_filter(array_keys((array) $element), 'element_property');
+}
+
+/**
+ * Check if the key is a child.
+ */
+function element_child($key) {
+  return !isset($key[0]) || $key[0] != '#';
+}
+
+/**
+ * Get keys of a structured array tree element that are not properties (i.e., do not begin with '#').
+ */
+function element_children($element) {
+  return array_filter(array_keys((array) $element), 'element_child');
+}
+
+/**
+ * Provide theme registration for themes across .inc files.
+ */
+function drupal_common_theme() {
+  return array(
+    // theme.inc
+    'placeholder' => array(
+      'arguments' => array('text' => NULL)
+    ),
+    'page' => array(
+      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
+      'template' => 'page',
+    ),
+    'maintenance_page' => array(
+      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
+      'template' => 'maintenance-page',
+    ),
+    'update_page' => array(
+      'arguments' => array('content' => NULL, 'show_messages' => TRUE),
+    ),
+    'install_page' => array(
+      'arguments' => array('content' => NULL),
+    ),
+    'task_list' => array(
+      'arguments' => array('items' => NULL, 'active' => NULL),
+    ),
+    'status_messages' => array(
+      'arguments' => array('display' => NULL),
+    ),
+    'links' => array(
+      'arguments' => array('links' => NULL, 'attributes' => array('class' => 'links')),
+    ),
+    'image' => array(
+      'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE),
+    ),
+    'breadcrumb' => array(
+      'arguments' => array('breadcrumb' => NULL),
+    ),
+    'help' => array(
+      'arguments' => array(),
+    ),
+    'submenu' => array(
+      'arguments' => array('links' => NULL),
+    ),
+    'table' => array(
+      'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL),
+    ),
+    'table_select_header_cell' => array(
+      'arguments' => array(),
+    ),
+    'tablesort_indicator' => array(
+      'arguments' => array('style' => NULL),
+    ),
+    'box' => array(
+      'arguments' => array('title' => NULL, 'content' => NULL, 'region' => 'main'),
+      'template' => 'box',
+    ),
+    'block' => array(
+      'arguments' => array('block' => NULL),
+      'template' => 'block',
+    ),
+    'mark' => array(
+      'arguments' => array('type' => MARK_NEW),
+    ),
+    'item_list' => array(
+      'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL),
+    ),
+    'more_help_link' => array(
+      'arguments' => array('url' => NULL),
+    ),
+    'xml_icon' => array(
+      'arguments' => array('url' => NULL),
+    ),
+    'feed_icon' => array(
+      'arguments' => array('url' => NULL, 'title' => NULL),
+    ),
+    'more_link' => array(
+      'arguments' => array('url' => NULL, 'title' => NULL)
+    ),
+    'closure' => array(
+      'arguments' => array('main' => 0),
+    ),
+    'blocks' => array(
+      'arguments' => array('region' => NULL),
+    ),
+    'username' => array(
+      'arguments' => array('object' => NULL),
+    ),
+    'progress_bar' => array(
+      'arguments' => array('percent' => NULL, 'message' => NULL),
+    ),
+    'indentation' => array(
+      'arguments' => array('size' => 1),
+    ),
+    // from pager.inc
+    'pager' => array(
+      'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
+    ),
+    'pager_first' => array(
+      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()),
+    ),
+    'pager_previous' => array(
+      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
+    ),
+    'pager_next' => array(
+      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
+    ),
+    'pager_last' => array(
+      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()),
+    ),
+    'pager_link' => array(
+      'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()),
+    ),
+    // from locale.inc
+    'locale_admin_manage_screen' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    // from menu.inc
+    'menu_item_link' => array(
+      'arguments' => array('item' => NULL),
+    ),
+    'menu_tree' => array(
+      'arguments' => array('tree' => NULL),
+    ),
+    'menu_item' => array(
+      'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''),
+    ),
+    'menu_local_task' => array(
+      'arguments' => array('link' => NULL, 'active' => FALSE),
+    ),
+    'menu_local_tasks' => array(
+      'arguments' => array(),
+    ),
+    // from form.inc
+    'select' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'fieldset' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'radio' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'radios' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'password_confirm' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'date' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'item' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'checkbox' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'checkboxes' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'submit' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'button' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'image_button' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'hidden' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'token' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'textfield' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'form' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'textarea' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'markup' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'password' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'file' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'form_element' => array(
+      'arguments' => array('element' => NULL, 'value' => NULL),
+    ),
+  );
+}
+
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * Get the schema definition of a table, or the whole database schema.
+ *
+ * The returned schema will include any modifications made by any
+ * module that implements hook_schema_alter().
+ *
+ * @param $table
+ *   The name of the table. If not given, the schema of all tables is returned.
+ * @param $rebuild
+ *   If true, the schema will be rebuilt instead of retrieved from the cache.
+ */
+function drupal_get_schema($table = NULL, $rebuild = FALSE) {
+  static $schema = array();
+
+  if (empty($schema) || $rebuild) {
+    // Try to load the schema from cache.
+    if (!$rebuild && $cached = cache_get('schema')) {
+      $schema = $cached->data;
+    }
+    // Otherwise, rebuild the schema cache.
+    else {
+      $schema = array();
+      // Load the .install files to get hook_schema.
+      module_load_all_includes('install');
+
+      // Invoke hook_schema for all modules.
+      foreach (module_implements('schema') as $module) {
+        $current = module_invoke($module, 'schema');
+        _drupal_initialize_schema($module, $current);
+        $schema = array_merge($schema, $current);
+      }
+
+      drupal_alter('schema', $schema);
+      cache_set('schema', $schema);
+    }
+  }
+
+  if (!isset($table)) {
+    return $schema;
+  }
+  elseif (isset($schema[$table])) {
+    return $schema[$table];
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Create all tables that a module defines in its hook_schema().
+ *
+ * Note: This function does not pass the module's schema through
+ * hook_schema_alter(). The module's tables will be created exactly as the
+ * module defines them.
+ *
+ * @param $module
+ *   The module for which the tables will be created.
+ * @return
+ *   An array of arrays with the following key/value pairs:
+ *      success: a boolean indicating whether the query succeeded
+ *      query: the SQL query(s) executed, passed through check_plain()
+ */
+function drupal_install_schema($module) {
+  $schema = drupal_get_schema_unprocessed($module);
+  _drupal_initialize_schema($module, $schema);
+
+  $ret = array();
+  foreach ($schema as $name => $table) {
+    db_create_table($ret, $name, $table);
+  }
+  return $ret;
+}
+
+/**
+ * Remove all tables that a module defines in its hook_schema().
+ *
+ * Note: This function does not pass the module's schema through
+ * hook_schema_alter(). The module's tables will be created exactly as the
+ * module defines them.
+ *
+ * @param $module
+ *   The module for which the tables will be removed.
+ * @return
+ *   An array of arrays with the following key/value pairs:
+ *      success: a boolean indicating whether the query succeeded
+ *      query: the SQL query(s) executed, passed through check_plain()
+ */
+function drupal_uninstall_schema($module) {
+  $schema = drupal_get_schema_unprocessed($module);
+  _drupal_initialize_schema($module, $schema);
+
+  $ret = array();
+  foreach ($schema as $table) {
+    db_drop_table($ret, $table['name']);
+  }
+  return $ret;
+}
+
+/**
+ * Returns the unprocessed and unaltered version of a module's schema.
+ *
+ * Use this function only if you explicitly need the original
+ * specification of a schema, as it was defined in a module's
+ * hook_schema(). No additional default values will be set,
+ * hook_schema_alter() is not invoked and these unprocessed
+ * definitions won't be cached.
+ *
+ * This function can be used to retrieve a schema specification in
+ * hook_schema(), so it allows you to derive your tables from existing
+ * specifications.
+ *
+ * It is also used by drupal_install_schema() and
+ * drupal_uninstall_schema() to ensure that a module's tables are
+ * created exactly as specified without any changes introduced by a
+ * module that implements hook_schema_alter().
+ *
+ * @param $module
+ *   The module to which the table belongs.
+ * @param $table
+ *   The name of the table. If not given, the module's complete schema
+ *   is returned.
+ */
+function drupal_get_schema_unprocessed($module, $table = NULL) {
+  // Load the .install file to get hook_schema.
+  module_load_include('install', $module);
+  $schema = module_invoke($module, 'schema');
+
+  if (!is_null($table) && isset($schema[$table])) {
+    return $schema[$table];
+  }
+  else {
+    return $schema;
+  }
+}
+
+/**
+ * Fill in required default values for table definitions returned by hook_schema().
+ *
+ * @param $module
+ *   The module for which hook_schema() was invoked.
+ * @param $schema
+ *   The schema definition array as it was returned by the module's
+ *   hook_schema().
+ */
+function _drupal_initialize_schema($module, &$schema) {
+  // Set the name and module key for all tables.
+  foreach ($schema as $name => $table) {
+    if (empty($table['module'])) {
+      $schema[$name]['module'] = $module;
+    }
+    if (!isset($table['name'])) {
+      $schema[$name]['name'] = $name;
+    }
+  }
+}
+
+/**
+ * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query.
+ *
+ * @param $table
+ *   The name of the table from which to retrieve fields.
+ * @param
+ *   An optional prefix to to all fields.
+ *
+ * @return An array of fields.
+ **/
+function drupal_schema_fields_sql($table, $prefix = NULL) {
+  $schema = drupal_get_schema($table);
+  $fields = array_keys($schema['fields']);
+  if ($prefix) {
+    $columns = array();
+    foreach ($fields as $field) {
+      $columns[] = "$prefix.$field";
+    }
+    return $columns;
+  }
+  else {
+    return $fields;
+  }
+}
+
+/**
+ * Save a record to the database based upon the schema.
+ *
+ * Default values are filled in for missing items, and 'serial' (auto increment)
+ * types are filled in with IDs.
+ *
+ * @param $table
+ *   The name of the table; this must exist in schema API.
+ * @param $object
+ *   The object to write. This is a reference, as defaults according to
+ *   the schema may be filled in on the object, as well as ID on the serial
+ *   type(s). Both array an object types may be passed.
+ * @param $update
+ *   If this is an update, specify the primary keys' field names. It is the
+ *   caller's responsibility to know if a record for this object already
+ *   exists in the database. If there is only 1 key, you may pass a simple string.
+ * @return
+ *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
+ *   SAVED_UPDATED is returned depending on the operation performed. The
+ *   $object parameter contains values for any serial fields defined by
+ *   the $table. For example, $object->nid will be populated after inserting
+ *   a new node.
+ */
+function drupal_write_record($table, &$object, $update = array()) {
+  // Standardize $update to an array.
+  if (is_string($update)) {
+    $update = array($update);
+  }
+
+  // Convert to an object if needed.
+  if (is_array($object)) {
+    $object = (object) $object;
+    $array = TRUE;
+  }
+  else {
+    $array = FALSE;
+  }
+
+  $schema = drupal_get_schema($table);
+  if (empty($schema)) {
+    return FALSE;
+  }
+
+  $fields = $defs = $values = $serials = $placeholders = array();
+
+  // Go through our schema, build SQL, and when inserting, fill in defaults for
+  // fields that are not set.
+  foreach ($schema['fields'] as $field => $info) {
+    // Special case -- skip serial types if we are updating.
+    if ($info['type'] == 'serial' && count($update)) {
+      continue;
+    }
+
+    // For inserts, populate defaults from Schema if not already provided
+    if (!isset($object->$field) && !count($update) && isset($info['default'])) {
+      $object->$field = $info['default'];
+    }
+
+    // Track serial fields so we can helpfully populate them after the query.
+    if ($info['type'] == 'serial') {
+      $serials[] = $field;
+      // Ignore values for serials when inserting data. Unsupported.
+      unset($object->$field);
+    }
+
+    // Build arrays for the fields, placeholders, and values in our query.
+    if (isset($object->$field)) {
+      $fields[] = $field;
+      $placeholders[] = db_type_placeholder($info['type']);
+
+      if (empty($info['serialize'])) {
+        $values[] = $object->$field;
+      }
+      else {
+        $values[] = serialize($object->$field);
+      }
+    }
+  }
+
+  // Build the SQL.
+  $query = '';
+  if (!count($update)) {
+    $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')';
+    $return = SAVED_NEW;
+  }
+  else {
+    $query = '';
+    foreach ($fields as $id => $field) {
+      if ($query) {
+        $query .= ', ';
+      }
+      $query .= $field .' = '. $placeholders[$id];
+    }
+
+    foreach ($update as $key){
+      $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']);
+      $values[] = $object->$key;
+    }
+
+    $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions);
+    $return = SAVED_UPDATED;
+  }
+
+  // Execute the SQL.
+  if (db_query($query, $values)) {
+    if ($serials) {
+      // Get last insert ids and fill them in.
+      foreach ($serials as $field) {
+        $object->$field = db_last_insert_id($table, $field);
+      }
+    }
+
+    // If we began with an array, convert back so we don't surprise the caller.
+    if ($array) {
+      $object = (array) $object;
+    }
+
+    return $return;
+  }
+
+  return FALSE;
+}
+
+/**
+ * @} End of "ingroup schemaapi".
+ */
+
+/**
+ * Parse Drupal info file format.
+ *
+ * Files should use an ini-like format to specify values.
+ * White-space generally doesn't matter, except inside values.
+ * e.g.
+ *
+ * @verbatim
+ *   key = value
+ *   key = "value"
+ *   key = 'value'
+ *   key = "multi-line
+ *
+ *   value"
+ *   key = 'multi-line
+ *
+ *   value'
+ *   key
+ *   =
+ *   'value'
+ * @endverbatim
+ *
+ * Arrays are created using a GET-like syntax:
+ *
+ * @verbatim
+ *   key[] = "numeric array"
+ *   key[index] = "associative array"
+ *   key[index][] = "nested numeric array"
+ *   key[index][index] = "nested associative array"
+ * @endverbatim
+ *
+ * PHP constants are substituted in, but only when used as the entire value:
+ *
+ * Comments should start with a semi-colon at the beginning of a line.
+ *
+ * This function is NOT for placing arbitrary module-specific settings. Use
+ * variable_get() and variable_set() for that.
+ *
+ * Information stored in the module.info file:
+ * - name: The real name of the module for display purposes.
+ * - description: A brief description of the module.
+ * - dependencies: An array of shortnames of other modules this module depends on.
+ * - package: The name of the package of modules this module belongs to.
+ *
+ * Example of .info file:
+ * @verbatim
+ *   name = Forum
+ *   description = Enables threaded discussions about general topics.
+ *   dependencies[] = taxonomy
+ *   dependencies[] = comment
+ *   package = Core - optional
+ *   version = VERSION
+ * @endverbatim
+ *
+ * @param $filename
+ *   The file we are parsing. Accepts file with relative or absolute path.
+ * @return
+ *   The info array.
+ */
+function drupal_parse_info_file($filename) {
+  $info = array();
+
+  if (!file_exists($filename)) {
+    return $info;
+  }
+
+  $data = file_get_contents($filename);
+  if (preg_match_all('
+    @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
+    ((?:
+      [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
+      \[[^\[\]]*\]                  # unless they are balanced and not nested
+    )+?)
+    \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
+    (?:
+      ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
+      (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
+      ([^\r\n]*?)                   # Non-quoted string
+    )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
+    @msx', $data, $matches, PREG_SET_ORDER)) {
+    foreach ($matches as $match) {
+      // Fetch the key and value string
+      $i = 0;
+      foreach (array('key', 'value1', 'value2', 'value3') as $var) {
+        $$var = isset($match[++$i]) ? $match[$i] : '';
+      }
+      $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
+
+      // Parse array syntax
+      $keys = preg_split('/\]?\[/', rtrim($key, ']'));
+      $last = array_pop($keys);
+      $parent = &$info;
+
+      // Create nested arrays
+      foreach ($keys as $key) {
+        if ($key == '') {
+          $key = count($parent);
+        }
+        if (!isset($parent[$key]) || !is_array($parent[$key])) {
+          $parent[$key] = array();
+        }
+        $parent = &$parent[$key];
+      }
+
+      // Handle PHP constants
+      if (defined($value)) {
+        $value = constant($value);
+      }
+
+      // Insert actual value
+      if ($last == '') {
+        $last = count($parent);
+      }
+      $parent[$last] = $value;
+    }
+  }
+
+  return $info;
+}
+
+/**
+ * @return
+ *   Array of the possible severity levels for log messages.
+ *
+ * @see watchdog
+ */
+function watchdog_severity_levels() {
+  return array(
+    WATCHDOG_EMERG    => t('emergency'),
+    WATCHDOG_ALERT    => t('alert'),
+    WATCHDOG_CRITICAL => t('critical'),
+    WATCHDOG_ERROR    => t('error'),
+    WATCHDOG_WARNING  => t('warning'),
+    WATCHDOG_NOTICE   => t('notice'),
+    WATCHDOG_INFO     => t('info'),
+    WATCHDOG_DEBUG    => t('debug'),
+  );
+}
+
+
+/**
+ * Explode a string of given tags into an array.
+ */
+function drupal_explode_tags($tags) {
+  // This regexp allows the following types of user input:
+  // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
+  $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
+  preg_match_all($regexp, $tags, $matches);
+  $typed_tags = array_unique($matches[1]);
+
+  $tags = array();
+  foreach ($typed_tags as $tag) {
+    // If a user has escaped a term (to demonstrate that it is a group,
+    // or includes a comma or quote character), we remove the escape
+    // formatting so to save the term into the database as the user intends.
+    $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
+    if ($tag != "") {
+      $tags[] = $tag;
+    }
+  }
+
+  return $tags;
+}
+
+/**
+ * Implode an array of tags into a string.
+ */
+function drupal_implode_tags($tags) {
+  $encoded_tags = array();
+  foreach ($tags as $tag) {
+    // Commas and quotes in tag names are special cases, so encode them.
+    if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
+      $tag = '"'. str_replace('"', '""', $tag) .'"';
+    }
+
+    $encoded_tags[] = $tag;
+  }
+  return implode(', ', $encoded_tags);
+}
+
+/**
+ * Flush all cached data on the site.
+ *
+ * Empties cache tables, rebuilds the menu cache and theme registries, and
+ * exposes a hook for other modules to clear their own cache data as well.
+ */
+function drupal_flush_all_caches() {
+  // Change query-strings on css/js files to enforce reload for all users.
+  _drupal_flush_css_js();
+
+  drupal_clear_css_cache();
+  drupal_clear_js_cache();
+  drupal_rebuild_theme_registry();
+  menu_rebuild();
+  node_types_rebuild();
+  // Don't clear cache_form - in-progress form submissions may break.
+  // Ordered so clearing the page cache will always be the last action.
+  $core = array('cache', 'cache_block', 'cache_filter', 'cache_page');
+  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
+  foreach ($cache_tables as $table) {
+    cache_clear_all('*', $table, TRUE);
+  }
+}
+
+/**
+ * Helper function to change query-strings on css/js files.
+ *
+ * Changes the character added to all css/js files as dummy query-string,
+ * so that all browsers are forced to reload fresh files. We keep
+ * 20 characters history (FIFO) to avoid repeats, but only the first
+ * (newest) character is actually used on urls, to keep them short.
+ * This is also called from update.php.
+ */
+function _drupal_flush_css_js() {
+  $string_history = variable_get('css_js_query_string', '00000000000000000000');
+  $new_character = $string_history[0];
+  $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+  while (strpos($string_history, $new_character) !== FALSE) {
+    $new_character = $characters[mt_rand(0, strlen($characters) - 1)];
+  }
+  variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/database.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,583 @@
+<?php
+// $Id: database.inc,v 1.92.2.1 2008/02/08 22:44:59 goba Exp $
+
+/**
+ * @file
+ * Wrapper for database interface code.
+ */
+
+/**
+ * A hash value to check when outputting database errors, md5('DB_ERROR').
+ *
+ * @see drupal_error_handler()
+ */
+define('DB_ERROR', 'a515ac9c2796ca0e23adbe92c68fc9fc');
+
+/**
+ * @defgroup database Database abstraction layer
+ * @{
+ * Allow the use of different database servers using the same code base.
+ *
+ * Drupal provides a slim database abstraction layer to provide developers with
+ * the ability to support multiple database servers easily. The intent of this
+ * layer is to preserve the syntax and power of SQL as much as possible, while
+ * letting Drupal control the pieces of queries that need to be written
+ * differently for different servers and provide basic security checks.
+ *
+ * Most Drupal database queries are performed by a call to db_query() or
+ * db_query_range(). Module authors should also consider using pager_query() for
+ * queries that return results that need to be presented on multiple pages, and
+ * tablesort_sql() for generating appropriate queries for sortable tables.
+ *
+ * For example, one might wish to return a list of the most recent 10 nodes
+ * authored by a given user. Instead of directly issuing the SQL query
+ * @code
+ *   SELECT n.title, n.body, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10;
+ * @endcode
+ * one would instead call the Drupal functions:
+ * @code
+ *   $result = db_query_range('SELECT n.title, n.body, n.created
+ *     FROM {node} n WHERE n.uid = %d', $uid, 0, 10);
+ *   while ($node = db_fetch_object($result)) {
+ *     // Perform operations on $node->body, etc. here.
+ *   }
+ * @endcode
+ * Curly braces are used around "node" to provide table prefixing via
+ * db_prefix_tables(). The explicit use of a user ID is pulled out into an
+ * argument passed to db_query() so that SQL injection attacks from user input
+ * can be caught and nullified. The LIMIT syntax varies between database servers,
+ * so that is abstracted into db_query_range() arguments. Finally, note the
+ * common pattern of iterating over the result set using db_fetch_object().
+ */
+
+/**
+ * Perform an SQL query and return success or failure.
+ *
+ * @param $sql
+ *   A string containing a complete SQL query.  %-substitution
+ *   parameters are not supported.
+ * @return
+ *   An array containing the keys:
+ *      success: a boolean indicating whether the query succeeded
+ *      query: the SQL query executed, passed through check_plain()
+ */
+function update_sql($sql) {
+  $result = db_query($sql, true);
+  return array('success' => $result !== FALSE, 'query' => check_plain($sql));
+}
+
+/**
+ * Append a database prefix to all tables in a query.
+ *
+ * Queries sent to Drupal should wrap all table names in curly brackets. This
+ * function searches for this syntax and adds Drupal's table prefix to all
+ * tables, allowing Drupal to coexist with other systems in the same database if
+ * necessary.
+ *
+ * @param $sql
+ *   A string containing a partial or entire SQL query.
+ * @return
+ *   The properly-prefixed string.
+ */
+function db_prefix_tables($sql) {
+  global $db_prefix;
+
+  if (is_array($db_prefix)) {
+    if (array_key_exists('default', $db_prefix)) {
+      $tmp = $db_prefix;
+      unset($tmp['default']);
+      foreach ($tmp as $key => $val) {
+        $sql = strtr($sql, array('{'. $key .'}' => $val . $key));
+      }
+      return strtr($sql, array('{' => $db_prefix['default'], '}' => ''));
+    }
+    else {
+      foreach ($db_prefix as $key => $val) {
+        $sql = strtr($sql, array('{'. $key .'}' => $val . $key));
+      }
+      return strtr($sql, array('{' => '', '}' => ''));
+    }
+  }
+  else {
+    return strtr($sql, array('{' => $db_prefix, '}' => ''));
+  }
+}
+
+/**
+ * Activate a database for future queries.
+ *
+ * If it is necessary to use external databases in a project, this function can
+ * be used to change where database queries are sent. If the database has not
+ * yet been used, it is initialized using the URL specified for that name in
+ * Drupal's configuration file. If this name is not defined, a duplicate of the
+ * default connection is made instead.
+ *
+ * Be sure to change the connection back to the default when done with custom
+ * code.
+ *
+ * @param $name
+ *   The name assigned to the newly active database connection. If omitted, the
+ *   default connection will be made active.
+ *
+ * @return the name of the previously active database or FALSE if non was found.
+ */
+function db_set_active($name = 'default') {
+  global $db_url, $db_type, $active_db;
+  static $db_conns, $active_name = FALSE;
+
+  if (empty($db_url)) {
+    include_once 'includes/install.inc';
+    install_goto('install.php');
+  }
+
+  if (!isset($db_conns[$name])) {
+    // Initiate a new connection, using the named DB URL specified.
+    if (is_array($db_url)) {
+      $connect_url = array_key_exists($name, $db_url) ? $db_url[$name] : $db_url['default'];
+    }
+    else {
+      $connect_url = $db_url;
+    }
+
+    $db_type = substr($connect_url, 0, strpos($connect_url, '://'));
+    $handler = "./includes/database.$db_type.inc";
+
+    if (is_file($handler)) {
+      include_once $handler;
+    }
+    else {
+      _db_error_page("The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
+    }
+
+    $db_conns[$name] = db_connect($connect_url);
+  }
+
+  $previous_name = $active_name;
+  // Set the active connection.
+  $active_name = $name;
+  $active_db = $db_conns[$name];
+
+  return $previous_name;
+}
+
+/**
+ * Helper function to show fatal database errors.
+ *
+ * Prints a themed maintenance page with the 'Site off-line' text,
+ * adding the provided error message in the case of 'display_errors'
+ * set to on. Ends the page request; no return.
+ *
+ * @param $error
+ *   The error message to be appended if 'display_errors' is on.
+ */
+function _db_error_page($error = '') {
+  global $db_type;
+  drupal_maintenance_theme();
+  drupal_set_header('HTTP/1.1 503 Service Unavailable');
+  drupal_set_title('Site off-line');
+
+  $message = '<p>The site is currently not available due to technical problems. Please try again later. Thank you for your understanding.</p>';
+  $message .= '<hr /><p><small>If you are the maintainer of this site, please check your database settings in the <code>settings.php</code> file and ensure that your hosting provider\'s database server is running. For more help, see the <a href="http://drupal.org/node/258">handbook</a>, or contact your hosting provider.</small></p>';
+
+  if ($error && ini_get('display_errors')) {
+    $message .= '<p><small>The '. theme('placeholder', $db_type) .' error was: '. theme('placeholder', $error) .'.</small></p>';
+  }
+
+  print theme('maintenance_page', $message);
+  exit;
+}
+
+/**
+ * Returns a boolean depending on the availability of the database.
+ */
+function db_is_active() {
+  global $active_db;
+  return !empty($active_db);
+}
+
+/**
+ * Helper function for db_query().
+ */
+function _db_query_callback($match, $init = FALSE) {
+  static $args = NULL;
+  if ($init) {
+    $args = $match;
+    return;
+  }
+
+  switch ($match[1]) {
+    case '%d': // We must use type casting to int to convert FALSE/NULL/(TRUE?)
+      return (int) array_shift($args); // We don't need db_escape_string as numbers are db-safe
+    case '%s':
+      return db_escape_string(array_shift($args));
+    case '%%':
+      return '%';
+    case '%f':
+      return (float) array_shift($args);
+    case '%b': // binary data
+      return db_encode_blob(array_shift($args));
+  }
+}
+
+/**
+ * Generate placeholders for an array of query arguments of a single type.
+ *
+ * Given a Schema API field type, return correct %-placeholders to
+ * embed in a query
+ *
+ * @param $arguments
+ *  An array with at least one element.
+ * @param $type
+ *   The Schema API type of a field (e.g. 'int', 'text', or 'varchar').
+ */
+function db_placeholders($arguments, $type = 'int') {
+  $placeholder = db_type_placeholder($type);
+  return implode(',', array_fill(0, count($arguments), $placeholder));
+}
+
+/**
+ * Indicates the place holders that should be replaced in _db_query_callback().
+ */
+define('DB_QUERY_REGEXP', '/(%d|%s|%%|%f|%b)/');
+
+/**
+ * Helper function for db_rewrite_sql.
+ *
+ * Collects JOIN and WHERE statements via hook_db_rewrite_sql()
+ * Decides whether to select primary_key or DISTINCT(primary_key)
+ *
+ * @param $query
+ *   Query to be rewritten.
+ * @param $primary_table
+ *   Name or alias of the table which has the primary key field for this query.
+ *   Typical table names would be: {blocks}, {comments}, {forum}, {node},
+ *   {menu}, {term_data} or {vocabulary}. However, in most cases the usual
+ *   table alias (b, c, f, n, m, t or v) is used instead of the table name.
+ * @param $primary_field
+ *   Name of the primary field.
+ * @param $args
+ *   Array of additional arguments.
+ * @return
+ *   An array: join statements, where statements, field or DISTINCT(field).
+ */
+function _db_rewrite_sql($query = '', $primary_table = 'n', $primary_field = 'nid', $args = array()) {
+  $where = array();
+  $join = array();
+  $distinct = FALSE;
+  foreach (module_implements('db_rewrite_sql') as $module) {
+    $result = module_invoke($module, 'db_rewrite_sql', $query, $primary_table, $primary_field, $args);
+    if (isset($result) && is_array($result)) {
+      if (isset($result['where'])) {
+        $where[] = $result['where'];
+      }
+      if (isset($result['join'])) {
+        $join[] = $result['join'];
+      }
+      if (isset($result['distinct']) && $result['distinct']) {
+        $distinct = TRUE;
+      }
+    }
+    elseif (isset($result)) {
+      $where[] = $result;
+    }
+  }
+
+  $where = empty($where) ? '' : '('. implode(') AND (', $where) .')';
+  $join = empty($join) ? '' : implode(' ', $join);
+
+  return array($join, $where, $distinct);
+}
+
+/**
+ * Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not
+ * use FROM table1, table2 syntax, use JOIN instead.
+ *
+ * @param $query
+ *   Query to be rewritten.
+ * @param $primary_table
+ *   Name or alias of the table which has the primary key field for this query.
+ *   Typical table names would be: {blocks}, {comments}, {forum}, {node},
+ *   {menu}, {term_data} or {vocabulary}. However, it is more common to use the
+ *   the usual table aliases: b, c, f, n, m, t or v.
+ * @param $primary_field
+ *   Name of the primary field.
+ * @param $args
+ *   An array of arguments, passed to the implementations of hook_db_rewrite_sql.
+ * @return
+ *   The original query with JOIN and WHERE statements inserted from
+ *   hook_db_rewrite_sql implementations. nid is rewritten if needed.
+ */
+function db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid',  $args = array()) {
+  list($join, $where, $distinct) = _db_rewrite_sql($query, $primary_table, $primary_field, $args);
+
+  if ($distinct) {
+    $query = db_distinct_field($primary_table, $primary_field, $query);
+  }
+
+  if (!empty($where) || !empty($join)) {
+    $pattern = '{
+      # Beginning of the string
+      ^
+      ((?P<anonymous_view>
+        # Everything within this set of parentheses is named "anonymous view"
+        (?:
+          [^()]++                   # anything not parentheses
+        |
+          \( (?P>anonymous_view) \)          # an open parenthesis, more "anonymous view" and finally a close parenthesis.
+        )*
+      )[^()]+WHERE)
+    }x';
+    preg_match($pattern, $query, $matches);
+    if (!$where) {
+      $where = '1 = 1';
+    }
+    if ($matches) {
+      $n = strlen($matches[1]);
+      $second_part = substr($query, $n);
+      $first_part = substr($matches[1], 0, $n - 5) ." $join WHERE $where AND ( ";
+      // PHP 4 does not support strrpos for strings. We emulate it.
+      $haystack_reverse = strrev($second_part);
+    }
+    else {
+      $haystack_reverse = strrev($query);
+    }
+    // No need to use strrev on the needle, we supply GROUP, ORDER, LIMIT
+    // reversed.
+    foreach (array('PUORG', 'REDRO', 'TIMIL') as $needle_reverse) {
+      $pos = strpos($haystack_reverse, $needle_reverse);
+      if ($pos !== FALSE) {
+        // All needles are five characters long.
+        $pos += 5;
+        break;
+      }
+    }
+    if ($matches) {
+      if ($pos === FALSE) {
+        $query = $first_part . $second_part .')';
+      }
+      else {
+        $query = $first_part . substr($second_part, 0, -$pos) .')'. substr($second_part, -$pos);
+      }
+    }
+    elseif ($pos === FALSE) {
+      $query .= " $join WHERE $where";
+    }
+    else {
+      $query = substr($query, 0, -$pos) . " $join WHERE $where " . substr($query, -$pos);
+    }
+  }
+
+  return $query;
+}
+
+/**
+ * Restrict a dynamic table, column or constraint name to safe characters.
+ *
+ * Only keeps alphanumeric and underscores.
+ */
+function db_escape_table($string) {
+  return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
+}
+
+/**
+ * @} End of "defgroup database".
+ */
+
+/**
+ * @defgroup schemaapi Schema API
+ * @{
+ *
+ * A Drupal schema definition is an array structure representing one or
+ * more tables and their related keys and indexes. A schema is defined by
+ * hook_schema(), which usually lives in a modulename.install file.
+ *
+ * By implementing hook_schema() and specifying the tables your module
+ * declares, you can easily create and drop these tables on all
+ * supported database engines. You don't have to deal with the
+ * different SQL dialects for table creation and alteration of the
+ * supported database engines.
+ *
+ * hook_schema() should return an array with a key for each table that
+ * the module defines.
+ *
+ * The following keys are defined:
+ *
+ *   - 'description': A string describing this table and its purpose.
+ *     References to other tables should be enclosed in
+ *     curly-brackets.  For example, the node_revisions table
+ *     description field might contain "Stores per-revision title and
+ *     body data for each {node}."
+ *   - 'fields': An associative array ('fieldname' => specification)
+ *     that describes the table's database columns.  The specification
+ *     is also an array.  The following specification parameters are defined:
+ *
+ *     - 'description': A string describing this field and its purpose.
+ *       References to other tables should be enclosed in
+ *       curly-brackets.  For example, the node table vid field
+ *       description might contain "Always holds the largest (most
+ *       recent) {node_revisions}.vid value for this nid."
+ *     - 'type': The generic datatype: 'varchar', 'int', 'serial'
+ *       'float', 'numeric', 'text', 'blob' or 'datetime'.  Most types
+ *       just map to the according database engine specific
+ *       datatypes.  Use 'serial' for auto incrementing fields. This
+ *       will expand to 'int auto_increment' on mysql.
+ *     - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
+ *       'big'.  This is a hint about the largest value the field will
+ *       store and determines which of the database engine specific
+ *       datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
+ *       'normal', the default, selects the base type (e.g. on MySQL,
+ *       INT, VARCHAR, BLOB, etc.).
+ *
+ *       Not all sizes are available for all data types. See
+ *       db_type_map() for possible combinations.
+ *     - 'not null': If true, no NULL values will be allowed in this
+ *       database column.  Defaults to false.
+ *     - 'default': The field's default value.  The PHP type of the
+ *       value matters: '', '0', and 0 are all different.  If you
+ *       specify '0' as the default value for a type 'int' field it
+ *       will not work because '0' is a string containing the
+ *       character "zero", not an integer.
+ *     - 'length': The maximal length of a type 'varchar' or 'text'
+ *       field.  Ignored for other field types.
+ *     - 'unsigned': A boolean indicating whether a type 'int', 'float'
+ *       and 'numeric' only is signed or unsigned.  Defaults to
+ *       FALSE.  Ignored for other field types.
+ *     - 'precision', 'scale': For type 'numeric' fields, indicates
+ *       the precision (total number of significant digits) and scale
+ *       (decimal digits right of the decimal point).  Both values are
+ *       mandatory.  Ignored for other field types.
+ *
+ *     All parameters apart from 'type' are optional except that type
+ *     'numeric' columns must specify 'precision' and 'scale'.
+ *
+ *  - 'primary key': An array of one or more key column specifiers (see below)
+ *    that form the primary key.
+ *  - 'unique key': An associative array of unique keys ('keyname' =>
+ *    specification).  Each specification is an array of one or more
+ *    key column specifiers (see below) that form a unique key on the table.
+ *  - 'indexes':  An associative array of indexes ('indexame' =>
+ *    specification).  Each specification is an array of one or more
+ *    key column specifiers (see below) that form an index on the
+ *    table.
+ *
+ * A key column specifier is either a string naming a column or an
+ * array of two elements, column name and length, specifying a prefix
+ * of the named column.
+ *
+ * As an example, here is a SUBSET of the schema definition for
+ * Drupal's 'node' table.  It show four fields (nid, vid, type, and
+ * title), the primary key on field 'nid', a unique key named 'vid' on
+ * field 'vid', and two indexes, one named 'nid' on field 'nid' and
+ * one named 'node_title_type' on the field 'title' and the first four
+ * bytes of the field 'type':
+ *
+ * @code
+ * $schema['node'] = array(
+ *   'fields' => array(
+ *     'nid'      => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+ *     'vid'      => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+ *     'type'     => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+ *     'title'    => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+ *   ),
+ *   'primary key' => array('nid'),
+ *   'unique keys' => array(
+ *     'vid'     => array('vid')
+ *   ),
+ *   'indexes' => array(
+ *     'nid'                 => array('nid'),
+ *     'node_title_type'     => array('title', array('type', 4)),
+ *   ),
+ * );
+ * @endcode
+ *
+ * @see drupal_install_schema()
+ */
+
+ /**
+ * Create a new table from a Drupal table definition.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $name
+ *   The name of the table to create.
+ * @param $table
+ *   A Schema API table definition array.
+ */
+function db_create_table(&$ret, $name, $table) {
+  $statements = db_create_table_sql($name, $table);
+  foreach ($statements as $statement) {
+    $ret[] = update_sql($statement);
+  }
+}
+
+/**
+ * Return an array of field names from an array of key/index column specifiers.
+ *
+ * This is usually an identity function but if a key/index uses a column prefix
+ * specification, this function extracts just the name.
+ *
+ * @param $fields
+ *   An array of key/index column specifiers.
+ * @return
+ *   An array of field names.
+ */
+function db_field_names($fields) {
+  $ret = array();
+  foreach ($fields as $field) {
+    if (is_array($field)) {
+      $ret[] = $field[0];
+    }
+    else {
+      $ret[] = $field;
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Given a Schema API field type, return the correct %-placeholder.
+ *
+ * Embed the placeholder in a query to be passed to db_query and and pass as an
+ * argument to db_query a value of the specified type.
+ *
+ * @param $type
+ *   The Schema API type of a field.
+ * @return
+ *   The placeholder string to embed in a query for that type.
+ */
+function db_type_placeholder($type) {
+  switch ($type) {
+    case 'varchar':
+    case 'char':
+    case 'text':
+    case 'datetime':
+      return '\'%s\'';
+
+    case 'numeric':
+      // For 'numeric' values, we use '%s', not '\'%s\'' as with
+      // string types, because numeric values should not be enclosed
+      // in quotes in queries (though they can be, at least on mysql
+      // and pgsql).  Numerics should only have [0-9.+-] and
+      // presumably no db's "escape string" function will mess with
+      // those characters.
+      return '%s';
+
+    case 'serial':
+    case 'int':
+      return '%d';
+
+    case 'float':
+      return '%f';
+
+    case 'blob':
+      return '%b';
+  }
+
+  // There is no safe value to return here, so return something that
+  // will cause the query to fail.
+  return 'unsupported type '. $type .'for db_type_placeholder';
+}
+
+/**
+ * @} End of "defgroup schemaapi".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/database.mysql-common.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,533 @@
+<?php
+// $Id: database.mysql-common.inc,v 1.17.2.1 2008/02/07 10:17:26 goba Exp $
+
+/**
+ * @file
+ * Functions shared between mysql and mysqli database engines.
+ */
+
+/**
+ * Runs a basic query in the active database.
+ *
+ * User-supplied arguments to the query should be passed in as separate
+ * parameters so that they can be properly escaped to avoid SQL injection
+ * attacks.
+ *
+ * @param $query
+ *   A string containing an SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. Instead of a variable number of query arguments,
+ *   you may also pass a single array containing the query arguments.
+ *
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @return
+ *   A database query result resource, or FALSE if the query was not
+ *   executed correctly.
+ */
+function db_query($query) {
+  $args = func_get_args();
+  array_shift($args);
+  $query = db_prefix_tables($query);
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  return _db_query($query);
+}
+
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $name
+ *   The name of the table to create.
+ * @param $table
+ *   A Schema API table definition array.
+ * @return
+ *   An array of SQL statements to create the table.
+ */
+function db_create_table_sql($name, $table) {
+
+  if (empty($table['mysql_suffix'])) {
+    $table['mysql_suffix'] = "/*!40100 DEFAULT CHARACTER SET UTF8 */";
+  }
+
+  $sql = "CREATE TABLE {". $name ."} (\n";
+
+  // Add the SQL statement for each field.
+  foreach ($table['fields'] as $field_name => $field) {
+    $sql .= _db_create_field_sql($field_name, _db_process_field($field)) .", \n";
+  }
+
+  // Process keys & indexes.
+  $keys = _db_create_keys_sql($table);
+  if (count($keys)) {
+    $sql .= implode(", \n", $keys) .", \n";
+  }
+
+  // Remove the last comma and space.
+  $sql = substr($sql, 0, -3) ."\n) ";
+
+  $sql .= $table['mysql_suffix'];
+
+  return array($sql);
+}
+
+function _db_create_keys_sql($spec) {
+  $keys = array();
+
+  if (!empty($spec['primary key'])) {
+    $keys[] = 'PRIMARY KEY ('. _db_create_key_sql($spec['primary key']) .')';
+  }
+  if (!empty($spec['unique keys'])) {
+    foreach ($spec['unique keys'] as $key => $fields) {
+      $keys[] = 'UNIQUE KEY '. $key .' ('. _db_create_key_sql($fields) .')';
+    }
+  }
+  if (!empty($spec['indexes'])) {
+    foreach ($spec['indexes'] as $index => $fields) {
+      $keys[] = 'INDEX '. $index .' ('. _db_create_key_sql($fields) .')';
+    }
+  }
+
+  return $keys;
+}
+
+function _db_create_key_sql($fields) {
+  $ret = array();
+  foreach ($fields as $field) {
+    if (is_array($field)) {
+      $ret[] = $field[0] .'('. $field[1] .')';
+    }
+    else {
+      $ret[] = $field;
+    }
+  }
+  return implode(', ', $ret);
+}
+
+/**
+ * Set database-engine specific properties for a field.
+ *
+ * @param $field
+ *   A field description array, as specified in the schema documentation.
+ */
+function _db_process_field($field) {
+
+  if (!isset($field['size'])) {
+    $field['size'] = 'normal';
+  }
+
+  // Set the correct database-engine specific datatype.
+  if (!isset($field['mysql_type'])) {
+    $map = db_type_map();
+    $field['mysql_type'] = $map[$field['type'] .':'. $field['size']];
+  }
+
+  if ($field['type'] == 'serial') {
+    $field['auto_increment'] = TRUE;
+  }
+
+  return $field;
+}
+
+/**
+ * Create an SQL string for a field to be used in table creation or alteration.
+ *
+ * Before passing a field out of a schema definition into this function it has
+ * to be processed by _db_process_field().
+ *
+ * @param $name
+ *    Name of the field.
+ * @param $spec
+ *    The field specification, as per the schema data structure format.
+ */
+function _db_create_field_sql($name, $spec) {
+  $sql = "`". $name ."` ". $spec['mysql_type'];
+
+  if (isset($spec['length'])) {
+    $sql .= '('. $spec['length'] .')';
+  }
+  elseif (isset($spec['precision']) && isset($spec['scale'])) {
+    $sql .= '('. $spec['precision'] .', '. $spec['scale'] .')';
+  }
+
+  if (!empty($spec['unsigned'])) {
+    $sql .= ' unsigned';
+  }
+
+  if (!empty($spec['not null'])) {
+    $sql .= ' NOT NULL';
+  }
+
+  if (!empty($spec['auto_increment'])) {
+    $sql .= ' auto_increment';
+  }
+
+  if (isset($spec['default'])) {
+    if (is_string($spec['default'])) {
+      $spec['default'] = "'". $spec['default'] ."'";
+    }
+    $sql .= ' DEFAULT '. $spec['default'];
+  }
+
+  if (empty($spec['not null']) && !isset($spec['default'])) {
+    $sql .= ' DEFAULT NULL';
+  }
+
+  return $sql;
+}
+
+/**
+ * This maps a generic data type in combination with its data size
+ * to the engine-specific data type.
+ */
+function db_type_map() {
+  // Put :normal last so it gets preserved by array_flip.  This makes
+  // it much easier for modules (such as schema.module) to map
+  // database types back into schema types.
+  $map = array(
+    'varchar:normal'  => 'VARCHAR',
+    'char:normal'     => 'CHAR',
+
+    'text:tiny'       => 'TINYTEXT',
+    'text:small'      => 'TINYTEXT',
+    'text:medium'     => 'MEDIUMTEXT',
+    'text:big'        => 'LONGTEXT',
+    'text:normal'     => 'TEXT',
+
+    'serial:tiny'     => 'TINYINT',
+    'serial:small'    => 'SMALLINT',
+    'serial:medium'   => 'MEDIUMINT',
+    'serial:big'      => 'BIGINT',
+    'serial:normal'   => 'INT',
+
+    'int:tiny'        => 'TINYINT',
+    'int:small'       => 'SMALLINT',
+    'int:medium'      => 'MEDIUMINT',
+    'int:big'         => 'BIGINT',
+    'int:normal'      => 'INT',
+
+    'float:tiny'      => 'FLOAT',
+    'float:small'     => 'FLOAT',
+    'float:medium'    => 'FLOAT',
+    'float:big'       => 'DOUBLE',
+    'float:normal'    => 'FLOAT',
+
+    'numeric:normal'  => 'DECIMAL',
+
+    'blob:big'        => 'LONGBLOB',
+    'blob:normal'     => 'BLOB',
+
+    'datetime:normal' => 'DATETIME',
+  );
+  return $map;
+}
+
+/**
+ * Rename a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be renamed.
+ * @param $new_name
+ *   The new name for the table.
+ */
+function db_rename_table(&$ret, $table, $new_name) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}');
+}
+
+/**
+ * Drop a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be dropped.
+ */
+function db_drop_table(&$ret, $table) {
+  $ret[] = update_sql('DROP TABLE {'. $table .'}');
+}
+
+/**
+ * Add a new field to a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   Name of the table to be altered.
+ * @param $field
+ *   Name of the field to be added.
+ * @param $spec
+ *   The field specification array, as taken from a schema definition.
+ *   The specification may also contain the key 'initial', the newly
+ *   created field will be set to the value of the key in all rows.
+ *   This is most useful for creating NOT NULL columns with no default
+ *   value in existing tables.
+ * @param $keys_new
+ *   Optional keys and indexes specification to be created on the
+ *   table along with adding the field. The format is the same as a
+ *   table specification but without the 'fields' element.  If you are
+ *   adding a type 'serial' field, you MUST specify at least one key
+ *   or index including it in this array. @see db_change_field for more
+ *   explanation why.
+ */
+function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) {
+  $fixnull = FALSE;
+  if (!empty($spec['not null']) && !isset($spec['default'])) {
+    $fixnull = TRUE;
+    $spec['not null'] = FALSE;
+  }
+  $query = 'ALTER TABLE {'. $table .'} ADD ';
+  $query .= _db_create_field_sql($field, _db_process_field($spec));
+  if (count($keys_new)) {
+    $query .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new));
+  }
+  $ret[] = update_sql($query);
+  if (isset($spec['initial'])) {
+    // All this because update_sql does not support %-placeholders.
+    $sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']);
+    $result = db_query($sql, $spec['initial']);
+    $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')'));
+  }
+  if ($fixnull) {
+    $spec['not null'] = TRUE;
+    db_change_field($ret, $table, $field, $field, $spec);
+  }
+}
+
+/**
+ * Drop a field.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be dropped.
+ */
+function db_drop_field(&$ret, $table, $field) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP '. $field);
+}
+
+/**
+ * Set the default value for a field.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be altered.
+ * @param $default
+ *   Default value to be set. NULL for 'default NULL'.
+ */
+function db_field_set_default(&$ret, $table, $field, $default) {
+  if ($default == NULL) {
+    $default = 'NULL';
+  }
+  else {
+    $default = is_string($default) ? "'$default'" : $default;
+  }
+
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
+}
+
+/**
+ * Set a field to have no default value.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be altered.
+ */
+function db_field_set_no_default(&$ret, $table, $field) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
+}
+
+/**
+ * Add a primary key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $fields
+ *   Fields for the primary key.
+ */
+function db_add_primary_key(&$ret, $table, $fields) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
+    _db_create_key_sql($fields) .')');
+}
+
+/**
+ * Drop the primary key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ */
+function db_drop_primary_key(&$ret, $table) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP PRIMARY KEY');
+}
+
+/**
+ * Add a unique key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the key.
+ * @param $fields
+ *   An array of field names.
+ */
+function db_add_unique_key(&$ret, $table, $name, $fields) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD UNIQUE KEY '.
+    $name .' ('. _db_create_key_sql($fields) .')');
+}
+
+/**
+ * Drop a unique key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the key.
+ */
+function db_drop_unique_key(&$ret, $table, $name) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP KEY '. $name);
+}
+
+/**
+ * Add an index.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the index.
+ * @param $fields
+ *   An array of field names.
+ */
+function db_add_index(&$ret, $table, $name, $fields) {
+  $query = 'ALTER TABLE {'. $table .'} ADD INDEX '. $name .' ('. _db_create_key_sql($fields) .')';
+  $ret[] = update_sql($query);
+}
+
+/**
+ * Drop an index.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the index.
+ */
+function db_drop_index(&$ret, $table, $name) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP INDEX '. $name);
+}
+
+/**
+ * Change a field definition.
+ *
+ * IMPORTANT NOTE: To maintain database portability, you have to explicitly
+ * recreate all indices and primary keys that are using the changed field.
+ *
+ * That means that you have to drop all affected keys and indexes with
+ * db_drop_{primary_key,unique_key,index}() before calling db_change_field().
+ * To recreate the keys and indices, pass the key definitions as the
+ * optional $keys_new argument directly to db_change_field().
+ *
+ * For example, suppose you have:
+ * @code
+ * $schema['foo'] = array(
+ *   'fields' => array(
+ *     'bar' => array('type' => 'int', 'not null' => TRUE)
+ *   ),
+ *   'primary key' => array('bar')
+ * );
+ * @endcode
+ * and you want to change foo.bar to be type serial, leaving it as the
+ * primary key.  The correct sequence is:
+ * @code
+ * db_drop_primary_key($ret, 'foo');
+ * db_change_field($ret, 'foo', 'bar', 'bar',
+ *   array('type' => 'serial', 'not null' => TRUE),
+ *   array('primary key' => array('bar')));
+ * @endcode
+ *
+ * The reasons for this are due to the different database engines:
+ *
+ * On PostgreSQL, changing a field definition involves adding a new field
+ * and dropping an old one which* causes any indices, primary keys and
+ * sequences (from serial-type fields) that use the changed field to be dropped.
+ *
+ * On MySQL, all type 'serial' fields must be part of at least one key
+ * or index as soon as they are created.  You cannot use
+ * db_add_{primary_key,unique_key,index}() for this purpose because
+ * the ALTER TABLE command will fail to add the column without a key
+ * or index specification.  The solution is to use the optional
+ * $keys_new argument to create the key or index at the same time as
+ * field.
+ *
+ * You could use db_add_{primary_key,unique_key,index}() in all cases
+ * unless you are converting a field to be type serial. You can use
+ * the $keys_new argument in all cases.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   Name of the table.
+ * @param $field
+ *   Name of the field to change.
+ * @param $field_new
+ *   New name for the field (set to the same as $field if you don't want to change the name).
+ * @param $spec
+ *   The field specification for the new field.
+ * @param $keys_new
+ *   Optional keys and indexes specification to be created on the
+ *   table along with changing the field. The format is the same as a
+ *   table specification but without the 'fields' element.
+ */
+
+function db_change_field(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) {
+  $sql = 'ALTER TABLE {'. $table .'} CHANGE '. $field .' '.
+    _db_create_field_sql($field_new, _db_process_field($spec));
+  if (count($keys_new)) {
+    $sql .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new));
+  }
+  $ret[] = update_sql($sql);
+}
+
+/**
+ * Returns the last insert id.
+ *
+ * @param $table
+ *   The name of the table you inserted into.
+ * @param $field
+ *   The name of the autoincrement field.
+ */
+function db_last_insert_id($table, $field) {
+  return db_result(db_query('SELECT LAST_INSERT_ID()'));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/database.mysql.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,371 @@
+<?php
+// $Id: database.mysql.inc,v 1.89 2008/01/24 10:46:54 goba Exp $
+
+/**
+ * @file
+ * Database interface code for MySQL database servers.
+ */
+
+/**
+ * @ingroup database
+ * @{
+ */
+
+// Include functions shared between mysql and mysqli.
+require_once './includes/database.mysql-common.inc';
+
+/**
+ * Report database status.
+ */
+function db_status_report($phase) {
+  $t = get_t();
+
+  $version = db_version();
+
+  $form['mysql'] = array(
+    'title' => $t('MySQL database'),
+    'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version,
+  );
+
+  if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) {
+    $form['mysql']['severity'] = REQUIREMENT_ERROR;
+    $form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL));
+  }
+
+  return $form;
+}
+
+/**
+ * Returns the version of the database server currently in use.
+ *
+ * @return Database server version
+ */
+function db_version() {
+  list($version) = explode('-', mysql_get_server_info());
+  return $version;
+}
+
+/**
+ * Initialize a database connection.
+ */
+function db_connect($url) {
+  $url = parse_url($url);
+
+  // Check if MySQL support is present in PHP
+  if (!function_exists('mysql_connect')) {
+    _db_error_page('Unable to use the MySQL database because the MySQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+  }
+
+  // Decode url-encoded information in the db connection string
+  $url['user'] = urldecode($url['user']);
+  // Test if database url has a password.
+  $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
+  $url['host'] = urldecode($url['host']);
+  $url['path'] = urldecode($url['path']);
+
+  // Allow for non-standard MySQL port.
+  if (isset($url['port'])) {
+    $url['host'] = $url['host'] .':'. $url['port'];
+  }
+
+  // - TRUE makes mysql_connect() always open a new link, even if
+  //   mysql_connect() was called before with the same parameters.
+  //   This is important if you are using two databases on the same
+  //   server.
+  // - 2 means CLIENT_FOUND_ROWS: return the number of found
+  //   (matched) rows, not the number of affected rows.
+  $connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2);
+  if (!$connection || !mysql_select_db(substr($url['path'], 1))) {
+    // Show error screen otherwise
+    _db_error_page(mysql_error());
+  }
+
+  // Force UTF-8.
+  mysql_query('SET NAMES "utf8"', $connection);
+  return $connection;
+}
+
+/**
+ * Helper function for db_query().
+ */
+function _db_query($query, $debug = 0) {
+  global $active_db, $queries, $user;
+
+  if (variable_get('dev_query', 0)) {
+    list($usec, $sec) = explode(' ', microtime());
+    $timer = (float)$usec + (float)$sec;
+    // If devel.module query logging is enabled, prepend a comment with the username and calling function
+    // to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact
+    // code is issueing the slow query.
+    $bt = debug_backtrace();
+    // t() may not be available yet so we don't wrap 'Anonymous'.
+    $name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous');
+    // str_replace() to prevent SQL injection via username or anonymous name.
+    $name = str_replace(array('*', '/'), '', $name);
+    $query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query;
+  }
+
+  $result = mysql_query($query, $active_db);
+
+  if (variable_get('dev_query', 0)) {
+    $query = $bt[2]['function'] ."\n". $query;
+    list($usec, $sec) = explode(' ', microtime());
+    $stop = (float)$usec + (float)$sec;
+    $diff = $stop - $timer;
+    $queries[] = array($query, $diff);
+  }
+
+  if ($debug) {
+    print '<p>query: '. $query .'<br />error:'. mysql_error($active_db) .'</p>';
+  }
+
+  if (!mysql_errno($active_db)) {
+    return $result;
+  }
+  else {
+    // Indicate to drupal_error_handler that this is a database error.
+    ${DB_ERROR} = TRUE;
+    trigger_error(check_plain(mysql_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
+    return FALSE;
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an object.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An object representing the next row of the result, or FALSE. The attributes
+ *   of this object are the table fields selected by the query.
+ */
+function db_fetch_object($result) {
+  if ($result) {
+    return mysql_fetch_object($result);
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an array.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An associative array representing the next row of the result, or FALSE.
+ *   The keys of this object are the names of the table fields selected by the
+ *   query, and the values are the field values for this result row.
+ */
+function db_fetch_array($result) {
+  if ($result) {
+    return mysql_fetch_array($result, MYSQL_ASSOC);
+  }
+}
+
+/**
+ * Return an individual result field from the previous query.
+ *
+ * Only use this function if exactly one field is being selected; otherwise,
+ * use db_fetch_object() or db_fetch_array().
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   The resulting field or FALSE.
+ */
+function db_result($result) {
+  if ($result && mysql_num_rows($result) > 0) {
+    // The mysql_fetch_row function has an optional second parameter $row
+    // but that can't be used for compatibility with Oracle, DB2, etc.
+    $array = mysql_fetch_row($result);
+    return $array[0];
+  }
+  return FALSE;
+}
+
+/**
+ * Determine whether the previous query caused an error.
+ */
+function db_error() {
+  global $active_db;
+  return mysql_errno($active_db);
+}
+
+/**
+ * Determine the number of rows changed by the preceding query.
+ */
+function db_affected_rows() {
+  global $active_db;
+  return mysql_affected_rows($active_db);
+}
+
+/**
+ * Runs a limited-range query in the active database.
+ *
+ * Use this as a substitute for db_query() when a subset of the query is to be
+ * returned.
+ * User-supplied arguments to the query should be passed in as separate parameters
+ * so that they can be properly escaped to avoid SQL injection attacks.
+ *
+ * @param $query
+ *   A string containing an SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. The query arguments can be enclosed in one
+ *   array instead.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $from
+ *   The first result row to return.
+ * @param $count
+ *   The maximum number of result rows to return.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_range($query) {
+  $args = func_get_args();
+  $count = array_pop($args);
+  $from = array_pop($args);
+  array_shift($args);
+
+  $query = db_prefix_tables($query);
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  $query .= ' LIMIT '. (int)$from .', '. (int)$count;
+  return _db_query($query);
+}
+
+/**
+ * Runs a SELECT query and stores its results in a temporary table.
+ *
+ * Use this as a substitute for db_query() when the results need to stored
+ * in a temporary table. Temporary tables exist for the duration of the page
+ * request.
+ * User-supplied arguments to the query should be passed in as separate parameters
+ * so that they can be properly escaped to avoid SQL injection attacks.
+ *
+ * Note that if you need to know how many results were returned, you should do
+ * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
+ * not give consistent result across different database types in this case.
+ *
+ * @param $query
+ *   A string containing a normal SELECT SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. The query arguments can be enclosed in one
+ *   array instead.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $table
+ *   The name of the temporary table to select into. This name will not be
+ *   prefixed as there is no risk of collision.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_temporary($query) {
+  $args = func_get_args();
+  $tablename = array_pop($args);
+  array_shift($args);
+
+  $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query));
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  return _db_query($query);
+}
+
+/**
+ * Returns a properly formatted Binary Large OBject value.
+ *
+ * @param $data
+ *   Data to encode.
+ * @return
+ *  Encoded data.
+ */
+function db_encode_blob($data) {
+  global $active_db;
+  return "'". mysql_real_escape_string($data, $active_db) ."'";
+}
+
+/**
+ * Returns text from a Binary Large Object value.
+ *
+ * @param $data
+ *   Data to decode.
+ * @return
+ *  Decoded data.
+ */
+function db_decode_blob($data) {
+  return $data;
+}
+
+/**
+ * Prepare user input for use in a database query, preventing SQL injection attacks.
+ */
+function db_escape_string($text) {
+  global $active_db;
+  return mysql_real_escape_string($text, $active_db);
+}
+
+/**
+ * Lock a table.
+ */
+function db_lock_table($table) {
+  db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE');
+}
+
+/**
+ * Unlock all locked tables.
+ */
+function db_unlock_tables() {
+  db_query('UNLOCK TABLES');
+}
+
+/**
+ * Check if a table exists.
+ */
+function db_table_exists($table) {
+  return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'"));
+}
+
+/**
+ * Check if a column exists in the given table.
+ */
+function db_column_exists($table, $column) {
+  return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'"));
+}
+
+/**
+ * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to
+ * the SELECT list entry of the given query and the resulting query is returned.
+ * This function only applies the wrapper if a DISTINCT doesn't already exist in
+ * the query.
+ *
+ * @param $table Table containing the field to set as DISTINCT
+ * @param $field Field to set as DISTINCT
+ * @param $query Query to apply the wrapper to
+ * @return SQL query with the DISTINCT wrapper surrounding the given table.field.
+ */
+function db_distinct_field($table, $field, $query) {
+  $field_to_select = 'DISTINCT('. $table .'.'. $field .')';
+  // (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
+  return preg_replace('/(SELECT.*)(?:'. $table .'\.|\s)(?<!DISTINCT\()(?<!DISTINCT\('. $table .'\.)'. $field .'(.*FROM )/AUsi', '\1 '. $field_to_select .'\2', $query);
+}
+
+/**
+ * @} End of "ingroup database".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/database.mysqli.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,374 @@
+<?php
+// $Id: database.mysqli.inc,v 1.54 2008/01/23 09:59:29 goba Exp $
+
+/**
+ * @file
+ * Database interface code for MySQL database servers using the mysqli client libraries. mysqli is included in PHP 5 by default and allows developers to use the advanced features of MySQL 4.1.x, 5.0.x and beyond.
+ */
+
+ // Maintainers of this file should consult:
+ // http://www.php.net/manual/en/ref.mysqli.php
+
+/**
+ * @ingroup database
+ * @{
+ */
+
+// Include functions shared between mysql and mysqli.
+require_once './includes/database.mysql-common.inc';
+
+/**
+ * Report database status.
+ */
+function db_status_report($phase) {
+  $t = get_t();
+
+  $version = db_version();
+
+  $form['mysql'] = array(
+    'title' => $t('MySQL database'),
+    'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version,
+  );
+
+  if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) {
+    $form['mysql']['severity'] = REQUIREMENT_ERROR;
+    $form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL));
+  }
+
+  return $form;
+}
+
+/**
+ * Returns the version of the database server currently in use.
+ *
+ * @return Database server version
+ */
+function db_version() {
+  global $active_db;
+  list($version) = explode('-', mysqli_get_server_info($active_db));
+  return $version;
+}
+
+/**
+ * Initialise a database connection.
+ *
+ * Note that mysqli does not support persistent connections.
+ */
+function db_connect($url) {
+  // Check if MySQLi support is present in PHP
+  if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
+    _db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+  }
+
+  $url = parse_url($url);
+
+  // Decode url-encoded information in the db connection string
+  $url['user'] = urldecode($url['user']);
+  // Test if database url has a password.
+  $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
+  $url['host'] = urldecode($url['host']);
+  $url['path'] = urldecode($url['path']);
+  if (!isset($url['port'])) {
+    $url['port'] = NULL;
+  }
+
+  $connection = mysqli_init();
+  @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
+
+  if (mysqli_connect_errno() > 0) {
+    _db_error_page(mysqli_connect_error());
+  }
+
+  // Force UTF-8.
+  mysqli_query($connection, 'SET NAMES "utf8"');
+
+  return $connection;
+}
+
+/**
+ * Helper function for db_query().
+ */
+function _db_query($query, $debug = 0) {
+  global $active_db, $queries, $user;
+
+  if (variable_get('dev_query', 0)) {
+    list($usec, $sec) = explode(' ', microtime());
+    $timer = (float)$usec + (float)$sec;
+    // If devel.module query logging is enabled, prepend a comment with the username and calling function
+    // to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact
+    // code is issueing the slow query.
+    $bt = debug_backtrace();
+    // t() may not be available yet so we don't wrap 'Anonymous'
+    $name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous');
+    // str_replace() to prevent SQL injection via username or anonymous name.
+    $name = str_replace(array('*', '/'), '', $name);
+    $query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query;
+  }
+
+  $result = mysqli_query($active_db, $query);
+
+  if (variable_get('dev_query', 0)) {
+    $query = $bt[2]['function'] ."\n". $query;
+    list($usec, $sec) = explode(' ', microtime());
+    $stop = (float)$usec + (float)$sec;
+    $diff = $stop - $timer;
+    $queries[] = array($query, $diff);
+  }
+
+  if ($debug) {
+    print '<p>query: '. $query .'<br />error:'. mysqli_error($active_db) .'</p>';
+  }
+
+  if (!mysqli_errno($active_db)) {
+    return $result;
+  }
+  else {
+    // Indicate to drupal_error_handler that this is a database error.
+    ${DB_ERROR} = TRUE;
+    trigger_error(check_plain(mysqli_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
+    return FALSE;
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an object.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An object representing the next row of the result, or FALSE. The attributes
+ *   of this object are the table fields selected by the query.
+ */
+function db_fetch_object($result) {
+  if ($result) {
+    $object = mysqli_fetch_object($result);
+    return isset($object) ? $object : FALSE;
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an array.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An associative array representing the next row of the result, or FALSE.
+ *   The keys of this object are the names of the table fields selected by the
+ *   query, and the values are the field values for this result row.
+ */
+function db_fetch_array($result) {
+  if ($result) {
+    $array = mysqli_fetch_array($result, MYSQLI_ASSOC);
+    return isset($array) ? $array : FALSE;
+  }
+}
+
+/**
+ * Return an individual result field from the previous query.
+ *
+ * Only use this function if exactly one field is being selected; otherwise,
+ * use db_fetch_object() or db_fetch_array().
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   The resulting field or FALSE.
+ */
+function db_result($result) {
+  if ($result && mysqli_num_rows($result) > 0) {
+    // The mysqli_fetch_row function has an optional second parameter $row
+    // but that can't be used for compatibility with Oracle, DB2, etc.
+    $array = mysqli_fetch_row($result);
+    return $array[0];
+  }
+  return FALSE;
+}
+
+/**
+ * Determine whether the previous query caused an error.
+ */
+function db_error() {
+  global $active_db;
+  return mysqli_errno($active_db);
+}
+
+/**
+ * Determine the number of rows changed by the preceding query.
+ */
+function db_affected_rows() {
+  global $active_db; /* mysqli connection resource */
+  return mysqli_affected_rows($active_db);
+}
+
+/**
+ * Runs a limited-range query in the active database.
+ *
+ * Use this as a substitute for db_query() when a subset of the query is to be
+ * returned.
+ * User-supplied arguments to the query should be passed in as separate parameters
+ * so that they can be properly escaped to avoid SQL injection attacks.
+ *
+ * @param $query
+ *   A string containing an SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. The query arguments can be enclosed in one
+ *   array instead.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $from
+ *   The first result row to return.
+ * @param $count
+ *   The maximum number of result rows to return.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_range($query) {
+  $args = func_get_args();
+  $count = array_pop($args);
+  $from = array_pop($args);
+  array_shift($args);
+
+  $query = db_prefix_tables($query);
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  $query .= ' LIMIT '. (int)$from .', '. (int)$count;
+  return _db_query($query);
+}
+
+/**
+ * Runs a SELECT query and stores its results in a temporary table.
+ *
+ * Use this as a substitute for db_query() when the results need to stored
+ * in a temporary table. Temporary tables exist for the duration of the page
+ * request.
+ * User-supplied arguments to the query should be passed in as separate parameters
+ * so that they can be properly escaped to avoid SQL injection attacks.
+ *
+ * Note that if you need to know how many results were returned, you should do
+ * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
+ * not give consistent result across different database types in this case.
+ *
+ * @param $query
+ *   A string containing a normal SELECT SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. The query arguments can be enclosed in one
+ *   array instead.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $table
+ *   The name of the temporary table to select into. This name will not be
+ *   prefixed as there is no risk of collision.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_temporary($query) {
+  $args = func_get_args();
+  $tablename = array_pop($args);
+  array_shift($args);
+
+  $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query));
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  return _db_query($query);
+}
+
+/**
+ * Returns a properly formatted Binary Large Object value.
+ *
+ * @param $data
+ *   Data to encode.
+ * @return
+ *  Encoded data.
+ */
+function db_encode_blob($data) {
+  global $active_db;
+  return "'". mysqli_real_escape_string($active_db, $data) ."'";
+}
+
+/**
+ * Returns text from a Binary Large OBject value.
+ *
+ * @param $data
+ *   Data to decode.
+ * @return
+ *  Decoded data.
+ */
+function db_decode_blob($data) {
+  return $data;
+}
+
+/**
+ * Prepare user input for use in a database query, preventing SQL injection attacks.
+ */
+function db_escape_string($text) {
+  global $active_db;
+  return mysqli_real_escape_string($active_db, $text);
+}
+
+/**
+ * Lock a table.
+ */
+function db_lock_table($table) {
+  db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE');
+}
+
+/**
+ * Unlock all locked tables.
+ */
+function db_unlock_tables() {
+  db_query('UNLOCK TABLES');
+}
+
+/**
+ * Check if a table exists.
+ */
+function db_table_exists($table) {
+  return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'"));
+}
+
+/**
+ * Check if a column exists in the given table.
+ */
+function db_column_exists($table, $column) {
+  return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'"));
+}
+
+/**
+ * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to
+ * the SELECT list entry of the given query and the resulting query is returned.
+ * This function only applies the wrapper if a DISTINCT doesn't already exist in
+ * the query.
+ *
+ * @param $table Table containing the field to set as DISTINCT
+ * @param $field Field to set as DISTINCT
+ * @param $query Query to apply the wrapper to
+ * @return SQL query with the DISTINCT wrapper surrounding the given table.field.
+ */
+function db_distinct_field($table, $field, $query) {
+  $field_to_select = 'DISTINCT('. $table .'.'. $field .')';
+  // (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
+  return preg_replace('/(SELECT.*)(?:'. $table .'\.|\s)(?<!DISTINCT\()(?<!DISTINCT\('. $table .'\.)'. $field .'(.*FROM )/AUsi', '\1 '. $field_to_select .'\2', $query);
+}
+
+/**
+ * @} End of "ingroup database".
+ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/database.pgsql.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,926 @@
+<?php
+// $Id: database.pgsql.inc,v 1.68.2.1 2008/02/07 10:17:26 goba Exp $
+
+/**
+ * @file
+ * Database interface code for PostgreSQL database servers.
+ */
+
+/**
+ * @ingroup database
+ * @{
+ */
+
+/**
+ * Report database status.
+ */
+function db_status_report() {
+  $t = get_t();
+
+  $version = db_version();
+
+  $form['pgsql'] = array(
+    'title' => $t('PostgreSQL database'),
+    'value' => $version,
+  );
+
+  if (version_compare($version, DRUPAL_MINIMUM_PGSQL) < 0) {
+    $form['pgsql']['severity'] = REQUIREMENT_ERROR;
+    $form['pgsql']['description'] = $t('Your PostgreSQL Server is too old. Drupal requires at least PostgreSQL %version.', array('%version' => DRUPAL_MINIMUM_PGSQL));
+  }
+
+  return $form;
+}
+
+/**
+ * Returns the version of the database server currently in use.
+ *
+ * @return Database server version
+ */
+function db_version() {
+  return db_result(db_query("SHOW SERVER_VERSION"));
+}
+
+/**
+ * Initialize a database connection.
+ */
+function db_connect($url) {
+  // Check if PostgreSQL support is present in PHP
+  if (!function_exists('pg_connect')) {
+    _db_error_page('Unable to use the PostgreSQL database because the PostgreSQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+  }
+
+  $url = parse_url($url);
+  $conn_string = '';
+
+  // Decode url-encoded information in the db connection string
+  if (isset($url['user'])) {
+    $conn_string .= ' user='. urldecode($url['user']);
+  }
+  if (isset($url['pass'])) {
+    $conn_string .= ' password='. urldecode($url['pass']);
+  }
+  if (isset($url['host'])) {
+    $conn_string .= ' host='. urldecode($url['host']);
+  }
+  if (isset($url['path'])) {
+    $conn_string .= ' dbname='. substr(urldecode($url['path']), 1);
+  }
+  if (isset($url['port'])) {
+    $conn_string .= ' port='. urldecode($url['port']);
+  }
+
+  // pg_last_error() does not return a useful error message for database
+  // connection errors. We must turn on error tracking to get at a good error
+  // message, which will be stored in $php_errormsg.
+  $track_errors_previous = ini_get('track_errors');
+  ini_set('track_errors', 1);
+
+  $connection = @pg_connect($conn_string);
+  if (!$connection) {
+    require_once './includes/unicode.inc';
+    _db_error_page(decode_entities($php_errormsg));
+  }
+
+  // Restore error tracking setting
+  ini_set('track_errors', $track_errors_previous);
+
+  return $connection;
+}
+
+/**
+ * Runs a basic query in the active database.
+ *
+ * User-supplied arguments to the query should be passed in as separate
+ * parameters so that they can be properly escaped to avoid SQL injection
+ * attacks.
+ *
+ * @param $query
+ *   A string containing an SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. Instead of a variable number of query arguments,
+ *   you may also pass a single array containing the query arguments.
+ *
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @return
+ *   A database query result resource, or FALSE if the query was not
+ *   executed correctly.
+ */
+function db_query($query) {
+  $args = func_get_args();
+  array_shift($args);
+  $query = db_prefix_tables($query);
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  return _db_query($query);
+}
+
+/**
+ * Helper function for db_query().
+ */
+function _db_query($query, $debug = 0) {
+  global $active_db, $last_result, $queries;
+
+  if (variable_get('dev_query', 0)) {
+    list($usec, $sec) = explode(' ', microtime());
+    $timer = (float)$usec + (float)$sec;
+  }
+
+  $last_result = pg_query($active_db, $query);
+
+  if (variable_get('dev_query', 0)) {
+    $bt = debug_backtrace();
+    $query = $bt[2]['function'] ."\n". $query;
+    list($usec, $sec) = explode(' ', microtime());
+    $stop = (float)$usec + (float)$sec;
+    $diff = $stop - $timer;
+    $queries[] = array($query, $diff);
+  }
+
+  if ($debug) {
+    print '<p>query: '. $query .'<br />error:'. pg_last_error($active_db) .'</p>';
+  }
+
+  if ($last_result !== FALSE) {
+    return $last_result;
+  }
+  else {
+    // Indicate to drupal_error_handler that this is a database error.
+    ${DB_ERROR} = TRUE;
+    trigger_error(check_plain(pg_last_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
+    return FALSE;
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an object.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An object representing the next row of the result, or FALSE. The attributes
+ *   of this object are the table fields selected by the query.
+ */
+function db_fetch_object($result) {
+  if ($result) {
+    return pg_fetch_object($result);
+  }
+}
+
+/**
+ * Fetch one result row from the previous query as an array.
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   An associative array representing the next row of the result, or FALSE.
+ *   The keys of this object are the names of the table fields selected by the
+ *   query, and the values are the field values for this result row.
+ */
+function db_fetch_array($result) {
+  if ($result) {
+    return pg_fetch_assoc($result);
+  }
+}
+
+/**
+ * Return an individual result field from the previous query.
+ *
+ * Only use this function if exactly one field is being selected; otherwise,
+ * use db_fetch_object() or db_fetch_array().
+ *
+ * @param $result
+ *   A database query result resource, as returned from db_query().
+ * @return
+ *   The resulting field or FALSE.
+ */
+function db_result($result) {
+  if ($result && pg_num_rows($result) > 0) {
+    $array = pg_fetch_row($result);
+    return $array[0];
+  }
+  return FALSE;
+}
+
+/**
+ * Determine whether the previous query caused an error.
+ */
+function db_error() {
+  global $active_db;
+  return pg_last_error($active_db);
+}
+
+/**
+ * Returns the last insert id. This function is thread safe.
+ *
+ * @param $table
+ *   The name of the table you inserted into.
+ * @param $field
+ *   The name of the autoincrement field.
+ */
+function db_last_insert_id($table, $field) {
+  return db_result(db_query("SELECT CURRVAL('{". db_escape_table($table) ."}_". db_escape_table($field) ."_seq')"));
+}
+
+/**
+ * Determine the number of rows changed by the preceding query.
+ */
+function db_affected_rows() {
+  global $last_result;
+  return empty($last_result) ? 0 : pg_affected_rows($last_result);
+}
+
+/**
+ * Runs a limited-range query in the active database.
+ *
+ * Use this as a substitute for db_query() when a subset of the query
+ * is to be returned.
+ * User-supplied arguments to the query should be passed in as separate
+ * parameters so that they can be properly escaped to avoid SQL injection
+ * attacks.
+ *
+ * @param $query
+ *   A string containing an SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. Instead of a variable number of query arguments,
+ *   you may also pass a single array containing the query arguments.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $from
+ *   The first result row to return.
+ * @param $count
+ *   The maximum number of result rows to return.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_range($query) {
+  $args = func_get_args();
+  $count = array_pop($args);
+  $from = array_pop($args);
+  array_shift($args);
+
+  $query = db_prefix_tables($query);
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  $query .= ' LIMIT '. (int)$count .' OFFSET '. (int)$from;
+  return _db_query($query);
+}
+
+/**
+ * Runs a SELECT query and stores its results in a temporary table.
+ *
+ * Use this as a substitute for db_query() when the results need to stored
+ * in a temporary table. Temporary tables exist for the duration of the page
+ * request.
+ * User-supplied arguments to the query should be passed in as separate parameters
+ * so that they can be properly escaped to avoid SQL injection attacks.
+ *
+ * Note that if you need to know how many results were returned, you should do
+ * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
+ * not give consistent result across different database types in this case.
+ *
+ * @param $query
+ *   A string containing a normal SELECT SQL query.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query
+ *   using printf() syntax. The query arguments can be enclosed in one
+ *   array instead.
+ *   Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
+ *   in '') and %%.
+ *
+ *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
+ *   and TRUE values to decimal 1.
+ *
+ * @param $table
+ *   The name of the temporary table to select into. This name will not be
+ *   prefixed as there is no risk of collision.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ */
+function db_query_temporary($query) {
+  $args = func_get_args();
+  $tablename = array_pop($args);
+  array_shift($args);
+
+  $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' AS SELECT', db_prefix_tables($query));
+  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
+    $args = $args[0];
+  }
+  _db_query_callback($args, TRUE);
+  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
+  return _db_query($query);
+}
+
+/**
+ * Returns a properly formatted Binary Large OBject value.
+ * In case of PostgreSQL encodes data for insert into bytea field.
+ *
+ * @param $data
+ *   Data to encode.
+ * @return
+ *  Encoded data.
+ */
+function db_encode_blob($data) {
+  return "'". pg_escape_bytea($data) ."'";
+}
+
+/**
+ * Returns text from a Binary Large OBject value.
+ * In case of PostgreSQL decodes data after select from bytea field.
+ *
+ * @param $data
+ *   Data to decode.
+ * @return
+ *  Decoded data.
+ */
+function db_decode_blob($data) {
+  return pg_unescape_bytea($data);
+}
+
+/**
+ * Prepare user input for use in a database query, preventing SQL injection attacks.
+ * Note: This function requires PostgreSQL 7.2 or later.
+ */
+function db_escape_string($text) {
+  return pg_escape_string($text);
+}
+
+/**
+ * Lock a table.
+ * This function automatically starts a transaction.
+ */
+function db_lock_table($table) {
+  db_query('BEGIN; LOCK TABLE {'. db_escape_table($table) .'} IN EXCLUSIVE MODE');
+}
+
+/**
+ * Unlock all locked tables.
+ * This function automatically commits a transaction.
+ */
+function db_unlock_tables() {
+  db_query('COMMIT');
+}
+
+/**
+ * Check if a table exists.
+ */
+function db_table_exists($table) {
+  return (bool) db_result(db_query("SELECT COUNT(*) FROM pg_class WHERE relname = '{". db_escape_table($table) ."}'"));
+}
+
+/**
+ * Check if a column exists in the given table.
+ */
+function db_column_exists($table, $column) {
+  return (bool) db_result(db_query("SELECT COUNT(pg_attribute.attname) FROM pg_class, pg_attribute WHERE pg_attribute.attrelid = pg_class.oid AND pg_class.relname = '{". db_escape_table($table) ."}' AND attname = '". db_escape_table($column) ."'"));
+}
+
+/**
+ * Verify if the database is set up correctly.
+ */
+function db_check_setup() {
+  $t = get_t();
+
+  $encoding = db_result(db_query('SHOW server_encoding'));
+  if (!in_array(strtolower($encoding), array('unicode', 'utf8'))) {
+    drupal_set_message($t('Your PostgreSQL database is set up with the wrong character encoding (%encoding). It is possible it will not work as expected. It is advised to recreate it with UTF-8/Unicode encoding. More information can be found in the <a href="@url">PostgreSQL documentation</a>.', array('%encoding' => $encoding, '@url' => 'http://www.postgresql.org/docs/7.4/interactive/multibyte.html')), 'status');
+  }
+}
+
+/**
+ * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to
+ * the SELECT list entry of the given query and the resulting query is returned.
+ * This function only applies the wrapper if a DISTINCT doesn't already exist in
+ * the query.
+ *
+ * @param $table Table containing the field to set as DISTINCT
+ * @param $field Field to set as DISTINCT
+ * @param $query Query to apply the wrapper to
+ * @return SQL query with the DISTINCT wrapper surrounding the given table.field.
+ */
+function db_distinct_field($table, $field, $query) {
+  $field_to_select = 'DISTINCT ON ('. $table .'.'. $field .") $table.$field";
+  // (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
+  $query = preg_replace('/(SELECT.*)(?:'. $table .'\.|\s)(?<!DISTINCT\()(?<!DISTINCT\('. $table .'\.)'. $field .'(.*FROM )/AUsi', '\1 '. $field_to_select .'\2', $query);
+  $query = preg_replace('/(ORDER BY )(?!'. $table .'\.'. $field .')/', '\1'."$table.$field, ", $query);
+  return $query;
+}
+
+/**
+ * @} End of "ingroup database".
+ */
+
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+/**
+ * This maps a generic data type in combination with its data size
+ * to the engine-specific data type.
+ */
+function db_type_map() {
+  // Put :normal last so it gets preserved by array_flip.  This makes
+  // it much easier for modules (such as schema.module) to map
+  // database types back into schema types.
+  $map = array(
+    'varchar:normal' => 'varchar',
+    'char:normal' => 'character',
+
+    'text:tiny' => 'text',
+    'text:small' => 'text',
+    'text:medium' => 'text',
+    'text:big' => 'text',
+    'text:normal' => 'text',
+
+    'int:tiny' => 'smallint',
+    'int:small' => 'smallint',
+    'int:medium' => 'int',
+    'int:big' => 'bigint',
+    'int:normal' => 'int',
+
+    'float:tiny' => 'real',
+    'float:small' => 'real',
+    'float:medium' => 'real',
+    'float:big' => 'double precision',
+    'float:normal' => 'real',
+
+    'numeric:normal' => 'numeric',
+
+    'blob:big' => 'bytea',
+    'blob:normal' => 'bytea',
+
+    'datetime:normal' => 'timestamp',
+
+    'serial:tiny' => 'serial',
+    'serial:small' => 'serial',
+    'serial:medium' => 'serial',
+    'serial:big' => 'bigserial',
+    'serial:normal' => 'serial',
+  );
+  return $map;
+}
+
+/**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $name
+ *   The name of the table to create.
+ * @param $table
+ *   A Schema API table definition array.
+ * @return
+ *   An array of SQL statements to create the table.
+ */
+function db_create_table_sql($name, $table) {
+  $sql_fields = array();
+  foreach ($table['fields'] as $field_name => $field) {
+    $sql_fields[] = _db_create_field_sql($field_name, _db_process_field($field));
+  }
+
+  $sql_keys = array();
+  if (isset($table['primary key']) && is_array($table['primary key'])) {
+    $sql_keys[] = 'PRIMARY KEY ('. implode(', ', $table['primary key']) .')';
+  }
+  if (isset($table['unique keys']) && is_array($table['unique keys'])) {
+    foreach ($table['unique keys'] as $key_name => $key) {
+      $sql_keys[] = 'CONSTRAINT {'. $name .'}_'. $key_name .'_key UNIQUE ('. implode(', ', $key) .')';
+    }
+  }
+
+  $sql = "CREATE TABLE {". $name ."} (\n\t";
+  $sql .= implode(",\n\t", $sql_fields);
+  if (count($sql_keys) > 0) {
+    $sql .= ",\n\t";
+  }
+  $sql .= implode(",\n\t", $sql_keys);
+  $sql .= "\n)";
+  $statements[] = $sql;
+
+  if (isset($table['indexes']) && is_array($table['indexes'])) {
+    foreach ($table['indexes'] as $key_name => $key) {
+      $statements[] = _db_create_index_sql($name, $key_name, $key);
+    }
+  }
+
+  return $statements;
+}
+
+function _db_create_index_sql($table, $name, $fields) {
+  $query = 'CREATE INDEX {'. $table .'}_'. $name .'_idx ON {'. $table .'} (';
+  $query .= _db_create_key_sql($fields) .')';
+  return $query;
+}
+
+function _db_create_key_sql($fields) {
+  $ret = array();
+  foreach ($fields as $field) {
+    if (is_array($field)) {
+      $ret[] = 'substr('. $field[0] .', 1, '. $field[1] .')';
+    }
+    else {
+      $ret[] = $field;
+    }
+  }
+  return implode(', ', $ret);
+}
+
+function _db_create_keys(&$ret, $table, $new_keys) {
+  if (isset($new_keys['primary key'])) {
+    db_add_primary_key($ret, $table, $new_keys['primary key']);
+  }
+  if (isset($new_keys['unique keys'])) {
+    foreach ($new_keys['unique keys'] as $name => $fields) {
+      db_add_unique_key($ret, $table, $name, $fields);
+    }
+  }
+  if (isset($new_keys['indexes'])) {
+    foreach ($new_keys['indexes'] as $name => $fields) {
+      db_add_index($ret, $table, $name, $fields);
+    }
+  }
+}
+
+/**
+ * Set database-engine specific properties for a field.
+ *
+ * @param $field
+ *   A field description array, as specified in the schema documentation.
+ */
+function _db_process_field($field) {
+  if (!isset($field['size'])) {
+    $field['size'] = 'normal';
+  }
+  // Set the correct database-engine specific datatype.
+  if (!isset($field['pgsql_type'])) {
+    $map = db_type_map();
+    $field['pgsql_type'] = $map[$field['type'] .':'. $field['size']];
+  }
+  if ($field['type'] == 'serial') {
+    unset($field['not null']);
+  }
+  return $field;
+}
+
+/**
+ * Create an SQL string for a field to be used in table creation or alteration.
+ *
+ * Before passing a field out of a schema definition into this function it has
+ * to be processed by _db_process_field().
+ *
+ * @param $name
+ *    Name of the field.
+ * @param $spec
+ *    The field specification, as per the schema data structure format.
+ */
+function _db_create_field_sql($name, $spec) {
+  $sql = $name .' '. $spec['pgsql_type'];
+
+  if ($spec['type'] == 'serial') {
+    unset($spec['not null']);
+  }
+  if (!empty($spec['unsigned'])) {
+    if ($spec['type'] == 'serial') {
+      $sql .= " CHECK ($name >= 0)";
+    }
+    else {
+      $sql .= '_unsigned';
+    }
+  }
+
+  if (!empty($spec['length'])) {
+    $sql .= '('. $spec['length'] .')';
+  }
+  elseif (isset($spec['precision']) && isset($spec['scale'])) {
+    $sql .= '('. $spec['precision'] .', '. $spec['scale'] .')';
+  }
+
+  if (isset($spec['not null']) && $spec['not null']) {
+    $sql .= ' NOT NULL';
+  }
+  if (isset($spec['default'])) {
+    $default = is_string($spec['default']) ? "'". $spec['default'] ."'" : $spec['default'];
+    $sql .= " default $default";
+  }
+
+  return $sql;
+}
+
+/**
+ * Rename a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be renamed.
+ * @param $new_name
+ *   The new name for the table.
+ */
+function db_rename_table(&$ret, $table, $new_name) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}');
+}
+
+/**
+ * Drop a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be dropped.
+ */
+function db_drop_table(&$ret, $table) {
+  $ret[] = update_sql('DROP TABLE {'. $table .'}');
+}
+
+/**
+ * Add a new field to a table.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   Name of the table to be altered.
+ * @param $field
+ *   Name of the field to be added.
+ * @param $spec
+ *   The field specification array, as taken from a schema definition.
+ *   The specification may also contain the key 'initial', the newly
+ *   created field will be set to the value of the key in all rows.
+ *   This is most useful for creating NOT NULL columns with no default
+ *   value in existing tables.
+ * @param $keys_new
+ *   Optional keys and indexes specification to be created on the
+ *   table along with adding the field. The format is the same as a
+ *   table specification but without the 'fields' element.  If you are
+ *   adding a type 'serial' field, you MUST specify at least one key
+ *   or index including it in this array. @see db_change_field for more
+ *   explanation why.
+ */
+function db_add_field(&$ret, $table, $field, $spec, $new_keys = array()) {
+  $fixnull = FALSE;
+  if (!empty($spec['not null']) && !isset($spec['default'])) {
+    $fixnull = TRUE;
+    $spec['not null'] = FALSE;
+  }
+  $query = 'ALTER TABLE {'. $table .'} ADD COLUMN ';
+  $query .= _db_create_field_sql($field, _db_process_field($spec));
+  $ret[] = update_sql($query);
+  if (isset($spec['initial'])) {
+    // All this because update_sql does not support %-placeholders.
+    $sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']);
+    $result = db_query($sql, $spec['initial']);
+    $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')'));
+  }
+  if ($fixnull) {
+    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field SET NOT NULL");
+  }
+  if (isset($new_keys)) {
+    _db_create_keys($ret, $table, $new_keys);
+  }
+}
+
+/**
+ * Drop a field.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be dropped.
+ */
+function db_drop_field(&$ret, $table, $field) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP COLUMN '. $field);
+}
+
+/**
+ * Set the default value for a field.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be altered.
+ * @param $default
+ *   Default value to be set. NULL for 'default NULL'.
+ */
+function db_field_set_default(&$ret, $table, $field, $default) {
+  if ($default == NULL) {
+    $default = 'NULL';
+  }
+  else {
+    $default = is_string($default) ? "'$default'" : $default;
+  }
+
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
+}
+
+/**
+ * Set a field to have no default value.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $field
+ *   The field to be altered.
+ */
+function db_field_set_no_default(&$ret, $table, $field) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
+}
+
+/**
+ * Add a primary key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $fields
+ *   Fields for the primary key.
+ */
+function db_add_primary_key(&$ret, $table, $fields) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
+    implode(',', $fields) .')');
+}
+
+/**
+ * Drop the primary key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ */
+function db_drop_primary_key(&$ret, $table) {
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT {'. $table .'}_pkey');
+}
+
+/**
+ * Add a unique key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the key.
+ * @param $fields
+ *   An array of field names.
+ */
+function db_add_unique_key(&$ret, $table, $name, $fields) {
+  $name = '{'. $table .'}_'. $name .'_key';
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD CONSTRAINT '.
+    $name .' UNIQUE ('. implode(',', $fields) .')');
+}
+
+/**
+ * Drop a unique key.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the key.
+ */
+function db_drop_unique_key(&$ret, $table, $name) {
+  $name = '{'. $table .'}_'. $name .'_key';
+  $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT '. $name);
+}
+
+/**
+ * Add an index.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the index.
+ * @param $fields
+ *   An array of field names.
+ */
+function db_add_index(&$ret, $table, $name, $fields) {
+  $ret[] = update_sql(_db_create_index_sql($table, $name, $fields));
+}
+
+/**
+ * Drop an index.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   The table to be altered.
+ * @param $name
+ *   The name of the index.
+ */
+function db_drop_index(&$ret, $table, $name) {
+  $name = '{'. $table .'}_'. $name .'_idx';
+  $ret[] = update_sql('DROP INDEX '. $name);
+}
+
+/**
+ * Change a field definition.
+ *
+ * IMPORTANT NOTE: To maintain database portability, you have to explicitly
+ * recreate all indices and primary keys that are using the changed field.
+ *
+ * That means that you have to drop all affected keys and indexes with
+ * db_drop_{primary_key,unique_key,index}() before calling db_change_field().
+ * To recreate the keys and indices, pass the key definitions as the
+ * optional $new_keys argument directly to db_change_field().
+ *
+ * For example, suppose you have:
+ * @code
+ * $schema['foo'] = array(
+ *   'fields' => array(
+ *     'bar' => array('type' => 'int', 'not null' => TRUE)
+ *   ),
+ *   'primary key' => array('bar')
+ * );
+ * @endcode
+ * and you want to change foo.bar to be type serial, leaving it as the
+ * primary key.  The correct sequence is:
+ * @code
+ * db_drop_primary_key($ret, 'foo');
+ * db_change_field($ret, 'foo', 'bar', 'bar',
+ *   array('type' => 'serial', 'not null' => TRUE),
+ *   array('primary key' => array('bar')));
+ * @endcode
+ *
+ * The reasons for this are due to the different database engines:
+ *
+ * On PostgreSQL, changing a field definition involves adding a new field
+ * and dropping an old one which* causes any indices, primary keys and
+ * sequences (from serial-type fields) that use the changed field to be dropped.
+ *
+ * On MySQL, all type 'serial' fields must be part of at least one key
+ * or index as soon as they are created.  You cannot use
+ * db_add_{primary_key,unique_key,index}() for this purpose because
+ * the ALTER TABLE command will fail to add the column without a key
+ * or index specification.  The solution is to use the optional
+ * $new_keys argument to create the key or index at the same time as
+ * field.
+ *
+ * You could use db_add_{primary_key,unique_key,index}() in all cases
+ * unless you are converting a field to be type serial. You can use
+ * the $new_keys argument in all cases.
+ *
+ * @param $ret
+ *   Array to which query results will be added.
+ * @param $table
+ *   Name of the table.
+ * @param $field
+ *   Name of the field to change.
+ * @param $field_new
+ *   New name for the field (set to the same as $field if you don't want to change the name).
+ * @param $spec
+ *   The field specification for the new field.
+ * @param $new_keys
+ *   Optional keys and indexes specification to be created on the
+ *   table along with changing the field. The format is the same as a
+ *   table specification but without the 'fields' element.
+ */
+function db_change_field(&$ret, $table, $field, $field_new, $spec, $new_keys = array()) {
+  $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $field TO ". $field ."_old");
+  $not_null = isset($spec['not null']) ? $spec['not null'] : FALSE;
+  unset($spec['not null']);
+
+  db_add_field($ret, $table, "$field_new", $spec);
+
+  $ret[] = update_sql("UPDATE {". $table ."} SET $field_new = ". $field ."_old");
+
+  if ($not_null) {
+    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field_new SET NOT NULL");
+  }
+
+  db_drop_field($ret, $table, $field .'_old');
+
+  if (isset($new_keys)) {
+    _db_create_keys($ret, $table, $new_keys);
+  }
+}
+
+/**
+ * @} End of "ingroup schemaapi".
+ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/file.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,984 @@
+<?php
+// $Id: file.inc,v 1.121.2.1 2008/02/07 18:20:37 goba Exp $
+
+/**
+ * @file
+ * API for handling file uploads and server file management.
+ */
+
+/**
+ * @defgroup file File interface
+ * @{
+ * Common file handling functions.
+ */
+
+define('FILE_DOWNLOADS_PUBLIC', 1);
+define('FILE_DOWNLOADS_PRIVATE', 2);
+define('FILE_CREATE_DIRECTORY', 1);
+define('FILE_MODIFY_PERMISSIONS', 2);
+define('FILE_EXISTS_RENAME', 0);
+define('FILE_EXISTS_REPLACE', 1);
+define('FILE_EXISTS_ERROR', 2);
+
+/**
+ * A files status can be one of two values: temporary or permanent. The status
+ * for each file Drupal manages is stored in the {files} tables. If the status
+ * is temporary Drupal's file garbage collection will delete the file and
+ * remove it from the files table after a set period of time.
+ *
+ * If you wish to add custom statuses for use by contrib modules please expand as
+ * binary flags and consider the first 8 bits reserved. (0,1,2,4,8,16,32,64,128)
+ */
+define('FILE_STATUS_TEMPORARY', 0);
+define('FILE_STATUS_PERMANENT', 1);
+
+/**
+ * Create the download path to a file.
+ *
+ * @param $path A string containing the path of the file to generate URL for.
+ * @return A string containing a URL that can be used to download the file.
+ */
+function file_create_url($path) {
+  // Strip file_directory_path from $path. We only include relative paths in urls.
+  if (strpos($path, file_directory_path() .'/') === 0) {
+    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
+  }
+  switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
+    case FILE_DOWNLOADS_PUBLIC:
+      return $GLOBALS['base_url'] .'/'. file_directory_path() .'/'. str_replace('\\', '/', $path);
+    case FILE_DOWNLOADS_PRIVATE:
+      return url('system/files/'. $path, array('absolute' => TRUE));
+  }
+}
+
+/**
+ * Make sure the destination is a complete path and resides in the file system
+ * directory, if it is not prepend the file system directory.
+ *
+ * @param $dest A string containing the path to verify. If this value is
+ *   omitted, Drupal's 'files' directory will be used.
+ * @return A string containing the path to file, with file system directory
+ *   appended if necessary, or FALSE if the path is invalid (i.e. outside the
+ *   configured 'files' or temp directories).
+ */
+function file_create_path($dest = 0) {
+  $file_path = file_directory_path();
+  if (!$dest) {
+    return $file_path;
+  }
+  // file_check_location() checks whether the destination is inside the Drupal files directory.
+  if (file_check_location($dest, $file_path)) {
+    return $dest;
+  }
+  // check if the destination is instead inside the Drupal temporary files directory.
+  else if (file_check_location($dest, file_directory_temp())) {
+    return $dest;
+  }
+  // Not found, try again with prefixed directory path.
+  else if (file_check_location($file_path .'/'. $dest, $file_path)) {
+    return $file_path .'/'. $dest;
+  }
+  // File not found.
+  return FALSE;
+}
+
+/**
+ * Check that the directory exists and is writable. Directories need to
+ * have execute permissions to be considered a directory by FTP servers, etc.
+ *
+ * @param $directory A string containing the name of a directory path.
+ * @param $mode A Boolean value to indicate if the directory should be created
+ *   if it does not exist or made writable if it is read-only.
+ * @param $form_item An optional string containing the name of a form item that
+ *   any errors will be attached to. This is useful for settings forms that
+ *   require the user to specify a writable directory. If it can't be made to
+ *   work, a form error will be set preventing them from saving the settings.
+ * @return FALSE when directory not found, or TRUE when directory exists.
+ */
+function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
+  $directory = rtrim($directory, '/\\');
+
+  // Check if directory exists.
+  if (!is_dir($directory)) {
+    if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
+      drupal_set_message(t('The directory %directory has been created.', array('%directory' => $directory)));
+      @chmod($directory, 0775); // Necessary for non-webserver users.
+    }
+    else {
+      if ($form_item) {
+        form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
+      }
+      return FALSE;
+    }
+  }
+
+  // Check to see if the directory is writable.
+  if (!is_writable($directory)) {
+    if (($mode & FILE_MODIFY_PERMISSIONS) && @chmod($directory, 0775)) {
+      drupal_set_message(t('The permissions of directory %directory have been changed to make it writable.', array('%directory' => $directory)));
+    }
+    else {
+      form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
+      watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
+      return FALSE;
+    }
+  }
+
+  if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
+    $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
+    if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
+      fclose($fp);
+      chmod($directory .'/.htaccess', 0664);
+    }
+    else {
+      $variables = array('%directory' => $directory, '!htaccess' => '<br />'. nl2br(check_plain($htaccess_lines)));
+      form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables));
+      watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR);
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Checks path to see if it is a directory, or a dir/file.
+ *
+ * @param $path A string containing a file path. This will be set to the
+ *   directory's path.
+ * @return If the directory is not in a Drupal writable directory, FALSE is
+ *   returned. Otherwise, the base name of the path is returned.
+ */
+function file_check_path(&$path) {
+  // Check if path is a directory.
+  if (file_check_directory($path)) {
+    return '';
+  }
+
+  // Check if path is a possible dir/file.
+  $filename = basename($path);
+  $path = dirname($path);
+  if (file_check_directory($path)) {
+    return $filename;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Check if a file is really located inside $directory. Should be used to make
+ * sure a file specified is really located within the directory to prevent
+ * exploits.
+ *
+ * @code
+ *   // Returns FALSE:
+ *   file_check_location('/www/example.com/files/../../../etc/passwd', '/www/example.com/files');
+ * @endcode
+ *
+ * @param $source A string set to the file to check.
+ * @param $directory A string where the file should be located.
+ * @return 0 for invalid path or the real path of the source.
+ */
+function file_check_location($source, $directory = '') {
+  $check = realpath($source);
+  if ($check) {
+    $source = $check;
+  }
+  else {
+    // This file does not yet exist
+    $source = realpath(dirname($source)) .'/'. basename($source);
+  }
+  $directory = realpath($directory);
+  if ($directory && strpos($source, $directory) !== 0) {
+    return 0;
+  }
+  return $source;
+}
+
+/**
+ * Copies a file to a new location. This is a powerful function that in many ways
+ * performs like an advanced version of copy().
+ * - Checks if $source and $dest are valid and readable/writable.
+ * - Performs a file copy if $source is not equal to $dest.
+ * - If file already exists in $dest either the call will error out, replace the
+ *   file or rename the file based on the $replace parameter.
+ *
+ * @param $source A string specifying the file location of the original file.
+ *   This parameter will contain the resulting destination filename in case of
+ *   success.
+ * @param $dest A string containing the directory $source should be copied to.
+ *   If this value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ * @return True for success, FALSE for failure.
+ */
+function file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+  $dest = file_create_path($dest);
+
+  $directory = $dest;
+  $basename = file_check_path($directory);
+
+  // Make sure we at least have a valid directory.
+  if ($basename === FALSE) {
+    $source = is_object($source) ? $source->filepath : $source;
+    drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => $source, '%directory' => $dest)), 'error');
+    watchdog('file system', 'The selected file %file could not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => $source, '%directory' => $dest), WATCHDOG_ERROR);
+    return 0;
+  }
+
+  // Process a file upload object.
+  if (is_object($source)) {
+    $file = $source;
+    $source = $file->filepath;
+    if (!$basename) {
+      $basename = $file->filename;
+    }
+  }
+
+  $source = realpath($source);
+  if (!file_exists($source)) {
+    drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
+    return 0;
+  }
+
+  // If the destination file is not specified then use the filename of the source file.
+  $basename = $basename ? $basename : basename($source);
+  $dest = $directory .'/'. $basename;
+
+  // Make sure source and destination filenames are not the same, makes no sense
+  // to copy it if they are. In fact copying the file will most likely result in
+  // a 0 byte file. Which is bad. Real bad.
+  if ($source != realpath($dest)) {
+    if (!$dest = file_destination($dest, $replace)) {
+      drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
+      return FALSE;
+    }
+
+    if (!@copy($source, $dest)) {
+      drupal_set_message(t('The selected file %file could not be copied.', array('%file' => $source)), 'error');
+      return 0;
+    }
+
+    // Give everyone read access so that FTP'd users or
+    // non-webserver users can see/read these files,
+    // and give group write permissions so group members
+    // can alter files uploaded by the webserver.
+    @chmod($dest, 0664);
+  }
+
+  if (isset($file) && is_object($file)) {
+    $file->filename = $basename;
+    $file->filepath = $dest;
+    $source = $file;
+  }
+  else {
+    $source = $dest;
+  }
+
+  return 1; // Everything went ok.
+}
+
+/**
+ * Determines the destination path for a file depending on how replacement of
+ * existing files should be handled.
+ *
+ * @param $destination A string specifying the desired path.
+ * @param $replace Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
+ *     unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ * @return The destination file path or FALSE if the file already exists and
+ *   FILE_EXISTS_ERROR was specified.
+ */
+function file_destination($destination, $replace) {
+  if (file_exists($destination)) {
+    switch ($replace) {
+      case FILE_EXISTS_RENAME:
+        $basename = basename($destination);
+        $directory = dirname($destination);
+        $destination = file_create_filename($basename, $directory);
+        break;
+
+      case FILE_EXISTS_ERROR:
+        drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $destination)), 'error');
+        return FALSE;
+    }
+  }
+  return $destination;
+}
+
+/**
+ * Moves a file to a new location.
+ * - Checks if $source and $dest are valid and readable/writable.
+ * - Performs a file move if $source is not equal to $dest.
+ * - If file already exists in $dest either the call will error out, replace the
+ *   file or rename the file based on the $replace parameter.
+ *
+ * @param $source A string specifying the file location of the original file.
+ *   This parameter will contain the resulting destination filename in case of
+ *   success.
+ * @param $dest A string containing the directory $source should be copied to.
+ *   If this value is omitted, Drupal's 'files' directory will be used.
+ * @param $replace Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ * @return True for success, FALSE for failure.
+ */
+function file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
+  $path_original = is_object($source) ? $source->filepath : $source;
+
+  if (file_copy($source, $dest, $replace)) {
+    $path_current = is_object($source) ? $source->filepath : $source;
+
+    if ($path_original == $path_current || file_delete($path_original)) {
+      return 1;
+    }
+    drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
+  }
+  return 0;
+}
+
+/**
+ * Munge the filename as needed for security purposes. For instance the file
+ * name "exploit.php.pps" would become "exploit.php_.pps".
+ *
+ * @param $filename The name of a file to modify.
+ * @param $extensions A space separated list of extensions that should not
+ *   be altered.
+ * @param $alerts Whether alerts (watchdog, drupal_set_message()) should be
+ *   displayed.
+ * @return $filename The potentially modified $filename.
+ */
+function file_munge_filename($filename, $extensions, $alerts = TRUE) {
+  $original = $filename;
+
+  // Allow potentially insecure uploads for very savvy users and admin
+  if (!variable_get('allow_insecure_uploads', 0)) {
+    $whitelist = array_unique(explode(' ', trim($extensions)));
+
+    // Split the filename up by periods. The first part becomes the basename
+    // the last part the final extension.
+    $filename_parts = explode('.', $filename);
+    $new_filename = array_shift($filename_parts); // Remove file basename.
+    $final_extension = array_pop($filename_parts); // Remove final extension.
+
+    // Loop through the middle parts of the name and add an underscore to the
+    // end of each section that could be a file extension but isn't in the list
+    // of allowed extensions.
+    foreach ($filename_parts as $filename_part) {
+      $new_filename .= '.'. $filename_part;
+      if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) {
+        $new_filename .= '_';
+      }
+    }
+    $filename = $new_filename .'.'. $final_extension;
+
+    if ($alerts && $original != $filename) {
+      drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename)));
+    }
+  }
+
+  return $filename;
+}
+
+/**
+ * Undo the effect of upload_munge_filename().
+ *
+ * @param $filename string filename
+ * @return string
+ */
+function file_unmunge_filename($filename) {
+  return str_replace('_.', '.', $filename);
+}
+
+/**
+ * Create a full file path from a directory and filename. If a file with the
+ * specified name already exists, an alternative will be used.
+ *
+ * @param $basename string filename
+ * @param $directory string directory
+ * @return
+ */
+function file_create_filename($basename, $directory) {
+  $dest = $directory .'/'. $basename;
+
+  if (file_exists($dest)) {
+    // Destination file already exists, generate an alternative.
+    if ($pos = strrpos($basename, '.')) {
+      $name = substr($basename, 0, $pos);
+      $ext = substr($basename, $pos);
+    }
+    else {
+      $name = $basename;
+    }
+
+    $counter = 0;
+    do {
+      $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
+    } while (file_exists($dest));
+  }
+
+  return $dest;
+}
+
+/**
+ * Delete a file.
+ *
+ * @param $path A string containing a file path.
+ * @return TRUE for success, FALSE for failure.
+ */
+function file_delete($path) {
+  if (is_file($path)) {
+    return unlink($path);
+  }
+}
+
+/**
+ * Determine total disk space used by a single user or the whole filesystem.
+ *
+ * @param $uid
+ *   An optional user id. A NULL value returns the total space used
+ *   by all files.
+ */
+function file_space_used($uid = NULL) {
+  if (isset($uid)) {
+    return (int) db_result(db_query('SELECT SUM(filesize) FROM {files} WHERE uid = %d', $uid));
+  }
+  return (int) db_result(db_query('SELECT SUM(filesize) FROM {files}'));
+}
+
+/**
+ * Saves a file upload to a new location. The source file is validated as a
+ * proper upload and handled as such.
+ *
+ * The file will be added to the files table as a temporary file. Temporary files
+ * are periodically cleaned. To make the file permanent file call
+ * file_set_status() to change its status.
+ *
+ * @param $source
+ *   A string specifying the name of the upload field to save.
+ * @param $validators
+ *   An optional, associative array of callback functions used to validate the
+ *   file. The keys are function names and the values arrays of callback
+ *   parameters which will be passed in after the user and file objects. The
+ *   functions should return an array of error messages, an empty array
+ *   indicates that the file passed validation. The functions will be called in
+ *   the order specified.
+ * @param $dest
+ *   A string containing the directory $source should be copied to. If this is
+ *   not provided or is not writable, the temporary directory will be used.
+ * @param $replace
+ *   A boolean indicating whether an existing file of the same name in the
+ *   destination directory should overwritten. A false value will generate a
+ *   new, unique filename in the destination directory.
+ * @return
+ *   An object containing the file information, or 0 in the event of an error.
+ */
+function file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME) {
+  global $user;
+  static $upload_cache;
+
+  // Add in our check of the the file name length.
+  $validators['file_validate_name_length'] = array();
+
+  // Return cached objects without processing since the file will have
+  // already been processed and the paths in _FILES will be invalid.
+  if (isset($upload_cache[$source])) {
+    return $upload_cache[$source];
+  }
+
+  // If a file was uploaded, process it.
+  if (isset($_FILES['files']) && $_FILES['files']['name'][$source] && is_uploaded_file($_FILES['files']['tmp_name'][$source])) {
+    // Check for file upload errors and return FALSE if a
+    // lower level system error occurred.
+    switch ($_FILES['files']['error'][$source]) {
+      // @see http://php.net/manual/en/features.file-upload.errors.php
+      case UPLOAD_ERR_OK:
+        break;
+
+      case UPLOAD_ERR_INI_SIZE:
+      case UPLOAD_ERR_FORM_SIZE:
+        drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(file_upload_max_size()))), 'error');
+        return 0;
+
+      case UPLOAD_ERR_PARTIAL:
+      case UPLOAD_ERR_NO_FILE:
+        drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error');
+        return 0;
+
+        // Unknown error
+      default:
+        drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error');
+        return 0;
+    }
+
+    // Build the list of non-munged extensions.
+    // @todo: this should not be here. we need to figure out the right place.
+    $extensions = '';
+    foreach ($user->roles as $rid => $name) {
+      $extensions .= ' '. variable_get("upload_extensions_$rid",
+      variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp'));
+    }
+
+    // Begin building file object.
+    $file = new stdClass();
+    $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions);
+    $file->filepath = $_FILES['files']['tmp_name'][$source];
+    $file->filemime = $_FILES['files']['type'][$source];
+
+    // Rename potentially executable files, to help prevent exploits.
+    if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
+      $file->filemime = 'text/plain';
+      $file->filepath .= '.txt';
+      $file->filename .= '.txt';
+    }
+
+    // If the destination is not provided, or is not writable, then use the
+    // temporary directory.
+    if (empty($dest) || file_check_path($dest) === FALSE) {
+      $dest = file_directory_temp();
+    }
+
+    $file->source = $source;
+    $file->destination = file_destination(file_create_path($dest .'/'. $file->filename), $replace);
+    $file->filesize = $_FILES['files']['size'][$source];
+
+    // Call the validation functions.
+    $errors = array();
+    foreach ($validators as $function => $args) {
+      array_unshift($args, $file);
+      $errors = array_merge($errors, call_user_func_array($function, $args));
+    }
+
+    // Check for validation errors.
+    if (!empty($errors)) {
+      $message = t('The selected file %name could not be uploaded.', array('%name' => $file->filename));
+      if (count($errors) > 1) {
+        $message .= '<ul><li>'. implode('</li><li>', $errors) .'</li></ul>';
+      }
+      else {
+        $message .= ' '. array_pop($errors);
+      }
+      form_set_error($source, $message);
+      return 0;
+    }
+
+    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory.
+    // This overcomes open_basedir restrictions for future file operations.
+    $file->filepath = $file->destination;
+    if (!move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->filepath)) {
+      form_set_error($source, t('File upload error. Could not move uploaded file.'));
+      watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->filepath));
+      return 0;
+    }
+
+    // If we made it this far it's safe to record this file in the database.
+    $file->uid = $user->uid;
+    $file->status = FILE_STATUS_TEMPORARY;
+    $file->timestamp = time();
+    drupal_write_record('files', $file);
+
+    // Add file to the cache.
+    $upload_cache[$source] = $file;
+    return $file;
+  }
+  return 0;
+}
+
+/**
+ * Check for files with names longer than we can store in the database.
+ *
+ * @param $file
+ *   A Drupal file object.
+ * @return
+ *   An array. If the file name is too long, it will contain an error message.
+ */
+function file_validate_name_length($file) {
+  $errors = array();
+
+  if (strlen($file->filename) > 255) {
+    $errors[] = t('Its name exceeds the 255 characters limit. Please rename the file and try again.');
+  }
+  return $errors;
+}
+
+/**
+ * Check that the filename ends with an allowed extension. This check is not
+ * enforced for the user #1.
+ *
+ * @param $file
+ *   A Drupal file object.
+ * @param $extensions
+ *   A string with a space separated
+ * @return
+ *   An array. If the file extension is not allowed, it will contain an error message.
+ */
+function file_validate_extensions($file, $extensions) {
+  global $user;
+
+  $errors = array();
+
+  // Bypass validation for uid  = 1.
+  if ($user->uid != 1) {
+    $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i';
+    if (!preg_match($regex, $file->filename)) {
+      $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions));
+    }
+  }
+  return $errors;
+}
+
+/**
+ * Check that the file's size is below certain limits. This check is not
+ * enforced for the user #1.
+ *
+ * @param $file
+ *   A Drupal file object.
+ * @param $file_limit
+ *   An integer specifying the maximum file size in bytes. Zero indicates that
+ *   no limit should be enforced.
+ * @param $$user_limit
+ *   An integer specifying the maximum number of bytes the user is allowed. Zero
+ *   indicates that no limit should be enforced.
+ * @return
+ *   An array. If the file size exceeds limits, it will contain an error message.
+ */
+function file_validate_size($file, $file_limit = 0, $user_limit = 0) {
+  global $user;
+
+  $errors = array();
+
+  // Bypass validation for uid  = 1.
+  if ($user->uid != 1) {
+    if ($file_limit && $file->filesize > $file_limit) {
+      $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
+    }
+
+    $total_size = file_space_used($user->uid) + $file->filesize;
+    if ($user_limit && $total_size > $user_limit) {
+      $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
+    }
+  }
+  return $errors;
+}
+
+/**
+ * Check that the file is recognized by image_get_info() as an image.
+ *
+ * @param $file
+ *   A Drupal file object.
+ * @return
+ *   An array. If the file is not an image, it will contain an error message.
+ */
+function file_validate_is_image(&$file) {
+  $errors = array();
+
+  $info = image_get_info($file->filepath);
+  if (!$info || empty($info['extension'])) {
+    $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
+  }
+
+  return $errors;
+}
+
+/**
+ * If the file is an image verify that its dimensions are within the specified
+ * maximum and minimum dimensions. Non-image files will be ignored.
+ *
+ * @param $file
+ *   A Drupal file object. This function may resize the file affecting its size.
+ * @param $maximum_dimensions
+ *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
+ *   an image toolkit is installed the image will be resized down to these
+ *   dimensions. A value of 0 indicates no restriction on size, so resizing
+ *   will be attempted.
+ * @param $minimum_dimensions
+ *   An optional string in the form WIDTHxHEIGHT. This will check that the image
+ *   meets a minimum size. A value of 0 indicates no restriction.
+ * @return
+ *   An array. If the file is an image and did not meet the requirements, it
+ *   will contain an error message.
+ */
+function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
+  $errors = array();
+
+  // Check first that the file is an image.
+  if ($info = image_get_info($file->filepath)) {
+    if ($maximum_dimensions) {
+      // Check that it is smaller than the given dimensions.
+      list($width, $height) = explode('x', $maximum_dimensions);
+      if ($info['width'] > $width || $info['height'] > $height) {
+        // Try to resize the image to fit the dimensions.
+        if (image_get_toolkit() && image_scale($file->filepath, $file->filepath, $width, $height)) {
+          drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
+
+          // Clear the cached filesize and refresh the image information.
+          clearstatcache();
+          $info = image_get_info($file->filepath);
+          $file->filesize = $info['file_size'];
+        }
+        else {
+          $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
+        }
+      }
+    }
+
+    if ($minimum_dimensions) {
+      // Check that it is larger than the given dimensions.
+      list($width, $height) = explode('x', $minimum_dimensions);
+      if ($info['width'] < $width || $info['height'] < $height) {
+        $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions));
+      }
+    }
+  }
+
+  return $errors;
+}
+
+/**
+ * Save a string to the specified destination.
+ *
+ * @param $data A string containing the contents of the file.
+ * @param $dest A string containing the destination location.
+ * @param $replace Replace behavior when the destination file already exists.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file
+ *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
+ *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
+ *
+ * @return A string containing the resulting filename or 0 on error
+ */
+function file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
+  $temp = file_directory_temp();
+  // On Windows, tempnam() requires an absolute path, so we use realpath().
+  $file = tempnam(realpath($temp), 'file');
+  if (!$fp = fopen($file, 'wb')) {
+    drupal_set_message(t('The file could not be created.'), 'error');
+    return 0;
+  }
+  fwrite($fp, $data);
+  fclose($fp);
+
+  if (!file_move($file, $dest, $replace)) {
+    return 0;
+  }
+
+  return $file;
+}
+
+/**
+ * Set the status of a file.
+ *
+ * @param file A Drupal file object
+ * @param status A status value to set the file to.
+ * @return FALSE on failure, TRUE on success and $file->status will contain the
+ *     status.
+ */
+function file_set_status(&$file, $status) {
+  if (db_query('UPDATE {files} SET status = %d WHERE fid = %d', $status, $file->fid)) {
+    $file->status = $status;
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Transfer file using http to client. Pipes a file through Drupal to the
+ * client.
+ *
+ * @param $source File to transfer.
+ * @param $headers An array of http headers to send along with file.
+ */
+function file_transfer($source, $headers) {
+  ob_end_clean();
+
+  foreach ($headers as $header) {
+    // To prevent HTTP header injection, we delete new lines that are
+    // not followed by a space or a tab.
+    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+    $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
+    drupal_set_header($header);
+  }
+
+  $source = file_create_path($source);
+
+  // Transfer file in 1024 byte chunks to save memory usage.
+  if ($fd = fopen($source, 'rb')) {
+    while (!feof($fd)) {
+      print fread($fd, 1024);
+    }
+    fclose($fd);
+  }
+  else {
+    drupal_not_found();
+  }
+  exit();
+}
+
+/**
+ * Call modules that implement hook_file_download() to find out if a file is
+ * accessible and what headers it should be transferred with. If a module
+ * returns -1 drupal_access_denied() will be returned. If one or more modules
+ * returned headers the download will start with the returned headers. If no
+ * modules respond drupal_not_found() will be returned.
+ */
+function file_download() {
+  // Merge remainder of arguments from GET['q'], into relative file path.
+  $args = func_get_args();
+  $filepath = implode('/', $args);
+
+  // Maintain compatibility with old ?file=paths saved in node bodies.
+  if (isset($_GET['file'])) {
+    $filepath =  $_GET['file'];
+  }
+
+  if (file_exists(file_create_path($filepath))) {
+    $headers = module_invoke_all('file_download', $filepath);
+    if (in_array(-1, $headers)) {
+      return drupal_access_denied();
+    }
+    if (count($headers)) {
+      file_transfer($filepath, $headers);
+    }
+  }
+  return drupal_not_found();
+}
+
+
+/**
+ * Finds all files that match a given mask in a given directory.
+ * Directories and files beginning with a period are excluded; this
+ * prevents hidden files and directories (such as SVN working directories)
+ * from being scanned.
+ *
+ * @param $dir
+ *   The base directory for the scan, without trailing slash.
+ * @param $mask
+ *   The regular expression of the files to find.
+ * @param $nomask
+ *   An array of files/directories to ignore.
+ * @param $callback
+ *   The callback function to call for each match.
+ * @param $recurse
+ *   When TRUE, the directory scan will recurse the entire tree
+ *   starting at the provided directory.
+ * @param $key
+ *   The key to be used for the returned array of files. Possible
+ *   values are "filename", for the path starting with $dir,
+ *   "basename", for the basename of the file, and "name" for the name
+ *   of the file without an extension.
+ * @param $min_depth
+ *   Minimum depth of directories to return files from.
+ * @param $depth
+ *   Current depth of recursion. This parameter is only used internally and should not be passed.
+ *
+ * @return
+ *   An associative array (keyed on the provided key) of objects with
+ *   "path", "basename", and "name" members corresponding to the
+ *   matching files.
+ */
+function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename', $min_depth = 0, $depth = 0) {
+  $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
+  $files = array();
+
+  if (is_dir($dir) && $handle = opendir($dir)) {
+    while ($file = readdir($handle)) {
+      if (!in_array($file, $nomask) && $file[0] != '.') {
+        if (is_dir("$dir/$file") && $recurse) {
+          // Give priority to files in this folder by merging them in after any subdirectory files.
+          $files = array_merge(file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key, $min_depth, $depth + 1), $files);
+        }
+        elseif ($depth >= $min_depth && ereg($mask, $file)) {
+          // Always use this match over anything already set in $files with the same $$key.
+          $filename = "$dir/$file";
+          $basename = basename($file);
+          $name = substr($basename, 0, strrpos($basename, '.'));
+          $files[$$key] = new stdClass();
+          $files[$$key]->filename = $filename;
+          $files[$$key]->basename = $basename;
+          $files[$$key]->name = $name;
+          if ($callback) {
+            $callback($filename);
+          }
+        }
+      }
+    }
+
+    closedir($handle);
+  }
+
+  return $files;
+}
+
+/**
+ * Determine the default temporary directory.
+ *
+ * @return A string containing a temp directory.
+ */
+function file_directory_temp() {
+  $temporary_directory = variable_get('file_directory_temp', NULL);
+
+  if (is_null($temporary_directory)) {
+    $directories = array();
+
+    // Has PHP been set with an upload_tmp_dir?
+    if (ini_get('upload_tmp_dir')) {
+      $directories[] = ini_get('upload_tmp_dir');
+    }
+
+    // Operating system specific dirs.
+    if (substr(PHP_OS, 0, 3) == 'WIN') {
+      $directories[] = 'c:\\windows\\temp';
+      $directories[] = 'c:\\winnt\\temp';
+      $path_delimiter = '\\';
+    }
+    else {
+      $directories[] = '/tmp';
+      $path_delimiter = '/';
+    }
+
+    foreach ($directories as $directory) {
+      if (!$temporary_directory && is_dir($directory)) {
+        $temporary_directory = $directory;
+      }
+    }
+
+    // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp';
+    $temporary_directory = $temporary_directory ? $temporary_directory : file_directory_path() . $path_delimiter .'tmp';
+    variable_set('file_directory_temp', $temporary_directory);
+  }
+
+  return $temporary_directory;
+}
+
+/**
+ * Determine the default 'files' directory.
+ *
+ * @return A string containing the path to Drupal's 'files' directory.
+ */
+function file_directory_path() {
+  return variable_get('file_directory_path', conf_path() .'/files');
+}
+
+/**
+ * Determine the maximum file upload size by querying the PHP settings.
+ *
+ * @return
+ *   A file size limit in bytes based on the PHP upload_max_filesize and post_max_size
+ */
+function file_upload_max_size() {
+  static $max_size = -1;
+
+  if ($max_size < 0) {
+    $upload_max = parse_size(ini_get('upload_max_filesize'));
+    $post_max = parse_size(ini_get('post_max_size'));
+    $max_size = ($upload_max < $post_max) ? $upload_max : $post_max;
+  }
+  return $max_size;
+}
+
+/**
+ * @} End of "defgroup file".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/form.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2508 @@
+<?php
+// $Id: form.inc,v 1.265.2.4 2008/02/11 14:45:57 goba Exp $
+
+/**
+ * @defgroup forms Form builder functions
+ * @{
+ * Functions that build an abstract representation of a HTML form.
+ *
+ * All modules should declare their form builder functions to be in this
+ * group and each builder function should reference its validate and submit
+ * functions using \@see. Conversely, validate and submit functions should
+ * reference the form builder function using \@see. For examples, of this see
+ * system_modules_uninstall() or user_pass(), the latter of which has the
+ * following in its doxygen documentation:
+ *
+ * \@ingroup forms
+ * \@see user_pass_validate().
+ * \@see user_pass_submit().
+ *
+ * @} End of "defgroup forms".
+ */
+
+/**
+ * @defgroup form_api Form generation
+ * @{
+ * Functions to enable the processing and display of HTML forms.
+ *
+ * Drupal uses these functions to achieve consistency in its form processing and
+ * presentation, while simplifying code and reducing the amount of HTML that
+ * must be explicitly generated by modules.
+ *
+ * The drupal_get_form() function handles retrieving, processing, and
+ * displaying a rendered HTML form for modules automatically. For example:
+ *
+ * @code
+ * // Display the user registration form.
+ * $output = drupal_get_form('user_register');
+ * @endcode
+ *
+ * Forms can also be built and submitted programmatically without any user input
+ * using the drupal_execute() function.
+ *
+ * For information on the format of the structured arrays used to define forms,
+ * and more detailed explanations of the Form API workflow, see the
+ * @link http://api.drupal.org/api/file/developer/topics/forms_api_reference.html reference @endlink
+ * and the @link http://api.drupal.org/api/file/developer/topics/forms_api.html quickstart guide. @endlink
+ */
+
+/**
+ * Retrieves a form from a constructor function, or from the cache if
+ * the form was built in a previous page-load. The form is then passesed
+ * on for processing, after and rendered for display if necessary.
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form. If a function
+ *   with that name exists, it is called to build the form array.
+ *   Modules that need to generate the same form (or very similar forms)
+ *   using different $form_ids can implement hook_forms(), which maps
+ *   different $form_id values to the proper form constructor function. Examples
+ *   may be found in node_forms(), search_forms(), and user_forms().
+ * @param ...
+ *   Any additional arguments are passed on to the functions called by
+ *   drupal_get_form(), including the unique form constructor function.
+ *   For example, the node_edit form requires that a node object be passed
+ *   in here when it is called.
+ * @return
+ *   The rendered form.
+ */
+function drupal_get_form($form_id) {
+  $form_state = array('storage' => NULL, 'submitted' => FALSE);
+
+  $args = func_get_args();
+  $cacheable = FALSE;
+
+  if (isset($_SESSION['batch_form_state'])) {
+    // We've been redirected here after a batch processing : the form has
+    // already been processed, so we grab the post-process $form_state value
+    // and move on to form display. See _batch_finished() function.
+    $form_state = $_SESSION['batch_form_state'];
+    unset($_SESSION['batch_form_state']);
+  }
+  else {
+    // If the incoming $_POST contains a form_build_id, we'll check the
+    // cache for a copy of the form in question. If it's there, we don't
+    // have to rebuild the form to proceed. In addition, if there is stored
+    // form_state data from a previous step, we'll retrieve it so it can
+    // be passed on to the form processing code.
+    if (isset($_POST['form_id']) && $_POST['form_id'] == $form_id && !empty($_POST['form_build_id'])) {
+      $form = form_get_cache($_POST['form_build_id'], $form_state);
+    }
+
+    // If the previous bit of code didn't result in a populated $form
+    // object, we're hitting the form for the first time and we need
+    // to build it from scratch.
+    if (!isset($form)) {
+      $form_state['post'] = $_POST;
+      // Use a copy of the function's arguments for manipulation
+      $args_temp = $args;
+      $args_temp[0] = &$form_state;
+      array_unshift($args_temp, $form_id);
+
+      $form = call_user_func_array('drupal_retrieve_form', $args_temp);
+      $form_build_id = 'form-'. md5(mt_rand());
+      $form['#build_id'] = $form_build_id;
+      drupal_prepare_form($form_id, $form, $form_state);
+      // Store a copy of the unprocessed form for caching and indicate that it
+      // is cacheable if #cache will be set.
+      $original_form = $form;
+      $cacheable = TRUE;
+      unset($form_state['post']);
+    }
+    $form['#post'] = $_POST;
+
+    // Now that we know we have a form, we'll process it (validating,
+    // submitting, and handling the results returned by its submission
+    // handlers. Submit handlers accumulate data in the form_state by
+    // altering the $form_state variable, which is passed into them by
+    // reference.
+    drupal_process_form($form_id, $form, $form_state);
+    if ($cacheable && !empty($form['#cache'])) {
+      // Caching is done past drupal_process_form so #process callbacks can
+      // set #cache. By not sending the form state, we avoid storing
+      // $form_state['storage'].
+      form_set_cache($form_build_id, $original_form, NULL);
+    }
+  }
+
+  // Most simple, single-step forms will be finished by this point --
+  // drupal_process_form() usually redirects to another page (or to
+  // a 'fresh' copy of the form) once processing is complete. If one
+  // of the form's handlers has set $form_state['redirect'] to FALSE,
+  // the form will simply be re-rendered with the values still in its
+  // fields.
+  //
+  // If $form_state['storage'] or $form_state['rebuild'] have been
+  // set by any submit or validate handlers, however, we know that
+  // we're in a complex multi-part process of some sort and the form's
+  // workflow is NOT complete. We need to construct a fresh copy of
+  // the form, passing in the latest $form_state in addition to any
+  // other variables passed into drupal_get_form().
+
+  if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) {
+    $form = drupal_rebuild_form($form_id, $form_state, $args);
+  }
+
+  // If we haven't redirected to a new location by now, we want to
+  // render whatever form array is currently in hand.
+  return drupal_render_form($form_id, $form);
+}
+
+/**
+ * Retrieves a form, caches it and processes it with an empty $_POST.
+ *
+ * This function clears $_POST and passes the empty $_POST to the form_builder.
+ * To preserve some parts from $_POST, pass them in $form_state.
+ *
+ * If your AHAH callback simulates the pressing of a button, then your AHAH
+ * callback will need to do the same as what drupal_get_form would do when the
+ * button is pressed: get the form from the cache, run drupal_process_form over
+ * it and then if it needs rebuild, run drupal_rebuild_form over it. Then send
+ * back a part of the returned form.
+ * $form_state['clicked_button']['#array_parents'] will help you to find which
+ * part.
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form. If a function
+ *   with that name exists, it is called to build the form array.
+ *   Modules that need to generate the same form (or very similar forms)
+ *   using different $form_ids can implement hook_forms(), which maps
+ *   different $form_id values to the proper form constructor function. Examples
+ *   may be found in node_forms(), search_forms(), and user_forms().
+ * @param $form_state
+ *   A keyed array containing the current state of the form. Most
+ *   important is the $form_state['storage'] collection.
+ * @param $args
+ *   Any additional arguments are passed on to the functions called by
+ *   drupal_get_form(), plus the original form_state in the beginning. If you
+ *   are getting a form from the cache, use $form['#parameters'] to shift off
+ *   the $form_id from its beginning then the resulting array can be used as
+ *   $arg here.
+ * @param $form_build_id
+ *   If the AHAH callback calling this function only alters part of the form,
+ *   then pass in the existing form_build_id so we can re-cache with the same
+ *   csid.
+ * @return
+ *   The newly built form.
+ */
+function drupal_rebuild_form($form_id, &$form_state, $args, $form_build_id = NULL) {
+  // Remove the first argument. This is $form_id.when called from
+  // drupal_get_form and the original $form_state when called from some AHAH
+  // callback. Neither is needed. After that, put in the current state.
+  $args[0] = &$form_state;
+  // And the form_id.
+  array_unshift($args, $form_id);
+  $form = call_user_func_array('drupal_retrieve_form', $args);
+
+  if (!isset($form_build_id)) {
+    // We need a new build_id for the new version of the form.
+    $form_build_id = 'form-'. md5(mt_rand());
+  }
+  $form['#build_id'] = $form_build_id;
+  drupal_prepare_form($form_id, $form, $form_state);
+
+  // Now, we cache the form structure so it can be retrieved later for
+  // validation. If $form_state['storage'] is populated, we'll also cache
+  // it so that it can be used to resume complex multi-step processes.
+  form_set_cache($form_build_id, $form, $form_state);
+
+  // Clear out all post data, as we don't want the previous step's
+  // data to pollute this one and trigger validate/submit handling,
+  // then process the form for rendering.
+  $_POST = array();
+  $form['#post'] = array();
+  drupal_process_form($form_id, $form, $form_state);
+  return $form;
+}
+
+/**
+ * Fetch a form from cache.
+ */
+function form_get_cache($form_build_id, &$form_state) {
+  if ($cached = cache_get('form_'. $form_build_id, 'cache_form')) {
+    $form = $cached->data;
+    if ($cached = cache_get('storage_'. $form_build_id, 'cache_form')) {
+      $form_state['storage'] = $cached->data;
+    }
+    return $form;
+  }
+}
+
+/**
+ * Store a form in the cache
+ */
+function form_set_cache($form_build_id, $form, $form_state) {
+  $expire = max(ini_get('session.cookie_lifetime'), 86400);
+
+  cache_set('form_'. $form_build_id, $form, 'cache_form', $expire);
+  if (!empty($form_state['storage'])) {
+    cache_set('storage_'. $form_build_id, $form_state['storage'], 'cache_form', $expire);
+  }
+}
+
+/**
+ * Retrieves a form using a form_id, populates it with $form_state['values'],
+ * processes it, and returns any validation errors encountered. This
+ * function is the programmatic counterpart to drupal_get_form().
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form. If a function
+ *   with that name exists, it is called to build the form array.
+ *   Modules that need to generate the same form (or very similar forms)
+ *   using different $form_ids can implement hook_forms(), which maps
+ *   different $form_id values to the proper form constructor function. Examples
+ *   may be found in node_forms(), search_forms(), and user_forms().
+ * @param $form_state
+ *   A keyed array containing the current state of the form. Most
+ *   important is the $form_state['values'] collection, a tree of data
+ *   used to simulate the incoming $_POST information from a user's
+ *   form submission.
+ * @param ...
+ *   Any additional arguments are passed on to the functions called by
+ *   drupal_execute(), including the unique form constructor function.
+ *   For example, the node_edit form requires that a node object be passed
+ *   in here when it is called.
+ * For example:
+ *
+ * // register a new user
+ * $form_state = array();
+ * $form_state['values']['name'] = 'robo-user';
+ * $form_state['values']['mail'] = 'robouser@example.com';
+ * $form_state['values']['pass'] = 'password';
+ * $form_state['values']['op'] = t('Create new account');
+ * drupal_execute('user_register', $form_state);
+ *
+ * // Create a new node
+ * $form_state = array();
+ * module_load_include('inc', 'node', 'node.pages');
+ * $node = array('type' => 'story');
+ * $form_state['values']['title'] = 'My node';
+ * $form_state['values']['body'] = 'This is the body text!';
+ * $form_state['values']['name'] = 'robo-user';
+ * $form_state['values']['op'] = t('Save');
+ * drupal_execute('story_node_form', $form_state, (object)$node);
+ */
+function drupal_execute($form_id, &$form_state) {
+  $args = func_get_args();
+  $form = call_user_func_array('drupal_retrieve_form', $args);
+  $form['#post'] = $form_state['values'];
+  drupal_prepare_form($form_id, $form, $form_state);
+  drupal_process_form($form_id, $form, $form_state);
+}
+
+/**
+ * Retrieves the structured array that defines a given form.
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form. If a function
+ *   with that name exists, it is called to build the form array.
+ *   Modules that need to generate the same form (or very similar forms)
+ *   using different $form_ids can implement hook_forms(), which maps
+ *   different $form_id values to the proper form constructor function.
+ * @param $form_state
+ *   A keyed array containing the current state of the form.
+ * @param ...
+ *   Any additional arguments needed by the unique form constructor
+ *   function. Generally, these are any arguments passed into the
+ *   drupal_get_form() or drupal_execute() functions after the first
+ *   argument. If a module implements hook_forms(), it can examine
+ *   these additional arguments and conditionally return different
+ *   builder functions as well.
+ */
+function drupal_retrieve_form($form_id, &$form_state) {
+  static $forms;
+
+  // We save two copies of the incoming arguments: one for modules to use
+  // when mapping form ids to constructor functions, and another to pass to
+  // the constructor function itself. We shift out the first argument -- the
+  // $form_id itself -- from the list to pass into the constructor function,
+  // since it's already known.
+  $args = func_get_args();
+  $saved_args = $args;
+  array_shift($args);
+  if (isset($form_state)) {
+    array_shift($args);
+  }
+
+  // We first check to see if there's a function named after the $form_id.
+  // If there is, we simply pass the arguments on to it to get the form.
+  if (!function_exists($form_id)) {
+    // In cases where many form_ids need to share a central constructor function,
+    // such as the node editing form, modules can implement hook_forms(). It
+    // maps one or more form_ids to the correct constructor functions.
+    //
+    // We cache the results of that hook to save time, but that only works
+    // for modules that know all their form_ids in advance. (A module that
+    // adds a small 'rate this comment' form to each comment in a list
+    // would need a unique form_id for each one, for example.)
+    //
+    // So, we call the hook if $forms isn't yet populated, OR if it doesn't
+    // yet have an entry for the requested form_id.
+    if (!isset($forms) || !isset($forms[$form_id])) {
+      $forms = module_invoke_all('forms', $form_id, $args);
+    }
+    $form_definition = $forms[$form_id];
+    if (isset($form_definition['callback arguments'])) {
+      $args = array_merge($form_definition['callback arguments'], $args);
+    }
+    if (isset($form_definition['callback'])) {
+      $callback = $form_definition['callback'];
+    }
+  }
+
+  array_unshift($args, NULL);
+  $args[0] = &$form_state;
+
+  // If $callback was returned by a hook_forms() implementation, call it.
+  // Otherwise, call the function named after the form id.
+  $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);
+
+  // We store the original function arguments, rather than the final $arg
+  // value, so that form_alter functions can see what was originally
+  // passed to drupal_retrieve_form(). This allows the contents of #parameters
+  // to be saved and passed in at a later date to recreate the form.
+  $form['#parameters'] = $saved_args;
+  return $form;
+}
+
+/**
+ * This function is the heart of form API. The form gets built, validated and in
+ * appropriate cases, submitted.
+ *
+ * @param $form_id
+ *   The unique string identifying the current form.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. This
+ *   includes the current persistent storage data for the form, and
+ *   any data passed along by earlier steps when displaying a
+ *   multi-step form. Additional information, like the sanitized $_POST
+ *   data, is also accumulated here.
+ */
+function drupal_process_form($form_id, &$form, &$form_state) {
+  $form_state['values'] = array();
+
+  $form = form_builder($form_id, $form, $form_state);
+  // Only process the form if it is programmed or the form_id coming
+  // from the POST data is set and matches the current form_id.
+  if ((!empty($form['#programmed'])) || (!empty($form['#post']) && (isset($form['#post']['form_id']) && ($form['#post']['form_id'] == $form_id)))) {
+    drupal_validate_form($form_id, $form, $form_state);
+
+    // form_clean_id() maintains a cache of element IDs it has seen,
+    // so it can prevent duplicates. We want to be sure we reset that
+    // cache when a form is processed, so scenerios that result in
+    // the form being built behind the scenes and again for the
+    // browser don't increment all the element IDs needlessly.
+    form_clean_id(NULL, TRUE);
+
+    if ((!empty($form_state['submitted'])) && !form_get_errors() && empty($form_state['rebuild'])) {
+      $form_state['redirect'] = NULL;
+      form_execute_handlers('submit', $form, $form_state);
+
+      // We'll clear out the cached copies of the form and its stored data
+      // here, as we've finished with them. The in-memory copies are still
+      // here, though.
+      if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) {
+        cache_clear_all('form_'. $form_state['values']['form_build_id'], 'cache_form');
+        cache_clear_all('storage_'. $form_state['values']['form_build_id'], 'cache_form');
+      }
+
+      // If batches were set in the submit handlers, we process them now,
+      // possibly ending execution. We make sure we do not react to the batch
+      // that is already being processed (if a batch operation performs a
+      // drupal_execute).
+      if ($batch =& batch_get() && !isset($batch['current_set'])) {
+        // The batch uses its own copies of $form and $form_state for
+        // late execution of submit handers and post-batch redirection.
+        $batch['form'] = $form;
+        $batch['form_state'] = $form_state;
+        $batch['progressive'] = !$form['#programmed'];
+        batch_process();
+        // Execution continues only for programmatic forms.
+        // For 'regular' forms, we get redirected to the batch processing
+        // page. Form redirection will be handled in _batch_finished(),
+        // after the batch is processed.
+      }
+
+      // If no submit handlers have populated the $form_state['storage']
+      // bundle, and the $form_state['rebuild'] flag has not been set,
+      // we're finished and should redirect to a new destination page
+      // if one has been set (and a fresh, unpopulated copy of the form
+      // if one hasn't). If the form was called by drupal_execute(),
+      // however, we'll skip this and let the calling function examine
+      // the resulting $form_state bundle itself.
+      if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) {
+        drupal_redirect_form($form, $form_state['redirect']);
+      }
+    }
+  }
+}
+
+/**
+ * Prepares a structured form array by adding required elements,
+ * executing any hook_form_alter functions, and optionally inserting
+ * a validation token to prevent tampering.
+ *
+ * @param $form_id
+ *   A unique string identifying the form for validation, submission,
+ *   theming, and hook_form_alter functions.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. Passed
+ *   in here so that hook_form_alter() calls can use it, as well.
+ */
+function drupal_prepare_form($form_id, &$form, &$form_state) {
+  global $user;
+
+  $form['#type'] = 'form';
+  $form['#programmed'] = isset($form['#post']);
+
+  if (isset($form['#build_id'])) {
+    $form['form_build_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $form['#build_id'],
+      '#id' => $form['#build_id'],
+      '#name' => 'form_build_id',
+    );
+  }
+
+  // Add a token, based on either #token or form_id, to any form displayed to
+  // authenticated users. This ensures that any submitted form was actually
+  // requested previously by the user and protects against cross site request
+  // forgeries.
+  if (isset($form['#token'])) {
+    if ($form['#token'] === FALSE || $user->uid == 0 || $form['#programmed']) {
+      unset($form['#token']);
+    }
+    else {
+      $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token']));
+    }
+  }
+  else if (isset($user->uid) && $user->uid && !$form['#programmed']) {
+    $form['#token'] = $form_id;
+    $form['form_token'] = array(
+      '#id' => form_clean_id('edit-'. $form_id .'-form-token'),
+      '#type' => 'token',
+      '#default_value' => drupal_get_token($form['#token']),
+    );
+  }
+
+  if (isset($form_id)) {
+    $form['form_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $form_id,
+      '#id' => form_clean_id("edit-$form_id"),
+    );
+  }
+  if (!isset($form['#id'])) {
+    $form['#id'] = form_clean_id($form_id);
+  }
+
+  $form += _element_info('form');
+
+  if (!isset($form['#validate'])) {
+    if (function_exists($form_id .'_validate')) {
+      $form['#validate'] = array($form_id .'_validate');
+    }
+  }
+
+  if (!isset($form['#submit'])) {
+    if (function_exists($form_id .'_submit')) {
+      // We set submit here so that it can be altered.
+      $form['#submit'] = array($form_id .'_submit');
+    }
+  }
+
+  // Normally, we would call drupal_alter($form_id, $form, $form_state).
+  // However, drupal_alter() normally supports just one byref parameter. Using
+  // the __drupal_alter_by_ref key, we can store any additional parameters
+  // that need to be altered, and they'll be split out into additional params
+  // for the hook_form_alter() implementations.
+  // @todo: Remove this in Drupal 7.
+  $data = &$form;
+  $data['__drupal_alter_by_ref'] = array(&$form_state);
+  drupal_alter('form_'. $form_id, $data);
+
+  // __drupal_alter_by_ref is unset in the drupal_alter() function, we need
+  // to repopulate it to ensure both calls get the data.
+  $data['__drupal_alter_by_ref'] = array(&$form_state);
+  drupal_alter('form', $data, $form_id);
+}
+
+
+/**
+ * Validates user-submitted form data from the $form_state using
+ * the validate functions defined in a structured form array.
+ *
+ * @param $form_id
+ *   A unique string identifying the form for validation, submission,
+ *   theming, and hook_form_alter functions.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. The current
+ *   user-submitted data is stored in $form_state['values'], though
+ *   form validation functions are passed an explicit copy of the
+ *   values for the sake of simplicity. Validation handlers can also
+ *   $form_state to pass information on to submit handlers. For example:
+ *     $form_state['data_for_submision'] = $data;
+ *   This technique is useful when validation requires file parsing,
+ *   web service requests, or other expensive requests that should
+ *   not be repeated in the submission step.
+ */
+function drupal_validate_form($form_id, $form, &$form_state) {
+  static $validated_forms = array();
+
+  if (isset($validated_forms[$form_id])) {
+    return;
+  }
+
+  // If the session token was set by drupal_prepare_form(), ensure that it
+  // matches the current user's session.
+  if (isset($form['#token'])) {
+    if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
+      // Setting this error will cause the form to fail validation.
+      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
+    }
+  }
+
+  _form_validate($form, $form_state, $form_id);
+  $validated_forms[$form_id] = TRUE;
+}
+
+/**
+ * Renders a structured form array into themed HTML.
+ *
+ * @param $form_id
+ *   A unique string identifying the form for validation, submission,
+ *   theming, and hook_form_alter functions.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @return
+ *   A string containing the path of the page to display when processing
+ *   is complete.
+ */
+function drupal_render_form($form_id, &$form) {
+  // Don't override #theme if someone already set it.
+  if (!isset($form['#theme'])) {
+    init_theme();
+    $registry = theme_get_registry();
+    if (isset($registry[$form_id])) {
+      $form['#theme'] = $form_id;
+    }
+  }
+
+  $output = drupal_render($form);
+  return $output;
+}
+
+/**
+ * Redirect the user to a URL after a form has been processed.
+ *
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $redirect
+ *   An optional value containing the destination path to redirect
+ *   to if none is specified by the form.
+ */
+function drupal_redirect_form($form, $redirect = NULL) {
+  $goto = NULL;
+  if (isset($redirect)) {
+    $goto = $redirect;
+  }
+  if ($goto !== FALSE && isset($form['#redirect'])) {
+    $goto = $form['#redirect'];
+  }
+  if (!isset($goto) || ($goto !== FALSE)) {
+    if (isset($goto)) {
+      if (is_array($goto)) {
+        call_user_func_array('drupal_goto', $goto);
+      }
+      else {
+        drupal_goto($goto);
+      }
+    }
+    drupal_goto($_GET['q']);
+  }
+}
+
+/**
+ * Performs validation on form elements. First ensures required fields are
+ * completed, #maxlength is not exceeded, and selected options were in the
+ * list of options given to the user. Then calls user-defined validators.
+ *
+ * @param $elements
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. The current
+ *   user-submitted data is stored in $form_state['values'], though
+ *   form validation functions are passed an explicit copy of the
+ *   values for the sake of simplicity. Validation handlers can also
+ *   $form_state to pass information on to submit handlers. For example:
+ *     $form_state['data_for_submision'] = $data;
+ *   This technique is useful when validation requires file parsing,
+ *   web service requests, or other expensive requests that should
+ *   not be repeated in the submission step.
+ * @param $form_id
+ *   A unique string identifying the form for validation, submission,
+ *   theming, and hook_form_alter functions.
+ */
+function _form_validate($elements, &$form_state, $form_id = NULL) {
+  static $complete_form;
+
+  // Also used in the installer, pre-database setup.
+  $t = get_t();
+
+  // Recurse through all children.
+  foreach (element_children($elements) as $key) {
+    if (isset($elements[$key]) && $elements[$key]) {
+      _form_validate($elements[$key], $form_state);
+    }
+  }
+  // Validate the current input.
+  if (!isset($elements['#validated']) || !$elements['#validated']) {
+    if (isset($elements['#needs_validation'])) {
+      // Make sure a value is passed when the field is required.
+      // A simple call to empty() will not cut it here as some fields, like
+      // checkboxes, can return a valid value of '0'. Instead, check the
+      // length if it's a string, and the item count if it's an array.
+      if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0))) {
+        form_error($elements, $t('!name field is required.', array('!name' => $elements['#title'])));
+      }
+
+      // Verify that the value is not longer than #maxlength.
+      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
+        form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value']))));
+      }
+
+      if (isset($elements['#options']) && isset($elements['#value'])) {
+        if ($elements['#type'] == 'select') {
+          $options = form_options_flatten($elements['#options']);
+        }
+        else {
+          $options = $elements['#options'];
+        }
+        if (is_array($elements['#value'])) {
+          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
+          foreach ($value as $v) {
+            if (!isset($options[$v])) {
+              form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
+              watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
+            }
+          }
+        }
+        elseif (!isset($options[$elements['#value']])) {
+          form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
+          watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
+        }
+      }
+    }
+
+    // Call user-defined form level validators and store a copy of the full
+    // form so that element-specific validators can examine the entire structure
+    // if necessary.
+    if (isset($form_id)) {
+      form_execute_handlers('validate', $elements, $form_state);
+      $complete_form = $elements;
+    }
+    // Call any element-specific validators. These must act on the element
+    // #value data.
+    elseif (isset($elements['#element_validate'])) {
+      foreach ($elements['#element_validate'] as $function) {
+        if (function_exists($function))  {
+          $function($elements, $form_state, $complete_form);
+        }
+      }
+    }
+    $elements['#validated'] = TRUE;
+  }
+}
+
+/**
+ * A helper function used to execute custom validation and submission
+ * handlers for a given form. Button-specific handlers are checked
+ * first. If none exist, the function falls back to form-level handlers.
+ *
+ * @param $type
+ *   The type of handler to execute. 'validate' or 'submit' are the
+ *   defaults used by Form API.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. If the user
+ *   submitted the form by clicking a button with custom handler functions
+ *   defined, those handlers will be stored here.
+ */
+function form_execute_handlers($type, &$form, &$form_state) {
+  $return = FALSE;
+  if (isset($form_state[$type .'_handlers'])) {
+    $handlers = $form_state[$type .'_handlers'];
+  }
+  elseif (isset($form['#'. $type])) {
+    $handlers = $form['#'. $type];
+  }
+  else {
+    $handlers = array();
+  }
+
+  foreach ($handlers as $function) {
+    if (function_exists($function))  {
+      if ($type == 'submit' && ($batch =& batch_get())) {
+        // Some previous _submit handler has set a batch. We store the call
+        // in a special 'control' batch set, for execution at the correct
+        // time during the batch processing workflow.
+        $batch['sets'][] = array('form_submit' => $function);
+      }
+      else {
+        $function($form, $form_state);
+      }
+      $return = TRUE;
+    }
+  }
+  return $return;
+}
+
+/**
+ * File an error against a form element.
+ *
+ * @param $name
+ *   The name of the form element. If the #parents property of your form
+ *   element is array('foo', 'bar', 'baz') then you may set an error on 'foo'
+ *   or 'foo][bar][baz'. Setting an error on 'foo' sets an error for every
+ *   element where the #parents array starts with 'foo'.
+ * @param $message
+ *   The error message to present to the user.
+ * @return
+ *   Never use the return value of this function, use form_get_errors and
+ *   form_get_error instead.
+ */
+function form_set_error($name = NULL, $message = '') {
+  static $form = array();
+  if (isset($name) && !isset($form[$name])) {
+    $form[$name] = $message;
+    if ($message) {
+      drupal_set_message($message, 'error');
+    }
+  }
+  return $form;
+}
+
+/**
+ * Return an associative array of all errors.
+ */
+function form_get_errors() {
+  $form = form_set_error();
+  if (!empty($form)) {
+    return $form;
+  }
+}
+
+/**
+ * Return the error message filed against the form with the specified name.
+ */
+function form_get_error($element) {
+  $form = form_set_error();
+  $key = $element['#parents'][0];
+  if (isset($form[$key])) {
+    return $form[$key];
+  }
+  $key = implode('][', $element['#parents']);
+  if (isset($form[$key])) {
+    return $form[$key];
+  }
+}
+
+/**
+ * Flag an element as having an error.
+ */
+function form_error(&$element, $message = '') {
+  form_set_error(implode('][', $element['#parents']), $message);
+}
+
+/**
+ * Walk through the structured form array, adding any required
+ * properties to each element and mapping the incoming $_POST
+ * data to the proper elements.
+ *
+ * @param $form_id
+ *   A unique string identifying the form for validation, submission,
+ *   theming, and hook_form_alter functions.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form. In this
+ *   context, it is used to accumulate information about which button
+ *   was clicked when the form was submitted, as well as the sanitized
+ *   $_POST data.
+ */
+function form_builder($form_id, $form, &$form_state) {
+  static $complete_form, $cache;
+
+  // Initialize as unprocessed.
+  $form['#processed'] = FALSE;
+
+  // Use element defaults.
+  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
+    // Overlay $info onto $form, retaining preexisting keys in $form.
+    $form += $info;
+  }
+
+  if (isset($form['#type']) && $form['#type'] == 'form') {
+    $cache = NULL;
+    $complete_form = $form;
+    if (!empty($form['#programmed'])) {
+      $form_state['submitted'] = TRUE;
+    }
+  }
+
+  if (isset($form['#input']) && $form['#input']) {
+    _form_builder_handle_input_element($form_id, $form, $form_state, $complete_form);
+  }
+  $form['#defaults_loaded'] = TRUE;
+
+  // We start off assuming all form elements are in the correct order.
+  $form['#sorted'] = TRUE;
+
+  // Recurse through all child elements.
+  $count = 0;
+  foreach (element_children($form) as $key) {
+    $form[$key]['#post'] = $form['#post'];
+    $form[$key]['#programmed'] = $form['#programmed'];
+    // Don't squash an existing tree value.
+    if (!isset($form[$key]['#tree'])) {
+      $form[$key]['#tree'] = $form['#tree'];
+    }
+
+    // Deny access to child elements if parent is denied.
+    if (isset($form['#access']) && !$form['#access']) {
+      $form[$key]['#access'] = FALSE;
+    }
+
+    // Don't squash existing parents value.
+    if (!isset($form[$key]['#parents'])) {
+      // Check to see if a tree of child elements is present. If so,
+      // continue down the tree if required.
+      $form[$key]['#parents'] = $form[$key]['#tree'] && $form['#tree'] ? array_merge($form['#parents'], array($key)) : array($key);
+      $array_parents = isset($form['#array_parents']) ? $form['#array_parents'] : array();
+      $array_parents[] = $key;
+      $form[$key]['#array_parents'] = $array_parents;
+    }
+
+    // Assign a decimal placeholder weight to preserve original array order.
+    if (!isset($form[$key]['#weight'])) {
+      $form[$key]['#weight'] = $count/1000;
+    }
+    else {
+      // If one of the child elements has a weight then we will need to sort
+      // later.
+      unset($form['#sorted']);
+    }
+    $form[$key] = form_builder($form_id, $form[$key], $form_state);
+    $count++;
+  }
+
+  // The #after_build flag allows any piece of a form to be altered
+  // after normal input parsing has been completed.
+  if (isset($form['#after_build']) && !isset($form['#after_build_done'])) {
+    foreach ($form['#after_build'] as $function) {
+      $form = $function($form, $form_state);
+      $form['#after_build_done'] = TRUE;
+    }
+  }
+
+  // Now that we've processed everything, we can go back to handle the funky
+  // Internet Explorer button-click scenario.
+  _form_builder_ie_cleanup($form, $form_state);
+
+  // We shoud keep the buttons array until the IE clean up function
+  // has recognized the submit button so the form has been marked
+  // as submitted. If we already know which button was submitted,
+  // we don't need the array.
+  if (!empty($form_state['submitted'])) {
+    unset($form_state['buttons']);
+  }
+
+  // If some callback set #cache, we need to flip a static flag so later it
+  // can be found.
+  if (isset($form['#cache'])) {
+    $cache = $form['#cache'];
+  }
+  // We are on the top form, we can copy back #cache if it's set.
+  if (isset($form['#type']) && $form['#type'] == 'form' && isset($cache)) {
+    $form['#cache'] = TRUE;
+  }
+  return $form;
+}
+
+/**
+ * Populate the #value and #name properties of input elements so they
+ * can be processed and rendered. Also, execute any #process handlers
+ * attached to a specific element.
+ */
+function _form_builder_handle_input_element($form_id, &$form, &$form_state, $complete_form) {
+  if (!isset($form['#name'])) {
+    $name = array_shift($form['#parents']);
+    $form['#name'] = $name;
+    if ($form['#type'] == 'file') {
+      // To make it easier to handle $_FILES in file.inc, we place all
+      // file fields in the 'files' array. Also, we do not support
+      // nested file names.
+      $form['#name'] = 'files['. $form['#name'] .']';
+    }
+    elseif (count($form['#parents'])) {
+      $form['#name'] .= '['. implode('][', $form['#parents']) .']';
+    }
+    array_unshift($form['#parents'], $name);
+  }
+  if (!isset($form['#id'])) {
+    $form['#id'] = form_clean_id('edit-'. implode('-', $form['#parents']));
+  }
+
+  unset($edit);
+  if (!empty($form['#disabled'])) {
+    $form['#attributes']['disabled'] = 'disabled';
+  }
+
+  if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
+    $function = !empty($form['#value_callback']) ? $form['#value_callback'] : 'form_type_'. $form['#type'] .'_value';
+    if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && (isset($form['#post']['form_id']) && $form['#post']['form_id'] == $form_id))) {
+      $edit = $form['#post'];
+      foreach ($form['#parents'] as $parent) {
+        $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
+      }
+      if (!$form['#programmed'] || isset($edit)) {
+        // Call #type_value to set the form value;
+        if (function_exists($function)) {
+          $form['#value'] = $function($form, $edit);
+        }
+        if (!isset($form['#value']) && isset($edit)) {
+          $form['#value'] = $edit;
+        }
+      }
+      // Mark all posted values for validation.
+      if (isset($form['#value']) || (isset($form['#required']) && $form['#required'])) {
+        $form['#needs_validation'] = TRUE;
+      }
+    }
+    // Load defaults.
+    if (!isset($form['#value'])) {
+      // Call #type_value without a second argument to request default_value handling.
+      if (function_exists($function)) {
+        $form['#value'] = $function($form);
+      }
+      // Final catch. If we haven't set a value yet, use the explicit default value.
+      // Avoid image buttons (which come with garbage value), so we only get value
+      // for the button actually clicked.
+      if (!isset($form['#value']) && empty($form['#has_garbage_value'])) {
+        $form['#value'] = isset($form['#default_value']) ? $form['#default_value'] : '';
+      }
+    }
+  }
+
+  // Determine which button (if any) was clicked to submit the form.
+  // We compare the incoming values with the buttons defined in the form,
+  // and flag the one that matches. We have to do some funky tricks to
+  // deal with Internet Explorer's handling of single-button forms, though.
+  if (!empty($form['#post']) && isset($form['#executes_submit_callback'])) {
+    // First, accumulate a collection of buttons, divided into two bins:
+    // those that execute full submit callbacks and those that only validate.
+    $button_type = $form['#executes_submit_callback'] ? 'submit' : 'button';
+    $form_state['buttons'][$button_type][] = $form;
+
+    if (_form_button_was_clicked($form)) {
+      $form_state['submitted'] = $form_state['submitted'] || $form['#executes_submit_callback'];
+
+      // In most cases, we want to use form_set_value() to manipulate
+      // the global variables. In this special case, we want to make sure that
+      // the value of this element is listed in $form_variables under 'op'.
+      $form_state['values'][$form['#name']] = $form['#value'];
+      $form_state['clicked_button'] = $form;
+
+      if (isset($form['#validate'])) {
+        $form_state['validate_handlers'] = $form['#validate'];
+      }
+      if (isset($form['#submit'])) {
+        $form_state['submit_handlers'] = $form['#submit'];
+      }
+    }
+  }
+  // Allow for elements to expand to multiple elements, e.g., radios,
+  // checkboxes and files.
+  if (isset($form['#process']) && !$form['#processed']) {
+    foreach ($form['#process'] as $process) {
+      if (function_exists($process)) {
+        $form = $process($form, isset($edit) ? $edit : NULL, $form_state, $complete_form);
+      }
+    }
+    $form['#processed'] = TRUE;
+  }
+  form_set_value($form, $form['#value'], $form_state);
+}
+
+/**
+ * Helper function to handle the sometimes-convoluted logic of button
+ * click detection.
+ *
+ * In Internet Explorer, if ONLY one submit button is present, AND the
+ * enter key is used to submit the form, no form value is sent for it
+ * and we'll never detect a match. That special case is handled by
+ * _form_builder_ie_cleanup().
+ */
+function _form_button_was_clicked($form) {
+  // First detect normal 'vanilla' button clicks. Traditionally, all
+  // standard buttons on a form share the same name (usually 'op'),
+  // and the specific return value is used to determine which was
+  // clicked. This ONLY works as long as $form['#name'] puts the
+  // value at the top level of the tree of $_POST data.
+  if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) {
+    return TRUE;
+  }
+  // When image buttons are clicked, browsers do NOT pass the form element
+  // value in $_POST. Instead they pass an integer representing the
+  // coordinates of the click on the button image. This means that image
+  // buttons MUST have unique $form['#name'] values, but the details of
+  // their $_POST data should be ignored.
+  elseif (!empty($form['#has_garbage_value']) && isset($form['#value']) && $form['#value'] !== '') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * In IE, if only one submit button is present, AND the enter key is
+ * used to submit the form, no form value is sent for it and our normal
+ * button detection code will never detect a match. We call this
+ * function after all other button-detection is complete to check
+ * for the proper conditions, and treat the single button on the form
+ * as 'clicked' if they are met.
+ */
+function _form_builder_ie_cleanup($form, &$form_state) {
+  // Quick check to make sure we're always looking at the full form
+  // and not a sub-element.
+  if (!empty($form['#type']) && $form['#type'] == 'form') {
+    // If we haven't recognized a submission yet, and there's a single
+    // submit button, we know that we've hit the right conditions. Grab
+    // the first one and treat it as the clicked button.
+    if (empty($form_state['submitted']) && !empty($form_state['buttons']['submit']) && empty($form_state['buttons']['button'])) {
+      $button = $form_state['buttons']['submit'][0];
+
+      // Set up all the $form_state information that would have been
+      // populated had the button been recognized earlier.
+      $form_state['submitted'] = TRUE;
+      $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
+      $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
+      $form_state['values'][$button['#name']] = $button['#value'];
+      $form_state['clicked_button'] = $button;
+    }
+  }
+}
+
+/**
+ * Helper function to determine the value for an image button form element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_image_button_value($form, $edit = FALSE) {
+  if ($edit !== FALSE) {
+    if (!empty($edit)) {
+      // If we're dealing with Mozilla or Opera, we're lucky. It will
+      // return a proper value, and we can get on with things.
+      return $form['#return_value'];
+    }
+    else {
+      // Unfortunately, in IE we never get back a proper value for THIS
+      // form element. Instead, we get back two split values: one for the
+      // X and one for the Y coordinates on which the user clicked the
+      // button. We'll find this element in the #post data, and search
+      // in the same spot for its name, with '_x'.
+      $post = $form['#post'];
+      foreach (split('\[', $form['#name']) as $element_name) {
+        // chop off the ] that may exist.
+        if (substr($element_name, -1) == ']') {
+          $element_name = substr($element_name, 0, -1);
+        }
+
+        if (!isset($post[$element_name])) {
+          if (isset($post[$element_name .'_x'])) {
+            return $form['#return_value'];
+          }
+          return NULL;
+        }
+        $post = $post[$element_name];
+      }
+      return $form['#return_value'];
+    }
+  }
+}
+
+/**
+ * Helper function to determine the value for a checkbox form element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_checkbox_value($form, $edit = FALSE) {
+  if ($edit !== FALSE) {
+    return !empty($edit) ? $form['#return_value'] : 0;
+  }
+}
+
+/**
+ * Helper function to determine the value for a checkboxes form element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_checkboxes_value($form, $edit = FALSE) {
+  if ($edit === FALSE) {
+    $value = array();
+    $form += array('#default_value' => array());
+    foreach ($form['#default_value'] as $key) {
+      $value[$key] = 1;
+    }
+    return $value;
+  }
+  elseif (!isset($edit)) {
+    return array();
+  }
+}
+
+/**
+ * Helper function to determine the value for a password_confirm form
+ * element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_password_confirm_value($form, $edit = FALSE) {
+  if ($edit === FALSE) {
+    $form += array('#default_value' => array());
+    return $form['#default_value'] + array('pass1' => '', 'pass2' => '');
+  }
+}
+
+/**
+ * Helper function to determine the value for a select form element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_select_value($form, $edit = FALSE) {
+  if ($edit !== FALSE) {
+    if (isset($form['#multiple']) && $form['#multiple']) {
+      return (is_array($edit)) ? drupal_map_assoc($edit) : array();
+    }
+    else {
+      return $edit;
+    }
+  }
+}
+
+/**
+ * Helper function to determine the value for a textfield form element.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_textfield_value($form, $edit = FALSE) {
+  if ($edit !== FALSE) {
+    // Equate $edit to the form value to ensure it's marked for
+    // validation.
+    return str_replace(array("\r", "\n"), '', $edit);
+  }
+}
+
+/**
+ * Helper function to determine the value for form's token value.
+ *
+ * @param $form
+ *   The form element whose value is being populated.
+ * @param $edit
+ *   The incoming POST data to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ * @return
+ *   The data that will appear in the $form_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_token_value($form, $edit = FALSE) {
+  if ($edit !== FALSE) {
+    return (string)$edit;
+  }
+}
+
+/**
+ * Use this function to make changes to form values in the form validate
+ * phase, so they will be available in the submit phase in $form_state.
+ *
+ * Specifically, if $form['#parents'] is array('foo', 'bar')
+ * and $value is 'baz' then this function will make
+ * $form_state['values']['foo']['bar'] to be 'baz'.
+ *
+ * @param $form
+ *   The form item. Keys used: #parents, #value
+ * @param $value
+ *   The value for the form item.
+ */
+function form_set_value($form, $value, &$form_state) {
+  _form_set_value($form_state['values'], $form, $form['#parents'], $value);
+}
+
+/**
+ * Helper function for form_set_value().
+ *
+ * We iterate over $parents and create nested arrays for them
+ * in $form_state['values'] if needed. Then we insert the value into
+ * the right array.
+ */
+function _form_set_value(&$form_values, $form, $parents, $value) {
+  $parent = array_shift($parents);
+  if (empty($parents)) {
+    $form_values[$parent] = $value;
+  }
+  else {
+    if (!isset($form_values[$parent])) {
+      $form_values[$parent] = array();
+    }
+    _form_set_value($form_values[$parent], $form, $parents, $value);
+  }
+}
+
+/**
+ * Retrieve the default properties for the defined element type.
+ */
+function _element_info($type, $refresh = NULL) {
+  static $cache;
+
+  $basic_defaults = array(
+    '#description' => NULL,
+    '#attributes' => array(),
+    '#required' => FALSE,
+    '#tree' => FALSE,
+    '#parents' => array()
+  );
+  if (!isset($cache) || $refresh) {
+    $cache = array();
+    foreach (module_implements('elements') as $module) {
+      $elements = module_invoke($module, 'elements');
+      if (isset($elements) && is_array($elements)) {
+        $cache = array_merge_recursive($cache, $elements);
+      }
+    }
+    if (sizeof($cache)) {
+      foreach ($cache as $element_type => $info) {
+        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
+      }
+    }
+  }
+
+  return $cache[$type];
+}
+
+function form_options_flatten($array, $reset = TRUE) {
+  static $return;
+
+  if ($reset) {
+    $return = array();
+  }
+
+  foreach ($array as $key => $value) {
+    if (is_object($value)) {
+      form_options_flatten($value->option, FALSE);
+    }
+    else if (is_array($value)) {
+      form_options_flatten($value, FALSE);
+    }
+    else {
+      $return[$key] = 1;
+    }
+  }
+
+  return $return;
+}
+
+/**
+ * Format a dropdown menu or scrolling selection box.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, value, options, description, extra, multiple, required
+ * @return
+ *   A themed HTML string representing the form element.
+ *
+ * @ingroup themeable
+ *
+ * It is possible to group options together; to do this, change the format of
+ * $options to an associative array in which the keys are group labels, and the
+ * values are associative arrays in the normal $options format.
+ */
+function theme_select($element) {
+  $select = '';
+  $size = $element['#size'] ? ' size="'. $element['#size'] .'"' : '';
+  _form_set_class($element, array('form-select'));
+  $multiple = $element['#multiple'];
+  return theme('form_element', $element, '<select name="'. $element['#name'] .''. ($multiple ? '[]' : '') .'"'. ($multiple ? ' multiple="multiple" ' : '') . drupal_attributes($element['#attributes']) .' id="'. $element['#id'] .'" '. $size .'>'. form_select_options($element) .'</select>');
+}
+
+function form_select_options($element, $choices = NULL) {
+  if (!isset($choices)) {
+    $choices = $element['#options'];
+  }
+  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
+  // isset() fails in this situation.
+  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
+  $value_is_array = is_array($element['#value']);
+  $options = '';
+  foreach ($choices as $key => $choice) {
+    if (is_array($choice)) {
+      $options .= '<optgroup label="'. $key .'">';
+      $options .= form_select_options($element, $choice);
+      $options .= '</optgroup>';
+    }
+    elseif (is_object($choice)) {
+      $options .= form_select_options($element, $choice->option);
+    }
+    else {
+      $key = (string)$key;
+      if ($value_valid && (!$value_is_array && (string)$element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) {
+        $selected = ' selected="selected"';
+      }
+      else {
+        $selected = '';
+      }
+      $options .= '<option value="'. check_plain($key) .'"'. $selected .'>'. check_plain($choice) .'</option>';
+    }
+  }
+  return $options;
+}
+
+/**
+ * Traverses a select element's #option array looking for any values
+ * that hold the given key. Returns an array of indexes that match.
+ *
+ * This function is useful if you need to modify the options that are
+ * already in a form element; for example, to remove choices which are
+ * not valid because of additional filters imposed by another module.
+ * One example might be altering the choices in a taxonomy selector.
+ * To correctly handle the case of a multiple hierarchy taxonomy,
+ * #options arrays can now hold an array of objects, instead of a
+ * direct mapping of keys to labels, so that multiple choices in the
+ * selector can have the same key (and label). This makes it difficult
+ * to manipulate directly, which is why this helper function exists.
+ *
+ * This function does not support optgroups (when the elements of the
+ * #options array are themselves arrays), and will return FALSE if
+ * arrays are found. The caller must either flatten/restore or
+ * manually do their manipulations in this case, since returning the
+ * index is not sufficient, and supporting this would make the
+ * "helper" too complicated and cumbersome to be of any help.
+ *
+ * As usual with functions that can return array() or FALSE, do not
+ * forget to use === and !== if needed.
+ *
+ * @param $element
+ *   The select element to search.
+ * @param $key
+ *   The key to look for.
+ * @return
+ *   An array of indexes that match the given $key. Array will be
+ *   empty if no elements were found. FALSE if optgroups were found.
+ */
+function form_get_options($element, $key) {
+  $keys = array();
+  foreach ($element['#options'] as $index => $choice) {
+    if (is_array($choice)) {
+      return FALSE;
+    }
+    else if (is_object($choice)) {
+      if (isset($choice->option[$key])) {
+        $keys[] = $index;
+      }
+    }
+    else if ($index == $key) {
+      $keys[] = $index;
+    }
+  }
+  return $keys;
+}
+
+/**
+ * Format a group of form items.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: attributes, title, value, description, children, collapsible, collapsed
+ * @return
+ *   A themed HTML string representing the form item group.
+ *
+ * @ingroup themeable
+ */
+function theme_fieldset($element) {
+  if ($element['#collapsible']) {
+    drupal_add_js('misc/collapse.js');
+
+    if (!isset($element['#attributes']['class'])) {
+      $element['#attributes']['class'] = '';
+    }
+
+    $element['#attributes']['class'] .= ' collapsible';
+    if ($element['#collapsed']) {
+      $element['#attributes']['class'] .= ' collapsed';
+    }
+  }
+
+  return '<fieldset'. drupal_attributes($element['#attributes']) .'>'. ($element['#title'] ? '<legend>'. $element['#title'] .'</legend>' : '') . (isset($element['#description']) && $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '') . (!empty($element['#children']) ? $element['#children'] : '') . $element['#value'] ."</fieldset>\n";
+}
+
+/**
+ * Format a radio button.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: required, return_value, value, attributes, title, description
+ * @return
+ *   A themed HTML string representing the form item group.
+ *
+ * @ingroup themeable
+ */
+function theme_radio($element) {
+  _form_set_class($element, array('form-radio'));
+  $output = '<input type="radio" ';
+  $output .= 'name="'. $element['#name'] .'" ';
+  $output .= 'value="'. $element['#return_value'] .'" ';
+  $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
+  $output .= drupal_attributes($element['#attributes']) .' />';
+  if (!is_null($element['#title'])) {
+    $output = '<label class="option">'. $output .' '. $element['#title'] .'</label>';
+  }
+
+  unset($element['#title']);
+  return theme('form_element', $element, $output);
+}
+
+/**
+ * Format a set of radio buttons.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, value, options, description, required and attributes.
+ * @return
+ *   A themed HTML string representing the radio button set.
+ *
+ * @ingroup themeable
+ */
+function theme_radios($element) {
+  $class = 'form-radios';
+  if (isset($element['#attributes']['class'])) {
+    $class .= ' '. $element['#attributes']['class'];
+  }
+  $element['#children'] = '<div class="'. $class .'">'. (!empty($element['#children']) ? $element['#children'] : '') .'</div>';
+  if ($element['#title'] || $element['#description']) {
+    unset($element['#id']);
+    return theme('form_element', $element, $element['#children']);
+  }
+  else {
+    return $element['#children'];
+  }
+}
+
+/**
+ * Format a password_confirm item.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, value, id, required, error.
+ * @return
+ *   A themed HTML string representing the form item.
+ *
+ * @ingroup themeable
+ */
+function theme_password_confirm($element) {
+  return theme('form_element', $element, $element['#children']);
+}
+
+/**
+ * Expand a password_confirm field into two text boxes.
+ */
+function expand_password_confirm($element) {
+  $element['pass1'] =  array(
+    '#type' => 'password',
+    '#title' => t('Password'),
+    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
+    '#required' => $element['#required'],
+    '#attributes' => array('class' => 'password-field'),
+  );
+  $element['pass2'] =  array(
+    '#type' => 'password',
+    '#title' => t('Confirm password'),
+    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
+    '#required' => $element['#required'],
+    '#attributes' => array('class' => 'password-confirm'),
+  );
+  $element['#element_validate'] = array('password_confirm_validate');
+  $element['#tree'] = TRUE;
+
+  if (isset($element['#size'])) {
+    $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
+  }
+
+  return $element;
+}
+
+/**
+ * Validate password_confirm element.
+ */
+function password_confirm_validate($form, &$form_state) {
+  $pass1 = trim($form['pass1']['#value']);
+  if (!empty($pass1)) {
+    $pass2 = trim($form['pass2']['#value']);
+    if ($pass1 != $pass2) {
+      form_error($form, t('The specified passwords do not match.'));
+    }
+  }
+  elseif ($form['#required'] && !empty($form['#post'])) {
+    form_error($form, t('Password field is required.'));
+  }
+
+  // Password field must be converted from a two-element array into a single
+  // string regardless of validation results.
+  form_set_value($form['pass1'], NULL, $form_state);
+  form_set_value($form['pass2'], NULL, $form_state);
+  form_set_value($form, $pass1, $form_state);
+
+  return $form;
+
+}
+
+/**
+ * Format a date selection element.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, value, options, description, required and attributes.
+ * @return
+ *   A themed HTML string representing the date selection boxes.
+ *
+ * @ingroup themeable
+ */
+function theme_date($element) {
+  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
+}
+
+/**
+ * Roll out a single date element.
+ */
+function expand_date($element) {
+  // Default to current date
+  if (empty($element['#value'])) {
+    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
+                            'month' => format_date(time(), 'custom', 'n'),
+                            'year' => format_date(time(), 'custom', 'Y'));
+  }
+
+  $element['#tree'] = TRUE;
+
+  // Determine the order of day, month, year in the site's chosen date format.
+  $format = variable_get('date_format_short', 'm/d/Y - H:i');
+  $sort = array();
+  $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j'));
+  $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M'));
+  $sort['year'] = strpos($format, 'Y');
+  asort($sort);
+  $order = array_keys($sort);
+
+  // Output multi-selector for date.
+  foreach ($order as $type) {
+    switch ($type) {
+      case 'day':
+        $options = drupal_map_assoc(range(1, 31));
+        break;
+      case 'month':
+        $options = drupal_map_assoc(range(1, 12), 'map_month');
+        break;
+      case 'year':
+        $options = drupal_map_assoc(range(1900, 2050));
+        break;
+    }
+    $parents = $element['#parents'];
+    $parents[] = $type;
+    $element[$type] = array(
+      '#type' => 'select',
+      '#value' => $element['#value'][$type],
+      '#attributes' => $element['#attributes'],
+      '#options' => $options,
+    );
+  }
+
+  return $element;
+}
+
+/**
+ * Validates the date type to stop dates like February 30, 2006.
+ */
+function date_validate($form) {
+  if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) {
+    form_error($form, t('The specified date is invalid.'));
+  }
+}
+
+/**
+ * Helper function for usage with drupal_map_assoc to display month names.
+ */
+function map_month($month) {
+  return format_date(gmmktime(0, 0, 0, $month, 2, 1970), 'custom', 'M', 0);
+}
+
+/**
+ * If no default value is set for weight select boxes, use 0.
+ */
+function weight_value(&$form) {
+  if (isset($form['#default_value'])) {
+    $form['#value'] = $form['#default_value'];
+  }
+  else {
+    $form['#value'] = 0;
+  }
+}
+
+/**
+ * Roll out a single radios element to a list of radios,
+ * using the options array as index.
+ */
+function expand_radios($element) {
+  if (count($element['#options']) > 0) {
+    foreach ($element['#options'] as $key => $choice) {
+      if (!isset($element[$key])) {
+        // Generate the parents as the autogenerator does, so we will have a
+        // unique id for each radio button.
+        $parents_for_id = array_merge($element['#parents'], array($key));
+        $element[$key] = array(
+          '#type' => 'radio',
+          '#title' => $choice,
+          '#return_value' => check_plain($key),
+          '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
+          '#attributes' => $element['#attributes'],
+          '#parents' => $element['#parents'],
+          '#id' => form_clean_id('edit-'. implode('-', $parents_for_id)),
+        );
+      }
+    }
+  }
+  return $element;
+}
+
+/**
+ * Add AHAH information about a form element to the page to communicate with
+ * javascript. If #ahah[path] is set on an element, this additional javascript is
+ * added to the page header to attach the AHAH behaviors. See ahah.js for more
+ * information.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: ahah_event, ahah_path, ahah_wrapper, ahah_parameters,
+ *   ahah_effect.
+ * @return
+ *   None. Additional code is added to the header of the page using
+ *   drupal_add_js.
+ */
+function form_expand_ahah($element) {
+  static $js_added = array();
+  // Add a reasonable default event handler if none specified.
+  if (isset($element['#ahah']['path']) && !isset($element['#ahah']['event'])) {
+    switch ($element['#type']) {
+      case 'submit':
+      case 'button':
+      case 'image_button':
+        // Use the mousedown instead of the click event because form
+        // submission via pressing the enter key triggers a click event on
+        // submit inputs, inappropriately triggering AHAH behaviors.
+        $element['#ahah']['event'] = 'mousedown';
+        // Attach an additional event handler so that AHAH behaviours
+        // can be triggered still via keyboard input.
+        $element['#ahah']['keypress'] = TRUE;
+        break;
+      case 'password':
+      case 'textfield':
+      case 'textarea':
+        $element['#ahah']['event'] = 'blur';
+        break;
+      case 'radio':
+      case 'checkbox':
+      case 'select':
+        $element['#ahah']['event'] = 'change';
+        break;
+    }
+  }
+
+  // Adding the same javascript settings twice will cause a recursion error,
+  // we avoid the problem by checking if the javascript has already been added.
+  if (isset($element['#ahah']['path']) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
+    drupal_add_js('misc/jquery.form.js');
+    drupal_add_js('misc/ahah.js');
+
+    $ahah_binding = array(
+      'url'      => url($element['#ahah']['path']),
+      'event'    => $element['#ahah']['event'],
+      'keypress' => empty($element['#ahah']['keypress']) ? NULL : $element['#ahah']['keypress'],
+      'wrapper'  => empty($element['#ahah']['wrapper']) ? NULL : $element['#ahah']['wrapper'],
+      'selector' => empty($element['#ahah']['selector']) ? '#'. $element['#id'] : $element['#ahah']['selector'],
+      'effect'   => empty($element['#ahah']['effect']) ? 'none' : $element['#ahah']['effect'],
+      'method'   => empty($element['#ahah']['method']) ? 'replace' : $element['#ahah']['method'],
+      'progress' => empty($element['#ahah']['progress']) ? array('type' => 'throbber') : $element['#ahah']['progress'],
+      'button'   => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE,
+    );
+
+    // Convert a simple #ahah[progress] type string into an array.
+    if (is_string($ahah_binding['progress'])) {
+      $ahah_binding['progress'] = array('type' => $ahah_binding['progress']);
+    }
+    // Change progress path to a full url.
+    if (isset($ahah_binding['progress']['path'])) {
+      $ahah_binding['progress']['url'] = url($ahah_binding['progress']['path']);
+    }
+
+    // Add progress.js if we're doing a bar display.
+    if ($ahah_binding['progress']['type'] == 'bar') {
+      drupal_add_js('misc/progress.js');
+    }
+
+    drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting');
+
+    $js_added[$element['#id']] = TRUE;
+    $element['#cache'] = TRUE;
+  }
+  return $element;
+}
+
+/**
+ * Format a form item.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used:  title, value, description, required, error
+ * @return
+ *   A themed HTML string representing the form item.
+ *
+ * @ingroup themeable
+ */
+function theme_item($element) {
+  return theme('form_element', $element, $element['#value'] . (!empty($element['#children']) ? $element['#children'] : ''));
+}
+
+/**
+ * Format a checkbox.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used:  title, value, return_value, description, required
+ * @return
+ *   A themed HTML string representing the checkbox.
+ *
+ * @ingroup themeable
+ */
+function theme_checkbox($element) {
+  _form_set_class($element, array('form-checkbox'));
+  $checkbox = '<input ';
+  $checkbox .= 'type="checkbox" ';
+  $checkbox .= 'name="'. $element['#name'] .'" ';
+  $checkbox .= 'id="'. $element['#id'] .'" ' ;
+  $checkbox .= 'value="'. $element['#return_value'] .'" ';
+  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
+  $checkbox .= drupal_attributes($element['#attributes']) .' />';
+
+  if (!is_null($element['#title'])) {
+    $checkbox = '<label class="option">'. $checkbox .' '. $element['#title'] .'</label>';
+  }
+
+  unset($element['#title']);
+  return theme('form_element', $element, $checkbox);
+}
+
+/**
+ * Format a set of checkboxes.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ * @return
+ *   A themed HTML string representing the checkbox set.
+ *
+ * @ingroup themeable
+ */
+function theme_checkboxes($element) {
+  $class = 'form-checkboxes';
+  if (isset($element['#attributes']['class'])) {
+    $class .= ' '. $element['#attributes']['class'];
+  }
+  $element['#children'] = '<div class="'. $class .'">'. (!empty($element['#children']) ? $element['#children'] : '') .'</div>';
+  if ($element['#title'] || $element['#description']) {
+    unset($element['#id']);
+    return theme('form_element', $element, $element['#children']);
+  }
+  else {
+    return $element['#children'];
+  }
+}
+
+function expand_checkboxes($element) {
+  $value = is_array($element['#value']) ? $element['#value'] : array();
+  $element['#tree'] = TRUE;
+  if (count($element['#options']) > 0) {
+    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
+      $element['#default_value'] = array();
+    }
+    foreach ($element['#options'] as $key => $choice) {
+      if (!isset($element[$key])) {
+        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
+      }
+    }
+  }
+  return $element;
+}
+
+/**
+ * Theme a form submit button.
+ *
+ * @ingroup themeable
+ */
+function theme_submit($element) {
+  return theme('button', $element);
+}
+
+/**
+ * Theme a form button.
+ *
+ * @ingroup themeable
+ */
+function theme_button($element) {
+  // Make sure not to overwrite classes.
+  if (isset($element['#attributes']['class'])) {
+    $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
+  }
+  else {
+    $element['#attributes']['class'] = 'form-'. $element['#button_type'];
+  }
+
+  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'id="'. $element['#id'] .'" value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
+}
+
+/**
+ * Theme a form image button.
+ *
+ * @ingroup themeable
+ */
+function theme_image_button($element) {
+  // Make sure not to overwrite classes.
+  if (isset($element['#attributes']['class'])) {
+    $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
+  }
+  else {
+    $element['#attributes']['class'] = 'form-'. $element['#button_type'];
+  }
+
+  return '<input type="image" name="'. $element['#name'] .'" '.
+    (!empty($element['#value']) ? ('value="'. check_plain($element['#value']) .'" ') : '') .
+    'id="'. $element['#id'] .'" '.
+    drupal_attributes($element['#attributes']) .
+    ' src="'. base_path() . $element['#src'] .'" '.
+    (!empty($element['#title']) ? 'alt="'. check_plain($element['#title']) .'" title="'. check_plain($element['#title']) .'" ' : '' ) .
+    "/>\n";
+}
+
+/**
+ * Format a hidden form field.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used:  value, edit
+ * @return
+ *   A themed HTML string representing the hidden form field.
+ *
+ * @ingroup themeable
+ */
+function theme_hidden($element) {
+  return '<input type="hidden" name="'. $element['#name'] .'" id="'. $element['#id'] .'" value="'. check_plain($element['#value']) ."\" ". drupal_attributes($element['#attributes']) ." />\n";
+}
+
+/**
+ * Format a form token.
+ *
+ * @ingroup themeable
+ */
+function theme_token($element) {
+  return theme('hidden', $element);
+}
+
+/**
+ * Format a textfield.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
+ * @return
+ *   A themed HTML string representing the textfield.
+ *
+ * @ingroup themeable
+ */
+function theme_textfield($element) {
+  $size = empty($element['#size']) ? '' : ' size="'. $element['#size'] .'"';
+  $maxlength = empty($element['#maxlength']) ? '' : ' maxlength="'. $element['#maxlength'] .'"';
+  $class = array('form-text');
+  $extra = '';
+  $output = '';
+
+  if ($element['#autocomplete_path']) {
+    drupal_add_js('misc/autocomplete.js');
+    $class[] = 'form-autocomplete';
+    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) .'" disabled="disabled" />';
+  }
+  _form_set_class($element, $class);
+
+  if (isset($element['#field_prefix'])) {
+    $output .= '<span class="field-prefix">'. $element['#field_prefix'] .'</span> ';
+  }
+
+  $output .= '<input type="text"'. $maxlength .' name="'. $element['#name'] .'" id="'. $element['#id'] .'"'. $size .' value="'. check_plain($element['#value']) .'"'. drupal_attributes($element['#attributes']) .' />';
+
+  if (isset($element['#field_suffix'])) {
+    $output .= ' <span class="field-suffix">'. $element['#field_suffix'] .'</span>';
+  }
+
+  return theme('form_element', $element, $output) . $extra;
+}
+
+/**
+ * Format a form.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: action, method, attributes, children
+ * @return
+ *   A themed HTML string representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_form($element) {
+  // Anonymous div to satisfy XHTML compliance.
+  $action = $element['#action'] ? 'action="'. check_url($element['#action']) .'" ' : '';
+  return '<form '. $action .' accept-charset="UTF-8" method="'. $element['#method'] .'" id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
+}
+
+/**
+ * Format a textarea.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, value, description, rows, cols, required, attributes
+ * @return
+ *   A themed HTML string representing the textarea.
+ *
+ * @ingroup themeable
+ */
+function theme_textarea($element) {
+  $class = array('form-textarea');
+
+  // Add teaser behavior (must come before resizable)
+  if (!empty($element['#teaser'])) {
+    drupal_add_js('misc/teaser.js');
+    // Note: arrays are merged in drupal_get_js().
+    drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox'])), 'setting');
+    drupal_add_js(array('teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
+    $class[] = 'teaser';
+  }
+
+  // Add resizable behavior
+  if ($element['#resizable'] !== FALSE) {
+    drupal_add_js('misc/textarea.js');
+    $class[] = 'resizable';
+  }
+
+  _form_set_class($element, $class);
+  return theme('form_element', $element, '<textarea cols="'. $element['#cols'] .'" rows="'. $element['#rows'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. drupal_attributes($element['#attributes']) .'>'. check_plain($element['#value']) .'</textarea>');
+}
+
+/**
+ * Format HTML markup for use in forms.
+ *
+ * This is used in more advanced forms, such as theme selection and filter format.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used: value, children.
+ * @return
+ *   A themed HTML string representing the HTML markup.
+ *
+ * @ingroup themeable
+ */
+
+function theme_markup($element) {
+  return (isset($element['#value']) ? $element['#value'] : '') . (isset($element['#children']) ? $element['#children'] : '');
+}
+
+/**
+ * Format a password field.
+ *
+ * @param $element
+ *   An associative array containing the properties of the element.
+ *   Properties used:  title, value, description, size, maxlength, required, attributes
+ * @return
+ *   A themed HTML string representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_password($element) {
+  $size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
+  $maxlength = $element['#maxlength'] ? ' maxlength="'. $element['#maxlength'] .'" ' : '';
+
+  _form_set_class($element, array('form-text'));
+  $output = '<input type="password" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. $maxlength . $size . drupal_attributes($element['#attributes']) .' />';
+  return theme('form_element', $element, $output);
+}
+
+/**
+ * Expand weight elements into selects.
+ */
+function process_weight($element) {
+  for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
+    $weights[$n] = $n;
+  }
+  $element['#options'] = $weights;
+  $element['#type'] = 'select';
+  $element['#is_weight'] = TRUE;
+  $element += _element_info('select');
+  return $element;
+}
+
+/**
+ * Format a file upload field.
+ *
+ * @param $title
+ *   The label for the file upload field.
+ * @param $name
+ *   The internal name used to refer to the field.
+ * @param $size
+ *   A measure of the visible size of the field (passed directly to HTML).
+ * @param $description
+ *   Explanatory text to display after the form item.
+ * @param $required
+ *   Whether the user must upload a file to the field.
+ * @return
+ *   A themed HTML string representing the field.
+ *
+ * @ingroup themeable
+ *
+ * For assistance with handling the uploaded file correctly, see the API
+ * provided by file.inc.
+ */
+function theme_file($element) {
+  _form_set_class($element, array('form-file'));
+  return theme('form_element', $element, '<input type="file" name="'. $element['#name'] .'"'. ($element['#attributes'] ? ' '. drupal_attributes($element['#attributes']) : '') .' id="'. $element['#id'] .'" size="'. $element['#size'] ."\" />\n");
+}
+
+/**
+ * Return a themed form element.
+ *
+ * @param element
+ *   An associative array containing the properties of the element.
+ *   Properties used: title, description, id, required
+ * @param $value
+ *   The form element's data.
+ * @return
+ *   A string representing the form element.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element($element, $value) {
+  // This is also used in the installer, pre-database setup.
+  $t = get_t();
+
+  $output = '<div class="form-item"';
+  if (!empty($element['#id'])) {
+    $output .= ' id="'. $element['#id'] .'-wrapper"';
+  }
+  $output .= ">\n";
+  $required = !empty($element['#required']) ? '<span class="form-required" title="'. $t('This field is required.') .'">*</span>' : '';
+
+  if (!empty($element['#title'])) {
+    $title = $element['#title'];
+    if (!empty($element['#id'])) {
+      $output .= ' <label for="'. $element['#id'] .'">'. $t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
+    }
+    else {
+      $output .= ' <label>'. $t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
+    }
+  }
+
+  $output .= " $value\n";
+
+  if (!empty($element['#description'])) {
+    $output .= ' <div class="description">'. $element['#description'] ."</div>\n";
+  }
+
+  $output .= "</div>\n";
+
+  return $output;
+}
+
+/**
+ * Sets a form element's class attribute.
+ *
+ * Adds 'required' and 'error' classes as needed.
+ *
+ * @param &$element
+ *   The form element.
+ * @param $name
+ *   Array of new class names to be added.
+ */
+function _form_set_class(&$element, $class = array()) {
+  if ($element['#required']) {
+    $class[] = 'required';
+  }
+  if (form_get_error($element)) {
+    $class[] = 'error';
+  }
+  if (isset($element['#attributes']['class'])) {
+    $class[] = $element['#attributes']['class'];
+  }
+  $element['#attributes']['class'] = implode(' ', $class);
+}
+
+/**
+ * Prepare an HTML ID attribute string for a form item.
+ *
+ * Remove invalid characters and guarantee uniqueness.
+ *
+ * @param $id
+ *   The ID to clean.
+ * @param $flush
+ *   If set to TRUE, the function will flush and reset the static array
+ *   which is built to test the uniqueness of element IDs. This is only
+ *   used if a form has completed the validation process. This parameter
+ *   should never be set to TRUE if this function is being called to
+ *   assign an ID to the #ID element.
+ * @return
+ *   The cleaned ID.
+ */
+function form_clean_id($id = NULL, $flush = FALSE) {
+  static $seen_ids = array();
+
+  if ($flush) {
+    $seen_ids = array();
+    return;
+  }
+  $id = str_replace(array('][', '_', ' '), '-', $id);
+
+  // Ensure IDs are unique. The first occurrence is held but left alone.
+  // Subsequent occurrences get a number appended to them. This incrementing
+  // will almost certainly break code that relies on explicit HTML IDs in
+  // forms that appear more than once on the page, but the alternative is
+  // outputting duplicate IDs, which would break JS code and XHTML
+  // validity anyways. For now, it's an acceptable stopgap solution.
+  if (isset($seen_ids[$id])) {
+    $id = $id .'-'. $seen_ids[$id]++;
+  }
+  else {
+    $seen_ids[$id] = 1;
+  }
+
+  return $id;
+}
+
+/**
+ * @} End of "defgroup form_api".
+ */
+
+/**
+ * @defgroup batch Batch operations
+ * @{
+ * Functions allowing forms processing to be spread out over several page
+ * requests, thus ensuring that the processing does not get interrupted
+ * because of a PHP timeout, while allowing the user to receive feedback
+ * on the progress of the ongoing operations.
+ *
+ * The API is primarily designed to integrate nicely with the Form API
+ * workflow, but can also be used by non-FAPI scripts (like update.php)
+ * or even simple page callbacks (which should probably be used sparingly).
+ *
+ * Example:
+ * @code
+ * $batch = array(
+ *   'title' => t('Exporting'),
+ *   'operations' => array(
+ *     array('my_function_1', array($account->uid, 'story')),
+ *     array('my_function_2', array()),
+ *   ),
+ *   'finished' => 'my_finished_callback',
+ * );
+ * batch_set($batch);
+ * // only needed if not inside a form _submit handler :
+ * batch_process();
+ * @endcode
+ *
+ * Sample batch operations:
+ * @code
+ * // Simple and artificial: load a node of a given type for a given user
+ * function my_function_1($uid, $type, &$context) {
+ *   // The $context array gathers batch context information about the execution (read),
+ *   // as well as 'return values' for the current operation (write)
+ *   // The following keys are provided :
+ *   // 'results' (read / write): The array of results gathered so far by
+ *   //   the batch processing, for the current operation to append its own.
+ *   // 'message' (write): A text message displayed in the progress page.
+ *   // The following keys allow for multi-step operations :
+ *   // 'sandbox' (read / write): An array that can be freely used to
+ *   //   store persistent data between iterations. It is recommended to
+ *   //   use this instead of $_SESSION, which is unsafe if the user
+ *   //   continues browsing in a separate window while the batch is processing.
+ *   // 'finished' (write): A float number between 0 and 1 informing
+ *   //   the processing engine of the completion level for the operation.
+ *   //   1 (or no value explicitly set) means the operation is finished
+ *   //   and the batch processing can continue to the next operation.
+ *
+ *   $node = node_load(array('uid' => $uid, 'type' => $type));
+ *   $context['results'][] = $node->nid .' : '. $node->title;
+ *   $context['message'] = $node->title;
+ * }
+ *
+ * // More advanced example: multi-step operation - load all nodes, five by five
+ * function my_function_2(&$context) {
+ *   if (empty($context['sandbox'])) {
+ *     $context['sandbox']['progress'] = 0;
+ *     $context['sandbox']['current_node'] = 0;
+ *     $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
+ *   }
+ *   $limit = 5;
+ *   $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
+ *   while ($row = db_fetch_array($result)) {
+ *     $node = node_load($row['nid'], NULL, TRUE);
+ *     $context['results'][] = $node->nid .' : '. $node->title;
+ *     $context['sandbox']['progress']++;
+ *     $context['sandbox']['current_node'] = $node->nid;
+ *     $context['message'] = $node->title;
+ *   }
+ *   if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ *     $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ *   }
+ * }
+ * @endcode
+ *
+ * Sample 'finished' callback:
+ * @code
+ * function batch_test_finished($success, $results, $operations) {
+ *   if ($success) {
+ *     $message = format_plural(count($results), 'One post processed.', '@count posts processed.');
+ *   }
+ *   else {
+ *     $message = t('Finished with an error.');
+ *   }
+ *   drupal_set_message($message);
+ *   // Providing data for the redirected page is done through $_SESSION.
+ *   foreach ($results as $result) {
+ *     $items[] = t('Loaded node %title.', array('%title' => $result));
+ *   }
+ *   $_SESSION['my_batch_results'] = $items;
+ * }
+ * @endcode
+ */
+
+/**
+ * Open a new batch.
+ *
+ * @param $batch
+ *   An array defining the batch. The following keys can be used:
+ *     'operations': an array of function calls to be performed.
+ *        Example:
+ *        @code
+ *        array(
+ *          array('my_function_1', array($arg1)),
+ *          array('my_function_2', array($arg2_1, $arg2_2)),
+ *        )
+ *        @endcode
+ *     All the other values below are optional.
+ *     batch_init() provides default values for the messages.
+ *     'title': title for the progress page.
+ *       Defaults to t('Processing').
+ *     'init_message': message displayed while the processing is initialized.
+ *       Defaults to t('Initializing.').
+ *     'progress_message': message displayed while processing the batch.
+ *       Available placeholders are @current, @remaining, @total and @percent.
+ *       Defaults to t('Remaining @remaining of @total.').
+ *     'error_message': message displayed if an error occurred while processing
+ *       the batch.
+ *       Defaults to t('An error has occurred.').
+ *     'finished': the name of a function to be executed after the batch has
+ *       completed. This should be used to perform any result massaging that
+ *       may be needed, and possibly save data in $_SESSION for display after
+ *       final page redirection.
+ *     'file': the path to the file containing the definitions of the
+ *       'operations' and 'finished' functions, for instance if they don't
+ *       reside in the original '.module' file. The path should be relative to
+ *       the base_path(), and thus should be built using drupal_get_path().
+ *
+ * Operations are added as new batch sets. Batch sets are used to ensure
+ * clean code independence, ensuring that several batches submitted by
+ * different parts of the code (core / contrib modules) can be processed
+ * correctly while not interfering or having to cope with each other. Each
+ * batch set gets to specify his own UI messages, operates on its own set
+ * of operations and results, and triggers its own 'finished' callback.
+ * Batch sets are processed sequentially, with the progress bar starting
+ * fresh for every new set.
+ */
+function batch_set($batch_definition) {
+  if ($batch_definition) {
+    $batch =& batch_get();
+    // Initialize the batch
+    if (empty($batch)) {
+      $batch = array(
+        'sets' => array(),
+      );
+    }
+
+    $init = array(
+      'sandbox' => array(),
+      'results' => array(),
+      'success' => FALSE,
+    );
+    // Use get_t() to allow batches at install time.
+    $t = get_t();
+    $defaults = array(
+      'title' => $t('Processing'),
+      'init_message' => $t('Initializing.'),
+      'progress_message' => $t('Remaining @remaining of @total.'),
+      'error_message' => $t('An error has occurred.'),
+    );
+    $batch_set = $init + $batch_definition + $defaults;
+
+    // Tweak init_message to avoid the bottom of the page flickering down after init phase.
+    $batch_set['init_message'] .= '<br/>&nbsp;';
+    $batch_set['total'] = count($batch_set['operations']);
+
+    // If the batch is being processed (meaning we are executing a stored submit handler),
+    // insert the new set after the current one.
+    if (isset($batch['current_set'])) {
+      // array_insert does not exist...
+      $slice1 = array_slice($batch['sets'], 0, $batch['current_set'] + 1);
+      $slice2 = array_slice($batch['sets'], $batch['current_set'] + 1);
+      $batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
+    }
+    else {
+      $batch['sets'][] = $batch_set;
+    }
+  }
+}
+
+/**
+ * Process the batch.
+ *
+ * Unless the batch has been marked with 'progressive' = FALSE, the function
+ * issues a drupal_goto and thus ends page execution.
+ *
+ * This function is not needed in form submit handlers; Form API takes care
+ * of batches that were set during form submission.
+ *
+ * @param $redirect
+ *   (optional) Path to redirect to when the batch has finished processing.
+ * @param $url
+ *   (optional - should only be used for separate scripts like update.php)
+ *   URL of the batch processing page.
+ */
+function batch_process($redirect = NULL, $url = NULL) {
+  $batch =& batch_get();
+
+  if (isset($batch)) {
+    // Add process information
+    $url = isset($url) ? $url : 'batch';
+    $process_info = array(
+      'current_set' => 0,
+      'progressive' => TRUE,
+      'url' => isset($url) ? $url : 'batch',
+      'source_page' => $_GET['q'],
+      'redirect' => $redirect,
+    );
+    $batch += $process_info;
+
+    if ($batch['progressive']) {
+      // Clear the way for the drupal_goto redirection to the batch processing
+      // page, by saving and unsetting the 'destination' if any, on both places
+      // drupal_goto looks for it.
+      if (isset($_REQUEST['destination'])) {
+        $batch['destination'] = $_REQUEST['destination'];
+        unset($_REQUEST['destination']);
+      }
+      elseif (isset($_REQUEST['edit']['destination'])) {
+        $batch['destination'] = $_REQUEST['edit']['destination'];
+        unset($_REQUEST['edit']['destination']);
+      }
+
+      // Initiate db storage in order to get a batch id. We have to provide
+      // at least an empty string for the (not null) 'token' column.
+      db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time());
+      $batch['id'] = db_last_insert_id('batch', 'bid');
+
+      // Now that we have a batch id, we can generate the redirection link in
+      // the generic error message.
+      $t = get_t();
+      $batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
+
+      // Actually store the batch data and the token generated form the batch id.
+      db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']);
+
+      drupal_goto($batch['url'], 'op=start&id='. $batch['id']);
+    }
+    else {
+      // Non-progressive execution: bypass the whole progressbar workflow
+      // and execute the batch in one pass.
+      require_once './includes/batch.inc';
+      _batch_process();
+    }
+  }
+}
+
+/**
+ * Retrieve the current batch.
+ */
+function &batch_get() {
+  static $batch = array();
+  return $batch;
+}
+
+/**
+ * @} End of "defgroup batch".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/image.gd.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,221 @@
+<?php
+// $Id: image.gd.inc,v 1.4 2008/01/15 10:17:42 goba Exp $
+
+/**
+ * @file
+ * GD2 toolkit for image manipulation within Drupal.
+ */
+
+/**
+ * @ingroup image
+ * @{
+ */
+
+/**
+ * Retrieve information about the toolkit.
+ */
+function image_gd_info() {
+  return array('name' => 'gd', 'title' => t('GD2 image manipulation toolkit'));
+}
+
+/**
+ * Retrieve settings for the GD2 toolkit.
+ */
+function image_gd_settings() {
+  if (image_gd_check_settings()) {
+    $form = array();
+    $form['status'] = array(
+      '#value' => t('The GD toolkit is installed and working properly.')
+    );
+
+    $form['image_jpeg_quality'] = array(
+      '#type' => 'textfield',
+      '#title' => t('JPEG quality'),
+      '#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
+      '#size' => 10,
+      '#maxlength' => 3,
+      '#default_value' => variable_get('image_jpeg_quality', 75),
+      '#field_suffix' => t('%'),
+    );
+    $form['#element_validate'] = array('image_gd_settings_validate');
+    
+    return $form;
+  }
+  else {
+    form_set_error('image_toolkit', t('The GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see <a href="@url">PHP\'s image documentation</a>.', array('@url' => 'http://php.net/image')));
+    return FALSE;
+  }
+}
+
+/**
+ * Validate the submitted GD settings.
+ */
+function image_gd_settings_validate($form, &$form_state) {
+  // Validate image quality range.
+  $value = $form_state['values']['image_jpeg_quality'];
+  if (!is_numeric($value) || $value < 0 || $value > 100) {
+    form_set_error('image_jpeg_quality', t('JPEG quality must be a number between 0 and 100.'));
+  }
+}
+
+/**
+ * Verify GD2 settings (that the right version is actually installed).
+ *
+ * @return
+ *   A boolean indicating if the GD toolkit is avaiable on this machine.
+ */
+function image_gd_check_settings() {
+  if ($check = get_extension_funcs('gd')) {
+    if (in_array('imagegd2', $check)) {
+      // GD2 support is available.
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Scale an image to the specified size using GD.
+ */
+function image_gd_resize($source, $destination, $width, $height) {
+  if (!file_exists($source)) {
+    return FALSE;
+  }
+
+  $info = image_get_info($source);
+  if (!$info) {
+    return FALSE;
+  }
+
+  $im = image_gd_open($source, $info['extension']);
+  if (!$im) {
+    return FALSE;
+  }
+
+  $res = imagecreatetruecolor($width, $height);
+  if ($info['extension'] == 'png') {
+    $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
+    imagealphablending($res, FALSE);
+    imagefilledrectangle($res, 0, 0, $width, $height, $transparency);
+    imagealphablending($res, TRUE);
+    imagesavealpha($res, TRUE);
+  }
+  elseif ($info['extension'] == 'gif') {
+    // If we have a specific transparent color.
+    $transparency_index = imagecolortransparent($im);
+    if ($transparency_index >= 0) {
+      // Get the original image's transparent color's RGB values.
+      $transparent_color = imagecolorsforindex($im, $transparency_index);
+      // Allocate the same color in the new image resource.
+      $transparency_index = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+      // Completely fill the background of the new image with allocated color.
+      imagefill($res, 0, 0, $transparency_index);
+      // Set the background color for new image to transparent.
+      imagecolortransparent($res, $transparency_index);
+      // Find number of colors in the images palette.
+      $number_colors = imagecolorstotal($im);
+      // Convert from true color to palette to fix transparency issues.
+      imagetruecolortopalette($res, TRUE, $number_colors);
+    }
+  }
+  imagecopyresampled($res, $im, 0, 0, 0, 0, $width, $height, $info['width'], $info['height']);
+  $result = image_gd_close($res, $destination, $info['extension']);
+
+  imagedestroy($res);
+  imagedestroy($im);
+
+  return $result;
+}
+
+/**
+ * Rotate an image the given number of degrees.
+ */
+function image_gd_rotate($source, $destination, $degrees, $background = 0x000000) {
+  if (!function_exists('imageRotate')) {
+    return FALSE;
+  }
+
+  $info = image_get_info($source);
+  if (!$info) {
+    return FALSE;
+  }
+
+  $im = image_gd_open($source, $info['extension']);
+  if (!$im) {
+    return FALSE;
+  }
+
+  $res = imageRotate($im, $degrees, $background);
+  $result = image_gd_close($res, $destination, $info['extension']);
+
+  return $result;
+}
+
+/**
+ * Crop an image using the GD toolkit.
+ */
+function image_gd_crop($source, $destination, $x, $y, $width, $height) {
+  $info = image_get_info($source);
+  if (!$info) {
+    return FALSE;
+  }
+
+  $im = image_gd_open($source, $info['extension']);
+  $res = imageCreateTrueColor($width, $height);
+  imageCopy($res, $im, 0, 0, $x, $y, $width, $height);
+  $result = image_gd_close($res, $destination, $info['extension']);
+
+  imageDestroy($res);
+  imageDestroy($im);
+
+  return $result;
+}
+
+/**
+ * GD helper function to create an image resource from a file.
+ *
+ * @param $file
+ *   A string file path where the iamge should be saved.
+ * @param $extension
+ *   A string containing one of the following extensions: gif, jpg, jpeg, png.
+ * @return
+ *   An image resource, or FALSE on error.
+ */
+function image_gd_open($file, $extension) {
+  $extension = str_replace('jpg', 'jpeg', $extension);
+  $open_func = 'imageCreateFrom'. $extension;
+  if (!function_exists($open_func)) {
+    return FALSE;
+  }
+  return $open_func($file);
+}
+
+/**
+ * GD helper to write an image resource to a destination file.
+ *
+ * @param $res
+ *   An image resource created with image_gd_open().
+ * @param $destination
+ *   A string file path where the iamge should be saved.
+ * @param $extension
+ *   A string containing one of the following extensions: gif, jpg, jpeg, png.
+ * @return
+ *   Boolean indicating success.
+ */
+function image_gd_close($res, $destination, $extension) {
+  $extension = str_replace('jpg', 'jpeg', $extension);
+  $close_func = 'image'. $extension;
+  if (!function_exists($close_func)) {
+    return FALSE;
+  }
+  if ($extension == 'jpeg') {
+    return $close_func($res, $destination, variable_get('image_jpeg_quality', 75));
+  }
+  else {
+    return $close_func($res, $destination);
+  }
+}
+
+/**
+ * @} End of "ingroup image".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/image.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,270 @@
+<?php
+// $Id: image.inc,v 1.24 2008/01/28 16:05:17 goba Exp $
+
+/**
+ * @file
+ * API for manipulating images.
+ */
+
+/**
+ * @defgroup image Image toolkits
+ * @{
+ * Drupal's image toolkits provide an abstraction layer for common image file
+ * manipulations like scaling, cropping, and rotating. The abstraction frees
+ * module authors from the need to support multiple image libraries, and it
+ * allows site administrators to choose the library that's best for them.
+ *
+ * PHP includes the GD library by default so a GD toolkit is installed with
+ * Drupal. Other toolkits like ImageMagic are available from contrib modules.
+ * GD works well for small images, but using it with larger files may cause PHP
+ * to run out of memory. In contrast the ImageMagick library does not suffer
+ * from this problem, but it requires the ISP to have installed additional
+ * software.
+ *
+ * Image toolkits are installed by copying the image.ToolkitName.inc file into
+ * Drupal's includes directory. The toolkit must then be enabled using the
+ * admin/settings/image-toolkit form.
+ *
+ * Only one toolkit maybe selected at a time. If a module author wishes to call
+ * a specific toolkit they can check that it is installed by calling
+ * image_get_available_toolkits(), and then calling its functions directly.
+ */
+
+/**
+ * Return a list of available toolkits.
+ *
+ * @return
+ *   An array of toolkit name => descriptive title.
+ */
+function image_get_available_toolkits() {
+  $toolkits = file_scan_directory('includes', 'image\..*\.inc$');
+
+  $output = array();
+  foreach ($toolkits as $file => $toolkit) {
+    include_once "./$file";
+    $function = str_replace('.', '_', $toolkit->name) .'_info';
+    if (function_exists($function)) {
+      $info = $function();
+      $output[$info['name']] = $info['title'];
+    }
+  }
+
+  return $output;
+}
+
+/**
+ * Retrieve the name of the currently used toolkit.
+ *
+ * @return
+ *   String containing the name of the selected toolkit, or FALSE on error.
+ */
+function image_get_toolkit() {
+  static $toolkit;
+
+  if (!$toolkit) {
+    $toolkit = variable_get('image_toolkit', 'gd');
+    $toolkit_file = './includes/image.'. $toolkit .'.inc';
+    if (isset($toolkit) && file_exists($toolkit_file)) {
+      include_once $toolkit_file;
+    }
+    elseif (!image_gd_check_settings()) {
+      $toolkit = FALSE;
+    }
+  }
+
+  return $toolkit;
+}
+
+/**
+ * Invokes the given method using the currently selected toolkit.
+ *
+ * @param $method
+ *   A string containing the method to invoke.
+ * @param $params
+ *   An optional array of parameters to pass to the toolkit method.
+ * @return
+ *   Mixed values (typically Boolean indicating successful operation).
+ */
+function image_toolkit_invoke($method, $params = array()) {
+  if ($toolkit = image_get_toolkit()) {
+    $function = 'image_'. $toolkit .'_'. $method;
+    if (function_exists($function)) {
+      return call_user_func_array($function, $params);
+    }
+    else {
+      watchdog('php', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $toolkit, '%function' => $function), WATCHDOG_ERROR);
+      return FALSE;
+    }
+  }
+}
+
+
+/**
+ * Get details about an image.
+ *
+ * Drupal only supports GIF, JPG and PNG file formats.
+ *
+ * @return
+ *   FALSE, if the file could not be found or is not an image. Otherwise, a
+ *   keyed array containing information about the image:
+ *    'width'     - Width in pixels.
+ *    'height'    - Height in pixels.
+ *    'extension' - Commonly used file extension for the image.
+ *    'mime_type' - MIME type ('image/jpeg', 'image/gif', 'image/png').
+ *    'file_size' - File size in bytes.
+ */
+function image_get_info($file) {
+  if (!is_file($file)) {
+    return FALSE;
+  }
+
+  $details = FALSE;
+  $data = @getimagesize($file);
+  $file_size = @filesize($file);
+
+  if (isset($data) && is_array($data)) {
+    $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
+    $extension = array_key_exists($data[2], $extensions) ?  $extensions[$data[2]] : '';
+    $details = array('width'     => $data[0],
+                     'height'    => $data[1],
+                     'extension' => $extension,
+                     'file_size' => $file_size,
+                     'mime_type' => $data['mime']);
+  }
+
+  return $details;
+}
+
+/**
+ * Scales an image to the exact width and height given. Achieves the
+ * target aspect ratio by cropping the original image equally on both
+ * sides, or equally on the top and bottom.  This function is, for
+ * example, useful to create uniform sized avatars from larger images.
+ *
+ * The resulting image always has the exact target dimensions.
+ *
+ * @param $source
+ *   The file path of the source image.
+ * @param $destination
+ *   The file path of the destination image.
+ * @param $width
+ *   The target width, in pixels.
+ * @param $height
+ *   The target height, in pixels.
+ * @return
+ *   TRUE or FALSE, based on success.
+ */
+function image_scale_and_crop($source, $destination, $width, $height) {
+  $info = image_get_info($source);
+
+  $scale = max($width / $info['width'], $height / $info['height']);
+  $x = round(($info['width'] * $scale - $width) / 2);
+  $y = round(($info['height'] * $scale - $height) / 2);
+
+  if (image_toolkit_invoke('resize', array($source, $destination, $info['width'] * $scale, $info['height'] * $scale))) {
+    return image_toolkit_invoke('crop', array($destination, $destination, $x, $y, $width, $height));
+  }
+  return FALSE;
+}
+
+/**
+ * Scales an image to the given width and height while maintaining aspect
+ * ratio.
+ *
+ * The resulting image can be smaller for one or both target dimensions.
+ *
+ * @param $source
+ *   The file path of the source image.
+ * @param $destination
+ *   The file path of the destination image.
+ * @param $width
+ *   The target width, in pixels.
+ * @param $height
+ *   The target height, in pixels.
+ * @return
+ *   TRUE or FALSE, based on success.
+ */
+function image_scale($source, $destination, $width, $height) {
+  $info = image_get_info($source);
+
+  // Don't scale up.
+  if ($width >= $info['width'] && $height >= $info['height']) {
+    return FALSE;
+  }
+
+  $aspect = $info['height'] / $info['width'];
+  if ($aspect < $height / $width) {
+    $width = (int)min($width, $info['width']);
+    $height = (int)round($width * $aspect);
+  }
+  else {
+    $height = (int)min($height, $info['height']);
+    $width = (int)round($height / $aspect);
+  }
+
+  return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
+}
+
+/**
+ * Resize an image to the given dimensions (ignoring aspect ratio).
+ *
+ * @param $source
+ *   The file path of the source image.
+ * @param $destination
+ *   The file path of the destination image.
+ * @param $width
+ *   The target width, in pixels.
+ * @param $height
+ *   The target height, in pixels.
+  * @return
+ *   TRUE or FALSE, based on success.
+ */
+function image_resize($source, $destination, $width, $height) {
+  return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
+}
+
+/**
+ * Rotate an image by the given number of degrees.
+ *
+ * @param $source
+ *   The file path of the source image.
+ * @param $destination
+ *   The file path of the destination image.
+ * @param $degrees
+ *   The number of (clockwise) degrees to rotate the image.
+ * @param $background
+ *   An hexidecimal integer specifying the background color to use for the
+ *   uncovered area of the image after the rotation. E.g. 0x000000 for black,
+ *   0xff00ff for magenta, and 0xffffff for white.
+ * @return
+ *   TRUE or FALSE, based on success.
+ */
+function image_rotate($source, $destination, $degrees, $background = 0x000000) {
+  return image_toolkit_invoke('rotate', array($source, $destination, $degrees, $background));
+}
+
+/**
+ * Crop an image to the rectangle specified by the given rectangle.
+ *
+ * @param $source
+ *   The file path of the source image.
+ * @param $destination
+ *   The file path of the destination image.
+ * @param $x
+ *   The top left co-ordinate, in pixels, of the crop area (x axis value).
+ * @param $y
+ *   The top left co-ordinate, in pixels, of the crop area (y axis value).
+ * @param $width
+ *   The target width, in pixels.
+ * @param $height
+ *   The target height, in pixels.
+ * @return
+ *   TRUE or FALSE, based on success.
+ */
+function image_crop($source, $destination, $x, $y, $width, $height) {
+  return image_toolkit_invoke('crop', array($source, $destination, $x, $y, $width, $height));
+}
+
+/**
+ * @} End of "defgroup image".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/install.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,736 @@
+<?php
+// $Id: install.inc,v 1.56.2.2 2008/02/11 15:10:26 goba Exp $
+
+define('SCHEMA_UNINSTALLED', -1);
+define('SCHEMA_INSTALLED', 0);
+
+define('REQUIREMENT_INFO', -1);
+define('REQUIREMENT_OK', 0);
+define('REQUIREMENT_WARNING', 1);
+define('REQUIREMENT_ERROR', 2);
+
+define('FILE_EXIST',          1);
+define('FILE_READABLE',       2);
+define('FILE_WRITABLE',       4);
+define('FILE_EXECUTABLE',     8);
+define('FILE_NOT_EXIST',      16);
+define('FILE_NOT_READABLE',   32);
+define('FILE_NOT_WRITABLE',   64);
+define('FILE_NOT_EXECUTABLE', 128);
+
+/**
+ * Initialize the update system by loading all installed module's .install files.
+ */
+function drupal_load_updates() {
+  foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
+    if ($schema_version > -1) {
+      module_load_install($module);
+    }
+  }
+}
+
+/**
+ * Returns an array of available schema versions for a module.
+ *
+ * @param $module
+ *   A module name.
+ * @return
+ *   If the module has updates, an array of available updates. Otherwise,
+ *   FALSE.
+ */
+function drupal_get_schema_versions($module) {
+  $updates = array();
+  $functions = get_defined_functions();
+  foreach ($functions['user'] as $function) {
+    if (strpos($function, $module .'_update_') === 0) {
+      $version = substr($function, strlen($module .'_update_'));
+      if (is_numeric($version)) {
+        $updates[] = $version;
+      }
+    }
+  }
+  if (count($updates) == 0) {
+    return FALSE;
+  }
+  return $updates;
+}
+
+/**
+ * Returns the currently installed schema version for a module.
+ *
+ * @param $module
+ *   A module name.
+ * @param $reset
+ *   Set to TRUE after modifying the system table.
+ * @param $array
+ *   Set to TRUE if you want to get information about all modules in the
+ *   system.
+ * @return
+ *   The currently installed schema version.
+ */
+function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
+  static $versions = array();
+
+  if ($reset) {
+    $versions = array();
+  }
+
+  if (!$versions) {
+    $versions = array();
+    $result = db_query("SELECT name, schema_version FROM {system} WHERE type = '%s'", 'module');
+    while ($row = db_fetch_object($result)) {
+      $versions[$row->name] = $row->schema_version;
+    }
+  }
+
+  return $array ? $versions : $versions[$module];
+}
+
+/**
+ * Update the installed version information for a module.
+ *
+ * @param $module
+ *   A module name.
+ * @param $version
+ *   The new schema version.
+ */
+function drupal_set_installed_schema_version($module, $version) {
+  db_query("UPDATE {system} SET schema_version = %d WHERE name = '%s'", $version, $module);
+}
+
+/**
+ * Loads the profile definition, extracting the profile's defined name.
+ *
+ * @return
+ *   The name defined in the profile's _profile_details() hook.
+ */
+function drupal_install_profile_name() {
+  global $profile;
+  static $name = NULL;
+
+  if (!isset($name)) {
+    // Load profile details.
+    $function = $profile .'_profile_details';
+    if (function_exists($function)) {
+      $details = $function();
+    }
+    $name = isset($details['name']) ? $details['name'] : 'Drupal';
+  }
+
+  return $name;
+}
+
+/**
+ * Auto detect the base_url with PHP predefined variables.
+ *
+ * @param $file
+ *   The name of the file calling this function so we can strip it out of
+ *   the URI when generating the base_url.
+ *
+ * @return
+ *   The auto-detected $base_url that should be configured in settings.php
+ */
+function drupal_detect_baseurl($file = 'install.php') {
+  global $profile;
+  $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
+  $host = $_SERVER['SERVER_NAME'];
+  $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':'. $_SERVER['SERVER_PORT']);
+  $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']);
+  $dir = str_replace("/$file", '', $uri);
+
+  return "$proto$host$port$dir";
+}
+
+/**
+ * Detect all databases supported by Drupal that are compiled into the current
+ * PHP installation.
+ *
+ * @return
+ *  An array of database types compiled into PHP.
+ */
+function drupal_detect_database_types() {
+  $databases = array();
+
+  foreach (array('mysql', 'mysqli', 'pgsql') as $type) {
+    if (file_exists('./includes/install.'. $type .'.inc')) {
+      include_once './includes/install.'. $type .'.inc';
+      $function = $type .'_is_available';
+      if ($function()) {
+        $databases[$type] = $type;
+      }
+    }
+  }
+
+  return $databases;
+}
+
+/**
+ * Read settings.php into a buffer line by line, changing values specified in
+ * $settings array, then over-writing the old settings.php file.
+ *
+ * @param $settings
+ *   An array of settings that need to be updated.
+ */
+function drupal_rewrite_settings($settings = array(), $prefix = '') {
+  $default_settings = './sites/default/default.settings.php';
+  $settings_file = './'. conf_path(FALSE, TRUE) .'/'. $prefix .'settings.php';
+
+  // Build list of setting names and insert the values into the global namespace.
+  $keys = array();
+  foreach ($settings as $setting => $data) {
+    $GLOBALS[$setting] = $data['value'];
+    $keys[] = $setting;
+  }
+
+  $buffer = NULL;
+  $first = TRUE;
+  if ($fp = fopen($default_settings, 'r')) {
+    // Step line by line through settings.php.
+    while (!feof($fp)) {
+      $line = fgets($fp);
+      if ($first && substr($line, 0, 5) != '<?php') {
+        $buffer = "<?php\n\n";
+      }
+      $first = FALSE;
+      // Check for constants.
+      if (substr($line, 0, 7) == 'define(') {
+        preg_match('/define\(\s*[\'"]([A-Z_-]+)[\'"]\s*,(.*?)\);/', $line, $variable);
+        if (in_array($variable[1], $keys)) {
+          $setting = $settings[$variable[1]];
+          $buffer .= str_replace($variable[2], " '". $setting['value'] ."'", $line);
+          unset($settings[$variable[1]]);
+          unset($settings[$variable[2]]);
+        }
+        else {
+          $buffer .= $line;
+        }
+      }
+      // Check for variables.
+      elseif (substr($line, 0, 1) == '$') {
+        preg_match('/\$([^ ]*) /', $line, $variable);
+        if (in_array($variable[1], $keys)) {
+          // Write new value to settings.php in the following format:
+          //    $'setting' = 'value'; // 'comment'
+          $setting = $settings[$variable[1]];
+          $buffer .= '$'. $variable[1] ." = '". $setting['value'] ."';". (!empty($setting['comment']) ? ' // '. $setting['comment'] ."\n" : "\n");
+          unset($settings[$variable[1]]);
+        }
+        else {
+          $buffer .= $line;
+        }
+      }
+      else {
+        $buffer .= $line;
+      }
+    }
+    fclose($fp);
+
+    // Add required settings that were missing from settings.php.
+    foreach ($settings as $setting => $data) {
+      if ($data['required']) {
+        $buffer .= "\$$setting = '". $data['value'] ."';\n";
+      }
+    }
+
+    $fp = fopen($settings_file, 'w');
+    if ($fp && fwrite($fp, $buffer) === FALSE) {
+      drupal_set_message(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)), 'error');
+    }
+  }
+  else {
+    drupal_set_message(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)), 'error');
+  }
+}
+
+/**
+ * Get list of all .install files.
+ *
+ * @param $module_list
+ *   An array of modules to search for their .install files.
+ */
+function drupal_get_install_files($module_list = array()) {
+  $installs = array();
+  foreach ($module_list as $module) {
+    $installs = array_merge($installs, drupal_system_listing($module .'.install$', 'modules'));
+  }
+  return $installs;
+}
+
+/**
+ * Verify a profile for installation.
+ *
+ * @param profile
+ *   Name of profile to verify.
+ * @param locale
+ *   Name of locale used (if any).
+ * @return
+ *   The list of modules to install.
+ */
+function drupal_verify_profile($profile, $locale) {
+  include_once './includes/file.inc';
+  include_once './includes/common.inc';
+
+  $profile_file = "./profiles/$profile/$profile.profile";
+
+  if (!isset($profile) || !file_exists($profile_file)) {
+    install_no_profile_error();
+  }
+
+  require_once($profile_file);
+
+  // Get a list of modules required by this profile.
+  $function = $profile .'_profile_modules';
+  $module_list = array_merge(drupal_required_modules(), $function(), ($locale != 'en' ? array('locale') : array()));
+
+  // Get a list of modules that exist in Drupal's assorted subdirectories.
+  $present_modules = array();
+  foreach (drupal_system_listing('\.module$', 'modules', 'name', 0) as $present_module) {
+    $present_modules[] = $present_module->name;
+  }
+
+  // Verify that all of the profile's required modules are present.
+  $missing_modules = array_diff($module_list, $present_modules);
+  if (count($missing_modules)) {
+    foreach ($missing_modules as $module) {
+      drupal_set_message(st('The %module module is required but was not found. Please move it into the <em>modules</em> subdirectory.', array('%module' => $module)), 'error');
+    }
+  }
+  else {
+    return $module_list;
+  }
+}
+
+/**
+ * Calls the install function and updates the system table for a given list of
+ * modules.
+ *
+ * @param module_list
+ *   The modules to install.
+ */
+function drupal_install_modules($module_list = array()) {
+  $files = module_rebuild_cache();
+  $module_list = array_flip(array_values($module_list));
+  do {
+    $moved = FALSE;
+    foreach ($module_list as $module => $weight) {
+      $file = $files[$module];
+      if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
+        foreach ($file->info['dependencies'] as $dependency) {
+          if (isset($module_list[$dependency]) && $module_list[$module] < $module_list[$dependency] +1) {
+            $module_list[$module] = $module_list[$dependency] +1;
+            $moved = TRUE;
+          }
+        }
+      }
+    }
+  } while ($moved);
+  asort($module_list);
+  $module_list = array_keys($module_list);
+  array_filter($module_list, '_drupal_install_module');
+  module_enable($module_list);
+}
+
+/**
+ * Callback to install an individual profile module.
+ *
+ * Used during installation to install modules one at a time and then
+ * enable them, or to install a number of modules at one time
+ * from admin/build/modules.
+ */
+function _drupal_install_module($module) {
+  if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
+    module_load_install($module);
+    module_invoke($module, 'install');
+    $versions = drupal_get_schema_versions($module);
+    drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
+    return TRUE;
+  }
+}
+
+/**
+ * Callback to install the system module.
+ *
+ * Separated from the installation of other modules so core system
+ * functions can be made available while other modules are installed.
+ */
+function drupal_install_system() {
+  $system_path = dirname(drupal_get_filename('module', 'system', NULL));
+  require_once './'. $system_path .'/system.install';
+  module_invoke('system', 'install');
+  $system_versions = drupal_get_schema_versions('system');
+  $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
+  db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $system_path .'/system.module', 'system', 'module', '', 1, 0, 0, $system_version);
+  // Now that we've installed things properly, bootstrap the full Drupal environment
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+  module_rebuild_cache();
+}
+
+
+/**
+ * Calls the uninstall function and updates the system table for a given module.
+ *
+ * @param $module
+ *   The module to uninstall.
+ */
+function drupal_uninstall_module($module) {
+  // First, retrieve all the module's menu paths from db.
+  drupal_load('module', $module);
+  $paths = module_invoke($module, 'menu');
+
+  // Uninstall the module(s).
+  module_load_install($module);
+  module_invoke($module, 'uninstall');
+
+  // Now remove the menu links for all paths declared by this module.
+  if (!empty($paths)) {
+    $paths = array_keys($paths);
+    // Clean out the names of load functions.
+    foreach ($paths as $index => $path) {
+      $parts = explode('/', $path, MENU_MAX_PARTS);
+      foreach ($parts as $k => $part) {
+        if (preg_match('/^%[a-z_]*$/', $part)) {
+          $parts[$k] = '%';
+        }
+      }
+      $paths[$index] = implode('/', $parts);
+    }
+    $placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
+
+    $result = db_query('SELECT * FROM {menu_links} WHERE router_path IN ('. $placeholders .') AND external = 0 ORDER BY depth DESC', $paths);
+    // Remove all such items. Starting from those with the greatest depth will
+    // minimize the amount of re-parenting done by menu_link_delete().
+    while ($item = db_fetch_array($result)) {
+      _menu_delete_item($item, TRUE);
+    }
+  }
+
+  drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
+}
+
+/**
+ * Verify the state of the specified file.
+ *
+ * @param $file
+ *   The file to check for.
+ * @param $mask
+ *   An optional bitmask created from various FILE_* constants.
+ * @param $type
+ *   The type of file. Can be file (default), dir, or link.
+ * @return
+ *   TRUE on success or FALSE on failure. A message is set for the latter.
+ */
+function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
+  $return = TRUE;
+  // Check for files that shouldn't be there.
+  if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
+    return FALSE;
+  }
+  // Verify that the file is the type of file it is supposed to be.
+  if (isset($type) && file_exists($file)) {
+    $check = 'is_'. $type;
+    if (!function_exists($check) || !$check($file)) {
+      $return = FALSE;
+    }
+  }
+
+  // Verify file permissions.
+  if (isset($mask)) {
+    $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+    foreach ($masks as $current_mask) {
+      if ($mask & $current_mask) {
+        switch ($current_mask) {
+          case FILE_EXIST:
+            if (!file_exists($file)) {
+              if ($type == 'dir') {
+                drupal_install_mkdir($file, $mask);
+              }
+              if (!file_exists($file)) {
+                $return = FALSE;
+              }
+            }
+            break;
+          case FILE_READABLE:
+            if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+          case FILE_WRITABLE:
+            if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+          case FILE_EXECUTABLE:
+            if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+          case FILE_NOT_READABLE:
+            if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+          case FILE_NOT_WRITABLE:
+            if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+          case FILE_NOT_EXECUTABLE:
+            if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
+              $return = FALSE;
+            }
+            break;
+        }
+      }
+    }
+  }
+  return $return;
+}
+
+/**
+ * Create a directory with specified permissions.
+ *
+ * @param file
+ *  The name of the directory to create;
+ * @param mask
+ *  The permissions of the directory to create.
+ * @param $message
+ *  (optional) Whether to output messages. Defaults to TRUE.
+ *
+ * @return
+ *  TRUE/FALSE whether or not the directory was successfully created.
+ */
+function drupal_install_mkdir($file, $mask, $message = TRUE) {
+  $mod = 0;
+  $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+  foreach ($masks as $m) {
+    if ($mask & $m) {
+      switch ($m) {
+        case FILE_READABLE:
+          $mod += 444;
+          break;
+        case FILE_WRITABLE:
+          $mod += 222;
+          break;
+        case FILE_EXECUTABLE:
+          $mod += 111;
+          break;
+      }
+    }
+  }
+
+  if (@mkdir($file, intval("0$mod", 8))) {
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Attempt to fix file permissions.
+ *
+ * The general approach here is that, because we do not know the security
+ * setup of the webserver, we apply our permission changes to all three
+ * digits of the file permission (i.e. user, group and all).
+ *
+ * To ensure that the values behave as expected (and numbers don't carry
+ * from one digit to the next) we do the calculation on the octal value
+ * using bitwise operations. This lets us remove, for example, 0222 from
+ * 0700 and get the correct value of 0500.
+ *
+ * @param $file
+ *  The name of the file with permissions to fix.
+ * @param $mask
+ *  The desired permissions for the file.
+ * @param $message
+ *  (optional) Whether to output messages. Defaults to TRUE.
+ *
+ * @return
+ *  TRUE/FALSE whether or not we were able to fix the file's permissions.
+ */
+function drupal_install_fix_file($file, $mask, $message = TRUE) {
+  $mod = fileperms($file) & 0777;
+  $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
+
+  // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
+  // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
+  // we set all three access types in case the administrator intends to
+  // change the owner of settings.php after installation.
+  foreach ($masks as $m) {
+    if ($mask & $m) {
+      switch ($m) {
+        case FILE_READABLE:
+          if (!is_readable($file)) {
+            $mod |= 0444;
+          }
+          break;
+        case FILE_WRITABLE:
+          if (!is_writable($file)) {
+            $mod |= 0222;
+          }
+          break;
+        case FILE_EXECUTABLE:
+          if (!is_executable($file)) {
+            $mod |= 0111;
+          }
+          break;
+        case FILE_NOT_READABLE:
+          if (is_readable($file)) {
+            $mod &= ~0444;
+          }
+          break;
+        case FILE_NOT_WRITABLE:
+          if (is_writable($file)) {
+            $mod &= ~0222;
+          }
+          break;
+        case FILE_NOT_EXECUTABLE:
+          if (is_executable($file)) {
+            $mod &= ~0111;
+          }
+          break;
+      }
+    }
+  }
+
+  // chmod() will work if the web server is running as owner of the file.
+  // If PHP safe_mode is enabled the currently executing script must also
+  // have the same owner.
+  if (@chmod($file, $mod)) {
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+
+/**
+ * Send the user to a different installer page. This issues an on-site HTTP
+ * redirect. Messages (and errors) are erased.
+ *
+ * @param $path
+ *   An installer path.
+ */
+function install_goto($path) {
+  global $base_url;
+  header('Location: '. $base_url .'/'. $path);
+  header('Cache-Control: no-cache'); // Not a permanent redirect.
+  exit();
+}
+
+/**
+ * Hardcoded function for doing the equivalent of t() during
+ * the install process, when database, theme, and localization
+ * system is possibly not yet available.
+ */
+function st($string, $args = array()) {
+  static $locale_strings = NULL;
+  global $profile, $install_locale;
+
+  if (!isset($locale_strings)) {
+    $locale_strings = array();
+    $filename = './profiles/'. $profile .'/translations/'. $install_locale .'.po';
+    if (file_exists($filename)) {
+      require_once './includes/locale.inc';
+      $file = (object) array('filepath' => $filename);
+      _locale_import_read_po('mem-store', $file);
+      $locale_strings = _locale_import_one_string('mem-report');
+    }
+  }
+
+  require_once './includes/theme.inc';
+  // Transform arguments before inserting them
+  foreach ($args as $key => $value) {
+    switch ($key[0]) {
+      // Escaped only
+      case '@':
+        $args[$key] = check_plain($value);
+        break;
+      // Escaped and placeholder
+      case '%':
+      default:
+        $args[$key] = '<em>'. check_plain($value) .'</em>';
+        break;
+      // Pass-through
+      case '!':
+    }
+  }
+  return strtr((!empty($locale_strings[$string]) ? $locale_strings[$string] : $string), $args);
+}
+
+/**
+ * Check a profile's requirements.
+ *
+ * @param profile
+ *   Name of profile to check.
+ */
+function drupal_check_profile($profile) {
+  include_once './includes/file.inc';
+
+  $profile_file = "./profiles/$profile/$profile.profile";
+
+  if (!isset($profile) || !file_exists($profile_file)) {
+    install_no_profile_error();
+  }
+
+  require_once($profile_file);
+
+  // Get a list of modules required by this profile.
+  $function = $profile .'_profile_modules';
+  $module_list = array_unique(array_merge(drupal_required_modules(), $function()));
+
+  // Get a list of all .install files.
+  $installs = drupal_get_install_files($module_list);
+
+  // Collect requirement testing results
+  $requirements = array();
+  foreach ($installs as $install) {
+    require_once $install->filename;
+    if (module_hook($install->name, 'requirements')) {
+      $requirements = array_merge($requirements, module_invoke($install->name, 'requirements', 'install'));
+    }
+  }
+  return $requirements;
+}
+
+/**
+ * Extract highest severity from requirements array.
+ */
+function drupal_requirements_severity(&$requirements) {
+  $severity = REQUIREMENT_OK;
+  foreach ($requirements as $requirement) {
+    if (isset($requirement['severity'])) {
+      $severity = max($severity, $requirement['severity']);
+    }
+  }
+  return $severity;
+}
+
+/**
+ * Check a module's requirements.
+ */
+function drupal_check_module($module) {
+  // Include install file
+  $install = drupal_get_install_files(array($module));
+  if (isset($install[$module])) {
+    require_once $install[$module]->filename;
+
+    // Check requirements
+    $requirements = module_invoke($module, 'requirements', 'install');
+    if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
+      // Print any error messages
+      foreach ($requirements as $requirement) {
+        if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
+          $message = $requirement['description'];
+          if (isset($requirement['value']) && $requirement['value']) {
+            $message .= ' ('. t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')';
+          }
+          drupal_set_message($message, 'error');
+        }
+      }
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/install.mysql.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,117 @@
+<?php
+// $Id: install.mysql.inc,v 1.9 2008/01/23 09:59:29 goba Exp $
+
+// MySQL specific install functions
+
+/**
+ * Check if MySQL is available.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function mysql_is_available() {
+  return function_exists('mysql_connect');
+}
+
+/**
+ * Check if we can connect to MySQL.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function drupal_test_mysql($url, &$success) {
+  if (!mysql_is_available()) {
+    drupal_set_message(st('PHP MySQL support not enabled.'), 'error');
+    return FALSE;
+  }
+
+  $url = parse_url($url);
+
+  // Decode url-encoded information in the db connection string.
+  $url['user'] = urldecode($url['user']);
+  $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
+  $url['host'] = urldecode($url['host']);
+  $url['path'] = urldecode($url['path']);
+
+  // Allow for non-standard MySQL port.
+  if (isset($url['port'])) {
+    $url['host'] = $url['host'] .':'. $url['port'];
+  }
+
+  // Test connecting to the database.
+  $connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2);
+  if (!$connection) {
+    drupal_set_message(st('Failed to connect to your MySQL database server. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error');
+    return FALSE;
+  }
+
+  // Test selecting the database.
+  if (!mysql_select_db(substr($url['path'], 1))) {
+    drupal_set_message(st('Failed to select your database on your MySQL database server, which means the connection username and password are valid, but there is a problem accessing your data. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct database name?</li><li>Are you sure the database exists?</li><li>Are you sure the username has permission to access the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error');
+    return FALSE;
+  }
+
+  $success = array('CONNECT');
+
+  // Test CREATE.
+  $query = 'CREATE TABLE drupal_install_test (id int NULL)';
+  $result = mysql_query($query);
+  if ($error = mysql_error()) {
+    drupal_set_message(st('Failed to create a test table on your MySQL database server with the command %query. MySQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary MySQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
+    return FALSE;
+  }
+  $err = FALSE;
+  $success[] = 'SELECT';
+  $success[] = 'CREATE';
+
+  // Test INSERT.
+  $query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
+  $result = mysql_query($query);
+  if ($error = mysql_error()) {
+    drupal_set_message(st('Failed to insert a value into a test table on your MySQL database server. We tried inserting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'INSERT';
+  }
+
+  // Test UPDATE.
+  $query = 'UPDATE drupal_install_test SET id = 2';
+  $result = mysql_query($query);
+  if ($error = mysql_error()) {
+    drupal_set_message(st('Failed to update a value in a test table on your MySQL database server. We tried updating a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'UPDATE';
+  }
+
+  // Test DELETE.
+  $query = 'DELETE FROM drupal_install_test';
+  $result = mysql_query($query);
+  if ($error = mysql_error()) {
+    drupal_set_message(st('Failed to delete a value from a test table on your MySQL database server. We tried deleting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DELETE';
+  }
+
+  // Test DROP.
+  $query = 'DROP TABLE drupal_install_test';
+  $result = mysql_query($query);
+  if ($error = mysql_error()) {
+    drupal_set_message(st('Failed to drop a test table from your MySQL database server. We tried dropping a table with the command %query and MySQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DROP';
+  }
+
+  if ($err) {
+    return FALSE;
+  }
+
+  mysql_close($connection);
+  return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/install.mysqli.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,112 @@
+<?php
+// $Id: install.mysqli.inc,v 1.12 2008/01/23 09:59:29 goba Exp $
+
+// MySQLi specific install functions
+
+/**
+ * Check if MySQLi is available.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function mysqli_is_available() {
+  return function_exists('mysqli_connect');
+}
+
+/**
+ * Check if we can connect to MySQL.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function drupal_test_mysqli($url, &$success) {
+  if (!mysqli_is_available()) {
+    drupal_set_message(st('PHP MySQLi support not enabled.'), 'error');
+    return FALSE;
+  }
+
+  $url = parse_url($url);
+
+  // Decode url-encoded information in the db connection string.
+  $url['user'] = urldecode($url['user']);
+  $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
+  $url['host'] = urldecode($url['host']);
+  $url['path'] = urldecode($url['path']);
+
+  $connection = mysqli_init();
+  @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
+  if (mysqli_connect_errno() >= 2000 || mysqli_connect_errno() == 1045) {
+    drupal_set_message(st('Failed to connect to your MySQL database server. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysqli_connect_error())), 'error');
+    return FALSE;
+  }
+
+  // Test selecting the database.
+  if (mysqli_connect_errno() > 0) {
+    drupal_set_message(st('Failed to select your database on your MySQL database server, which means the connection username and password are valid, but there is a problem accessing your data. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct database name?</li><li>Are you sure the database exists?</li><li>Are you sure the username has permission to access the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysqli_connect_error())), 'error');
+    return FALSE;
+  }
+
+  $success = array('CONNECT');
+
+  // Test CREATE.
+  $query = 'CREATE TABLE drupal_install_test (id int NULL)';
+  $result = mysqli_query($connection, $query);
+  if ($error = mysqli_error($connection)) {
+    drupal_set_message(st('Failed to create a test table on your MySQL database server with the command %query. MySQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary MySQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
+    return FALSE;
+  }
+  $err = FALSE;
+  $success[] = 'SELECT';
+  $success[] = 'CREATE';
+
+  // Test INSERT.
+  $query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
+  $result = mysqli_query($connection, $query);
+  if ($error = mysqli_error($connection)) {
+    drupal_set_message(st('Failed to insert a value into a test table on your MySQL database server. We tried inserting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'INSERT';
+  }
+
+  // Test UPDATE.
+  $query = 'UPDATE drupal_install_test SET id = 2';
+  $result = mysqli_query($connection, $query);
+  if ($error = mysqli_error($connection)) {
+    drupal_set_message(st('Failed to update a value in a test table on your MySQL database server. We tried updating a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'UPDATE';
+  }
+
+  // Test DELETE.
+  $query = 'DELETE FROM drupal_install_test';
+  $result = mysqli_query($connection, $query);
+  if ($error = mysqli_error($connection)) {
+    drupal_set_message(st('Failed to delete a value from a test table on your MySQL database server. We tried deleting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DELETE';
+  }
+
+  // Test DROP.
+  $query = 'DROP TABLE drupal_install_test';
+  $result = mysqli_query($connection, $query);
+  if ($error = mysqli_error($connection)) {
+    drupal_set_message(st('Failed to drop a test table from your MySQL database server. We tried dropping a table with the command %query and MySQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DROP';
+  }
+
+  if ($err) {
+    return FALSE;
+  }
+
+  mysqli_close($connection);
+  return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/install.pgsql.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,140 @@
+<?php
+// $Id: install.pgsql.inc,v 1.6 2007/10/22 15:22:39 dries Exp $
+
+// PostgreSQL specific install functions
+
+/**
+ * Check if PostgreSQL is available.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function pgsql_is_available() {
+  return function_exists('pg_connect');
+}
+
+/**
+ * Check if we can connect to PostgreSQL.
+ *
+ * @return
+ *  TRUE/FALSE
+ */
+function drupal_test_pgsql($url, &$success) {
+  if (!pgsql_is_available()) {
+    drupal_set_message(st('PHP PostgreSQL support not enabled.'), 'error');
+    return FALSE;
+  }
+
+  $url = parse_url($url);
+  $conn_string = '';
+
+  // Decode url-encoded information in the db connection string
+  if (isset($url['user'])) {
+    $conn_string .= ' user='. urldecode($url['user']);
+  }
+  if (isset($url['pass'])) {
+    $conn_string .= ' password='. urldecode($url['pass']);
+  }
+  if (isset($url['host'])) {
+    $conn_string .= ' host='. urldecode($url['host']);
+  }
+  if (isset($url['path'])) {
+    $conn_string .= ' dbname='. substr(urldecode($url['path']), 1);
+  }
+  if (isset($url['port'])) {
+    $conn_string .= ' port='. urldecode($url['port']);
+  }
+
+  // Test connecting to the database.
+  $connection = @pg_connect($conn_string);
+  if (!$connection) {
+    drupal_set_message(st('Failed to connect to your PostgreSQL database server. PostgreSQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li><li>Are you sure you typed the correct database name?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => 'Connection failed. See log file for failure reason')), 'error');
+    return FALSE;
+  }
+
+  $success = array('CONNECT');
+
+  // Test CREATE.
+  $query = 'CREATE TABLE drupal_install_test (id integer NOT NULL)';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error($result)) {
+    drupal_set_message(st('Failed to create a test table on your PostgreSQL database server with the command %query. PostgreSQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary PostgreSQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
+    return FALSE;
+  }
+  $err = FALSE;
+  $success[] = 'SELECT';
+  $success[] = 'CREATE';
+
+  // Test INSERT.
+  $query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error($result)) {
+    drupal_set_message(st('Failed to insert a value into a test table on your PostgreSQL database server. We tried inserting a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'INSERT';
+  }
+
+  // Test UPDATE.
+  $query = 'UPDATE drupal_install_test SET id = 2';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error($result)) {
+    drupal_set_message(st('Failed to update a value in a test table on your PostgreSQL database server. We tried updating a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'UPDATE';
+  }
+
+  // Test LOCK.
+  $query = 'BEGIN; LOCK drupal_install_test IN SHARE ROW EXCLUSIVE MODE';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error($result)) {
+    drupal_set_message(st('Failed to lock a test table on your PostgreSQL database server. We tried locking a table with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'LOCK';
+  }
+
+  // Test UNLOCK, which is done automatically upon transaction end in PostgreSQL
+  $query = 'COMMIT';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error()) {
+    drupal_set_message(st('Failed to unlock a test table on your PostgreSQL database server. We tried unlocking a table with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'UNLOCK';
+  }
+
+  // Test DELETE.
+  $query = 'DELETE FROM drupal_install_test';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error()) {
+    drupal_set_message(st('Failed to delete a value from a test table on your PostgreSQL database server. We tried deleting a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DELETE';
+  }
+
+  // Test DROP.
+  $query = 'DROP TABLE drupal_install_test';
+  $result = pg_query($connection, $query);
+  if ($error = pg_result_error()) {
+    drupal_set_message(st('Failed to drop a test table from your PostgreSQL database server. We tried dropping a table with the command %query and PostgreSQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
+    $err = TRUE;
+  }
+  else {
+    $success[] = 'DROP';
+  }
+
+  if ($err) {
+    return FALSE;
+  }
+
+  pg_close($connection);
+  return TRUE;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/language.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,144 @@
+<?php
+// $Id: language.inc,v 1.14 2008/01/06 16:46:02 goba Exp $
+
+/**
+ * @file
+ * Multiple language handling functionality.
+ */
+
+/**
+ *  Choose a language for the page, based on language negotiation settings.
+ */
+function language_initialize() {
+  global $user;
+
+  // Configured presentation language mode.
+  $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
+  // Get a list of enabled languages.
+  $languages = language_list('enabled');
+  $languages = $languages[1];
+  
+  switch ($mode) {
+    case LANGUAGE_NEGOTIATION_NONE:
+      return language_default();
+
+    case LANGUAGE_NEGOTIATION_DOMAIN:
+      foreach ($languages as $language) {
+        $parts = parse_url($language->domain);
+        if (!empty($parts['host']) && ($_SERVER['SERVER_NAME'] == $parts['host'])) {
+          return $language;
+        }
+      }
+      return language_default();
+
+    case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
+    case LANGUAGE_NEGOTIATION_PATH:
+      // $_GET['q'] might not be available at this time, because
+      // path initialization runs after the language bootstrap phase.
+      $args = isset($_GET['q']) ? explode('/', $_GET['q']) : array();
+      $prefix = array_shift($args);
+      // Search prefix within enabled languages.
+      foreach ($languages as $language) {
+        if (!empty($language->prefix) && $language->prefix == $prefix) {
+          // Rebuild $GET['q'] with the language removed.
+          $_GET['q'] = implode('/', $args);
+          return $language;
+        }
+      }
+      if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
+        // If we did not found the language by prefix, choose the default.
+        return language_default();
+      }
+      break;
+  }
+
+  // User language.
+  if ($user->uid && isset($languages[$user->language])) {
+    return $languages[$user->language];
+  }
+
+  // Browser accept-language parsing.
+  if ($language = language_from_browser()) {
+    return $language;
+  }
+
+  // Fall back on the default if everything else fails.
+  return language_default();
+}
+
+/**
+ * Identify language from the Accept-language HTTP header we got.
+ */
+function language_from_browser() {
+  // Specified by the user via the browser's Accept Language setting
+  // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
+  $browser_langs = array();
+
+  if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+    $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+    for ($i = 0; $i < count($browser_accept); $i++) {
+      // The language part is either a code or a code with a quality.
+      // We cannot do anything with a * code, so it is skipped.
+      // If the quality is missing, it is assumed to be 1 according to the RFC.
+      if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) {
+        $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
+      }
+    }
+  }
+
+  // Order the codes by quality
+  arsort($browser_langs);
+
+  // Try to find the first preferred language we have
+  $languages = language_list('enabled');
+  foreach ($browser_langs as $langcode => $q) {
+    if (isset($languages['1'][$langcode])) {
+      return $languages['1'][$langcode];
+    }
+  }
+}
+
+/**
+ * Rewrite URL's with language based prefix. Parameters are the same
+ * as those of the url() function.
+ */
+function language_url_rewrite(&$path, &$options) {
+  global $language;
+
+  // Only modify relative (insite) URLs.
+  if (!$options['external']) {
+
+    // Language can be passed as an option, or we go for current language.
+    if (!isset($options['language'])) {
+      $options['language'] = $language;
+    }
+
+    switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) {
+      case LANGUAGE_NEGOTIATION_NONE:
+        // No language dependent path allowed in this mode.
+        unset($options['language']);
+        break;
+
+      case LANGUAGE_NEGOTIATION_DOMAIN:
+        if ($options['language']->domain) {
+          // Ask for an absolute URL with our modified base_url.
+          $options['absolute'] = TRUE;
+          $options['base_url'] = $options['language']->domain;
+        }
+        break;
+
+      case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
+        $default = language_default();
+        if ($options['language']->language == $default->language) {
+          break;
+        }
+        // Intentionally no break here.
+
+      case LANGUAGE_NEGOTIATION_PATH:
+        if (!empty($options['language']->prefix)) {
+          $options['prefix'] = $options['language']->prefix .'/';
+        }
+        break;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/locale.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2570 @@
+<?php
+// $Id: locale.inc,v 1.174 2008/01/09 21:36:13 goba Exp $
+
+/**
+ * @file
+ *   Administration functions for locale.module.
+ */
+
+define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
+
+/**
+ * Translation import mode overwriting all existing translations
+ * if new translated version available.
+ */
+define('LOCALE_IMPORT_OVERWRITE', 0);
+
+/**
+ * Translation import mode keeping existing translations and only
+ * inserting new strings.
+ */
+define('LOCALE_IMPORT_KEEP', 1);
+
+/**
+ * @defgroup locale-language-overview Language overview functionality
+ * @{
+ */
+
+/**
+ * User interface for the language overview screen.
+ */
+function locale_languages_overview_form() {
+  $languages = language_list('language', TRUE);
+
+  $options = array();
+  $form['weight'] = array('#tree' => TRUE);
+  foreach ($languages as $langcode => $language) {
+
+    $options[$langcode] = '';
+    if ($language->enabled) {
+      $enabled[] = $langcode;
+    }
+    $form['weight'][$langcode] = array(
+      '#type' => 'weight',
+      '#default_value' => $language->weight
+    );
+    $form['name'][$langcode] = array('#value' => check_plain($language->name));
+    $form['native'][$langcode] = array('#value' => check_plain($language->native));
+    $form['direction'][$langcode] = array('#value' => ($language->direction == LANGUAGE_RTL ? t('Right to left') : t('Left to right')));
+  }
+  $form['enabled'] = array('#type' => 'checkboxes',
+    '#options' => $options,
+    '#default_value' => $enabled,
+  );
+  $form['site_default'] = array('#type' => 'radios',
+    '#options' => $options,
+    '#default_value' => language_default('language'),
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+  $form['#theme'] = 'locale_languages_overview_form';
+
+  return $form;
+}
+
+/**
+ * Theme the language overview form.
+ *
+ * @ingroup themeable
+ */
+function theme_locale_languages_overview_form($form) {
+  $default = language_default();
+  foreach ($form['name'] as $key => $element) {
+    // Do not take form control structures.
+    if (is_array($element) && element_child($key)) {
+      // Disable checkbox for the default language, because it cannot be disabled.
+      if ($key == $default->language) {
+        $form['enabled'][$key]['#attributes']['disabled'] = 'disabled';
+      }
+      $rows[] = array(
+        array('data' => drupal_render($form['enabled'][$key]), 'align' => 'center'),
+        check_plain($key),
+        '<strong>'. drupal_render($form['name'][$key]) .'</strong>',
+        drupal_render($form['native'][$key]),
+        drupal_render($form['direction'][$key]),
+        drupal_render($form['site_default'][$key]),
+        drupal_render($form['weight'][$key]),
+        l(t('edit'), 'admin/settings/language/edit/'. $key) . (($key != 'en' && $key != $default->language) ? ' '. l(t('delete'), 'admin/settings/language/delete/'. $key) : '')
+      );
+    }
+  }
+  $header = array(array('data' => t('Enabled')), array('data' => t('Code')), array('data' => t('English name')), array('data' => t('Native name')), array('data' => t('Direction')), array('data' => t('Default')), array('data' => t('Weight')), array('data' => t('Operations')));
+  $output = theme('table', $header, $rows);
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Process language overview form submissions, updating existing languages.
+ */
+function locale_languages_overview_form_submit($form, &$form_state) {
+  $languages = language_list();
+  $default = language_default();
+  $enabled_count = 0;
+  foreach ($languages as $langcode => $language) {
+    if ($form_state['values']['site_default'] == $langcode || $default->language == $langcode) {
+      // Automatically enable the default language and the language
+      // which was default previously (because we will not get the
+      // value from that disabled checkox).
+      $form_state['values']['enabled'][$langcode] = 1;
+    }
+    if ($form_state['values']['enabled'][$langcode]) {
+      $enabled_count++;
+      $language->enabled = 1;
+    }
+    else {
+      $language->enabled = 0;
+    }
+    $language->weight = $form_state['values']['weight'][$langcode];
+    db_query("UPDATE {languages} SET enabled = %d, weight = %d WHERE language = '%s'", $language->enabled, $language->weight, $langcode);
+    $languages[$langcode] = $language;
+  }
+  drupal_set_message(t('Configuration saved.'));
+  variable_set('language_default', $languages[$form_state['values']['site_default']]);
+  variable_set('language_count', $enabled_count);
+
+  // Changing the language settings impacts the interface.
+  cache_clear_all('*', 'cache_page', TRUE);
+
+  $form_state['redirect'] = 'admin/settings/language';
+  return;
+}
+/**
+ * @} End of "locale-language-overview"
+ */
+
+/**
+ * @defgroup locale-language-add-edit Language addition and editing functionality
+ * @{
+ */
+
+/**
+ * User interface for the language addition screen.
+ */
+function locale_languages_add_screen() {
+  $output = drupal_get_form('locale_languages_predefined_form');
+  $output .= drupal_get_form('locale_languages_custom_form');
+  return $output;
+}
+
+/**
+ * Predefined language setup form.
+ */
+function locale_languages_predefined_form() {
+  $predefined = _locale_prepare_predefined_list();
+  $form = array();
+  $form['language list'] = array('#type' => 'fieldset',
+    '#title' => t('Predefined language'),
+    '#collapsible' => TRUE,
+  );
+  $form['language list']['langcode'] = array('#type' => 'select',
+    '#title' => t('Language name'),
+    '#default_value' => key($predefined),
+    '#options' => $predefined,
+    '#description' => t('Select the desired language and click the <em>Add language</em> button. (Use the <em>Custom language</em> options if your desired language does not appear in this list.)'),
+  );
+  $form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language'));
+  return $form;
+}
+
+/**
+ * Custom language addition form.
+ */
+function locale_languages_custom_form() {
+  $form = array();
+  $form['custom language'] = array('#type' => 'fieldset',
+    '#title' => t('Custom language'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  _locale_languages_common_controls($form['custom language']);
+  $form['custom language']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Add custom language')
+  );
+  // Reuse the validation and submit functions of the predefined language setup form.
+  $form['#submit'][] = 'locale_languages_predefined_form_submit';
+  $form['#validate'][] = 'locale_languages_predefined_form_validate';
+  return $form;
+}
+
+/**
+ * Editing screen for a particular language.
+ *
+ * @param $langcode
+ *   Language code of the language to edit.
+ */
+function locale_languages_edit_form(&$form_state, $langcode) {
+  if ($language = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $langcode))) {
+    $form = array();
+    _locale_languages_common_controls($form, $language);
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save language')
+    );
+    $form['#submit'][] = 'locale_languages_edit_form_submit';
+    $form['#validate'][] = 'locale_languages_edit_form_validate';
+    return $form;
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+/**
+ * Common elements of the language addition and editing form.
+ *
+ * @param $form
+ *   A parent form item (or empty array) to add items below.
+ * @param $language
+ *   Language object to edit.
+ */
+function _locale_languages_common_controls(&$form, $language = NULL) {
+  if (!is_object($language)) {
+    $language = new stdClass();
+  }
+  if (isset($language->language)) {
+    $form['langcode_view'] = array(
+      '#type' => 'item',
+      '#title' => t('Language code'),
+      '#value' => $language->language
+    );
+    $form['langcode'] = array(
+      '#type' => 'value',
+      '#value' => $language->language
+    );
+  }
+  else {
+    $form['langcode'] = array('#type' => 'textfield',
+      '#title' => t('Language code'),
+      '#size' => 12,
+      '#maxlength' => 60,
+      '#required' => TRUE,
+      '#default_value' => @$language->language,
+      '#disabled' => (isset($language->language)),
+      '#description' => t('<a href="@rfc4646">RFC 4646</a> compliant language identifier. Language codes typically use a country code, and optionally, a script or regional variant name. <em>Examples: "en", "en-US" and "zh-Hant".</em>', array('@rfc4646' => 'http://www.ietf.org/rfc/rfc4646.txt')),
+    );
+  }
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Language name in English'),
+    '#maxlength' => 64,
+    '#default_value' => @$language->name,
+    '#required' => TRUE,
+    '#description' => t('Name of the language in English. Will be available for translation in all languages.'),
+  );
+  $form['native'] = array('#type' => 'textfield',
+    '#title' => t('Native language name'),
+    '#maxlength' => 64,
+    '#default_value' => @$language->native,
+    '#required' => TRUE,
+    '#description' => t('Name of the language in the language being added.'),
+  );
+  $form['prefix'] = array('#type' => 'textfield',
+    '#title' => t('Path prefix'),
+    '#maxlength' => 64,
+    '#default_value' => @$language->prefix,
+    '#description' => t('Language code or other custom string for pattern matching within the path. With language negotiation set to <em>Path prefix only</em> or <em>Path prefix with language fallback</em>, this site is presented in this language when the Path prefix value matches an element in the path. For the default language, this value may be left blank. <strong>Modifying this value will break existing URLs and should be used with caution in a production environment.</strong> <em>Example: Specifying "deutsch" as the path prefix for German results in URLs in the form "www.example.com/deutsch/node".</em>')
+  );
+  $form['domain'] = array('#type' => 'textfield',
+    '#title' => t('Language domain'),
+    '#maxlength' => 64,
+    '#default_value' => @$language->domain,
+    '#description' => t('Language-specific URL, with protocol. With language negotiation set to <em>Domain name only</em>, the site is presented in this language when the URL accessing the site references this domain. For the default language, this value may be left blank. <strong>This value must include a protocol as part of the string.</strong> <em>Example: Specifying "http://example.de" or "http://de.example.com" as language domains for German results in URLs in the forms "http://example.de/node" and "http://de.example.com/node", respectively.</em>'),
+  );
+  $form['direction'] = array('#type' => 'radios',
+    '#title' => t('Direction'),
+    '#required' => TRUE,
+    '#description' => t('Direction that text in this language is presented.'),
+    '#default_value' => @$language->direction,
+    '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL => t('Right to left'))
+  );
+  return $form;
+}
+
+/**
+ * Validate the language addition form.
+ */
+function locale_languages_predefined_form_validate($form, &$form_state) {
+  $langcode = $form_state['values']['langcode'];
+
+  if ($duplicate = db_result(db_query("SELECT COUNT(*) FROM {languages} WHERE language = '%s'", $langcode)) != 0) {
+    form_set_error('langcode', t('The language %language (%code) already exists.', array('%language' => $form_state['values']['name'], '%code' => $langcode)));
+  }
+
+  if (!isset($form_state['values']['name'])) {
+    // Predefined language selection.
+    $predefined = _locale_get_predefined_list();
+    if (!isset($predefined[$langcode])) {
+      form_set_error('langcode', t('Invalid language code.'));
+    }
+  }
+  else {
+    // Reuse the editing form validation routine if we add a custom language.
+    locale_languages_edit_form_validate($form, $form_state);
+  }
+}
+
+/**
+ * Process the language addition form submission.
+ */
+function locale_languages_predefined_form_submit($form, &$form_state) {
+  $langcode = $form_state['values']['langcode'];
+  if (isset($form_state['values']['name'])) {
+    // Custom language form.
+    locale_add_language($langcode, $form_state['values']['name'], $form_state['values']['native'], $form_state['values']['direction'], $form_state['values']['domain'], $form_state['values']['prefix']);
+    drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($form_state['values']['name']), '@locale-help' => url('admin/help/locale'))));
+  }
+  else {
+    // Predefined language selection.
+    $predefined = _locale_get_predefined_list();
+    locale_add_language($langcode);
+    drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($predefined[$langcode][0]), '@locale-help' => url('admin/help/locale'))));
+  }
+
+  // See if we have language files to import for the newly added
+  // language, collect and import them.
+  if ($batch = locale_batch_by_language($langcode, '_locale_batch_language_finished')) {
+    batch_set($batch);
+  }
+
+  $form_state['redirect'] = 'admin/settings/language';
+  return;
+}
+
+/**
+ * Validate the language editing form. Reused for custom language addition too.
+ */
+function locale_languages_edit_form_validate($form, &$form_state) {
+  if (!empty($form_state['values']['domain']) && !empty($form_state['values']['prefix'])) {
+    form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.'));
+  }
+  if (!empty($form_state['values']['domain']) && $duplicate = db_fetch_object(db_query("SELECT language FROM {languages} WHERE domain = '%s' AND language != '%s'", $form_state['values']['domain'], $form_state['values']['langcode']))) {
+    form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate->language)));
+  }
+  if (empty($form_state['values']['prefix']) && language_default('language') != $form_state['values']['langcode'] && empty($form_state['values']['domain'])) {
+    form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.'));
+  }
+  if (!empty($form_state['values']['prefix']) && $duplicate = db_fetch_object(db_query("SELECT language FROM {languages} WHERE prefix = '%s' AND language != '%s'", $form_state['values']['prefix'], $form_state['values']['langcode']))) {
+    form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate->language)));
+  }
+}
+
+/**
+ * Process the language editing form submission.
+ */
+function locale_languages_edit_form_submit($form, &$form_state) {
+  db_query("UPDATE {languages} SET name = '%s', native = '%s', domain = '%s', prefix = '%s', direction = %d WHERE language = '%s'", $form_state['values']['name'], $form_state['values']['native'], $form_state['values']['domain'], $form_state['values']['prefix'], $form_state['values']['direction'], $form_state['values']['langcode']);
+  $default = language_default();
+  if ($default->language == $form_state['values']['langcode']) {
+    $properties = array('name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight');
+    foreach ($properties as $keyname) {
+      if (isset($form_state['values'][$keyname])) {
+        $default->$keyname = $form_state['values'][$keyname];
+      }
+    }
+    variable_set('language_default', $default);
+  }
+  $form_state['redirect'] = 'admin/settings/language';
+  return;
+}
+/**
+ * @} End of "locale-language-add-edit"
+ */
+
+/**
+ * @defgroup locale-language-delete Language deletion functionality
+ * @{
+ */
+
+/**
+ * User interface for the language deletion confirmation screen.
+ */
+function locale_languages_delete_form(&$form_state, $langcode) {
+
+  // Do not allow deletion of English locale.
+  if ($langcode == 'en') {
+    drupal_set_message(t('The English language cannot be deleted.'));
+    drupal_goto('admin/settings/language');
+  }
+
+  if (language_default('language') == $langcode) {
+    drupal_set_message(t('The default language cannot be deleted.'));
+    drupal_goto('admin/settings/language');
+  }
+
+  // For other languages, warn user that data loss is ahead.
+  $languages = language_list();
+
+  if (!isset($languages[$langcode])) {
+    drupal_not_found();
+  }
+  else {
+    $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
+    return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages[$langcode]->name))), 'admin/settings/language', t('Deleting a language will remove all interface translations associated with it, and posts in this language will be set to be language neutral. This action cannot be undone.'), t('Delete'), t('Cancel'));
+  }
+}
+
+/**
+ * Process language deletion submissions.
+ */
+function locale_languages_delete_form_submit($form, &$form_state) {
+  $languages = language_list();
+  if (isset($languages[$form_state['values']['langcode']])) {
+    // Remove translations first.
+    db_query("DELETE FROM {locales_target} WHERE language = '%s'", $form_state['values']['langcode']);
+    cache_clear_all('locale:'. $form_state['values']['langcode'], 'cache');
+    // With no translations, this removes existing JavaScript translations file.
+    _locale_rebuild_js($form_state['values']['langcode']);
+    // Remove the language.
+    db_query("DELETE FROM {languages} WHERE language = '%s'", $form_state['values']['langcode']);
+    db_query("UPDATE {node} SET language = '' WHERE language = '%s'", $form_state['values']['langcode']);
+    $variables = array('%locale' => $languages[$form_state['values']['langcode']]->name);
+    drupal_set_message(t('The language %locale has been removed.', $variables));
+    watchdog('locale', 'The language %locale has been removed.', $variables);
+  }
+
+  // Changing the language settings impacts the interface:
+  cache_clear_all('*', 'cache_page', TRUE);
+
+  $form_state['redirect'] = 'admin/settings/language';
+  return;
+}
+/**
+ * @} End of "locale-language-add-edit"
+ */
+
+/**
+ * @defgroup locale-languages-negotiation Language negotiation options screen
+ * @{
+ */
+
+/**
+ * Setting for language negotiation options
+ */
+function locale_languages_configure_form() {
+  $form['language_negotiation'] = array(
+    '#title' => t('Language negotiation'),
+    '#type' => 'radios',
+    '#options' => array(
+      LANGUAGE_NEGOTIATION_NONE => t('None.'),
+      LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'),
+      LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback.'),
+      LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')),
+    '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE),
+    '#description' => t("Select the mechanism used to determine your site's presentation language. <strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>")
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save settings')
+  );
+  return $form;
+}
+
+/**
+ * Submit function for language negotiation settings.
+ */
+function locale_languages_configure_form_submit($form, &$form_state) {
+  variable_set('language_negotiation', $form_state['values']['language_negotiation']);
+  drupal_set_message(t('Language negotiation configuration saved.'));
+  $form_state['redirect'] = 'admin/settings/language';
+  return;
+}
+/**
+ * @} End of "locale-languages-negotiation"
+ */
+
+/**
+ * @defgroup locale-translate-overview Translation overview screen.
+ * @{
+ */
+
+/**
+ * Overview screen for translations.
+ */
+function locale_translate_overview_screen() {
+  $languages = language_list('language', TRUE);
+  $groups = module_invoke_all('locale', 'groups');
+
+  // Build headers with all groups in order.
+  $headers = array_merge(array(t('Language')), array_values($groups));
+
+  // Collect summaries of all source strings in all groups.
+  $sums = db_query("SELECT COUNT(*) AS strings, textgroup FROM {locales_source} GROUP BY textgroup");
+  $groupsums = array();
+  while ($group = db_fetch_object($sums)) {
+    $groupsums[$group->textgroup] = $group->strings;
+  }
+
+  // Set up overview table with default values, ensuring common order for values.
+  $rows = array();
+  foreach ($languages as $langcode => $language) {
+    $rows[$langcode] = array('name' => ($langcode == 'en' ? t('English (built-in)') : t($language->name)));
+    foreach ($groups as $group => $name) {
+      $rows[$langcode][$group] = ($langcode == 'en' ? t('n/a') : '0/'. (isset($groupsums[$group]) ? $groupsums[$group] : 0) .' (0%)');
+    }
+  }
+
+  // Languages with at least one record in the locale table.
+  $translations = db_query("SELECT COUNT(*) AS translation, t.language, s.textgroup FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY textgroup, language");
+  while ($data = db_fetch_object($translations)) {
+    $ratio = (!empty($groupsums[$data->textgroup]) && $data->translation > 0) ? round(($data->translation/$groupsums[$data->textgroup])*100., 2) : 0;
+    $rows[$data->language][$data->textgroup] = $data->translation .'/'. $groupsums[$data->textgroup] ." ($ratio%)";
+  }
+
+  return theme('table', $headers, $rows);
+}
+/**
+ * @} End of "locale-translate-overview"
+ */
+
+/**
+ * @defgroup locale-translate-seek Translation search screen.
+ * @{
+ */
+
+/**
+ * String search screen.
+ */
+function locale_translate_seek_screen() {
+  $output = _locale_translate_seek();
+  $output .= drupal_get_form('locale_translate_seek_form');
+  return $output;
+}
+
+/**
+ * User interface for the string search screen.
+ */
+function locale_translate_seek_form() {
+  // Get all languages, except English
+  $languages = locale_language_list('name', TRUE);
+  unset($languages['en']);
+
+  // Present edit form preserving previous user settings
+  $query = _locale_translate_seek_query();
+  $form = array();
+  $form['search'] = array('#type' => 'fieldset',
+    '#title' => t('Search'),
+  );
+  $form['search']['string'] = array('#type' => 'textfield',
+    '#title' => t('String contains'),
+    '#default_value' => @$query['string'],
+    '#description' => t('Leave blank to show all strings. The search is case sensitive.'),
+  );
+  $form['search']['language'] = array(
+    // Change type of form widget if more the 5 options will
+    // be present (2 of the options are added below).
+    '#type' => (count($languages) <= 3 ? 'radios' : 'select'),
+    '#title' => t('Language'),
+    '#default_value' => (!empty($query['language']) ? $query['language'] : 'all'),
+    '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $languages),
+  );
+  $form['search']['translation'] = array('#type' => 'radios',
+    '#title' => t('Search in'),
+    '#default_value' => (!empty($query['translation']) ? $query['translation'] : 'all'),
+    '#options' => array('all' => t('Both translated and untranslated strings'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')),
+  );
+  $groups = module_invoke_all('locale', 'groups');
+  $form['search']['group'] = array('#type' => 'radios',
+    '#title' => t('Limit search to'),
+    '#default_value' => (!empty($query['group']) ? $query['group'] : 'all'),
+    '#options' => array_merge(array('all' => t('All text groups')), $groups),
+  );
+
+  $form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
+  $form['#redirect'] = FALSE;
+
+  return $form;
+}
+/**
+ * @} End of "locale-translate-seek"
+ */
+
+/**
+ * @defgroup locale-translate-import Translation import screen.
+ * @{
+ */
+
+/**
+ * User interface for the translation import screen.
+ */
+function locale_translate_import_form() {
+  // Get all languages, except English
+  $names = locale_language_list('name', TRUE);
+  unset($names['en']);
+
+  if (!count($names)) {
+    $languages = _locale_prepare_predefined_list();
+    $default = array_shift(array_keys($languages));
+  }
+  else {
+    $languages = array(
+      t('Already added languages') => $names,
+      t('Languages not yet added') => _locale_prepare_predefined_list()
+    );
+    $default = array_shift(array_keys($names));
+  }
+
+  $form = array();
+  $form['import'] = array('#type' => 'fieldset',
+    '#title' => t('Import translation'),
+  );
+  $form['import']['file'] = array('#type' => 'file',
+    '#title' => t('Language file'),
+    '#size' => 50,
+    '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
+  );
+  $form['import']['langcode'] = array('#type' => 'select',
+    '#title' => t('Import into'),
+    '#options' => $languages,
+    '#default_value' => $default,
+    '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, it will be added.'),
+  );
+  $form['import']['group'] = array('#type' => 'radios',
+    '#title' => t('Text group'),
+    '#default_value' => 'default',
+    '#options' => module_invoke_all('locale', 'groups'),
+    '#description' => t('Imported translations will be added to this text group.'),
+  );
+  $form['import']['mode'] = array('#type' => 'radios',
+    '#title' => t('Mode'),
+    '#default_value' => LOCALE_IMPORT_KEEP,
+    '#options' => array(
+      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added'),
+      LOCALE_IMPORT_KEEP => t('Existing strings are kept, only new strings are added')
+    ),
+  );
+  $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
+  $form['#attributes']['enctype'] = 'multipart/form-data';
+
+  return $form;
+}
+
+/**
+ * Process the locale import form submission.
+ */
+function locale_translate_import_form_submit($form, &$form_state) {
+  // Ensure we have the file uploaded
+  if ($file = file_save_upload('file')) {
+
+    // Add language, if not yet supported
+    $languages = language_list('language', TRUE);
+    $langcode = $form_state['values']['langcode'];
+    if (!isset($languages[$langcode])) {
+      $predefined = _locale_get_predefined_list();
+      locale_add_language($langcode);
+      drupal_set_message(t('The language %language has been created.', array('%language' => t($predefined[$langcode][0]))));
+    }
+
+    // Now import strings into the language
+    if ($ret = _locale_import_po($file, $langcode, $form_state['values']['mode'], $form_state['values']['group']) == FALSE) {
+      $variables = array('%filename' => $file->filename);
+      drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
+      watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
+    }
+  }
+  else {
+    drupal_set_message(t('File to import not found.'), 'error');
+    return 'admin/build/translate/import';
+  }
+
+  $form_state['redirect'] = 'admin/build/translate';
+  return;
+}
+/**
+ * @} End of "locale-translate-import"
+ */
+
+/**
+ * @defgroup locale-translate-export Translation export screen.
+ * @{
+ */
+
+/**
+ * User interface for the translation export screen.
+ */
+function locale_translate_export_screen() {
+  // Get all languages, except English
+  $names = locale_language_list('name', TRUE);
+  unset($names['en']);
+  $output = '';
+  // Offer translation export if any language is set up.
+  if (count($names)) {
+    $output = drupal_get_form('locale_translate_export_po_form', $names);
+  }
+  $output .= drupal_get_form('locale_translate_export_pot_form');
+  return $output;
+}
+
+/**
+ * Form to export PO files for the languages provided.
+ *
+ * @param $names
+ *   An associate array with localized language names
+ */
+function locale_translate_export_po_form(&$form_state, $names) {
+  $form['export'] = array('#type' => 'fieldset',
+    '#title' => t('Export translation'),
+    '#collapsible' => TRUE,
+  );
+  $form['export']['langcode'] = array('#type' => 'select',
+    '#title' => t('Language name'),
+    '#options' => $names,
+    '#description' => t('Select the language to export in Gettext Portable Object (<em>.po</em>) format.'),
+  );
+  $form['export']['group'] = array('#type' => 'radios',
+    '#title' => t('Text group'),
+    '#default_value' => 'default',
+    '#options' => module_invoke_all('locale', 'groups'),
+  );
+  $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
+  return $form;
+}
+
+/**
+ * Translation template export form.
+ */
+function locale_translate_export_pot_form() {
+  // Complete template export of the strings
+  $form['export'] = array('#type' => 'fieldset',
+    '#title' => t('Export template'),
+    '#collapsible' => TRUE,
+    '#description' => t('Generate a Gettext Portable Object Template (<em>.pot</em>) file with all strings from the Drupal locale database.'),
+  );
+  $form['export']['group'] = array('#type' => 'radios',
+    '#title' => t('Text group'),
+    '#default_value' => 'default',
+    '#options' => module_invoke_all('locale', 'groups'),
+  );
+  $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
+  // Reuse PO export submission callback.
+  $form['#submit'][] = 'locale_translate_export_po_form_submit';
+  $form['#validate'][] = 'locale_translate_export_po_form_validate';
+  return $form;
+}
+
+/**
+ * Process a translation (or template) export form submission.
+ */
+function locale_translate_export_po_form_submit($form, &$form_state) {
+  // If template is required, language code is not given.
+  $language = NULL;
+  if (isset($form_state['values']['langcode'])) {
+    $languages = language_list();
+    $language = $languages[$form_state['values']['langcode']];
+  }
+  _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language, $form_state['values']['group'])));
+}
+/**
+ * @} End of "locale-translate-export"
+ */
+
+/**
+ * @defgroup locale-translate-edit Translation text editing
+ * @{
+ */
+
+/**
+ * User interface for string editing.
+ */
+function locale_translate_edit_form(&$form_state, $lid) {
+  // Fetch source string, if possible.
+  $source = db_fetch_object(db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = %d', $lid));
+  if (!$source) {
+    drupal_set_message(t('String not found.'), 'error');
+    drupal_goto('admin/build/translate/search');
+  }
+
+  // Add original text to the top and some values for form altering.
+  $form = array(
+    'original' => array(
+      '#type'  => 'item',
+      '#title' => t('Original text'),
+      '#value' => check_plain(wordwrap($source->source, 0)),
+    ),
+    'lid' => array(
+      '#type'  => 'value',
+      '#value' => $lid
+    ),
+    'textgroup' => array(
+      '#type'  => 'value',
+      '#value' => $source->textgroup,
+    ),
+    'location' => array(
+      '#type'  => 'value',
+      '#value' => $source->location
+    ),
+  );
+
+  // Include default form controls with empty values for all languages.
+  // This ensures that the languages are always in the same order in forms.
+  $languages = language_list();
+  $default = language_default();
+  // We don't need the default language value, that value is in $source.
+  $omit = $source->textgroup == 'default' ? 'en' : $default->language;
+  unset($languages[($omit)]);
+  $form['translations'] = array('#tree' => TRUE);
+  // Approximate the number of rows to use in the default textarea.
+  $rows = min(ceil(str_word_count($source->source) / 12), 10);
+  foreach ($languages as $langcode => $language) {
+    $form['translations'][$langcode] = array(
+      '#type' => 'textarea',
+      '#title' => t($language->name),
+      '#rows' => $rows,
+      '#default_value' => '',
+    );
+  }
+
+  // Fetch translations and fill in default values in the form.
+  $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = %d AND language != '%s'", $lid, $omit);
+  while ($translation = db_fetch_object($result)) {
+    $form['translations'][$translation->language]['#default_value'] = $translation->translation;
+  }
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
+  return $form;
+}
+
+/**
+ * Process string editing form submissions.
+ * Saves all translations of one string submitted from a form.
+ */
+function locale_translate_edit_form_submit($form, &$form_state) {
+  $lid = $form_state['values']['lid'];
+  foreach ($form_state['values']['translations'] as $key => $value) {
+    $translation = db_result(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
+    if (!empty($value)) {
+      // Only update or insert if we have a value to use.
+      if (!empty($translation)) {
+        db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND language = '%s'", $value, $lid, $key);
+      }
+      else {
+        db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key);
+      }
+    }
+    elseif (!empty($translation)) {
+      // Empty translation entered: remove existing entry from database.
+      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key);
+    }
+
+    // Force JavaScript translation file recreation for this language.
+    _locale_invalidate_js($key);
+  }
+
+  drupal_set_message(t('The string has been saved.'));
+
+  // Clear locale cache.
+  cache_clear_all('locale:', 'cache', TRUE);
+
+  $form_state['redirect'] = 'admin/build/translate/search';
+  return;
+}
+/**
+ * @} End of "locale-translate-edit"
+ */
+
+/**
+ * @defgroup locale-translate-delete Translation delete interface.
+ * @{
+ */
+
+/**
+ * Delete a language string.
+ */
+function locale_translate_delete($lid) {
+  db_query('DELETE FROM {locales_source} WHERE lid = %d', $lid);
+  db_query('DELETE FROM {locales_target} WHERE lid = %d', $lid);
+  // Force JavaScript translation file recreation for all languages.
+  _locale_invalidate_js();
+  cache_clear_all('locale:', 'cache', TRUE);
+  drupal_set_message(t('The string has been removed.'));
+  drupal_goto('admin/build/translate/search');
+}
+/**
+ * @} End of "locale-translate-delete"
+ */
+
+/**
+ * @defgroup locale-api-add Language addition API.
+ * @{
+ */
+
+/**
+ * API function to add a language.
+ *
+ * @param $langcode
+ *   Language code.
+ * @param $name
+ *   English name of the language
+ * @param $native
+ *   Native name of the language
+ * @param $direction
+ *   LANGUAGE_LTR or LANGUAGE_RTL
+ * @param $domain
+ *   Optional custom domain name with protocol, without
+ *   trailing slash (eg. http://de.example.com).
+ * @param $prefix
+ *   Optional path prefix for the language. Defaults to the
+ *   language code if omitted.
+ * @param $enabled
+ *   Optionally TRUE to enable the language when created or FALSE to disable.
+ * @param $default
+ *   Optionally set this language to be the default.
+ */
+function locale_add_language($langcode, $name = NULL, $native = NULL, $direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE, $default = FALSE) {
+  // Default prefix on language code.
+  if (empty($prefix)) {
+    $prefix = $langcode;
+  }
+
+  // If name was not set, we add a predefined language.
+  if (!isset($name)) {
+    $predefined = _locale_get_predefined_list();
+    $name = $predefined[$langcode][0];
+    $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1] : $predefined[$langcode][0];
+    $direction = isset($predefined[$langcode][2]) ? $predefined[$langcode][2] : LANGUAGE_LTR;
+  }
+
+  db_query("INSERT INTO {languages} (language, name, native, direction, domain, prefix, enabled) VALUES ('%s', '%s', '%s', %d, '%s', '%s', %d)", $langcode, $name, $native, $direction, $domain, $prefix, $enabled);
+
+  // Only set it as default if enabled.
+  if ($enabled && $default) {
+    variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0, 'javascript' => ''));
+  }
+
+  if ($enabled) {
+    // Increment enabled language count if we are adding an enabled language.
+    variable_set('language_count', variable_get('language_count', 1) + 1);
+  }
+
+  // Force JavaScript translation file creation for the newly added language.
+  _locale_invalidate_js($langcode);
+
+  watchdog('locale', 'The %language language (%code) has been created.', array('%language' => $name, '%code' => $langcode));
+}
+/**
+ * @} End of "locale-api-add"
+ */
+
+/**
+ * @defgroup locale-api-import Translation import API.
+ * @{
+ */
+
+/**
+ * Parses Gettext Portable Object file information and inserts into database
+ *
+ * @param $file
+ *   Drupal file object corresponding to the PO file to import
+ * @param $langcode
+ *   Language code
+ * @param $mode
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
+ * @param $group
+ *   Text group to import PO file into (eg. 'default' for interface translations)
+ */
+function _locale_import_po($file, $langcode, $mode, $group = NULL) {
+  // If not in 'safe mode', increase the maximum execution time.
+  if (!ini_get('safe_mode')) {
+    set_time_limit(240);
+  }
+
+  // Check if we have the language already in the database.
+  if (!db_fetch_object(db_query("SELECT language FROM {languages} WHERE language = '%s'", $langcode))) {
+    drupal_set_message(t('The language selected for import is not supported.'), 'error');
+    return FALSE;
+  }
+
+  // Get strings from file (returns on failure after a partial import, or on success)
+  $status = _locale_import_read_po('db-store', $file, $mode, $langcode, $group);
+  if ($status === FALSE) {
+    // Error messages are set in _locale_import_read_po().
+    return FALSE;
+  }
+
+  // Get status information on import process.
+  list($headerdone, $additions, $updates, $deletes) = _locale_import_one_string('db-report');
+
+  if (!$headerdone) {
+    drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
+  }
+
+  // Clear cache and force refresh of JavaScript translations.
+  _locale_invalidate_js($langcode);
+  cache_clear_all('locale:', 'cache', TRUE);
+
+  // Rebuild the menu, strings may have changed.
+  menu_rebuild();
+
+  drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+  watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+  return TRUE;
+}
+
+/**
+ * Parses Gettext Portable Object file into an array
+ *
+ * @param $op
+ *   Storage operation type: db-store or mem-store
+ * @param $file
+ *   Drupal file object corresponding to the PO file to import
+ * @param $mode
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
+ * @param $lang
+ *   Language code
+ * @param $group
+ *   Text group to import PO file into (eg. 'default' for interface translations)
+ */
+function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
+
+  $fd = fopen($file->filepath, "rb"); // File will get closed by PHP on return
+  if (!$fd) {
+    _locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
+    return FALSE;
+  }
+
+  $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
+  $current = array();   // Current entry being read
+  $plural = 0;          // Current plural form
+  $lineno = 0;          // Current line
+
+  while (!feof($fd)) {
+    $line = fgets($fd, 10*1024); // A line should not be this long
+    if ($lineno == 0) {
+      // The first line might come with a UTF-8 BOM, which should be removed.
+      $line = str_replace("\xEF\xBB\xBF", '', $line);
+    }
+    $lineno++;
+    $line = trim(strtr($line, array("\\\n" => "")));
+
+    if (!strncmp("#", $line, 1)) { // A comment
+      if ($context == "COMMENT") { // Already in comment context: add
+        $current["#"][] = substr($line, 1);
+      }
+      elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
+        _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+        $current = array();
+        $current["#"][] = substr($line, 1);
+        $context = "COMMENT";
+      }
+      else { // Parse error
+        _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
+        return FALSE;
+      }
+    }
+    elseif (!strncmp("msgid_plural", $line, 12)) {
+      if ($context != "MSGID") { // Must be plural form for current entry
+        _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $line = trim(substr($line, 12));
+      $quoted = _locale_import_parse_quoted($line);
+      if ($quoted === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $current["msgid"] = $current["msgid"] ."\0". $quoted;
+      $context = "MSGID_PLURAL";
+    }
+    elseif (!strncmp("msgid", $line, 5)) {
+      if ($context == "MSGSTR") {   // End current entry, start a new one
+        _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+        $current = array();
+      }
+      elseif ($context == "MSGID") { // Already in this context? Parse error
+        _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $line = trim(substr($line, 5));
+      $quoted = _locale_import_parse_quoted($line);
+      if ($quoted === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $current["msgid"] = $quoted;
+      $context = "MSGID";
+    }
+    elseif (!strncmp("msgstr[", $line, 7)) {
+      if (($context != "MSGID") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or msgstr[]
+        _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      if (strpos($line, "]") === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $frombracket = strstr($line, "[");
+      $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
+      $line = trim(strstr($line, " "));
+      $quoted = _locale_import_parse_quoted($line);
+      if ($quoted === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $current["msgstr"][$plural] = $quoted;
+      $context = "MSGSTR_ARR";
+    }
+    elseif (!strncmp("msgstr", $line, 6)) {
+      if ($context != "MSGID") {   // Should come just after a msgid block
+        _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $line = trim(substr($line, 6));
+      $quoted = _locale_import_parse_quoted($line);
+      if ($quoted === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      $current["msgstr"] = $quoted;
+      $context = "MSGSTR";
+    }
+    elseif ($line != "") {
+      $quoted = _locale_import_parse_quoted($line);
+      if ($quoted === FALSE) {
+        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+        return FALSE;
+      }
+      if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
+        $current["msgid"] .= $quoted;
+      }
+      elseif ($context == "MSGSTR") {
+        $current["msgstr"] .= $quoted;
+      }
+      elseif ($context == "MSGSTR_ARR") {
+        $current["msgstr"][$plural] .= $quoted;
+      }
+      else {
+        _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno);
+        return FALSE;
+      }
+    }
+  }
+
+  // End of PO file, flush last entry
+  if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
+    _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
+  }
+  elseif ($context != "COMMENT") {
+    _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
+    return FALSE;
+  }
+
+}
+
+/**
+ * Sets an error message occurred during locale file parsing.
+ *
+ * @param $message
+ *   The message to be translated
+ * @param $file
+ *   Drupal file object corresponding to the PO file to import
+ * @param $lineno
+ *   An optional line number argument
+ */
+function _locale_import_message($message, $file, $lineno = NULL) {
+  $vars = array('%filename' => $file->filename);
+  if (isset($lineno)) {
+    $vars['%line'] = $lineno;
+  }
+  $t = get_t();
+  drupal_set_message($t($message, $vars), 'error');
+}
+
+/**
+ * Imports a string into the database
+ *
+ * @param $op
+ *   Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'
+ * @param $value
+ *   Details of the string stored
+ * @param $mode
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
+ * @param $lang
+ *   Language to store the string in
+ * @param $file
+ *   Object representation of file being imported, only required when op is 'db-store'
+ * @param $group
+ *   Text group to import PO file into (eg. 'default' for interface translations)
+ */
+function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') {
+  static $report = array(0, 0, 0);
+  static $headerdone = FALSE;
+  static $strings = array();
+
+  switch ($op) {
+    // Return stored strings
+    case 'mem-report':
+      return $strings;
+
+    // Store string in memory (only supports single strings)
+    case 'mem-store':
+      $strings[$value['msgid']] = $value['msgstr'];
+      return;
+
+    // Called at end of import to inform the user
+    case 'db-report':
+      return array($headerdone, $report[0], $report[1], $report[2]);
+
+    // Store the string we got in the database.
+    case 'db-store':
+      // We got header information.
+      if ($value['msgid'] == '') {
+        $header = _locale_import_parse_header($value['msgstr']);
+
+        // Get the plural formula and update in database.
+        if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->filename)) {
+          list($nplurals, $plural) = $p;
+          db_query("UPDATE {languages} SET plurals = %d, formula = '%s' WHERE language = '%s'", $nplurals, $plural, $lang);
+        }
+        else {
+          db_query("UPDATE {languages} SET plurals = %d, formula = '%s' WHERE language = '%s'", 0, '', $lang);
+        }
+        $headerdone = TRUE;
+      }
+
+      else {
+        // Some real string to import.
+        $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
+
+        if (strpos($value['msgid'], "\0")) {
+          // This string has plural versions.
+          $english = explode("\0", $value['msgid'], 2);
+          $entries = array_keys($value['msgstr']);
+          for ($i = 3; $i <= count($entries); $i++) {
+            $english[] = $english[1];
+          }
+          $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
+          $english = array_map('_locale_import_append_plural', $english, $entries);
+          foreach ($translation as $key => $trans) {
+            if ($key == 0) {
+              $plid = 0;
+            }
+            $plid = _locale_import_one_string_db($report, $lang, $english[$key], $trans, $group, $comments, $mode, $plid, $key);
+          }
+        }
+
+        else {
+          // A simple string to import.
+          $english = $value['msgid'];
+          $translation = $value['msgstr'];
+          _locale_import_one_string_db($report, $lang, $english, $translation, $group, $comments, $mode);
+        }
+      }
+  } // end of db-store operation
+}
+
+/**
+ * Import one string into the database.
+ *
+ * @param $report
+ *   Report array summarizing the number of changes done in the form:
+ *   array(inserts, updates, deletes).
+ * @param $langcode
+ *   Language code to import string into.
+ * @param $source
+ *   Source string.
+ * @param $translation
+ *   Translation to language specified in $langcode.
+ * @param $textgroup
+ *   Name of textgroup to store translation in.
+ * @param $location
+ *   Location value to save with source string.
+ * @param $mode
+ *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $plid
+ *   Optional plural ID to use.
+ * @param $plural
+ *   Optional plural value to use.
+ * @return
+ *   The string ID of the existing string modified or the new string added.
+ */
+function _locale_import_one_string_db(&$report, $langcode, $source, $translation, $textgroup, $location, $mode, $plid = NULL, $plural = NULL) {
+  $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup));
+
+  if (!empty($translation)) {
+    if ($lid) {
+      // We have this source string saved already.
+      db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $location, $lid);
+      $exists = (bool) db_result(db_query("SELECT lid FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $langcode));
+      if (!$exists) {
+        // No translation in this language.
+        db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode, $translation, $plid, $plural);
+        $report[0]++;
+      }
+      else if ($mode == LOCALE_IMPORT_OVERWRITE) {
+        // Translation exists, only overwrite if instructed.
+        db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE language = '%s' AND lid = %d", $translation, $plid, $plural, $langcode, $lid);
+        $report[1]++;
+      }
+    }
+    else {
+      // No such source string in the database yet.
+      db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', '%s')", $location, $source, $textgroup);
+      $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup));
+      db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode, $translation, $plid, $plural);
+      $report[0]++;
+    }
+  }
+  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+    // Empty translation, remove existing if instructed.
+    db_query("DELETE FROM {locales_target} WHERE language = '%s' AND lid = %d AND plid = %d AND plural = %d", $translation, $langcode, $lid, $plid, $plural);
+    $report[2]++;
+  }
+
+  return $lid;
+}
+
+/**
+ * Parses a Gettext Portable Object file header
+ *
+ * @param $header
+ *   A string containing the complete header
+ * @return
+ *   An associative array of key-value pairs
+ */
+function _locale_import_parse_header($header) {
+  $header_parsed = array();
+  $lines = array_map('trim', explode("\n", $header));
+  foreach ($lines as $line) {
+    if ($line) {
+      list($tag, $contents) = explode(":", $line, 2);
+      $header_parsed[trim($tag)] = trim($contents);
+    }
+  }
+  return $header_parsed;
+}
+
+/**
+ * Parses a Plural-Forms entry from a Gettext Portable Object file header
+ *
+ * @param $pluralforms
+ *   A string containing the Plural-Forms entry
+ * @param $filename
+ *   A string containing the filename
+ * @return
+ *   An array containing the number of plurals and a
+ *   formula in PHP for computing the plural form
+ */
+function _locale_import_parse_plural_forms($pluralforms, $filename) {
+  // First, delete all whitespace
+  $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
+
+  // Select the parts that define nplurals and plural
+  $nplurals = strstr($pluralforms, "nplurals=");
+  if (strpos($nplurals, ";")) {
+    $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
+  }
+  else {
+    return FALSE;
+  }
+  $plural = strstr($pluralforms, "plural=");
+  if (strpos($plural, ";")) {
+    $plural = substr($plural, 7, strpos($plural, ";") - 7);
+  }
+  else {
+    return FALSE;
+  }
+
+  // Get PHP version of the plural formula
+  $plural = _locale_import_parse_arithmetic($plural);
+
+  if ($plural !== FALSE) {
+    return array($nplurals, $plural);
+  }
+  else {
+    drupal_set_message(t('The translation file %filename contains an error: the plural formula could not be parsed.', array('%filename' => $filename)), 'error');
+    return FALSE;
+  }
+}
+
+/**
+ * Parses and sanitizes an arithmetic formula into a PHP expression
+ *
+ * While parsing, we ensure, that the operators have the right
+ * precedence and associativity.
+ *
+ * @param $string
+ *   A string containing the arithmetic formula
+ * @return
+ *   The PHP version of the formula
+ */
+function _locale_import_parse_arithmetic($string) {
+  // Operator precedence table
+  $prec = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
+  // Right associativity
+  $rasc = array("?" => 1, ":" => 1);
+
+  $tokens = _locale_import_tokenize_formula($string);
+
+  // Parse by converting into infix notation then back into postfix
+  $opstk = array();
+  $elstk = array();
+
+  foreach ($tokens as $token) {
+    $ctok = $token;
+
+    // Numbers and the $n variable are simply pushed into $elarr
+    if (is_numeric($token)) {
+      $elstk[] = $ctok;
+    }
+    elseif ($ctok == "n") {
+      $elstk[] = '$n';
+    }
+    elseif ($ctok == "(") {
+      $opstk[] = $ctok;
+    }
+    elseif ($ctok == ")") {
+      $topop = array_pop($opstk);
+      while (isset($topop) && ($topop != "(")) {
+        $elstk[] = $topop;
+        $topop = array_pop($opstk);
+      }
+    }
+    elseif (!empty($prec[$ctok])) {
+      // If it's an operator, then pop from $oparr into $elarr until the
+      // precedence in $oparr is less than current, then push into $oparr
+      $topop = array_pop($opstk);
+      while (isset($topop) && ($prec[$topop] >= $prec[$ctok]) && !(($prec[$topop] == $prec[$ctok]) && !empty($rasc[$topop]) && !empty($rasc[$ctok]))) {
+        $elstk[] = $topop;
+        $topop = array_pop($opstk);
+      }
+      if ($topop) {
+        $opstk[] = $topop;   // Return element to top
+      }
+      $opstk[] = $ctok;      // Parentheses are not needed
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  // Flush operator stack
+  $topop = array_pop($opstk);
+  while ($topop != NULL) {
+    $elstk[] = $topop;
+    $topop = array_pop($opstk);
+  }
+
+  // Now extract formula from stack
+  $prevsize = count($elstk) + 1;
+  while (count($elstk) < $prevsize) {
+    $prevsize = count($elstk);
+    for ($i = 2; $i < count($elstk); $i++) {
+      $op = $elstk[$i];
+      if (!empty($prec[$op])) {
+        $f = "";
+        if ($op == ":") {
+          $f = $elstk[$i - 2] ."):". $elstk[$i - 1] .")";
+        }
+        elseif ($op == "?") {
+          $f = "(". $elstk[$i - 2] ."?(". $elstk[$i - 1];
+        }
+        else {
+          $f = "(". $elstk[$i - 2] . $op . $elstk[$i - 1] .")";
+        }
+        array_splice($elstk, $i - 2, 3, $f);
+        break;
+      }
+    }
+  }
+
+  // If only one element is left, the number of operators is appropriate
+  if (count($elstk) == 1) {
+    return $elstk[0];
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Backward compatible implementation of token_get_all() for formula parsing
+ *
+ * @param $string
+ *   A string containing the arithmetic formula
+ * @return
+ *   The PHP version of the formula
+ */
+function _locale_import_tokenize_formula($formula) {
+  $formula = str_replace(" ", "", $formula);
+  $tokens = array();
+  for ($i = 0; $i < strlen($formula); $i++) {
+    if (is_numeric($formula[$i])) {
+      $num = $formula[$i];
+      $j = $i + 1;
+      while ($j < strlen($formula) && is_numeric($formula[$j])) {
+        $num .= $formula[$j];
+        $j++;
+      }
+      $i = $j - 1;
+      $tokens[] = $num;
+    }
+    elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space
+      $next = $formula[$i + 1];
+      switch ($pos) {
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+          if ($next == '=') {
+            $tokens[] = $formula[$i] .'=';
+            $i++;
+          }
+          else {
+            $tokens[] = $formula[$i];
+          }
+          break;
+        case 5:
+          if ($next == '&') {
+            $tokens[] = '&&';
+            $i++;
+          }
+          else {
+            $tokens[] = $formula[$i];
+          }
+          break;
+        case 6:
+          if ($next == '|') {
+            $tokens[] = '||';
+            $i++;
+          }
+          else {
+            $tokens[] = $formula[$i];
+          }
+          break;
+      }
+    }
+    else {
+      $tokens[] = $formula[$i];
+    }
+  }
+  return $tokens;
+}
+
+/**
+ * Modify a string to contain proper count indices
+ *
+ * This is a callback function used via array_map()
+ *
+ * @param $entry
+ *   An array element
+ * @param $key
+ *   Index of the array element
+ */
+function _locale_import_append_plural($entry, $key) {
+  // No modifications for 0, 1
+  if ($key == 0 || $key == 1) {
+    return $entry;
+  }
+
+  // First remove any possibly false indices, then add new ones
+  $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
+  return preg_replace('/(@count)/', "\\1[$key]", $entry);
+}
+
+/**
+ * Generate a short, one string version of the passed comment array
+ *
+ * @param $comment
+ *   An array of strings containing a comment
+ * @return
+ *   Short one string version of the comment
+ */
+function _locale_import_shorten_comments($comment) {
+  $comm = '';
+  while (count($comment)) {
+    $test = $comm . substr(array_shift($comment), 1) .', ';
+    if (strlen($comm) < 130) {
+      $comm = $test;
+    }
+    else {
+      break;
+    }
+  }
+  return substr($comm, 0, -2);
+}
+
+/**
+ * Parses a string in quotes
+ *
+ * @param $string
+ *   A string specified with enclosing quotes
+ * @return
+ *   The string parsed from inside the quotes
+ */
+function _locale_import_parse_quoted($string) {
+  if (substr($string, 0, 1) != substr($string, -1, 1)) {
+    return FALSE;   // Start and end quotes must be the same
+  }
+  $quote = substr($string, 0, 1);
+  $string = substr($string, 1, -1);
+  if ($quote == '"') {        // Double quotes: strip slashes
+    return stripcslashes($string);
+  }
+  elseif ($quote == "'") {  // Simple quote: return as-is
+    return $string;
+  }
+  else {
+    return FALSE;             // Unrecognized quote
+  }
+}
+/**
+ * @} End of "locale-api-import"
+ */
+
+/**
+ * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
+ * Drupal.formatPlural() and inserts them into the database.
+ */
+function _locale_parse_js_file($filepath) {
+  global $language;
+
+  // Load the JavaScript file.
+  $file = file_get_contents($filepath);
+
+  // Match all calls to Drupal.t() in an array.
+  // Note: \s also matches newlines with the 's' modifier.
+  preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*('. LOCALE_JS_STRING .')\s*[,\)]~s', $file, $t_matches);
+
+  // Match all Drupal.formatPlural() calls in another array.
+  preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*('. LOCALE_JS_STRING .')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $file, $plural_matches);
+
+  // Loop through all matches and process them.
+  $all_matches = array_merge($plural_matches[1], $t_matches[1]);
+  foreach ($all_matches as $key => $string) {
+    $strings = array($string);
+
+    // If there is also a plural version of this string, add it to the strings array.
+    if (isset($plural_matches[2][$key])) {
+      $strings[] = $plural_matches[2][$key];
+    }
+
+    foreach ($strings as $key => $string) {
+      // Remove the quotes and string concatenations from the string.
+      $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1)));
+
+      $result = db_query("SELECT lid, location FROM {locales_source} WHERE source = '%s' AND textgroup = 'default'", $string);
+      if ($source = db_fetch_object($result)) {
+        // We already have this source string and now have to add the location
+        // to the location column, if this file is not yet present in there.
+        $locations = preg_split('~\s*;\s*~', $source->location);
+
+        if (!in_array($filepath, $locations)) {
+          $locations[] = $filepath;
+          $locations = implode('; ', $locations);
+
+          // Save the new locations string to the database.
+          db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $locations, $source->lid);
+        }
+      }
+      else {
+        // We don't have the source string yet, thus we insert it into the database.
+        db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', 'default')", $filepath, $string);
+      }
+    }
+  }
+}
+
+/**
+ * @defgroup locale-api-export Translation (template) export API.
+ * @{
+ */
+
+/**
+ * Generates a structured array of all strings with translations in
+ * $language, if given. This array can be used to generate an export
+ * of the string in the database.
+ *
+ * @param $language
+ *   Language object to generate the output for, or NULL if generating
+ *   translation template.
+ * @param $group
+ *   Text group to export PO file from (eg. 'default' for interface translations)
+ */
+function _locale_export_get_strings($language = NULL, $group = 'default') {
+  if (isset($language)) {
+    $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.textgroup = '%s' ORDER BY t.plid, t.plural", $language->language, $group);
+  }
+  else {
+    $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = '%s' ORDER BY t.plid, t.plural", $group);
+  }
+  $strings = array();
+  while ($child = db_fetch_object($result)) {
+    $string = array(
+      'comment'     => $child->location,
+      'source'      => $child->source,
+      'translation' => isset($child->translation) ? $child->translation : ''
+    );
+    if ($child->plid) {
+      // Has a parent lid. Since we process in the order of plids,
+      // we already have the parent in the array, so we can add the
+      // lid to the next plural version to it. This builds a linked
+      // list of plurals.
+      $string['child'] = TRUE;
+      $strings[$child->plid]['plural'] = $child->lid;
+    }
+    $strings[$child->lid] = $string;
+  }
+  return $strings;
+}
+
+/**
+ * Generates the PO(T) file contents for given strings.
+ *
+ * @param $language
+ *   Language object to generate the output for, or NULL if generating
+ *   translation template.
+ * @param $strings
+ *   Array of strings to export. See _locale_export_get_strings()
+ *   on how it should be formatted.
+ * @param $header
+ *   The header portion to use for the output file. Defaults
+ *   are provided for PO and POT files.
+ */
+function _locale_export_po_generate($language = NULL, $strings = array(), $header = NULL) {
+  global $user;
+
+  if (!isset($header)) {
+    if (isset($language)) {
+      $header = '# '. $language->name .' translation of '. variable_get('site_name', 'Drupal') ."\n";
+      $header .= '# Generated by '. $user->name .' <'. $user->mail .">\n";
+      $header .= "#\n";
+      $header .= "msgid \"\"\n";
+      $header .= "msgstr \"\"\n";
+      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+      $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
+      $header .= "\"PO-Revision-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
+      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
+      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
+      $header .= "\"MIME-Version: 1.0\\n\"\n";
+      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+      if ($language->formula && $language->plurals) {
+        $header .= "\"Plural-Forms: nplurals=". $language->plurals ."; plural=". strtr($language->formula, array('$' => '')) .";\\n\"\n";
+      }
+    }
+    else {
+      $header = "# LANGUAGE translation of PROJECT\n";
+      $header .= "# Copyright (c) YEAR NAME <EMAIL@ADDRESS>\n";
+      $header .= "#\n";
+      $header .= "msgid \"\"\n";
+      $header .= "msgstr \"\"\n";
+      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+      $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n";
+      $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
+      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
+      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
+      $header .= "\"MIME-Version: 1.0\\n\"\n";
+      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+      $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n";
+    }
+  }
+
+  $output = $header ."\n";
+
+  foreach ($strings as $lid => $string) {
+    // Only process non-children, children are output below their parent.
+    if (!isset($string['child'])) {
+      if ($string['comment']) {
+        $output .= '#: '. $string['comment'] ."\n";
+      }
+      $output .= 'msgid '. _locale_export_string($string['source']);
+      if (!empty($string['plural'])) {
+        $plural = $string['plural'];
+        $output .= 'msgid_plural '. _locale_export_string($strings[$plural]['source']);
+        if (isset($language)) {
+          $translation = $string['translation'];
+          for ($i = 0; $i < $language->plurals; $i++) {
+            $output .= 'msgstr['. $i .'] '. _locale_export_string($translation);
+            if ($plural) {
+              $translation = _locale_export_remove_plural($strings[$plural]['translation']);
+              $plural = isset($strings[$plural]['plural']) ? $strings[$plural]['plural'] : 0;
+            }
+            else {
+              $translation = '';
+            }
+          }
+        }
+        else {
+          $output .= 'msgstr[0] ""'."\n";
+          $output .= 'msgstr[1] ""'."\n";
+        }
+      }
+      else {
+        $output .= 'msgstr '. _locale_export_string($string['translation']);
+      }
+      $output .= "\n";
+    }
+  }
+  return $output;
+}
+
+/**
+ * Write a generated PO or POT file to the output.
+ *
+ * @param $language
+ *   Language object to generate the output for, or NULL if generating
+ *   translation template.
+ * @param $output
+ *   The PO(T) file to output as a string. See _locale_export_generate_po()
+ *   on how it can be generated.
+ */
+function _locale_export_po($language = NULL, $output = NULL) {
+  // Log the export event.
+  if (isset($language)) {
+    $filename = $language->language .'.po';
+    watchdog('locale', 'Exported %locale translation file: %filename.', array('%locale' => $language->name, '%filename' => $filename));
+  }
+  else {
+    $filename = 'drupal.pot';
+    watchdog('locale', 'Exported translation file: %filename.', array('%filename' => $filename));
+  }
+  // Download the file fo the client.
+  header("Content-Disposition: attachment; filename=$filename");
+  header("Content-Type: text/plain; charset=utf-8");
+  print $output;
+  die();
+}
+
+/**
+ * Print out a string on multiple lines
+ */
+function _locale_export_string($str) {
+  $stri = addcslashes($str, "\0..\37\\\"");
+  $parts = array();
+
+  // Cut text into several lines
+  while ($stri != "") {
+    $i = strpos($stri, "\\n");
+    if ($i === FALSE) {
+      $curstr = $stri;
+      $stri = "";
+    }
+    else {
+      $curstr = substr($stri, 0, $i + 2);
+      $stri = substr($stri, $i + 2);
+    }
+    $curparts = explode("\n", _locale_export_wrap($curstr, 70));
+    $parts = array_merge($parts, $curparts);
+  }
+
+  // Multiline string
+  if (count($parts) > 1) {
+    return "\"\"\n\"". implode("\"\n\"", $parts) ."\"\n";
+  }
+  // Single line string
+  elseif (count($parts) == 1) {
+    return "\"$parts[0]\"\n";
+  }
+  // No translation
+  else {
+    return "\"\"\n";
+  }
+}
+
+/**
+ * Custom word wrapping for Portable Object (Template) files.
+ */
+function _locale_export_wrap($str, $len) {
+  $words = explode(' ', $str);
+  $ret = array();
+
+  $cur = "";
+  $nstr = 1;
+  while (count($words)) {
+    $word = array_shift($words);
+    if ($nstr) {
+      $cur = $word;
+      $nstr = 0;
+    }
+    elseif (strlen("$cur $word") > $len) {
+      $ret[] = $cur ." ";
+      $cur = $word;
+    }
+    else {
+      $cur = "$cur $word";
+    }
+  }
+  $ret[] = $cur;
+
+  return implode("\n", $ret);
+}
+
+/**
+ * Removes plural index information from a string
+ */
+function _locale_export_remove_plural($entry) {
+  return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
+}
+/**
+ * @} End of "locale-api-export"
+ */
+
+/**
+ * @defgroup locale-api-seek String search functions.
+ * @{
+ */
+
+/**
+ * Perform a string search and display results in a table
+ */
+function _locale_translate_seek() {
+  $output = '';
+
+  // We have at least one criterion to match
+  if ($query = _locale_translate_seek_query()) {
+    $join = "SELECT s.source, s.location, s.lid, s.textgroup, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid ";
+
+    $arguments = array();
+    $limit_language = FALSE;
+    // Compute LIKE section
+    switch ($query['translation']) {
+      case 'translated':
+        $where = "WHERE (t.translation LIKE '%%%s%%')";
+        $orderby = "ORDER BY t.translation";
+        $arguments[] = $query['string'];
+        break;
+      case 'untranslated':
+        $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation IS NULL)";
+        $orderby = "ORDER BY s.source";
+        $arguments[] = $query['string'];
+        break;
+      case 'all' :
+      default:
+        $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')";
+        $orderby = '';
+        $arguments[] = $query['string'];
+        $arguments[] = $query['string'];
+        break;
+    }
+    $grouplimit = '';
+    if (!empty($query['group']) && $query['group'] != 'all') {
+      $grouplimit = " AND s.textgroup = '%s'";
+      $arguments[] = $query['group'];
+    }
+
+    switch ($query['language']) {
+      // Force search in source strings
+      case "en":
+        $sql = $join ." WHERE s.source LIKE '%%%s%%' $grouplimit ORDER BY s.source";
+        $arguments = array($query['string']); // $where is not used, discard its arguments
+        if (!empty($grouplimit)) {
+          $arguments[] = $query['group'];
+        }
+        break;
+      // Search in all languages
+      case "all":
+        $sql = "$join $where $grouplimit $orderby";
+        break;
+      // Some different language
+      default:
+        $sql = "$join AND t.language = '%s' $where $grouplimit $orderby";
+        array_unshift($arguments, $query['language']);
+        // Don't show translation flags for other languages, we can't see them with this search.
+        $limit_language = $query['language'];
+    }
+
+    $result = pager_query($sql, 50, 0, NULL, $arguments);
+
+    $groups = module_invoke_all('locale', 'groups');
+    $header = array(t('Text group'), t('String'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2'));
+    $arr = array();
+    while ($locale = db_fetch_object($result)) {
+      $arr[$locale->lid]['group'] = $groups[$locale->textgroup];
+      $arr[$locale->lid]['languages'][$locale->language] = $locale->translation;
+      $arr[$locale->lid]['location'] = $locale->location;
+      $arr[$locale->lid]['source'] = $locale->source;
+    }
+    $rows = array();
+    foreach ($arr as $lid => $value) {
+      $rows[] = array(
+        $value['group'],
+        array('data' => check_plain(truncate_utf8($value['source'], 150, FALSE, TRUE)) .'<br /><small>'. $value['location'] .'</small>'),
+        array('data' => _locale_translate_language_list($value['languages'], $limit_language), 'align' => 'center'),
+        array('data' => l(t('edit'), "admin/build/translate/edit/$lid"), 'class' => 'nowrap'),
+        array('data' => l(t('delete'), "admin/build/translate/delete/$lid"), 'class' => 'nowrap'),
+      );
+    }
+
+    if (count($rows)) {
+      $output .= theme('table', $header, $rows);
+      if ($pager = theme('pager', NULL, 50)) {
+        $output .= $pager;
+      }
+    }
+    else {
+      $output .= t('No strings found for your search.');
+    }
+  }
+
+  return $output;
+}
+
+/**
+ * Build array out of search criteria specified in request variables
+ */
+function _locale_translate_seek_query() {
+  static $query = NULL;
+  if (!isset($query)) {
+    $query = array();
+    $fields = array('string', 'language', 'translation', 'group');
+    foreach ($fields as $field) {
+      if (isset($_REQUEST[$field])) {
+        $query[$field] = $_REQUEST[$field];
+      }
+    }
+  }
+  return $query;
+}
+
+/**
+ * Force the JavaScript translation file(s) to be refreshed.
+ *
+ * This function sets a refresh flag for a specified language, or all
+ * languages except English, if none specified. JavaScript translation
+ * files are rebuilt (with locale_update_js_files()) the next time a
+ * request is served in that language.
+ *
+ * @param $langcode
+ *   The language code for which the file needs to be refreshed.
+ * @return
+ *   New content of the 'javascript_parsed' variable.
+ */
+function _locale_invalidate_js($langcode = NULL) {
+  $parsed = variable_get('javascript_parsed', array());
+
+  if (empty($langcode)) {
+    // Invalidate all languages.
+    $languages = language_list();
+    unset($languages['en']);
+    foreach ($languages as $lcode => $data) {
+      $parsed['refresh:'. $lcode] = 'waiting';
+    }
+  }
+  else {
+    // Invalidate single language.
+    $parsed['refresh:'. $langcode] = 'waiting';
+  }
+
+  variable_set('javascript_parsed', $parsed);
+  return $parsed;
+}
+
+/**
+ * (Re-)Creates the JavaScript translation file for a language.
+ *
+ * @param $language
+ *   The language, the translation file should be (re)created for.
+ */
+function _locale_rebuild_js($langcode = NULL) {
+  if (!isset($langcode)) {
+    global $language;
+  }
+  else {
+    // Get information about the locale.
+    $languages = language_list();
+    $language = $languages[$langcode];
+  }
+
+  // Construct the array for JavaScript translations.
+  // We sort on plural so that we have all plural forms before singular forms.
+  $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.location LIKE '%%.js%%' AND s.textgroup = 'default' ORDER BY t.plural DESC", $language->language);
+
+  $translations = $plurals = array();
+  while ($data = db_fetch_object($result)) {
+    // Only add this to the translations array when there is actually a translation.
+    if (!empty($data->translation)) {
+      if ($data->plural) {
+        // When the translation is a plural form, first add it to another array and
+        // wait for the singular (parent) translation.
+        if (!isset($plurals[$data->plid])) {
+          $plurals[$data->plid] = array($data->plural => $data->translation);
+        }
+        else {
+          $plurals[$data->plid] += array($data->plural => $data->translation);
+        }
+      }
+      elseif (isset($plurals[$data->lid])) {
+        // There are plural translations for this translation, so get them from
+        // the plurals array and add them to the final translations array.
+        $translations[$data->source] = array($data->plural => $data->translation) + $plurals[$data->lid];
+        unset($plurals[$data->lid]);
+      }
+      else {
+        // There are no plural forms for this translation, so just add it to
+        // the translations array.
+        $translations[$data->source] = $data->translation;
+      }
+    }
+  }
+
+  // Construct the JavaScript file, if there are translations.
+  $data = $status = '';
+  if (!empty($translations)) {
+
+    $data = "Drupal.locale = { ";
+
+    if (!empty($language->formula)) {
+      $data .= "'pluralFormula': function(\$n) { return Number({$language->formula}); }, ";
+    }
+
+    $data .= "'strings': ". drupal_to_js($translations) ." };";
+    $data_hash = md5($data);
+  }
+
+  // Construct the filepath where JS translation files are stored.
+  // There is (on purpose) no front end to edit that variable.
+  $dir = file_create_path(variable_get('locale_js_directory', 'languages'));
+
+  // Delete old file, if we have no translations anymore, or a different file to be saved.
+  if (!empty($language->javascript) && (!$data || $language->javascript != $data_hash)) {
+    file_delete(file_create_path($dir .'/'. $language->language .'_'. $language->javascript .'.js'));
+    $language->javascript = '';
+    $status = 'deleted';
+  }
+
+  // Only create a new file if the content has changed.
+  if ($data && $language->javascript != $data_hash) {
+    // Ensure that the directory exists and is writable, if possible.
+    file_check_directory($dir, TRUE);
+
+    // Save the file.
+    $dest = $dir .'/'. $language->language .'_'. $data_hash .'.js';
+    if (file_save_data($data, $dest)) {
+      $language->javascript = $data_hash;
+      $status = ($status == 'deleted') ? 'updated' : 'created';
+    }
+    else {
+      $language->javascript = '';
+      $status = 'error';
+    }
+  }
+
+  // Save the new JavaScript hash (or an empty value if the file
+  // just got deleted). Act only if some operation was executed.
+  if ($status) {
+    db_query("UPDATE {languages} SET javascript = '%s' WHERE language = '%s'", $language->javascript, $language->language);
+
+    // Update the default language variable if the default language has been altered.
+    // This is necessary to keep the variable consistent with the database
+    // version of the language and to prevent checking against an outdated hash.
+    $default_langcode = language_default('language');
+    if ($default_langcode == $language->language) {
+      $default = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $default_langcode));
+      variable_set('language_default', $default);
+    }
+  }
+
+  // Log the operation and return success flag.
+  switch ($status) {
+    case 'updated':
+      watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => t($language->name)));
+      return TRUE;
+    case 'created':
+      watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => t($language->name)));
+      return TRUE;
+    case 'deleted':
+      watchdog('locale', 'Removed JavaScript translation file for the language %language, because no translations currently exist for that language.', array('%language' => t($language->name)));
+      return TRUE;
+    case 'error':
+      watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => t($language->name)), WATCHDOG_ERROR);
+      return FALSE;
+    default:
+      // No operation needed.
+      return TRUE;
+  }
+}
+
+/**
+ * List languages in search result table
+ */
+function _locale_translate_language_list($translation, $limit_language) {
+  // Add CSS
+  drupal_add_css(drupal_get_path('module', 'locale') .'/locale.css', 'module', 'all', FALSE);
+
+  $languages = language_list();
+  unset($languages['en']);
+  $output = '';
+  foreach ($languages as $langcode => $language) {
+    if (!$limit_language || $limit_language == $langcode) {
+      $output .= (!empty($translation[$langcode])) ? $langcode .' ' : "<em class=\"locale-untranslated\">$langcode</em> ";
+    }
+  }
+
+  return $output;
+}
+/**
+ * @} End of "locale-api-seek"
+ */
+
+/**
+ * @defgroup locale-api-predefined List of predefined languages
+ * @{
+ */
+
+/**
+ * Prepares the language code list for a select form item with only the unsupported ones
+ */
+function _locale_prepare_predefined_list() {
+  $languages = language_list();
+  $predefined = _locale_get_predefined_list();
+  foreach ($predefined as $key => $value) {
+    if (isset($languages[$key])) {
+      unset($predefined[$key]);
+      continue;
+    }
+    // Include native name in output, if possible
+    if (count($value) > 1) {
+      $tname = t($value[0]);
+      $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])";
+    }
+    else {
+      $predefined[$key] = t($value[0]);
+    }
+  }
+  asort($predefined);
+  return $predefined;
+}
+
+/**
+ * Some of the common languages with their English and native names
+ *
+ * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
+ */
+function _locale_get_predefined_list() {
+  return array(
+    "aa" => array("Afar"),
+    "ab" => array("Abkhazian", "аҧсуа бызшәа"),
+    "ae" => array("Avestan"),
+    "af" => array("Afrikaans"),
+    "ak" => array("Akan"),
+    "am" => array("Amharic", "አማርኛ"),
+    "ar" => array("Arabic", /* Left-to-right marker "‭" */ "العربية", LANGUAGE_RTL),
+    "as" => array("Assamese"),
+    "av" => array("Avar"),
+    "ay" => array("Aymara"),
+    "az" => array("Azerbaijani", "azərbaycan"),
+    "ba" => array("Bashkir"),
+    "be" => array("Belarusian", "Беларуская"),
+    "bg" => array("Bulgarian", "Български"),
+    "bh" => array("Bihari"),
+    "bi" => array("Bislama"),
+    "bm" => array("Bambara", "Bamanankan"),
+    "bn" => array("Bengali"),
+    "bo" => array("Tibetan"),
+    "br" => array("Breton"),
+    "bs" => array("Bosnian", "Bosanski"),
+    "ca" => array("Catalan", "Català"),
+    "ce" => array("Chechen"),
+    "ch" => array("Chamorro"),
+    "co" => array("Corsican"),
+    "cr" => array("Cree"),
+    "cs" => array("Czech", "Čeština"),
+    "cu" => array("Old Slavonic"),
+    "cv" => array("Chuvash"),
+    "cy" => array("Welsh", "Cymraeg"),
+    "da" => array("Danish", "Dansk"),
+    "de" => array("German", "Deutsch"),
+    "dv" => array("Maldivian"),
+    "dz" => array("Bhutani"),
+    "ee" => array("Ewe", "Ɛʋɛ"),
+    "el" => array("Greek", "Ελληνικά"),
+    "en" => array("English"),
+    "eo" => array("Esperanto"),
+    "es" => array("Spanish", "Español"),
+    "et" => array("Estonian", "Eesti"),
+    "eu" => array("Basque", "Euskera"),
+    "fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی", LANGUAGE_RTL),
+    "ff" => array("Fulah", "Fulfulde"),
+    "fi" => array("Finnish", "Suomi"),
+    "fj" => array("Fiji"),
+    "fo" => array("Faeroese"),
+    "fr" => array("French", "Français"),
+    "fy" => array("Frisian", "Frysk"),
+    "ga" => array("Irish", "Gaeilge"),
+    "gd" => array("Scots Gaelic"),
+    "gl" => array("Galician", "Galego"),
+    "gn" => array("Guarani"),
+    "gu" => array("Gujarati"),
+    "gv" => array("Manx"),
+    "ha" => array("Hausa"),
+    "he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית", LANGUAGE_RTL),
+    "hi" => array("Hindi", "हिन्दी"),
+    "ho" => array("Hiri Motu"),
+    "hr" => array("Croatian", "Hrvatski"),
+    "hu" => array("Hungarian", "Magyar"),
+    "hy" => array("Armenian", "Հայերեն"),
+    "hz" => array("Herero"),
+    "ia" => array("Interlingua"),
+    "id" => array("Indonesian", "Bahasa Indonesia"),
+    "ie" => array("Interlingue"),
+    "ig" => array("Igbo"),
+    "ik" => array("Inupiak"),
+    "is" => array("Icelandic", "Íslenska"),
+    "it" => array("Italian", "Italiano"),
+    "iu" => array("Inuktitut"),
+    "ja" => array("Japanese", "日本語"),
+    "jv" => array("Javanese"),
+    "ka" => array("Georgian"),
+    "kg" => array("Kongo"),
+    "ki" => array("Kikuyu"),
+    "kj" => array("Kwanyama"),
+    "kk" => array("Kazakh", "Қазақ"),
+    "kl" => array("Greenlandic"),
+    "km" => array("Cambodian"),
+    "kn" => array("Kannada", "ಕನ್ನಡ"),
+    "ko" => array("Korean", "한국어"),
+    "kr" => array("Kanuri"),
+    "ks" => array("Kashmiri"),
+    "ku" => array("Kurdish", "Kurdî"),
+    "kv" => array("Komi"),
+    "kw" => array("Cornish"),
+    "ky" => array("Kirghiz", "Кыргыз"),
+    "la" => array("Latin", "Latina"),
+    "lb" => array("Luxembourgish"),
+    "lg" => array("Luganda"),
+    "ln" => array("Lingala"),
+    "lo" => array("Laothian"),
+    "lt" => array("Lithuanian", "Lietuvių"),
+    "lv" => array("Latvian", "Latviešu"),
+    "mg" => array("Malagasy"),
+    "mh" => array("Marshallese"),
+    "mi" => array("Maori"),
+    "mk" => array("Macedonian", "Македонски"),
+    "ml" => array("Malayalam", "മലയാളം"),
+    "mn" => array("Mongolian"),
+    "mo" => array("Moldavian"),
+    "mr" => array("Marathi"),
+    "ms" => array("Malay", "Bahasa Melayu"),
+    "mt" => array("Maltese", "Malti"),
+    "my" => array("Burmese"),
+    "na" => array("Nauru"),
+    "nd" => array("North Ndebele"),
+    "ne" => array("Nepali"),
+    "ng" => array("Ndonga"),
+    "nl" => array("Dutch", "Nederlands"),
+    "nb" => array("Norwegian Bokmål", "Bokmål"),
+    "nn" => array("Norwegian Nynorsk", "Nynorsk"),
+    "nr" => array("South Ndebele"),
+    "nv" => array("Navajo"),
+    "ny" => array("Chichewa"),
+    "oc" => array("Occitan"),
+    "om" => array("Oromo"),
+    "or" => array("Oriya"),
+    "os" => array("Ossetian"),
+    "pa" => array("Punjabi"),
+    "pi" => array("Pali"),
+    "pl" => array("Polish", "Polski"),
+    "ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو", LANGUAGE_RTL),
+    "pt" => array("Portuguese, Portugal", "Português"),
+    "pt-br" => array("Portuguese, Brazil", "Português"),
+    "qu" => array("Quechua"),
+    "rm" => array("Rhaeto-Romance"),
+    "rn" => array("Kirundi"),
+    "ro" => array("Romanian", "Română"),
+    "ru" => array("Russian", "Русский"),
+    "rw" => array("Kinyarwanda"),
+    "sa" => array("Sanskrit"),
+    "sc" => array("Sardinian"),
+    "sd" => array("Sindhi"),
+    "se" => array("Northern Sami"),
+    "sg" => array("Sango"),
+    "sh" => array("Serbo-Croatian"),
+    "si" => array("Singhalese"),
+    "sk" => array("Slovak", "Slovenčina"),
+    "sl" => array("Slovenian", "Slovenščina"),
+    "sm" => array("Samoan"),
+    "sn" => array("Shona"),
+    "so" => array("Somali"),
+    "sq" => array("Albanian", "Shqip"),
+    "sr" => array("Serbian", "Српски"),
+    "ss" => array("Siswati"),
+    "st" => array("Sesotho"),
+    "su" => array("Sudanese"),
+    "sv" => array("Swedish", "Svenska"),
+    "sw" => array("Swahili", "Kiswahili"),
+    "ta" => array("Tamil", "தமிழ்"),
+    "te" => array("Telugu", "తెలుగు"),
+    "tg" => array("Tajik"),
+    "th" => array("Thai", "ภาษาไทย"),
+    "ti" => array("Tigrinya"),
+    "tk" => array("Turkmen"),
+    "tl" => array("Tagalog"),
+    "tn" => array("Setswana"),
+    "to" => array("Tonga"),
+    "tr" => array("Turkish", "Türkçe"),
+    "ts" => array("Tsonga"),
+    "tt" => array("Tatar", "Tatarça"),
+    "tw" => array("Twi"),
+    "ty" => array("Tahitian"),
+    "ug" => array("Uighur"),
+    "uk" => array("Ukrainian", "Українська"),
+    "ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو", LANGUAGE_RTL),
+    "uz" => array("Uzbek", "o'zbek"),
+    "ve" => array("Venda"),
+    "vi" => array("Vietnamese", "Tiếng Việt"),
+    "wo" => array("Wolof"),
+    "xh" => array("Xhosa", "isiXhosa"),
+    "yi" => array("Yiddish"),
+    "yo" => array("Yoruba", "Yorùbá"),
+    "za" => array("Zhuang"),
+    "zh-hans" => array("Chinese, Simplified", "简体中文"),
+    "zh-hant" => array("Chinese, Traditional", "繁體中文"),
+    "zu" => array("Zulu", "isiZulu"),
+  );
+}
+/**
+ * @} End of "locale-api-languages-predefined"
+ */
+
+/**
+ * @defgroup locale-autoimport Automatic interface translation import
+ * @{
+ */
+
+/**
+ * Prepare a batch to import translations for all enabled
+ * modules in a given language.
+ *
+ * @param $langcode
+ *   Language code to import translations for.
+ * @param $finished
+ *   Optional finished callback for the batch.
+ * @param $skip
+ *   Array of component names to skip. Used in the installer for the
+ *   second pass import, when most components are already imported.
+ * @return
+ *   A batch structure or FALSE if no files found.
+ */
+function locale_batch_by_language($langcode, $finished = NULL, $skip = array()) {
+  // Collect all files to import for all enabled modules and themes.
+  $files = array();
+  $components = array();
+  $query = "SELECT name, filename FROM {system} WHERE status = 1";
+  if (count($skip)) {
+    $query .= " AND name NOT IN (". db_placeholders($skip, 'varchar') .")";
+  }
+  $result = db_query($query, $skip);
+  while ($component = db_fetch_object($result)) {
+    // Collect all files for all components, names as $langcode.po or
+    // with names ending with $langcode.po. This allows for filenames
+    // like node-module.de.po to let translators use small files and
+    // be able to import in smaller chunks.
+    $files = array_merge($files, file_scan_directory(dirname($component->filename) .'/translations', '(^|\.)'. $langcode .'\.po$', array('.', '..', 'CVS'), 0, FALSE));
+    $components[] = $component->name;
+  }
+
+  return _locale_batch_build($files, $finished, $components);
+}
+
+/**
+ * Prepare a batch to run when installing modules or enabling themes.
+ * This batch will import translations for the newly added components
+ * in all the languages already set up on the site.
+ *
+ * @param $components
+ *   An array of component (theme and/or module) names to import
+ *   translations for.
+ * @param $finished
+ *   Optional finished callback for the batch.
+ */
+function locale_batch_by_component($components, $finished = '_locale_batch_system_finished') {
+  $files = array();
+  $languages = language_list('enabled');
+  unset($languages[1]['en']);
+  if (count($languages[1])) {
+    $language_list = join('|', array_keys($languages[1]));
+    // Collect all files to import for all $components.
+    $result = db_query("SELECT name, filename FROM {system} WHERE status = 1");
+    while ($component = db_fetch_object($result)) {
+      if (in_array($component->name, $components)) {
+        // Collect all files for this component in all enabled languages, named
+        // as $langcode.po or with names ending with $langcode.po. This allows
+        // for filenames like node-module.de.po to let translators use small
+        // files and be able to import in smaller chunks.
+        $files = array_merge($files, file_scan_directory(dirname($component->filename) .'/translations', '(^|\.)('. $language_list .')\.po$', array('.', '..', 'CVS'), 0, FALSE));
+      }
+    }
+    return _locale_batch_build($files, $finished);
+  }
+  return FALSE;
+}
+
+/**
+ * Build a locale batch from an array of files.
+ *
+ * @param $files
+ *   Array of files to import
+ * @param $finished
+ *   Optional finished callback for the batch.
+ * @param $components
+ *   Optional list of component names the batch covers. Used in the installer.
+ * @return
+ *   A batch structure
+ */
+function _locale_batch_build($files, $finished = NULL, $components = array()) {
+  $t = get_t();
+  if (count($files)) {
+    $operations = array();
+    foreach ($files as $file) {
+      // We call _locale_batch_import for every batch operation.
+      $operations[] = array('_locale_batch_import', array($file->filename));    }
+      $batch = array(
+        'operations'    => $operations,
+        'title'         => $t('Importing interface translations'),
+        'init_message'  => $t('Starting import'),
+        'error_message' => $t('Error importing interface translations'),
+        'file'          => './includes/locale.inc',
+        // This is not a batch API construct, but data passed along to the
+        // installer, so we know what did we import already.
+        '#components'   => $components,
+      );
+      if (isset($finished)) {
+        $batch['finished'] = $finished;
+      }
+    return $batch;
+  }
+  return FALSE;
+}
+
+/**
+ * Perform interface translation import as a batch step.
+ *
+ * @param $filepath
+ *   Path to a file to import.
+ * @param $results
+ *   Contains a list of files imported.
+ */
+function _locale_batch_import($filepath, &$context) {
+  // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
+  // we can extract the language code to use for the import from the end.
+  if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
+    $file = (object) array('filename' => basename($filepath), 'filepath' => $filepath);
+    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
+    $context['results'][] = $filepath;
+  }
+}
+
+/**
+ * Finished callback of system page locale import batch.
+ * Inform the user of translation files imported.
+ */
+function _locale_batch_system_finished($success, $results) {
+  if ($success) {
+    drupal_set_message(format_plural(count($results), 'One translation file imported for the newly installed modules.', '@count translation files imported for the newly installed modules.'));
+  }
+}
+
+/**
+ * Finished callback of language addition locale import batch.
+ * Inform the user of translation files imported.
+ */
+function _locale_batch_language_finished($success, $results) {
+  if ($success) {
+    drupal_set_message(format_plural(count($results), 'One translation file imported for the enabled modules.', '@count translation files imported for the enabled modules.'));
+  }
+}
+
+/**
+ * @} End of "locale-autoimport"
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/mail.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,479 @@
+<?php
+// $Id: mail.inc,v 1.8 2008/01/25 17:04:00 goba Exp $
+
+/**
+ * Compose and optionally send an e-mail message.
+ *
+ * Sending an e-mail works with defining an e-mail template (subject, text
+ * and possibly e-mail headers) and the replacement values to use in the
+ * appropriate places in the template. Processed e-mail templates are
+ * requested from hook_mail() from the module sending the e-mail. Any module
+ * can modify the composed e-mail message array using hook_mail_alter().
+ * Finally drupal_mail_send() sends the e-mail, which can be reused
+ * if the exact same composed e-mail is to be sent to multiple recipients.
+ *
+ * Finding out what language to send the e-mail with needs some consideration.
+ * If you send e-mail to a user, her preferred language should be fine, so
+ * use user_preferred_language(). If you send email based on form values
+ * filled on the page, there are two additional choices if you are not
+ * sending the e-mail to a user on the site. You can either use the language
+ * used to generate the page ($language global variable) or the site default
+ * language. See language_default(). The former is good if sending e-mail to
+ * the person filling the form, the later is good if you send e-mail to an
+ * address previously set up (like contact addresses in a contact form).
+ *
+ * Taking care of always using the proper language is even more important
+ * when sending e-mails in a row to multiple users. Hook_mail() abstracts
+ * whether the mail text comes from an administrator setting or is
+ * static in the source code. It should also deal with common mail tokens,
+ * only receiving $params which are unique to the actual e-mail at hand.
+ *
+ * An example:
+ *
+ * @code
+ *   function example_notify($accounts) {
+ *     foreach ($accounts as $account) {
+ *       $params['account'] = $account;
+ *       // example_mail() will be called based on the first drupal_mail() parameter.
+ *       drupal_mail('example', 'notify', $account->mail, user_preferred_language($account), $params);
+ *     }
+ *   }
+ *
+ *   function example_mail($key, &$message, $params) {
+ *     $language = $message['language'];
+ *     $variables = user_mail_tokens($params['account'], $language);
+ *     switch($key) {
+ *       case 'notice':
+ *         $message['subject'] = t('Notification from !site', $variables, $language->language);
+ *         $message['body'] = t("Dear !username\n\nThere is new content available on the site.", $variables, $language->language);
+ *         break;
+ *     }
+ *   }
+ * @endcode
+ *
+ * @param $module
+ *   A module name to invoke hook_mail() on. The {$module}_mail() hook will be
+ *   called to complete the $message structure which will already contain common
+ *   defaults.
+ * @param $key
+ *   A key to identify the e-mail sent. The final e-mail id for e-mail altering
+ *   will be {$module}_{$key}.
+ * @param $to
+ *   The e-mail address or addresses where the message will be sent to. The
+ *   formatting of this string must comply with RFC 2822. Some examples are:
+ *    user@example.com
+ *    user@example.com, anotheruser@example.com
+ *    User <user@example.com>
+ *    User <user@example.com>, Another User <anotheruser@example.com>
+ * @param $language
+ *   Language object to use to compose the e-mail.
+ * @param $params
+ *   Optional parameters to build the e-mail.
+ * @param $from
+ *   Sets From, Reply-To, Return-Path and Error-To to this value, if given.
+ * @param $send
+ *   Send the message directly, without calling drupal_mail_send() manually.
+ * @return
+ *   The $message array structure containing all details of the
+ *   message. If already sent ($send = TRUE), then the 'result' element
+ *   will contain the success indicator of the e-mail, failure being already
+ *   written to the watchdog. (Success means nothing more than the message being
+ *   accepted at php-level, which still doesn't guarantee it to be delivered.)
+ */
+function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
+  $default_from = variable_get('site_mail', ini_get('sendmail_from'));
+
+  // Bundle up the variables into a structured array for altering.
+  $message = array(
+    'id'       => $module .'_'. $key,
+    'to'       => $to,
+    'from'     => isset($from) ? $from : $default_from,
+    'language' => $language,
+    'params'   => $params,
+    'subject'  => '',
+    'body'     => array()
+  );
+
+  // Build the default headers
+  $headers = array(
+    'MIME-Version'              => '1.0',
+    'Content-Type'              => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
+    'Content-Transfer-Encoding' => '8Bit',
+    'X-Mailer'                  => 'Drupal'
+  );
+  if ($default_from) {
+    // To prevent e-mail from looking like spam, the addresses in the Sender and
+    // Return-Path headers should have a domain authorized to use the originating
+    // SMTP server. Errors-To is redundant, but shouldn't hurt.
+    $headers['From'] = $headers['Reply-To'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
+  }
+  if ($from) {
+    $headers['From'] = $headers['Reply-To'] = $from;
+  }
+  $message['headers'] = $headers;
+
+  // Build the e-mail (get subject and body, allow additional headers) by
+  // invoking hook_mail() on this module. We cannot use module_invoke() as
+  // we need to have $message by reference in hook_mail().
+  if (function_exists($function = $module .'_mail')) {
+    $function($key, $message, $params);
+  }
+
+  // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
+  drupal_alter('mail', $message);
+
+  // Concatenate and wrap the e-mail body.
+  $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);
+
+  // Optionally send e-mail.
+  if ($send) {
+    $message['result'] = drupal_mail_send($message);
+
+    // Log errors
+    if (!$message['result']) {
+      watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR);
+      drupal_set_message(t('Unable to send e-mail. Please contact the site admin, if the problem persists.'), 'error');
+    }
+  }
+
+  return $message;
+}
+
+/**
+ * Send an e-mail message, using Drupal variables and default settings.
+ * More information in the <a href="http://php.net/manual/en/function.mail.php">
+ * PHP function reference for mail()</a>. See drupal_mail() for information on
+ * how $message is composed.
+ *
+ * @param $message
+ *  Message array with at least the following elements:
+ *   - id
+ *      A unique identifier of the e-mail type. Examples: 'contact_user_copy',
+ *      'user_password_reset'.
+ *   - to
+ *      The mail address or addresses where the message will be sent to. The
+ *      formatting of this string must comply with RFC 2822. Some examples are:
+ *       user@example.com
+ *       user@example.com, anotheruser@example.com
+ *       User <user@example.com>
+ *       User <user@example.com>, Another User <anotheruser@example.com>
+ *   - subject
+ *      Subject of the e-mail to be sent. This must not contain any newline
+ *      characters, or the mail may not be sent properly.
+ *   - body
+ *      Message to be sent. Accepts both CRLF and LF line-endings.
+ *      E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
+ *      smart plain text wrapping.
+ *   - headers
+ *      Associative array containing all mail headers.
+ * @return
+ *   Returns TRUE if the mail was successfully accepted for delivery,
+ *   FALSE otherwise.
+ */
+function drupal_mail_send($message) {
+  // Allow for a custom mail backend.
+  if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
+    include_once './'. variable_get('smtp_library', '');
+    return drupal_mail_wrapper($message);
+  }
+  else {
+    $mimeheaders = array();
+    foreach ($message['headers'] as $name => $value) {
+      $mimeheaders[] = $name .': '. mime_header_encode($value);
+    }
+    return mail(
+      $message['to'],
+      mime_header_encode($message['subject']),
+      // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
+      // They will appear correctly in the actual e-mail that is sent.
+      str_replace("\r", '', $message['body']),
+      join("\n", $mimeheaders)
+    );
+  }
+}
+
+/**
+ * Perform format=flowed soft wrapping for mail (RFC 3676).
+ *
+ * We use delsp=yes wrapping, but only break non-spaced languages when
+ * absolutely necessary to avoid compatibility issues.
+ *
+ * We deliberately use LF rather than CRLF, see drupal_mail().
+ *
+ * @param $text
+ *   The plain text to process.
+ * @param $indent (optional)
+ *   A string to indent the text with. Only '>' characters are repeated on
+ *   subsequent wrapped lines. Others are replaced by spaces.
+ */
+function drupal_wrap_mail($text, $indent = '') {
+  // Convert CRLF into LF.
+  $text = str_replace("\r", '', $text);
+  // See if soft-wrapping is allowed.
+  $clean_indent = _drupal_html_to_text_clean($indent);
+  $soft = strpos($clean_indent, ' ') === FALSE;
+  // Check if the string has line breaks.
+  if (strpos($text, "\n") !== FALSE) {
+    // Remove trailing spaces to make existing breaks hard.
+    $text = preg_replace('/ +\n/m', "\n", $text);
+    // Wrap each line at the needed width.
+    $lines = explode("\n", $text);
+    array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent)));
+    $text = implode("\n", $lines);
+  }
+  else {
+    // Wrap this line.
+    _drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent)));
+  }
+  // Empty lines with nothing but spaces.
+  $text = preg_replace('/^ +\n/m', "\n", $text);
+  // Space-stuff special lines.
+  $text = preg_replace('/^(>| |From)/m', ' $1', $text);
+  // Apply indentation. We only include non-'>' indentation on the first line.
+  $text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent));
+
+  return $text;
+}
+
+/**
+ * Transform an HTML string into plain text, preserving the structure of the
+ * markup. Useful for preparing the body of a node to be sent by e-mail.
+ *
+ * The output will be suitable for use as 'format=flowed; delsp=yes' text
+ * (RFC 3676) and can be passed directly to drupal_mail() for sending.
+ *
+ * We deliberately use LF rather than CRLF, see drupal_mail().
+ *
+ * This function provides suitable alternatives for the following tags:
+ * <a> <em> <i> <strong> <b> <br> <p> <blockquote> <ul> <ol> <li> <dl> <dt>
+ * <dd> <h1> <h2> <h3> <h4> <h5> <h6> <hr>
+ *
+ * @param $string
+ *   The string to be transformed.
+ * @param $allowed_tags (optional)
+ *   If supplied, a list of tags that will be transformed. If omitted, all
+ *   all supported tags are transformed.
+ * @return
+ *   The transformed string.
+ */
+function drupal_html_to_text($string, $allowed_tags = NULL) {
+  // Cache list of supported tags.
+  static $supported_tags;
+  if (empty($supported_tags)) {
+    $supported_tags = array('a', 'em', 'i', 'strong', 'b', 'br', 'p', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr');
+  }
+
+  // Make sure only supported tags are kept.
+  $allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags;
+
+  // Make sure tags, entities and attributes are well-formed and properly nested.
+  $string = _filter_htmlcorrector(filter_xss($string, $allowed_tags));
+
+  // Apply inline styles.
+  $string = preg_replace('!</?(em|i)>!i', '/', $string);
+  $string = preg_replace('!</?(strong|b)>!i', '*', $string);
+
+  // Replace inline <a> tags with the text of link and a footnote.
+  // 'See <a href="http://drupal.org">the Drupal site</a>' becomes
+  // 'See the Drupal site [1]' with the URL included as a footnote.
+  _drupal_html_to_mail_urls(NULL, TRUE);
+  $pattern = '@(<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>)@i';
+  $string = preg_replace_callback($pattern, '_drupal_html_to_mail_urls', $string);
+  $urls = _drupal_html_to_mail_urls();
+  $footnotes = '';
+  if (count($urls)) {
+    $footnotes .= "\n";
+    for ($i = 0, $max = count($urls); $i < $max; $i++) {
+      $footnotes .= '['. ($i + 1) .'] '. $urls[$i] ."\n";
+    }
+  }
+
+  // Split tags from text.
+  $split = preg_split('/<([^>]+?)>/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+  // Note: PHP ensures the array consists of alternating delimiters and literals
+  // and begins and ends with a literal (inserting $null as required).
+
+  $tag = FALSE; // Odd/even counter (tag or no tag)
+  $casing = NULL; // Case conversion function
+  $output = '';
+  $indent = array(); // All current indentation string chunks
+  $lists = array(); // Array of counters for opened lists
+  foreach ($split as $value) {
+    $chunk = NULL; // Holds a string ready to be formatted and output.
+
+    // Process HTML tags (but don't output any literally).
+    if ($tag) {
+      list($tagname) = explode(' ', strtolower($value), 2);
+      switch ($tagname) {
+        // List counters
+        case 'ul':
+          array_unshift($lists, '*');
+          break;
+        case 'ol':
+          array_unshift($lists, 1);
+          break;
+        case '/ul':
+        case '/ol':
+          array_shift($lists);
+          $chunk = ''; // Ensure blank new-line.
+          break;
+
+        // Quotation/list markers, non-fancy headers
+        case 'blockquote':
+          // Format=flowed indentation cannot be mixed with lists.
+          $indent[] = count($lists) ? ' "' : '>';
+          break;
+        case 'li':
+          $indent[] = is_numeric($lists[0]) ? ' '. $lists[0]++ .') ' : ' * ';
+          break;
+        case 'dd':
+          $indent[] = '    ';
+          break;
+        case 'h3':
+          $indent[] = '.... ';
+          break;
+        case 'h4':
+          $indent[] = '.. ';
+          break;
+        case '/blockquote':
+          if (count($lists)) {
+            // Append closing quote for inline quotes (immediately).
+            $output = rtrim($output, "> \n") ."\"\n";
+            $chunk = ''; // Ensure blank new-line.
+          }
+          // Fall-through
+        case '/li':
+        case '/dd':
+          array_pop($indent);
+          break;
+        case '/h3':
+        case '/h4':
+          array_pop($indent);
+        case '/h5':
+        case '/h6':
+          $chunk = ''; // Ensure blank new-line.
+          break;
+
+        // Fancy headers
+        case 'h1':
+          $indent[] = '======== ';
+          $casing = 'drupal_strtoupper';
+          break;
+        case 'h2':
+          $indent[] = '-------- ';
+          $casing = 'drupal_strtoupper';
+          break;
+        case '/h1':
+        case '/h2':
+          $casing = NULL;
+          // Pad the line with dashes.
+          $output = _drupal_html_to_text_pad($output, ($tagname == '/h1') ? '=' : '-', ' ');
+          array_pop($indent);
+          $chunk = ''; // Ensure blank new-line.
+          break;
+
+        // Horizontal rulers
+        case 'hr':
+          // Insert immediately.
+          $output .= drupal_wrap_mail('', implode('', $indent)) ."\n";
+          $output = _drupal_html_to_text_pad($output, '-');
+          break;
+
+        // Paragraphs and definition lists
+        case '/p':
+        case '/dl':
+          $chunk = ''; // Ensure blank new-line.
+          break;
+      }
+    }
+    // Process blocks of text.
+    else {
+      // Convert inline HTML text to plain text.
+      $value = trim(preg_replace('/\s+/', ' ', decode_entities($value)));
+      if (strlen($value)) {
+        $chunk = $value;
+      }
+    }
+
+    // See if there is something waiting to be output.
+    if (isset($chunk)) {
+      // Apply any necessary case conversion.
+      if (isset($casing)) {
+        $chunk = $casing($chunk);
+      }
+      // Format it and apply the current indentation.
+      $output .= drupal_wrap_mail($chunk, implode('', $indent)) ."\n";
+      // Remove non-quotation markers from indentation.
+      $indent = array_map('_drupal_html_to_text_clean', $indent);
+    }
+
+    $tag = !$tag;
+  }
+
+  return $output . $footnotes;
+}
+
+/**
+ * Helper function for array_walk in drupal_wrap_mail().
+ *
+ * Wraps words on a single line.
+ */
+function _drupal_wrap_mail_line(&$line, $key, $values) {
+  // Use soft-breaks only for purely quoted or unindented text.
+  $line = wordwrap($line, 77 - $values['length'], $values['soft'] ? "  \n" : "\n");
+  // Break really long words at the maximum width allowed.
+  $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n");
+}
+
+/**
+ * Helper function for drupal_html_to_text().
+ *
+ * Keeps track of URLs and replaces them with placeholder tokens.
+ */
+function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) {
+  global $base_url, $base_path;
+  static $urls = array(), $regexp;
+  
+  if ($reset) {
+    // Reset internal URL list.
+    $urls = array();
+  }
+  else {
+    if (empty($regexp)) {
+      $regexp = '@^'. preg_quote($base_path, '@') .'@';
+    }
+    if ($match) {
+      list(, , $url, $label) = $match;
+      // Ensure all URLs are absolute.
+      $urls[] = strpos($url, '://') ? $url : preg_replace($regexp, $base_url .'/', $url);
+      return $label .' ['. count($urls) .']';
+    }
+  }
+  return $urls;
+}
+
+/**
+ * Helper function for drupal_wrap_mail() and drupal_html_to_text().
+ *
+ * Replace all non-quotation markers from a given piece of indentation with spaces.
+ */
+function _drupal_html_to_text_clean($indent) {
+  return preg_replace('/[^>]/', ' ', $indent);
+}
+
+/**
+ * Helper function for drupal_html_to_text().
+ *
+ * Pad the last line with the given character.
+ */
+function _drupal_html_to_text_pad($text, $pad, $prefix = '') {
+  // Remove last line break.
+  $text = substr($text, 0, -1);
+  // Calculate needed padding space and add it.
+  if (($p = strrpos($text, "\n")) === FALSE) {
+    $p = -1;
+  }
+  $n = max(0, 79 - (strlen($text) - $p));
+  // Add prefix and padding, and restore linebreak.
+  return $text . $prefix . str_repeat($pad, $n - strlen($prefix)) ."\n";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/menu.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2413 @@
+<?php
+// $Id: menu.inc,v 1.255.2.7 2008/02/12 22:33:51 goba Exp $
+
+/**
+ * @file
+ * API for the Drupal menu system.
+ */
+
+/**
+ * @defgroup menu Menu system
+ * @{
+ * Define the navigation menus, and route page requests to code based on URLs.
+ *
+ * The Drupal menu system drives both the navigation system from a user
+ * perspective and the callback system that Drupal uses to respond to URLs
+ * passed from the browser. For this reason, a good understanding of the
+ * menu system is fundamental to the creation of complex modules.
+ *
+ * Drupal's menu system follows a simple hierarchy defined by paths.
+ * Implementations of hook_menu() define menu items and assign them to
+ * paths (which should be unique). The menu system aggregates these items
+ * and determines the menu hierarchy from the paths. For example, if the
+ * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system
+ * would form the structure:
+ * - a
+ *   - a/b
+ *     - a/b/c/d
+ *     - a/b/h
+ * - e
+ * - f/g
+ * Note that the number of elements in the path does not necessarily
+ * determine the depth of the menu item in the tree.
+ *
+ * When responding to a page request, the menu system looks to see if the
+ * path requested by the browser is registered as a menu item with a
+ * callback. If not, the system searches up the menu tree for the most
+ * complete match with a callback it can find. If the path a/b/i is
+ * requested in the tree above, the callback for a/b would be used.
+ *
+ * The found callback function is called with any arguments specified
+ * in the "page arguments" attribute of its menu item. The
+ * attribute must be an array. After these arguments, any remaining
+ * components of the path are appended as further arguments. In this
+ * way, the callback for a/b above could respond to a request for
+ * a/b/i differently than a request for a/b/j.
+ *
+ * For an illustration of this process, see page_example.module.
+ *
+ * Access to the callback functions is also protected by the menu system.
+ * The "access callback" with an optional "access arguments" of each menu
+ * item is called before the page callback proceeds. If this returns TRUE,
+ * then access is granted; if FALSE, then access is denied. Menu items may
+ * omit this attribute to use the value provided by an ancestor item.
+ *
+ * In the default Drupal interface, you will notice many links rendered as
+ * tabs. These are known in the menu system as "local tasks", and they are
+ * rendered as tabs by default, though other presentations are possible.
+ * Local tasks function just as other menu items in most respects. It is
+ * convention that the names of these tasks should be short verbs if
+ * possible. In addition, a "default" local task should be provided for
+ * each set. When visiting a local task's parent menu item, the default
+ * local task will be rendered as if it is selected; this provides for a
+ * normal tab user experience. This default task is special in that it
+ * links not to its provided path, but to its parent item's path instead.
+ * The default task's path is only used to place it appropriately in the
+ * menu hierarchy.
+ *
+ * Everything described so far is stored in the menu_router table. The
+ * menu_links table holds the visible menu links. By default these are
+ * derived from the same hook_menu definitions, however you are free to
+ * add more with menu_link_save().
+ */
+
+/**
+ * @name Menu flags
+ * @{
+ * Flags for use in the "type" attribute of menu items.
+ */
+
+define('MENU_IS_ROOT', 0x0001);
+define('MENU_VISIBLE_IN_TREE', 0x0002);
+define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004);
+define('MENU_LINKS_TO_PARENT', 0x0008);
+define('MENU_MODIFIED_BY_ADMIN', 0x0020);
+define('MENU_CREATED_BY_ADMIN', 0x0040);
+define('MENU_IS_LOCAL_TASK', 0x0080);
+
+/**
+ * @} End of "Menu flags".
+ */
+
+/**
+ * @name Menu item types
+ * @{
+ * Menu item definitions provide one of these constants, which are shortcuts for
+ * combinations of the above flags.
+ */
+
+/**
+ * Normal menu items show up in the menu tree and can be moved/hidden by
+ * the administrator. Use this for most menu items. It is the default value if
+ * no menu item type is specified.
+ */
+define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB);
+
+/**
+ * Callbacks simply register a path so that the correct function is fired
+ * when the URL is accessed. They are not shown in the menu.
+ */
+define('MENU_CALLBACK', MENU_VISIBLE_IN_BREADCRUMB);
+
+/**
+ * Modules may "suggest" menu items that the administrator may enable. They act
+ * just as callbacks do until enabled, at which time they act like normal items.
+ * Note for the value: 0x0010 was a flag which is no longer used, but this way
+ * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate.
+ */
+define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010);
+
+/**
+ * Local tasks are rendered as tabs by default. Use this for menu items that
+ * describe actions to be performed on their parent item. An example is the path
+ * "node/52/edit", which performs the "edit" task on "node/52".
+ */
+define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK);
+
+/**
+ * Every set of local tasks should provide one "default" task, that links to the
+ * same path as its parent when clicked.
+ */
+define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT);
+
+/**
+ * @} End of "Menu item types".
+ */
+
+/**
+ * @name Menu status codes
+ * @{
+ * Status codes for menu callbacks.
+ */
+
+define('MENU_FOUND', 1);
+define('MENU_NOT_FOUND', 2);
+define('MENU_ACCESS_DENIED', 3);
+define('MENU_SITE_OFFLINE', 4);
+
+/**
+ * @} End of "Menu status codes".
+ */
+
+/**
+ * @Name Menu tree parameters
+ * @{
+ * Menu tree
+ */
+
+ /**
+ * The maximum number of path elements for a menu callback
+ */
+define('MENU_MAX_PARTS', 7);
+
+
+/**
+ * The maximum depth of a menu links tree - matches the number of p columns.
+ */
+define('MENU_MAX_DEPTH', 9);
+
+
+/**
+ * @} End of "Menu tree parameters".
+ */
+
+/**
+ * Returns the ancestors (and relevant placeholders) for any given path.
+ *
+ * For example, the ancestors of node/12345/edit are:
+ *
+ * node/12345/edit
+ * node/12345/%
+ * node/%/edit
+ * node/%/%
+ * node/12345
+ * node/%
+ * node
+ *
+ * To generate these, we will use binary numbers. Each bit represents a
+ * part of the path. If the bit is 1, then it represents the original
+ * value while 0 means wildcard. If the path is node/12/edit/foo
+ * then the 1011 bitstring represents node/%/edit/foo where % means that
+ * any argument matches that part.  We limit ourselves to using binary
+ * numbers that correspond the patterns of wildcards of router items that
+ * actually exists.  This list of 'masks' is built in menu_rebuild().
+ *
+ * @param $parts
+ *   An array of path parts, for the above example
+ *   array('node', '12345', 'edit').
+ * @return
+ *   An array which contains the ancestors and placeholders. Placeholders
+ *   simply contain as many '%s' as the ancestors.
+ */
+function menu_get_ancestors($parts) {
+  $number_parts = count($parts);
+  $placeholders = array();
+  $ancestors = array();
+  $length =  $number_parts - 1;
+  $end = (1 << $number_parts) - 1;
+  $masks = variable_get('menu_masks', array());
+  // Only examine patterns that actually exist as router items (the masks).
+  foreach ($masks as $i) {
+    if ($i > $end) {
+      // Only look at masks that are not longer than the path of interest.
+      continue;
+    }
+    elseif ($i < (1 << $length)) {
+      // We have exhausted the masks of a given length, so decrease the length.
+      --$length;
+    }
+    $current = '';
+    for ($j = $length; $j >= 0; $j--) {
+      if ($i & (1 << $j)) {
+        $current .= $parts[$length - $j];
+      }
+      else {
+        $current .= '%';
+      }
+      if ($j) {
+        $current .= '/';
+      }
+    }
+    $placeholders[] = "'%s'";
+    $ancestors[] = $current;
+  }
+  return array($ancestors, $placeholders);
+}
+
+/**
+ * The menu system uses serialized arrays stored in the database for
+ * arguments. However, often these need to change according to the
+ * current path. This function unserializes such an array and does the
+ * necessary change.
+ *
+ * Integer values are mapped according to the $map parameter. For
+ * example, if unserialize($data) is array('view', 1) and $map is
+ * array('node', '12345') then 'view' will not be changed because
+ * it is not an integer, but 1 will as it is an integer. As $map[1]
+ * is '12345', 1 will be replaced with '12345'. So the result will
+ * be array('node_load', '12345').
+ *
+ * @param @data
+ *   A serialized array.
+ * @param @map
+ *   An array of potential replacements.
+ * @return
+ *   The $data array unserialized and mapped.
+ */
+function menu_unserialize($data, $map) {
+  if ($data = unserialize($data)) {
+    foreach ($data as $k => $v) {
+      if (is_int($v)) {
+        $data[$k] = isset($map[$v]) ? $map[$v] : '';
+      }
+    }
+    return $data;
+  }
+  else {
+    return array();
+  }
+}
+
+
+
+/**
+ * Replaces the statically cached item for a given path.
+ *
+ * @param $path
+ *   The path.
+ * @param $router_item
+ *   The router item. Usually you take a router entry from menu_get_item and
+ *   set it back either modified or to a different path. This lets you modify the
+ *   navigation block, the page title, the breadcrumb and the page help in one
+ *   call.
+ */
+function menu_set_item($path, $router_item) {
+  menu_get_item($path, $router_item);
+}
+
+/**
+ * Get a router item.
+ *
+ * @param $path
+ *   The path, for example node/5. The function will find the corresponding
+ *   node/% item and return that.
+ * @param $router_item
+ *   Internal use only.
+ * @return
+ *   The router item, an associate array corresponding to one row in the
+ *   menu_router table. The value of key map holds the loaded objects. The
+ *   value of key access is TRUE if the current user can access this page.
+ *   The values for key title, page_arguments, access_arguments will be
+ *   filled in based on the database values and the objects loaded.
+ */
+function menu_get_item($path = NULL, $router_item = NULL) {
+  static $router_items;
+  if (!isset($path)) {
+    $path = $_GET['q'];
+  }
+  if (isset($router_item)) {
+    $router_items[$path] = $router_item;
+  }
+  if (!isset($router_items[$path])) {
+    $original_map = arg(NULL, $path);
+    $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
+    list($ancestors, $placeholders) = menu_get_ancestors($parts);
+
+    if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
+      $map = _menu_translate($router_item, $original_map);
+      if ($map === FALSE) {
+        $router_items[$path] = FALSE;
+        return FALSE;
+      }
+      if ($router_item['access']) {
+        $router_item['map'] = $map;
+        $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
+      }
+    }
+    $router_items[$path] = $router_item;
+  }
+  return $router_items[$path];
+}
+
+/**
+ * Execute the page callback associated with the current path
+ */
+function menu_execute_active_handler($path = NULL) {
+  if (_menu_site_is_offline()) {
+    return MENU_SITE_OFFLINE;
+  }
+  if (variable_get('menu_rebuild_needed', FALSE)) {
+    menu_rebuild();
+  }
+  if ($router_item = menu_get_item($path)) {
+    if ($router_item['access']) {
+      if ($router_item['file']) {
+        require_once($router_item['file']);
+      }
+      return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+    }
+    else {
+      return MENU_ACCESS_DENIED;
+    }
+  }
+  return MENU_NOT_FOUND;
+}
+
+/**
+ * Loads objects into the map as defined in the $item['load_functions'].
+ *
+ * @param $item
+ *   A menu router or menu link item
+ * @param $map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @return
+ *   Returns TRUE for success, FALSE if an object cannot be loaded.
+ *   Names of object loading functions are placed in $item['load_functions'].
+ *   Loaded objects are placed in $map[]; keys are the same as keys in the 
+ *   $item['load_functions'] array.
+ *   $item['access'] is set to FALSE if an object cannot be loaded.
+ */
+function _menu_load_objects(&$item, &$map) {
+  if ($load_functions = $item['load_functions']) {
+    // If someone calls this function twice, then unserialize will fail.
+    if ($load_functions_unserialized = unserialize($load_functions)) {
+      $load_functions = $load_functions_unserialized;
+    }
+    $path_map = $map;
+    foreach ($load_functions as $index => $function) {
+      if ($function) {
+        $value = isset($path_map[$index]) ? $path_map[$index] : '';
+        if (is_array($function)) {
+          // Set up arguments for the load function. These were pulled from
+          // 'load arguments' in the hook_menu() entry, but they need
+          // some processing. In this case the $function is the key to the
+          // load_function array, and the value is the list of arguments.
+          list($function, $args) = each($function);
+          $load_functions[$index] = $function;
+
+          // Some arguments are placeholders for dynamic items to process.
+          foreach ($args as $i => $arg) {
+            if ($arg === '%index') {
+              // Pass on argument index to the load function, so multiple
+              // occurances of the same placeholder can be identified.
+              $args[$i] = $index;
+            }
+            if ($arg === '%map') {
+              // Pass on menu map by reference. The accepting function must
+              // also declare this as a reference if it wants to modify
+              // the map.
+              $args[$i] = &$map;
+            }
+            if (is_int($arg)) {
+              $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : '';
+            }
+          }
+          array_unshift($args, $value);
+          $return = call_user_func_array($function, $args);
+        }
+        else {
+          $return = $function($value);
+        }
+        // If callback returned an error or there is no callback, trigger 404.
+        if ($return === FALSE) {
+          $item['access'] = FALSE;
+          $map = FALSE;
+          return FALSE;
+        }
+        $map[$index] = $return;
+      }
+    }
+    $item['load_functions'] = $load_functions;
+  }
+  return TRUE;
+}
+
+/**
+ * Check access to a menu item using the access callback
+ *
+ * @param $item
+ *   A menu router or menu link item
+ * @param $map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @return
+ *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
+ */
+function _menu_check_access(&$item, $map) {
+  // Determine access callback, which will decide whether or not the current
+  // user has access to this path.
+  $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
+  // Check for a TRUE or FALSE value.
+  if (is_numeric($callback)) {
+    $item['access'] = (bool)$callback;
+  }
+  else {
+    $arguments = menu_unserialize($item['access_arguments'], $map);
+    // As call_user_func_array is quite slow and user_access is a very common
+    // callback, it is worth making a special case for it.
+    if ($callback == 'user_access') {
+      $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
+    }
+    else {
+      $item['access'] = call_user_func_array($callback, $arguments);
+    }
+  }
+}
+
+/**
+ * Localize the router item title using t() or another callback.
+ *
+ * Translate the title and description to allow storage of English title
+ * strings in the database, yet display of them in the language required
+ * by the current user.
+ *
+ * @param $item
+ *   A menu router item or a menu link item.
+ * @param $map
+ *   The path as an array with objects already replaced. E.g., for path
+ *   node/123 $map would be array('node', $node) where $node is the node
+ *   object for node 123.
+ * @param $link_translate
+ *   TRUE if we are translating a menu link item; FALSE if we are
+ *   translating a menu router item.
+ * @return
+ *   No return value.
+ *   $item['title'] is localized according to $item['title_callback'].
+ *   If an item's callback is check_plain(), $item['options']['html'] becomes 
+ *   TRUE.
+ *   $item['description'] is translated using t().
+ *   When doing link translation and the $item['options']['attributes']['title'] 
+ *   (link title attribute) matches the description, it is translated as well.
+ */
+function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
+  $callback = $item['title_callback'];
+  $item['localized_options'] = $item['options'];
+  // If we are not doing link translation or if the title matches the
+  // link title of its router item, localize it.
+  if (!$link_translate || (!empty($item['title']) && ($item['title'] == $item['link_title']))) {
+    // t() is a special case. Since it is used very close to all the time,
+    // we handle it directly instead of using indirect, slower methods.
+    if ($callback == 't') {
+      if (empty($item['title_arguments'])) {
+        $item['title'] = t($item['title']);
+      }
+      else {
+        $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
+      }
+    }
+    elseif ($callback) {
+      if (empty($item['title_arguments'])) {
+        $item['title'] = $callback($item['title']);
+      }
+      else {
+        $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
+      }
+      // Avoid calling check_plain again on l() function.
+      if ($callback == 'check_plain') {
+        $item['localized_options']['html'] = TRUE;
+      }
+    }
+  }
+  elseif ($link_translate) {
+    $item['title'] = $item['link_title'];
+  }
+
+  // Translate description, see the motivation above.
+  if (!empty($item['description'])) {
+    $original_description = $item['description'];
+    $item['description'] = t($item['description']);
+    if ($link_translate && $item['options']['attributes']['title'] == $original_description) {
+      $item['localized_options']['attributes']['title'] = $item['description'];
+    }
+  }
+}
+
+/**
+ * Handles dynamic path translation and menu access control.
+ *
+ * When a user arrives on a page such as node/5, this function determines
+ * what "5" corresponds to, by inspecting the page's menu path definition,
+ * node/%node. This will call node_load(5) to load the corresponding node
+ * object.
+ *
+ * It also works in reverse, to allow the display of tabs and menu items which
+ * contain these dynamic arguments, translating node/%node to node/5.
+ *
+ * Translation of menu item titles and descriptions are done here to
+ * allow for storage of English strings in the database, and translation
+ * to the language required to generate the current page
+ *
+ * @param $router_item
+ *   A menu router item
+ * @param $map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @param $to_arg
+ *   Execute $item['to_arg_functions'] or not. Use only if you want to render a
+ *   path from the menu table, for example tabs.
+ * @return
+ *   Returns the map with objects loaded as defined in the
+ *   $item['load_functions. $item['access'] becomes TRUE if the item is
+ *   accessible, FALSE otherwise. $item['href'] is set according to the map.
+ *   If an error occurs during calling the load_functions (like trying to load
+ *   a non existing node) then this function return FALSE.
+ */
+function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
+  $path_map = $map;
+  if (!_menu_load_objects($router_item, $map)) {
+    // An error occurred loading an object.
+    $router_item['access'] = FALSE;
+    return FALSE;
+  }
+  if ($to_arg) {
+    _menu_link_map_translate($path_map, $router_item['to_arg_functions']);
+  }
+
+  // Generate the link path for the page request or local tasks.
+  $link_map = explode('/', $router_item['path']);
+  for ($i = 0; $i < $router_item['number_parts']; $i++) {
+    if ($link_map[$i] == '%') {
+      $link_map[$i] = $path_map[$i];
+    }
+  }
+  $router_item['href'] = implode('/', $link_map);
+  $router_item['options'] = array();
+  _menu_check_access($router_item, $map);
+
+  _menu_item_localize($router_item, $map);
+
+  return $map;
+}
+
+/**
+ * This function translates the path elements in the map using any to_arg
+ * helper function. These functions take an argument and return an object.
+ * See http://drupal.org/node/109153 for more information.
+ *
+ * @param map
+ *   An array of path arguments (ex: array('node', '5'))
+ * @param $to_arg_functions
+ *   An array of helper function (ex: array(2 => 'menu_tail_to_arg'))
+ */
+function _menu_link_map_translate(&$map, $to_arg_functions) {
+  if ($to_arg_functions) {
+    $to_arg_functions = unserialize($to_arg_functions);
+    foreach ($to_arg_functions as $index => $function) {
+      // Translate place-holders into real values.
+      $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index);
+      if (!empty($map[$index]) || isset($arg)) {
+        $map[$index] = $arg;
+      }
+      else {
+        unset($map[$index]);
+      }
+    }
+  }
+}
+
+function menu_tail_to_arg($arg, $map, $index) {
+  return implode('/', array_slice($map, $index));
+}
+
+/**
+ * This function is similar to _menu_translate() but does link-specific
+ * preparation such as always calling to_arg functions
+ *
+ * @param $item
+ *   A menu link
+ * @return
+ *   Returns the map of path arguments with objects loaded as defined in the
+ *   $item['load_functions'].
+ *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
+ *   $item['href'] is generated from link_path, possibly by to_arg functions.
+ *   $item['title'] is generated from link_title, and may be localized.
+ *   $item['options'] is unserialized; it is also changed within the call here 
+ *   to $item['localized_options'] by _menu_item_localize().
+ */
+function _menu_link_translate(&$item) {
+  $item['options'] = unserialize($item['options']);
+  if ($item['external']) {
+    $item['access'] = 1;
+    $map = array();
+    $item['href'] = $item['link_path'];
+    $item['title'] = $item['link_title'];
+    $item['localized_options'] = $item['options'];
+  }
+  else {
+    $map = explode('/', $item['link_path']);
+    _menu_link_map_translate($map, $item['to_arg_functions']);
+    $item['href'] = implode('/', $map);
+
+    // Note - skip callbacks without real values for their arguments.
+    if (strpos($item['href'], '%') !== FALSE) {
+      $item['access'] = FALSE;
+      return FALSE;
+    }
+    // menu_tree_check_access() may set this ahead of time for links to nodes.
+    if (!isset($item['access'])) {
+      if (!_menu_load_objects($item, $map)) {
+        // An error occurred loading an object.
+        $item['access'] = FALSE;
+        return FALSE;
+      }
+      _menu_check_access($item, $map);
+    }
+
+    _menu_item_localize($item, $map, TRUE);
+  }
+  
+  // Allow other customizations - e.g. adding a page-specific query string to the
+  // options array. For performance reasons we only invoke this hook if the link
+  // has the 'alter' flag set in the options array.
+  if (!empty($item['options']['alter'])) {
+    drupal_alter('translated_menu_link', $item, $map);
+  }
+  
+  return $map;
+}
+
+/**
+ * Get a loaded object from a router item.
+ *
+ * menu_get_object() will provide you the current node on paths like node/5,
+ * node/5/revisions/48 etc. menu_get_object('user') will give you the user
+ * account on user/5 etc. Note - this function should never be called within a
+ * _to_arg function (like user_current_to_arg()) since this may result in an
+ * infinite recursion.
+ *
+ * @param $type
+ *   Type of the object. These appear in hook_menu definitons as %type. Core
+ *   provides aggregator_feed, aggregator_category, contact, filter_format,
+ *   forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the
+ *   relevant {$type}_load function for more on each. Defaults to node.
+ * @param $position
+ *   The expected position for $type object. For node/%node this is 1, for
+ *   comment/reply/%node this is 2. Defaults to 1.
+ * @param $path
+ *   See @menu_get_item for more on this. Defaults to the current path.
+ */
+function menu_get_object($type = 'node', $position = 1, $path = NULL) {
+  $router_item = menu_get_item($path);
+  if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type .'_load') {
+    return $router_item['map'][$position];
+  }
+}
+
+/**
+ * Render a menu tree based on the current path.
+ *
+ * The tree is expanded based on the current path and dynamic paths are also
+ * changed according to the defined to_arg functions (for example the 'My account'
+ * link is changed from user/% to a link with the current user's uid).
+ *
+ * @param $menu_name
+ *   The name of the menu.
+ * @return
+ *   The rendered HTML of that menu on the current page.
+ */
+function menu_tree($menu_name = 'navigation') {
+  static $menu_output = array();
+
+  if (!isset($menu_output[$menu_name])) {
+    $tree = menu_tree_page_data($menu_name);
+    $menu_output[$menu_name] = menu_tree_output($tree);
+  }
+  return $menu_output[$menu_name];
+}
+
+/**
+ * Returns a rendered menu tree.
+ *
+ * @param $tree
+ *   A data structure representing the tree as returned from menu_tree_data.
+ * @return
+ *   The rendered HTML of that data structure.
+ */
+function menu_tree_output($tree) {
+  $output = '';
+  $items = array();
+
+  // Pull out just the menu items we are going to render so that we
+  // get an accurate count for the first/last classes.
+  foreach ($tree as $data) {
+    if (!$data['link']['hidden']) {
+      $items[] = $data;
+    }
+  }
+
+  $num_items = count($items);
+  foreach ($items as $i => $data) {
+    $extra_class = NULL;
+    if ($i == 0) {
+      $extra_class = 'first';
+    }
+    if ($i == $num_items - 1) {
+      $extra_class = 'last';
+    }
+    $link = theme('menu_item_link', $data['link']);
+    if ($data['below']) {
+      $output .= theme('menu_item', $link, $data['link']['has_children'], menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class);
+    }
+    else {
+      $output .= theme('menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class);
+    }
+  }
+  return $output ? theme('menu_tree', $output) : '';
+}
+
+/**
+ * Get the data structure representing a named menu tree.
+ *
+ * Since this can be the full tree including hidden items, the data returned
+ * may be used for generating an an admin interface or a select.
+ *
+ * @param $menu_name
+ *   The named menu links to return
+ * @param $item
+ *   A fully loaded menu link, or NULL.  If a link is supplied, only the
+ *   path to root will be included in the returned tree- as if this link
+ *   represented the current page in a visible menu.
+ * @return
+ *   An tree of menu links in an array, in the order they should be rendered.
+ */
+function menu_tree_all_data($menu_name = 'navigation', $item = NULL) {
+  static $tree = array();
+
+  // Use $mlid as a flag for whether the data being loaded is for the whole tree.
+  $mlid = isset($item['mlid']) ? $item['mlid'] : 0;
+  // Generate the cache ID.
+  $cid = 'links:'. $menu_name .':all:'. $mlid;
+
+  if (!isset($tree[$cid])) {
+    // If the static variable doesn't have the data, check {cache_menu}.
+    $cache = cache_get($cid, 'cache_menu');
+    if ($cache && isset($cache->data)) {
+      $data = $cache->data;
+    }
+    else {
+      // Build and run the query, and build the tree.
+      if ($mlid) {
+        // The tree is for a single item, so we need to match the values in its
+        // p columns and 0 (the top level) with the plid values of other links.
+        $args = array(0);
+        for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
+          $args[] = $item["p$i"];
+        }
+        $args = array_unique($args);
+        $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+        $where = ' AND ml.plid IN ('. $placeholders .')';
+        $parents = $args;
+        $parents[] = $item['mlid'];
+      }
+      else {
+        // Get all links in this menu.
+        $where = '';
+        $args = array();
+        $parents = array();
+      }
+      array_unshift($args, $menu_name);
+      // Select the links from the table, and recursively build the tree.  We
+      // LEFT JOIN since there is no match in {menu_router} for an external
+      // link.
+      $data['tree'] = menu_tree_data(db_query("
+        SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+        FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+        WHERE ml.menu_name = '%s'". $where ."
+        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
+      $data['node_links'] = array();
+      menu_tree_collect_node_links($data['tree'], $data['node_links']);
+      // Cache the data.
+      cache_set($cid, $data, 'cache_menu');
+    }
+    // Check access for the current user to each item in the tree.
+    menu_tree_check_access($data['tree'], $data['node_links']);
+    $tree[$cid] = $data['tree'];
+  }
+
+  return $tree[$cid];
+}
+
+/**
+ * Get the data structure representing a named menu tree, based on the current page.
+ *
+ * The tree order is maintained by storing each parent in an individual
+ * field, see http://drupal.org/node/141866 for more.
+ *
+ * @param $menu_name
+ *   The named menu links to return
+ * @return
+ *   An array of menu links, in the order they should be rendered. The array
+ *   is a list of associative arrays -- these have two keys, link and below.
+ *   link is a menu item, ready for theming as a link. Below represents the
+ *   submenu below the link if there is one, and it is a subtree that has the
+ *   same structure described for the top-level array.
+ */
+function menu_tree_page_data($menu_name = 'navigation') {
+  static $tree = array();
+
+  // Load the menu item corresponding to the current page.
+  if ($item = menu_get_item()) {
+    // Generate the cache ID.
+    $cid = 'links:'. $menu_name .':page:'. $item['href'] .':'. (int)$item['access'];
+
+    if (!isset($tree[$cid])) {
+      // If the static variable doesn't have the data, check {cache_menu}.
+      $cache = cache_get($cid, 'cache_menu');
+      if ($cache && isset($cache->data)) {
+        $data = $cache->data;
+      }
+      else {
+        // Build and run the query, and build the tree.
+        if ($item['access']) {
+          // Check whether a menu link exists that corresponds to the current path.
+          $args = array($menu_name, $item['href']);
+          $placeholders = "'%s'";
+          if (drupal_is_front_page()) {
+            $args[] = '<front>';
+            $placeholders .= ", '%s'";
+          }
+          $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path IN (". $placeholders .")", $args));
+
+          if (empty($parents)) {
+            // If no link exists, we may be on a local task that's not in the links.
+            // TODO: Handle the case like a local task on a specific node in the menu.
+            $parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6, p7, p8 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
+          }
+          // We always want all the top-level links with plid == 0.
+          $parents[] = '0';
+
+          // Use array_values() so that the indices are numeric for array_merge().
+          $args = $parents = array_unique(array_values($parents));
+          $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+          $expanded = variable_get('menu_expanded', array());
+          // Check whether the current menu has any links set to be expanded.
+          if (in_array($menu_name, $expanded)) {
+            // Collect all the links set to be expanded, and then add all of
+            // their children to the list as well.
+            do {
+              $result = db_query("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND expanded = 1 AND has_children = 1 AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args));
+              $num_rows = FALSE;
+              while ($item = db_fetch_array($result)) {
+                $args[] = $item['mlid'];
+                $num_rows = TRUE;
+              }
+              $placeholders = implode(', ', array_fill(0, count($args), '%d'));
+            } while ($num_rows);
+          }
+          array_unshift($args, $menu_name);
+        }
+        else {
+          // Show only the top-level menu items when access is denied.
+          $args = array($menu_name, '0');
+          $placeholders = '%d';
+          $parents = array();
+        }
+        // Select the links from the table, and recursively build the tree. We
+        // LEFT JOIN since there is no match in {menu_router} for an external
+        // link.
+        $data['tree'] = menu_tree_data(db_query("
+          SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+          FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+          WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .")
+          ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC", $args), $parents);
+        $data['node_links'] = array();
+        menu_tree_collect_node_links($data['tree'], $data['node_links']);
+        // Cache the data.
+        cache_set($cid, $data, 'cache_menu');
+      }
+      // Check access for the current user to each item in the tree.
+      menu_tree_check_access($data['tree'], $data['node_links']);
+      $tree[$cid] = $data['tree'];
+    }
+    return $tree[$cid];
+  }
+
+  return array();
+}
+
+/**
+ * Recursive helper function - collect node links.
+ */
+function menu_tree_collect_node_links(&$tree, &$node_links) {
+  foreach ($tree as $key => $v) {
+    if ($tree[$key]['link']['router_path'] == 'node/%') {
+      $nid = substr($tree[$key]['link']['link_path'], 5);
+      if (is_numeric($nid)) {
+        $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
+        $tree[$key]['link']['access'] = FALSE;
+      }
+    }
+    if ($tree[$key]['below']) {
+      menu_tree_collect_node_links($tree[$key]['below'], $node_links);
+    }
+  }
+}
+
+/**
+ * Check access and perform other dynamic operations for each link in the tree.
+ */
+function menu_tree_check_access(&$tree, $node_links = array()) {
+
+  if ($node_links) {
+    // Use db_rewrite_sql to evaluate view access without loading each full node.
+    $nids = array_keys($node_links);
+    $placeholders = '%d'. str_repeat(', %d', count($nids) - 1);
+    $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.status = 1 AND n.nid IN (". $placeholders .")"), $nids);
+    while ($node = db_fetch_array($result)) {
+      $nid = $node['nid'];
+      foreach ($node_links[$nid] as $mlid => $link) {
+        $node_links[$nid][$mlid]['access'] = TRUE;
+      }
+    }
+  }
+  _menu_tree_check_access($tree);
+  return;
+}
+
+/**
+ * Recursive helper function for menu_tree_check_access()
+ */
+function _menu_tree_check_access(&$tree) {
+  $new_tree = array();
+  foreach ($tree as $key => $v) {
+    $item = &$tree[$key]['link'];
+    _menu_link_translate($item);
+    if ($item['access']) {
+      if ($tree[$key]['below']) {
+        _menu_tree_check_access($tree[$key]['below']);
+      }
+      // The weights are made a uniform 5 digits by adding 50000 as an offset.
+      // After _menu_link_translate(), $item['title'] has the localized link title.
+      // Adding the mlid to the end of the index insures that it is unique.
+      $new_tree[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $tree[$key];
+    }
+  }
+  // Sort siblings in the tree based on the weights and localized titles.
+  ksort($new_tree);
+  $tree = $new_tree;
+}
+
+/**
+ * Build the data representing a menu tree.
+ *
+ * @param $result
+ *   The database result.
+ * @param $parents
+ *   An array of the plid values that represent the path from the current page
+ *   to the root of the menu tree.
+ * @param $depth
+ *   The depth of the current menu tree.
+ * @return
+ *   See menu_tree_page_data for a description of the data structure.
+ */
+function menu_tree_data($result = NULL, $parents = array(), $depth = 1) {
+  list(, $tree) = _menu_tree_data($result, $parents, $depth);
+  return $tree;
+}
+
+/**
+ * Recursive helper function to build the data representing a menu tree.
+ *
+ * The function is a bit complex because the rendering of an item depends on
+ * the next menu item. So we are always rendering the element previously
+ * processed not the current one.
+ */
+function _menu_tree_data($result, $parents, $depth, $previous_element = '') {
+  $remnant = NULL;
+  $tree = array();
+  while ($item = db_fetch_array($result)) {
+    // We need to determine if we're on the path to root so we can later build
+    // the correct active trail and breadcrumb.
+    $item['in_active_trail'] = in_array($item['mlid'], $parents);
+    // The current item is the first in a new submenu.
+    if ($item['depth'] > $depth) {
+      // _menu_tree returns an item and the menu tree structure.
+      list($item, $below) = _menu_tree_data($result, $parents, $item['depth'], $item);
+      if ($previous_element) {
+        $tree[$previous_element['mlid']] = array(
+          'link' => $previous_element,
+          'below' => $below,
+        );
+      }
+      else {
+        $tree = $below;
+      }
+      // We need to fall back one level.
+      if (!isset($item) || $item['depth'] < $depth) {
+        return array($item, $tree);
+      }
+      // This will be the link to be output in the next iteration.
+      $previous_element = $item;
+    }
+    // We are at the same depth, so we use the previous element.
+    elseif ($item['depth'] == $depth) {
+      if ($previous_element) {
+        // Only the first time.
+        $tree[$previous_element['mlid']] = array(
+          'link' => $previous_element,
+          'below' => FALSE,
+        );
+      }
+      // This will be the link to be output in the next iteration.
+      $previous_element = $item;
+    }
+    // The submenu ended with the previous item, so pass back the current item.
+    else {
+      $remnant = $item;
+      break;
+    }
+  }
+  if ($previous_element) {
+    // We have one more link dangling.
+    $tree[$previous_element['mlid']] = array(
+      'link' => $previous_element,
+      'below' => FALSE,
+    );
+  }
+  return array($remnant, $tree);
+}
+
+/**
+ * Generate the HTML output for a single menu link.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_item_link($link) {
+  if (empty($link['localized_options'])) {
+    $link['localized_options'] = array();
+  }
+
+  return l($link['title'], $link['href'], $link['localized_options']);
+}
+
+/**
+ * Generate the HTML output for a menu tree
+ *
+ * @ingroup themeable
+ */
+function theme_menu_tree($tree) {
+  return '<ul class="menu">'. $tree .'</ul>';
+}
+
+/**
+ * Generate the HTML output for a menu item and submenu.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) {
+  $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
+  if (!empty($extra_class)) {
+    $class .= ' '. $extra_class;
+  }
+  if ($in_active_trail) {
+    $class .= ' active-trail';
+  }
+  return '<li class="'. $class .'">'. $link . $menu ."</li>\n";
+}
+
+/**
+ * Generate the HTML output for a single local task link.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_local_task($link, $active = FALSE) {
+  return '<li '. ($active ? 'class="active" ' : '') .'>'. $link ."</li>\n";
+}
+
+/**
+ * Generates elements for the $arg array in the help hook.
+ */
+function drupal_help_arg($arg = array()) {
+  // Note - the number of empty elements should be > MENU_MAX_PARTS.
+  return $arg + array('', '', '', '', '', '', '', '', '', '', '', '');
+}
+
+/**
+ * Returns the help associated with the active menu item.
+ */
+function menu_get_active_help() {
+  $output = '';
+  $router_path = menu_tab_root_path();
+
+  $arg = drupal_help_arg(arg(NULL));
+  $empty_arg = drupal_help_arg();
+
+  foreach (module_list() as $name) {
+    if (module_hook($name, 'help')) {
+      // Lookup help for this path.
+      if ($help = module_invoke($name, 'help', $router_path, $arg)) {
+        $output .= $help ."\n";
+      }
+      // Add "more help" link on admin pages if the module provides a
+      // standalone help page.
+      if ($arg[0] == "admin" && module_exists('help') && module_invoke($name, 'help', 'admin/help#'. $arg[2], $empty_arg) && $help) {
+        $output .= theme("more_help_link", url('admin/help/'. $arg[2]));
+      }
+    }
+  }
+  return $output;
+}
+
+/**
+ * Build a list of named menus.
+ */
+function menu_get_names($reset = FALSE) {
+  static $names;
+
+  if ($reset || empty($names)) {
+    $names = array();
+    $result = db_query("SELECT DISTINCT(menu_name) FROM {menu_links} ORDER BY menu_name");
+    while ($name = db_fetch_array($result)) {
+      $names[] = $name['menu_name'];
+    }
+  }
+  return $names;
+}
+
+/**
+ * Return an array containing the names of system-defined (default) menus.
+ */
+function menu_list_system_menus() {
+  return array('navigation', 'primary-links', 'secondary-links');
+}
+
+/**
+ * Return an array of links to be rendered as the Primary links.
+ */
+function menu_primary_links() {
+  return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'));
+}
+
+/**
+ * Return an array of links to be rendered as the Secondary links.
+ */
+function menu_secondary_links() {
+
+  // If the secondary menu source is set as the primary menu, we display the
+  // second level of the primary menu.
+  if (variable_get('menu_secondary_links_source', 'secondary-links') == variable_get('menu_primary_links_source', 'primary-links')) {
+    return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'), 1);
+  }
+  else {
+    return menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 0);
+  }
+}
+
+/**
+ * Return an array of links for a navigation menu.
+ *
+ * @param $menu_name
+ *   The name of the menu.
+ * @param $level
+ *   Optional, the depth of the menu to be returned.
+ * @return
+ *   An array of links of the specified menu and level.
+ */
+function menu_navigation_links($menu_name, $level = 0) {
+  // Don't even bother querying the menu table if no menu is specified.
+  if (empty($menu_name)) {
+    return array();
+  }
+
+  // Get the menu hierarchy for the current page.
+  $tree = menu_tree_page_data($menu_name);
+
+  // Go down the active trail until the right level is reached.
+  while ($level-- > 0 && $tree) {
+    // Loop through the current level's items until we find one that is in trail.
+    while ($item = array_shift($tree)) {
+      if ($item['link']['in_active_trail']) {
+        // If the item is in the active trail, we continue in the subtree.
+        $tree = empty($item['below']) ? array() : $item['below'];
+        break;
+      }
+    }
+  }
+
+  // Create a single level of links.
+  $links = array();
+  foreach ($tree as $item) {
+    if (!$item['link']['hidden']) {
+      $l = $item['link']['localized_options'];
+      $l['href'] = $item['link']['href'];
+      $l['title'] = $item['link']['title'];
+      // Keyed with unique menu id to generate classes from theme_links().
+      $links['menu-'. $item['link']['mlid']] = $l;
+    }
+  }
+  return $links;
+}
+
+/**
+ * Collects the local tasks (tabs) for a given level.
+ *
+ * @param $level
+ *   The level of tasks you ask for. Primary tasks are 0, secondary are 1.
+ * @param $return_root
+ *   Whether to return the root path for the current page.
+ * @return
+ *   Themed output corresponding to the tabs of the requested level, or
+ *   router path if $return_root == TRUE. This router path corresponds to
+ *   a parent tab, if the current page is a default local task.
+ */
+function menu_local_tasks($level = 0, $return_root = FALSE) {
+  static $tabs;
+  static $root_path;
+
+  if (!isset($tabs)) {
+    $tabs = array();
+
+    $router_item = menu_get_item();
+    if (!$router_item || !$router_item['access']) {
+      return '';
+    }
+    // Get all tabs and the root page.
+    $result = db_query("SELECT * FROM {menu_router} WHERE tab_root = '%s' ORDER BY weight, title", $router_item['tab_root']);
+    $map = arg();
+    $children = array();
+    $tasks = array();
+    $root_path = $router_item['path'];
+
+    while ($item = db_fetch_array($result)) {
+      _menu_translate($item, $map, TRUE);
+      if ($item['tab_parent']) {
+        // All tabs, but not the root page.
+        $children[$item['tab_parent']][$item['path']] = $item;
+      }
+      // Store the translated item for later use.
+      $tasks[$item['path']] = $item;
+    }
+
+    // Find all tabs below the current path.
+    $path = $router_item['path'];
+    // Tab parenting may skip levels, so the number of parts in the path may not
+    // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
+    $depth = 1001;
+    while (isset($children[$path])) {
+      $tabs_current = '';
+      $next_path = '';
+      $count = 0;
+      foreach ($children[$path] as $item) {
+        if ($item['access']) {
+          $count++;
+          // The default task is always active.
+          if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
+            // Find the first parent which is not a default local task.
+            for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
+            $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item);
+            $tabs_current .= theme('menu_local_task', $link, TRUE);
+            $next_path = $item['path'];
+          }
+          else {
+            $link = theme('menu_item_link', $item);
+            $tabs_current .= theme('menu_local_task', $link);
+          }
+        }
+      }
+      $path = $next_path;
+      $tabs[$depth]['count'] = $count;
+      $tabs[$depth]['output'] = $tabs_current;
+      $depth++;
+    }
+
+    // Find all tabs at the same level or above the current one.
+    $parent = $router_item['tab_parent'];
+    $path = $router_item['path'];
+    $current = $router_item;
+    $depth = 1000;
+    while (isset($children[$parent])) {
+      $tabs_current = '';
+      $next_path = '';
+      $next_parent = '';
+      $count = 0;
+      foreach ($children[$parent] as $item) {
+        if ($item['access']) {
+          $count++;
+          if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) {
+            // Find the first parent which is not a default local task.
+            for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']);
+            $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item);
+            if ($item['path'] == $router_item['path']) {
+              $root_path = $tasks[$p]['path'];
+            }
+          }
+          else {
+            $link = theme('menu_item_link', $item);
+          }
+          // We check for the active tab.
+          if ($item['path'] == $path) {
+            $tabs_current .= theme('menu_local_task', $link, TRUE);
+            $next_path = $item['tab_parent'];
+            if (isset($tasks[$next_path])) {
+              $next_parent = $tasks[$next_path]['tab_parent'];
+            }
+          }
+          else {
+            $tabs_current .= theme('menu_local_task', $link);
+          }
+        }
+      }
+      $path = $next_path;
+      $parent = $next_parent;
+      $tabs[$depth]['count'] = $count;
+      $tabs[$depth]['output'] = $tabs_current;
+      $depth--;
+    }
+    // Sort by depth.
+    ksort($tabs);
+    // Remove the depth, we are interested only in their relative placement.
+    $tabs = array_values($tabs);
+  }
+
+  if ($return_root) {
+    return $root_path;
+  }
+  else {
+    // We do not display single tabs.
+    return (isset($tabs[$level]) && $tabs[$level]['count'] > 1) ? $tabs[$level]['output'] : '';
+  }
+}
+
+/**
+ * Returns the rendered local tasks at the top level.
+ */
+function menu_primary_local_tasks() {
+  return menu_local_tasks(0);
+}
+
+/**
+ * Returns the rendered local tasks at the second level.
+ */
+function menu_secondary_local_tasks() {
+  return menu_local_tasks(1);
+}
+
+/**
+ * Returns the router path, or the path of the parent tab of a default local task.
+ */
+function menu_tab_root_path() {
+  return menu_local_tasks(0, TRUE);
+}
+
+/**
+ * Returns the rendered local tasks. The default implementation renders them as tabs.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_local_tasks() {
+  $output = '';
+
+  if ($primary = menu_primary_local_tasks()) {
+    $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
+  }
+  if ($secondary = menu_secondary_local_tasks()) {
+    $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
+  }
+
+  return $output;
+}
+
+/**
+ * Set (or get) the active menu for the current page - determines the active trail.
+ */
+function menu_set_active_menu_name($menu_name = NULL) {
+  static $active;
+
+  if (isset($menu_name)) {
+    $active = $menu_name;
+  }
+  elseif (!isset($active)) {
+    $active = 'navigation';
+  }
+  return $active;
+}
+
+/**
+ * Get the active menu for the current page - determines the active trail.
+ */
+function menu_get_active_menu_name() {
+  return menu_set_active_menu_name();
+}
+
+/**
+ * Set the active path, which determines which page is loaded.
+ *
+ * @param $path
+ *   A Drupal path - not a path alias.
+ *
+ * Note that this may not have the desired effect unless invoked very early
+ * in the page load, such as during hook_boot, or unless you call
+ * menu_execute_active_handler() to generate your page output.
+ */
+function menu_set_active_item($path) {
+  $_GET['q'] = $path;
+}
+
+/**
+ * Set (or get) the active trail for the current page - the path to root in the menu tree.
+ */
+function menu_set_active_trail($new_trail = NULL) {
+  static $trail;
+
+  if (isset($new_trail)) {
+    $trail = $new_trail;
+  }
+  elseif (!isset($trail)) {
+    $trail = array();
+    $trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array(), 'type' => 0);
+    $item = menu_get_item();
+
+    // Check whether the current item is a local task (displayed as a tab).
+    if ($item['tab_parent']) {
+      // The title of a local task is used for the tab, never the page title.
+      // Thus, replace it with the item corresponding to the root path to get
+      // the relevant href and title.  For example, the menu item corresponding
+      // to 'admin' is used when on the 'By module' tab at 'admin/by-module'.
+      $parts = explode('/', $item['tab_root']);
+      $args = arg();
+      // Replace wildcards in the root path using the current path.
+      foreach ($parts as $index => $part) {
+        if ($part == '%') {
+          $parts[$index] = $args[$index];
+        }
+      }
+      // Retrieve the menu item using the root path after wildcard replacement.
+      $root_item = menu_get_item(implode('/', $parts));
+      if ($root_item && $root_item['access']) {
+        $item = $root_item;
+      }
+    }
+
+    $tree = menu_tree_page_data(menu_get_active_menu_name());
+    list($key, $curr) = each($tree);
+
+    while ($curr) {
+      // Terminate the loop when we find the current path in the active trail.
+      if ($curr['link']['href'] == $item['href']) {
+        $trail[] = $curr['link'];
+        $curr = FALSE;
+      }
+      else {
+        // Move to the child link if it's in the active trail.
+        if ($curr['below'] && $curr['link']['in_active_trail']) {
+          $trail[] = $curr['link'];
+          $tree = $curr['below'];
+        }
+        list($key, $curr) = each($tree);
+      }
+    }
+    // Make sure the current page is in the trail (needed for the page title),
+    // but exclude tabs and the front page.
+    $last = count($trail) - 1;
+    if ($trail[$last]['href'] != $item['href'] && !(bool)($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) {
+      $trail[] = $item;
+    }
+  }
+  return $trail;
+}
+
+/**
+ * Get the active trail for the current page - the path to root in the menu tree.
+ */
+function menu_get_active_trail() {
+  return menu_set_active_trail();
+}
+
+/**
+ * Get the breadcrumb for the current page, as determined by the active trail.
+ */
+function menu_get_active_breadcrumb() {
+  $breadcrumb = array();
+  
+  // No breadcrumb for the front page.
+  if (drupal_is_front_page()) {
+    return $breadcrumb;
+  }
+
+  $item = menu_get_item();
+  if ($item && $item['access']) {
+    $active_trail = menu_get_active_trail();
+
+    foreach ($active_trail as $parent) {
+      $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
+    }
+    $end = end($active_trail);
+
+    // Don't show a link to the current page in the breadcrumb trail.
+    if ($item['href'] == $end['href'] || ($item['type'] == MENU_DEFAULT_LOCAL_TASK && $end['href'] != '<front>')) {
+      array_pop($breadcrumb);
+    }
+  }
+  return $breadcrumb;
+}
+
+/**
+ * Get the title of the current page, as determined by the active trail.
+ */
+function menu_get_active_title() {
+  $active_trail = menu_get_active_trail();
+
+  foreach (array_reverse($active_trail) as $item) {
+    if (!(bool)($item['type'] & MENU_IS_LOCAL_TASK)) {
+      return $item['title'];
+    }
+  }
+}
+
+/**
+ * Get a menu link by its mlid, access checked and link translated for rendering.
+ *
+ * This function should never be called from within node_load() or any other
+ * function used as a menu object load function since an infinite recursion may
+ * occur.
+ *
+ * @param $mlid
+ *   The mlid of the menu item.
+ * @return
+ *   A menu link, with $item['access'] filled and link translated for
+ *   rendering.
+ */
+function menu_link_load($mlid) {
+  if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
+    _menu_link_translate($item);
+    return $item;
+  }
+  return FALSE;
+}
+
+/**
+ * Clears the cached cached data for a single named menu.
+ */
+function menu_cache_clear($menu_name = 'navigation') {
+  static $cache_cleared = array();
+
+  if (empty($cache_cleared[$menu_name])) {
+    cache_clear_all('links:'. $menu_name .':', 'cache_menu', TRUE);
+    $cache_cleared[$menu_name] = 1;
+  }
+  elseif ($cache_cleared[$menu_name] == 1) {
+    register_shutdown_function('cache_clear_all', 'links:'. $menu_name .':', 'cache_menu', TRUE);
+    $cache_cleared[$menu_name] = 2;
+  }
+}
+
+/**
+ * Clears all cached menu data.  This should be called any time broad changes
+ * might have been made to the router items or menu links.
+ */
+function menu_cache_clear_all() {
+  cache_clear_all('*', 'cache_menu', TRUE);
+}
+
+/**
+ * (Re)populate the database tables used by various menu functions.
+ *
+ * This function will clear and populate the {menu_router} table, add entries
+ * to {menu_links} for new router items, then remove stale items from
+ * {menu_links}. If called from update.php or install.php, it will also
+ * schedule a call to itself on the first real page load from
+ * menu_execute_active_handler(), because the maintenance page environment
+ * is different and leaves stale data in the menu tables.
+ */
+function menu_rebuild() {
+  variable_del('menu_rebuild_needed');
+  menu_cache_clear_all();
+  $menu = menu_router_build(TRUE);
+  _menu_navigation_links_rebuild($menu);
+  // Clear the page and block caches.
+  _menu_clear_page_cache();
+  if (defined('MAINTENANCE_MODE')) {
+    variable_set('menu_rebuild_needed', TRUE);
+  }
+}
+
+/**
+ * Collect, alter and store the menu definitions.
+ */
+function menu_router_build($reset = FALSE) {
+  static $menu;
+
+  if (!isset($menu) || $reset) {
+    if (!$reset && ($cache = cache_get('router:', 'cache_menu')) && isset($cache->data)) {
+      $menu = $cache->data;
+    }
+    else {
+      db_query('DELETE FROM {menu_router}');
+      // We need to manually call each module so that we can know which module
+      // a given item came from.
+      $callbacks = array();
+      foreach (module_implements('menu') as $module) {
+        $router_items = call_user_func($module .'_menu');
+        if (isset($router_items) && is_array($router_items)) {
+          foreach (array_keys($router_items) as $path) {
+            $router_items[$path]['module'] = $module;
+          }
+          $callbacks = array_merge($callbacks, $router_items);
+        }
+      }
+      // Alter the menu as defined in modules, keys are like user/%user.
+      drupal_alter('menu', $callbacks);
+      $menu = _menu_router_build($callbacks);
+      cache_set('router:', $menu, 'cache_menu');
+    }
+  }
+  return $menu;
+}
+
+/**
+ * Builds a link from a router item.
+ */
+function _menu_link_build($item) {
+  if ($item['type'] == MENU_CALLBACK) {
+    $item['hidden'] = -1;
+  }
+  elseif ($item['type'] == MENU_SUGGESTED_ITEM) {
+    $item['hidden'] = 1;
+  }
+  // Note, we set this as 'system', so that we can be sure to distinguish all
+  // the menu links generated automatically from entries in {menu_router}.
+  $item['module'] = 'system';
+  $item += array(
+    'menu_name' => 'navigation',
+    'link_title' => $item['title'],
+    'link_path' => $item['path'],
+    'hidden' => 0,
+    'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
+  );
+  return $item;
+}
+
+/**
+ * Helper function to build menu links for the items in the menu router.
+ */
+function _menu_navigation_links_rebuild($menu) {
+  // Add normal and suggested items as links.
+  $menu_links = array();
+  foreach ($menu as $path => $item) {
+    if ($item['_visible']) {
+      $item = _menu_link_build($item);
+      $menu_links[$path] = $item;
+      $sort[$path] = $item['_number_parts'];
+    }
+  }
+  if ($menu_links) {
+    // Make sure no child comes before its parent.
+    array_multisort($sort, SORT_NUMERIC, $menu_links);
+
+    foreach ($menu_links as $item) {
+      $existing_item = db_fetch_array(db_query("SELECT mlid, menu_name, plid, customized, has_children, updated FROM {menu_links} WHERE link_path = '%s' AND module = '%s'", $item['link_path'], 'system'));
+      if ($existing_item) {
+        $item['mlid'] = $existing_item['mlid'];
+        $item['menu_name'] = $existing_item['menu_name'];
+        $item['plid'] = $existing_item['plid'];
+        $item['has_children'] = $existing_item['has_children'];
+        $item['updated'] = $existing_item['updated'];
+      }
+      if (!$existing_item || !$existing_item['customized']) {
+        menu_link_save($item);
+      }
+    }
+  }
+  $placeholders = db_placeholders($menu, 'varchar');
+  $paths = array_keys($menu);
+  // Updated items and customized items which router paths are gone need new
+  // router paths.
+  $result = db_query("SELECT ml.link_path, ml.mlid, ml.router_path, ml.updated FROM {menu_links} ml WHERE ml.updated = 1 OR (router_path NOT IN ($placeholders) AND external = 0 AND customized = 1)", $paths);
+  while ($item = db_fetch_array($result)) {
+    $router_path = _menu_find_router_path($menu, $item['link_path']);
+    if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) {
+      // If the router path and the link path matches, it's surely a working
+      // item, so we clear the updated flag.
+      $updated = $item['updated'] && $router_path != $item['link_path'];
+      db_query("UPDATE {menu_links} SET router_path = '%s', updated = %d WHERE mlid = %d", $router_path, $updated, $item['mlid']);
+    }
+  }
+  // Find any items where their router path does not exist any more.
+  $result = db_query("SELECT * FROM {menu_links} WHERE router_path NOT IN ($placeholders) AND external = 0 AND updated = 0 AND customized = 0 ORDER BY depth DESC", $paths);
+  // Remove all such items. Starting from those with the greatest depth will
+  // minimize the amount of re-parenting done by menu_link_delete().
+  while ($item = db_fetch_array($result)) {
+    _menu_delete_item($item, TRUE);
+  }
+}
+
+/**
+ * Delete one or several menu links.
+ *
+ * @param $mlid
+ *   A valid menu link mlid or NULL. If NULL, $path is used.
+ * @param $path
+ *   The path to the menu items to be deleted. $mlid must be NULL.
+ */
+function menu_link_delete($mlid, $path = NULL) {
+  if (isset($mlid)) {
+    _menu_delete_item(db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $mlid)));
+  }
+  else {
+    $result = db_query("SELECT * FROM {menu_links} WHERE link_path = '%s'", $path);
+    while ($link = db_fetch_array($result)) {
+      _menu_delete_item($link);
+    }
+  }
+}
+
+/**
+ * Helper function for menu_link_delete; deletes a single menu link.
+ *
+ * @param $item
+ *   Item to be deleted.
+ * @param $force
+ *   Forces deletion. Internal use only, setting to TRUE is discouraged.
+ */
+function _menu_delete_item($item, $force = FALSE) {
+  if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) {
+    // Children get re-attached to the item's parent.
+    if ($item['has_children']) {
+      $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = %d", $item['mlid']);
+      while ($m = db_fetch_array($result)) {
+        $child = menu_link_load($m['mlid']);
+        $child['plid'] = $item['plid'];
+        menu_link_save($child);
+      }
+    }
+    db_query('DELETE FROM {menu_links} WHERE mlid = %d', $item['mlid']);
+
+    // Update the has_children status of the parent.
+    _menu_update_parental_status($item);
+    menu_cache_clear($item['menu_name']);
+    _menu_clear_page_cache();
+  }
+}
+
+/**
+ * Save a menu link.
+ *
+ * @param $item
+ *   An array representing a menu link item. The only mandatory keys are
+ *   link_path and link_title. Possible keys are
+ *     menu_name   default is navigation
+ *     weight      default is 0
+ *     expanded    whether the item is expanded.
+ *     options     An array of options, @see l for more.
+ *     mlid        Set to an existing value, or 0 or NULL to insert a new link.
+ *     plid        The mlid of the parent.
+ *     router_path The path of the relevant router item.
+ */
+function menu_link_save(&$item) {
+  $menu = menu_router_build();
+
+  drupal_alter('menu_link', $item, $menu);
+
+  // This is the easiest way to handle the unique internal path '<front>',
+  // since a path marked as external does not need to match a router path.
+  $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>';
+  // Load defaults.
+  $item += array(
+    'menu_name' => 'navigation',
+    'weight' => 0,
+    'link_title' => '',
+    'hidden' => 0,
+    'has_children' => 0,
+    'expanded' => 0,
+    'options' => array(),
+    'module' => 'menu',
+    'customized' => 0,
+    'updated' => 0,
+  );
+  $existing_item = FALSE;
+  if (isset($item['mlid'])) {
+    $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
+  }
+
+  if (isset($item['plid'])) {
+    $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
+  }
+  else {
+    // Find the parent - it must be unique.
+    $parent_path = $item['link_path'];
+    $where = "WHERE link_path = '%s'";
+    // Only links derived from router items should have module == 'system', and
+    // we want to find the parent even if it's in a different menu.
+    if ($item['module'] == 'system') {
+      $where .= " AND module = '%s'";
+      $arg2 = 'system';
+    }
+    else {
+      // If not derived from a router item, we respect the specified menu name.
+      $where .= " AND menu_name = '%s'";
+      $arg2 = $item['menu_name'];
+    }
+    do {
+      $parent = FALSE;
+      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
+      $result = db_query("SELECT COUNT(*) FROM {menu_links} ". $where, $parent_path, $arg2);
+      // Only valid if we get a unique result.
+      if (db_result($result) == 1) {
+        $parent = db_fetch_array(db_query("SELECT * FROM {menu_links} ". $where, $parent_path, $arg2));
+      }
+    } while ($parent === FALSE && $parent_path);
+  }
+  if ($parent !== FALSE) {
+    $item['menu_name'] = $parent['menu_name'];
+  }
+  $menu_name = $item['menu_name'];
+  // Menu callbacks need to be in the links table for breadcrumbs, but can't
+  // be parents if they are generated directly from a router item.
+  if (empty($parent['mlid']) || $parent['hidden'] < 0) {
+    $item['plid'] =  0;
+  }
+  else {
+    $item['plid'] = $parent['mlid'];
+  }
+
+  if (!$existing_item) {
+    db_query("INSERT INTO {menu_links} (
+       menu_name, plid, link_path,
+      hidden, external, has_children,
+      expanded, weight,
+      module, link_title, options,
+      customized, updated) VALUES (
+      '%s', %d, '%s',
+      %d, %d, %d,
+      %d, %d,
+      '%s', '%s', '%s', %d, %d)",
+      $item['menu_name'], $item['plid'], $item['link_path'],
+      $item['hidden'], $item['_external'], $item['has_children'],
+      $item['expanded'], $item['weight'],
+      $item['module'],  $item['link_title'], serialize($item['options']),
+      $item['customized'], $item['updated']);
+    $item['mlid'] = db_last_insert_id('menu_links', 'mlid');
+  }
+
+  if (!$item['plid']) {
+    $item['p1'] = $item['mlid'];
+    for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
+      $item["p$i"] = 0;
+    }
+    $item['depth'] = 1;
+  }
+  else {
+    // Cannot add beyond the maximum depth.
+    if ($item['has_children'] && $existing_item) {
+      $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
+    }
+    else {
+      $limit = MENU_MAX_DEPTH - 1;
+    }
+    if ($parent['depth'] > $limit) {
+      return FALSE;
+    }
+    $item['depth'] = $parent['depth'] + 1;
+    _menu_link_parents_set($item, $parent);
+  }
+  // Need to check both plid and menu_name, since plid can be 0 in any menu.
+  if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
+    _menu_link_move_children($item, $existing_item);
+  }
+  // Find the callback. During the menu update we store empty paths to be
+  // fixed later, so we skip this.
+  if (!isset($_SESSION['system_update_6021']) && (empty($item['router_path'])  || !$existing_item || ($existing_item['link_path'] != $item['link_path']))) {
+    if ($item['_external']) {
+      $item['router_path'] = '';
+    }
+    else {
+      // Find the router path which will serve this path.
+      $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
+      $item['router_path'] = _menu_find_router_path($menu, $item['link_path']);
+    }
+  }
+  db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
+    router_path = '%s', hidden = %d, external = %d, has_children = %d,
+    expanded = %d, weight = %d, depth = %d,
+    p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d, p7 = %d, p8 = %d, p9 = %d,
+    module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
+    $item['menu_name'], $item['plid'], $item['link_path'],
+    $item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
+    $item['expanded'], $item['weight'],  $item['depth'],
+    $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'], $item['p7'], $item['p8'], $item['p9'],
+    $item['module'],  $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
+  // Check the has_children status of the parent.
+  _menu_update_parental_status($item);
+  menu_cache_clear($menu_name);
+  if ($existing_item && $menu_name != $existing_item['menu_name']) {
+    menu_cache_clear($existing_item['menu_name']);
+  }
+
+  _menu_clear_page_cache();
+  return $item['mlid'];
+}
+
+/**
+ * Helper function to clear the page and block caches at most twice per page load.
+ */
+function _menu_clear_page_cache() {
+  static $cache_cleared = 0;
+
+  // Clear the page and block caches, but at most twice, including at
+  //  the end of the page load when there are multple links saved or deleted.
+  if (empty($cache_cleared)) {
+    cache_clear_all();
+    // Keep track of which menus have expanded items.
+    _menu_set_expanded_menus();
+    $cache_cleared = 1;
+  }
+  elseif ($cache_cleared == 1) {
+    register_shutdown_function('cache_clear_all');
+    // Keep track of which menus have expanded items.
+    register_shutdown_function('_menu_set_expanded_menus');
+    $cache_cleared = 2;
+  }
+}
+
+/**
+ * Helper function to update a list of menus with expanded items
+ */
+function _menu_set_expanded_menus() {
+  $names = array();
+  $result = db_query("SELECT menu_name FROM {menu_links} WHERE expanded != 0 GROUP BY menu_name");
+  while ($n = db_fetch_array($result)) {
+    $names[] = $n['menu_name'];
+  }
+  variable_set('menu_expanded', $names);
+}
+
+/**
+ * Find the router path which will serve this path.
+ *
+ * @param $menu
+ *  The full built menu.
+ * @param $link_path
+ *  The path for we are looking up its router path.
+ * @return
+ *  A path from $menu keys or empty if $link_path points to a nonexisting
+ *  place.
+ */
+function _menu_find_router_path($menu, $link_path) {
+  $parts = explode('/', $link_path, MENU_MAX_PARTS);
+  $router_path = $link_path;
+  if (!isset($menu[$router_path])) {
+    list($ancestors) = menu_get_ancestors($parts);
+    $ancestors[] = '';
+    foreach ($ancestors as $key => $router_path) {
+      if (isset($menu[$router_path])) {
+        break;
+      }
+    }
+  }
+  return $router_path;
+}
+
+/**
+ * Insert, update or delete an uncustomized menu link related to a module.
+ *
+ * @param $module
+ *   The name of the module.
+ * @param $op
+ *   Operation to perform: insert, update or delete.
+ * @param $link_path
+ *   The path this link points to.
+ * @param $link_title
+ *   Title of the link to insert or new title to update the link to.
+ *   Unused for delete.
+ * @return
+ *   The insert op returns the mlid of the new item. Others op return NULL.
+ */
+function menu_link_maintain($module, $op, $link_path, $link_title) {
+  switch ($op) {
+    case 'insert':
+      $menu_link = array(
+        'link_title' => $link_title,
+        'link_path' => $link_path,
+        'module' => $module,
+      );
+      return menu_link_save($menu_link);
+      break;
+    case 'update':
+      db_query("UPDATE {menu_links} SET link_title = '%s' WHERE link_path = '%s' AND customized = 0 AND module = '%s'", $link_title, $link_path, $module);
+      menu_cache_clear();
+      break;
+    case 'delete':
+      menu_link_delete(NULL, $link_path);
+      break;
+  }
+}
+
+/**
+ * Find the depth of an item's children relative to its depth.
+ *
+ * For example, if the item has a depth of 2, and the maximum of any child in
+ * the menu link tree is 5, the relative depth is 3.
+ *
+ * @param $item
+ *   An array representing a menu link item.
+ * @return
+ *   The relative depth, or zero.
+ *
+ */
+function menu_link_children_relative_depth($item) {
+  $i = 1;
+  $match = '';
+  $args[] = $item['menu_name'];
+  $p = 'p1';
+  while ($i <= MENU_MAX_DEPTH && $item[$p]) {
+    $match .= " AND $p = %d";
+    $args[] = $item[$p];
+    $p = 'p'. ++$i;
+  }
+
+  $max_depth = db_result(db_query_range("SELECT depth FROM {menu_links} WHERE menu_name = '%s'". $match ." ORDER BY depth DESC", $args, 0, 1));
+
+  return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
+}
+
+/**
+ * Update the children of a menu link that's being moved.
+ *
+ * The menu name, parents (p1 - p6), and depth are updated for all children of
+ * the link, and the has_children status of the previous parent is updated.
+ */
+function _menu_link_move_children($item, $existing_item) {
+
+  $args[] = $item['menu_name'];
+  $set[] = "menu_name = '%s'";
+
+  $i = 1;
+  while ($i <= $item['depth']) {
+    $p = 'p'. $i++;
+    $set[] = "$p = %d";
+    $args[] = $item[$p];
+  }
+  $j = $existing_item['depth'] + 1;
+  while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
+    $set[] = 'p'. $i++ .' = p'. $j++;
+  }
+  while ($i <= MENU_MAX_DEPTH) {
+    $set[] = 'p'. $i++ .' = 0';
+  }
+
+  $shift = $item['depth'] - $existing_item['depth'];
+  if ($shift < 0) {
+    $args[] = -$shift;
+    $set[] = 'depth = depth - %d';
+  }
+  elseif ($shift > 0) {
+    // The order of $set must be reversed so the new values don't overwrite the
+    // old ones before they can be used because "Single-table UPDATE
+    // assignments are generally evaluated from left to right"
+    // see: http://dev.mysql.com/doc/refman/5.0/en/update.html
+    $set = array_reverse($set);
+    $args = array_reverse($args);
+
+    $args[] = $shift;
+    $set[] = 'depth = depth + %d';
+  }
+  $where[] = "menu_name = '%s'";
+  $args[] = $existing_item['menu_name'];
+  $p = 'p1';
+  for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p'. ++$i) {
+    $where[] = "$p = %d";
+    $args[] = $existing_item[$p];
+  }
+
+  db_query("UPDATE {menu_links} SET ". implode(', ', $set) ." WHERE ". implode(' AND ', $where), $args);
+  // Check the has_children status of the parent, while excluding this item.
+  _menu_update_parental_status($existing_item, TRUE);
+}
+
+/**
+ * Check and update the has_children status for the parent of a link.
+ */
+function _menu_update_parental_status($item, $exclude = FALSE) {
+  // If plid == 0, there is nothing to update.
+  if ($item['plid']) {
+    // We may want to exclude the passed link as a possible child.
+    $where = $exclude ? " AND mlid != %d" : '';
+    // Check if at least one visible child exists in the table.
+    $parent_has_children = (bool)db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0". $where, $item['menu_name'], $item['plid'], $item['mlid'], 0, 1));
+    db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']);
+  }
+}
+
+/**
+ * Helper function that sets the p1..p9 values for a menu link being saved.
+ */
+function _menu_link_parents_set(&$item, $parent) {
+  $i = 1;
+  while ($i < $item['depth']) {
+    $p = 'p'. $i++;
+    $item[$p] = $parent[$p];
+  }
+  $p = 'p'. $i++;
+  // The parent (p1 - p9) corresponding to the depth always equals the mlid.
+  $item[$p] = $item['mlid'];
+  while ($i <= MENU_MAX_DEPTH) {
+    $p = 'p'. $i++;
+    $item[$p] = 0;
+  }
+}
+
+/**
+ * Helper function to build the router table based on the data from hook_menu.
+ */
+function _menu_router_build($callbacks) {
+  // First pass: separate callbacks from paths, making paths ready for
+  // matching. Calculate fitness, and fill some default values.
+  $menu = array();
+  foreach ($callbacks as $path => $item) {
+    $load_functions = array();
+    $to_arg_functions = array();
+    $fit = 0;
+    $move = FALSE;
+
+    $parts = explode('/', $path, MENU_MAX_PARTS);
+    $number_parts = count($parts);
+    // We store the highest index of parts here to save some work in the fit
+    // calculation loop.
+    $slashes = $number_parts - 1;
+    // Extract load and to_arg functions.
+    foreach ($parts as $k => $part) {
+      $match = FALSE;
+      if (preg_match('/^%([a-z_]*)$/', $part, $matches)) {
+        if (empty($matches[1])) {
+          $match = TRUE;
+          $load_functions[$k] = NULL;
+        }
+        else {
+          if (function_exists($matches[1] .'_to_arg')) {
+            $to_arg_functions[$k] = $matches[1] .'_to_arg';
+            $load_functions[$k] = NULL;
+            $match = TRUE;
+          }
+          if (function_exists($matches[1] .'_load')) {
+            $function = $matches[1] .'_load';
+            // Create an array of arguments that will be passed to the _load
+            // function when this menu path is checked, if 'load arguments'
+            // exists.
+            $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function;
+            $match = TRUE;
+          }
+        }
+      }
+      if ($match) {
+        $parts[$k] = '%';
+      }
+      else {
+        $fit |=  1 << ($slashes - $k);
+      }
+    }
+    if ($fit) {
+      $move = TRUE;
+    }
+    else {
+      // If there is no %, it fits maximally.
+      $fit = (1 << $number_parts) - 1;
+    }
+    $masks[$fit] = 1;
+    $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
+    $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
+    $item += array(
+      'title' => '',
+      'weight' => 0,
+      'type' => MENU_NORMAL_ITEM,
+      '_number_parts' => $number_parts,
+      '_parts' => $parts,
+      '_fit' => $fit,
+    );
+    $item += array(
+      '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_BREADCRUMB),
+      '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK),
+    );
+    if ($move) {
+      $new_path = implode('/', $item['_parts']);
+      $menu[$new_path] = $item;
+      $sort[$new_path] = $number_parts;
+    }
+    else {
+      $menu[$path] = $item;
+      $sort[$path] = $number_parts;
+    }
+  }
+  array_multisort($sort, SORT_NUMERIC, $menu);
+
+  // Apply inheritance rules.
+  foreach ($menu as $path => $v) {
+    $item = &$menu[$path];
+    if (!$item['_tab']) {
+      // Non-tab items.
+      $item['tab_parent'] = '';
+      $item['tab_root'] = $path;
+    }
+    for ($i = $item['_number_parts'] - 1; $i; $i--) {
+      $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
+      if (isset($menu[$parent_path])) {
+
+        $parent = $menu[$parent_path];
+
+        if (!isset($item['tab_parent'])) {
+          // Parent stores the parent of the path.
+          $item['tab_parent'] = $parent_path;
+        }
+        if (!isset($item['tab_root']) && !$parent['_tab']) {
+          $item['tab_root'] = $parent_path;
+        }
+        // If a callback is not found, we try to find the first parent that
+        // has a callback.
+        if (!isset($item['access callback']) && isset($parent['access callback'])) {
+          $item['access callback'] = $parent['access callback'];
+          if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
+            $item['access arguments'] = $parent['access arguments'];
+          }
+        }
+        // Same for page callbacks.
+        if (!isset($item['page callback']) && isset($parent['page callback'])) {
+          $item['page callback'] = $parent['page callback'];
+          if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
+            $item['page arguments'] = $parent['page arguments'];
+          }
+          if (!isset($item['file']) && isset($parent['file'])) {
+            $item['file'] = $parent['file'];
+          }
+          if (!isset($item['file path']) && isset($parent['file path'])) {
+            $item['file path'] = $parent['file path'];
+          }
+        }
+      }
+    }
+    if (!isset($item['access callback']) && isset($item['access arguments'])) {
+      // Default callback.
+      $item['access callback'] = 'user_access';
+    }
+    if (!isset($item['access callback']) || empty($item['page callback'])) {
+      $item['access callback'] = 0;
+    }
+    if (is_bool($item['access callback'])) {
+      $item['access callback'] = intval($item['access callback']);
+    }
+
+    $item += array(
+      'access arguments' => array(),
+      'access callback' => '',
+      'page arguments' => array(),
+      'page callback' => '',
+      'block callback' => '',
+      'title arguments' => array(),
+      'title callback' => 't',
+      'description' => '',
+      'position' => '',
+      'tab_parent' => '',
+      'tab_root' => $path,
+      'path' => $path,
+      'file' => '',
+      'file path' => '',
+      'include file' => '',
+    );
+
+    // Calculate out the file to be included for each callback, if any.
+    if ($item['file']) {
+      $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
+      $item['include file'] = $file_path .'/'. $item['file'];
+    }
+
+    $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
+    db_query("INSERT INTO {menu_router}
+      (path, load_functions, to_arg_functions, access_callback,
+      access_arguments, page_callback, page_arguments, fit,
+      number_parts, tab_parent, tab_root,
+      title, title_callback, title_arguments,
+      type, block_callback, description, position, weight, file)
+      VALUES ('%s', '%s', '%s', '%s',
+      '%s', '%s', '%s', %d,
+      %d, '%s', '%s',
+      '%s', '%s', '%s',
+      %d, '%s', '%s', '%s', %d, '%s')",
+      $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
+      serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
+      $item['_number_parts'], $item['tab_parent'], $item['tab_root'],
+      $item['title'], $item['title callback'], $title_arguments,
+      $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
+  }
+  // Sort the masks so they are in order of descending fit, and store them.
+  $masks = array_keys($masks);
+  rsort($masks);
+  variable_set('menu_masks', $masks);
+  return $menu;
+}
+
+/**
+ * Returns TRUE if a path is external (e.g. http://example.com).
+ */
+function menu_path_is_external($path) {
+  $colonpos = strpos($path, ':');
+  return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
+}
+
+/**
+ * Checks whether the site is off-line for maintenance.
+ *
+ * This function will log the current user out and redirect to front page
+ * if the current user has no 'administer site configuration' permission.
+ *
+ * @return
+ *   FALSE if the site is not off-line or its the login page or the user has
+ *     'administer site configuration' permission.
+ *   TRUE for anonymous users not on the login page if the site is off-line.
+ */
+function _menu_site_is_offline() {
+  // Check if site is set to off-line mode.
+  if (variable_get('site_offline', 0)) {
+    // Check if the user has administration privileges.
+    if (user_access('administer site configuration')) {
+      // Ensure that the off-line message is displayed only once [allowing for
+      // page redirects], and specifically suppress its display on the site
+      // maintenance page.
+      if (drupal_get_normal_path($_GET['q']) != 'admin/settings/site-maintenance') {
+        drupal_set_message(t('Operating in off-line mode.'), 'status', FALSE);
+      }
+    }
+    else {
+      // Anonymous users get a FALSE at the login prompt, TRUE otherwise.
+      if (user_is_anonymous()) {
+        return $_GET['q'] != 'user' && $_GET['q'] != 'user/login';
+      }
+      // Logged in users are unprivileged here, so they are logged out.
+      require_once drupal_get_path('module', 'user') .'/user.pages.inc';
+      user_logout();
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Validates the path of a menu link being created or edited.
+ *
+ * @return
+ *   TRUE if it is a valid path AND the current user has access permission,
+ *   FALSE otherwise.
+ */
+function menu_valid_path($form_item) {
+  global $menu_admin;
+  $item = array();
+  $path = $form_item['link_path'];
+  // We indicate that a menu administrator is running the menu access check.
+  $menu_admin = TRUE;
+  if ($path == '<front>' || menu_path_is_external($path)) {
+    $item = array('access' => TRUE);
+  }
+  elseif (preg_match('/\/\%/', $path)) {
+    // Path is dynamic (ie 'user/%'), so check directly against menu_router table.
+    if ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path))) {
+      $item['link_path']  = $form_item['link_path'];
+      $item['link_title'] = $form_item['link_title'];
+      $item['external']   = FALSE;
+      $item['options'] = '';
+      _menu_link_translate($item);
+    }
+  }
+  else {
+    $item = menu_get_item($path);
+  }
+  $menu_admin = FALSE;
+  return $item && $item['access'];
+}
+
+/**
+ * @} End of "defgroup menu".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/module.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,492 @@
+<?php
+// $Id: module.inc,v 1.115 2007/12/27 12:31:05 goba Exp $
+
+/**
+ * @file
+ * API for loading and interacting with Drupal modules.
+ */
+
+/**
+ * Load all the modules that have been enabled in the system table.
+ */
+function module_load_all() {
+  foreach (module_list(TRUE, FALSE) as $module) {
+    drupal_load('module', $module);
+  }
+}
+
+/**
+ * Call a function repeatedly with each module in turn as an argument.
+ */
+function module_iterate($function, $argument = '') {
+  foreach (module_list() as $name) {
+    $function($name, $argument);
+  }
+}
+
+/**
+ * Collect a list of all loaded modules. During the bootstrap, return only
+ * vital modules. See bootstrap.inc
+ *
+ * @param $refresh
+ *   Whether to force the module list to be regenerated (such as after the
+ *   administrator has changed the system settings).
+ * @param $bootstrap
+ *   Whether to return the reduced set of modules loaded in "bootstrap mode"
+ *   for cached pages. See bootstrap.inc.
+ * @param $sort
+ *   By default, modules are ordered by weight and filename, settings this option
+ *   to TRUE, module list will be ordered by module name.
+ * @param $fixed_list
+ *   (Optional) Override the module list with the given modules. Stays until the
+ *   next call with $refresh = TRUE.
+ * @return
+ *   An associative array whose keys and values are the names of all loaded
+ *   modules.
+ */
+function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE, $fixed_list = NULL) {
+  static $list, $sorted_list;
+
+  if ($refresh || $fixed_list) {
+    unset($sorted_list);
+    $list = array();
+    if ($fixed_list) {
+      foreach ($fixed_list as $name => $module) {
+        drupal_get_filename('module', $name, $module['filename']);
+        $list[$name] = $name;
+      }
+    }
+    else {
+      if ($bootstrap) {
+        $result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC");
+      }
+      else {
+        $result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC");
+      }
+      while ($module = db_fetch_object($result)) {
+        if (file_exists($module->filename)) {
+          // Determine the current throttle status and see if the module should be
+          // loaded based on server load. We have to directly access the throttle
+          // variables, since throttle.module may not be loaded yet.
+          $throttle = ($module->throttle && variable_get('throttle_level', 0) > 0);
+          if (!$throttle) {
+            drupal_get_filename('module', $module->name, $module->filename);
+            $list[$module->name] = $module->name;
+          }
+        }
+      }
+    }
+  }
+  if ($sort) {
+    if (!isset($sorted_list)) {
+      $sorted_list = $list;
+      ksort($sorted_list);
+    }
+    return $sorted_list;
+  }
+  return $list;
+}
+
+/**
+ * Rebuild the database cache of module files.
+ *
+ * @return
+ *   The array of filesystem objects used to rebuild the cache.
+ */
+function module_rebuild_cache() {
+  // Get current list of modules
+  $files = drupal_system_listing('\.module$', 'modules', 'name', 0);
+
+  // Extract current files from database.
+  system_get_files_database($files, 'module');
+
+  ksort($files);
+
+  // Set defaults for module info
+  $defaults = array(
+    'dependencies' => array(),
+    'dependents' => array(),
+    'description' => '',
+    'version' => NULL,
+    'php' => DRUPAL_MINIMUM_PHP,
+  );
+
+  foreach ($files as $filename => $file) {
+    // Look for the info file.
+    $file->info = drupal_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info');
+
+    // Skip modules that don't provide info.
+    if (empty($file->info)) {
+      unset($files[$filename]);
+      continue;
+    }
+    // Merge in defaults and save.
+    $files[$filename]->info = $file->info + $defaults;
+
+    // Invoke hook_system_info_alter() to give installed modules a chance to
+    // modify the data in the .info files if necessary.
+    drupal_alter('system_info', $files[$filename]->info, $files[$filename]);
+
+    // Log the critical hooks implemented by this module.
+    $bootstrap = 0;
+    foreach (bootstrap_hooks() as $hook) {
+      if (module_hook($file->name, $hook)) {
+        $bootstrap = 1;
+        break;
+      }
+    }
+
+    // Update the contents of the system table:
+    if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) {
+      db_query("UPDATE {system} SET info = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", serialize($files[$filename]->info), $file->name, $file->filename, $bootstrap, $file->old_filename);
+    }
+    else {
+      // This is a new module.
+      $files[$filename]->status = 0;
+      $files[$filename]->throttle = 0;
+      db_query("INSERT INTO {system} (name, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, serialize($files[$filename]->info), 'module', $file->filename, 0, 0, $bootstrap);
+    }
+  }
+  $files = _module_build_dependencies($files);
+  return $files;
+}
+
+/**
+ * Find dependencies any level deep and fill in dependents information too.
+ *
+ * If module A depends on B which in turn depends on C then this function will
+ * add C to the list of modules A depends on. This will be repeated until
+ * module A has a list of all modules it depends on. If it depends on itself,
+ * called a circular dependency, that's marked by adding a nonexistent module,
+ * called -circular- to this list of modules. Because this does not exist,
+ * it'll be impossible to switch module A on.
+ *
+ * Also we fill in a dependents array in $file->info. Using the names above,
+ * the dependents array of module B lists A.
+ *
+ * @param $files
+ *   The array of filesystem objects used to rebuild the cache.
+ * @return
+ *   The same array with dependencies and dependents added where applicable.
+ */
+function _module_build_dependencies($files) {
+  do {
+    $new_dependency = FALSE;
+    foreach ($files as $filename => $file) {
+      // We will modify this object (module A, see doxygen for module A, B, C).
+      $file = &$files[$filename];
+      if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
+        foreach ($file->info['dependencies'] as $dependency_name) {
+          // This is a nonexistent module.
+          if ($dependency_name == '-circular-' || !isset($files[$dependency_name])) {
+            continue;
+          }
+          // $dependency_name is module B (again, see doxygen).
+          $files[$dependency_name]->info['dependents'][$filename] = $filename;
+          $dependency = $files[$dependency_name];
+          if (isset($dependency->info['dependencies']) && is_array($dependency->info['dependencies'])) {
+            // Let's find possible C modules.
+            foreach ($dependency->info['dependencies'] as $candidate) {
+              if (array_search($candidate, $file->info['dependencies']) === FALSE) {
+                // Is this a circular dependency?
+                if ($candidate == $filename) {
+                  // As a module name can not contain dashes, this makes
+                  // impossible to switch on the module.
+                  $candidate = '-circular-';
+                  // Do not display the message or add -circular- more than once.
+                  if (array_search($candidate, $file->info['dependencies']) !== FALSE) {
+                    continue;
+                  }
+                  drupal_set_message(t('%module is part of a circular dependency. This is not supported and you will not be able to switch it on.', array('%module' => $file->info['name'])), 'error');
+                }
+                else {
+                  // We added a new dependency to module A. The next loop will
+                  // be able to use this as "B module" thus finding even
+                  // deeper dependencies.
+                  $new_dependency = TRUE;
+                }
+                $file->info['dependencies'][] = $candidate;
+              }
+            }
+          }
+        }
+      }
+      // Don't forget to break the reference.
+      unset($file);
+    }
+  } while ($new_dependency);
+  return $files;
+}
+
+/**
+ * Determine whether a given module exists.
+ *
+ * @param $module
+ *   The name of the module (without the .module extension).
+ * @return
+ *   TRUE if the module is both installed and enabled.
+ */
+function module_exists($module) {
+  $list = module_list();
+  return array_key_exists($module, $list);
+}
+
+/**
+ * Load a module's installation hooks.
+ */
+function module_load_install($module) {
+  // Make sure the installation API is available
+  include_once './includes/install.inc';
+
+  module_load_include('install', $module);
+}
+
+/**
+ * Load a module include file.
+ *
+ * @param $type
+ *   The include file's type (file extension).
+ * @param $module
+ *   The module to which the include file belongs.
+ * @param $name
+ *   Optionally, specify the file name. If not set, the module's name is used.
+ */
+function module_load_include($type, $module, $name = NULL) {
+  if (empty($name)) {
+    $name = $module;
+  }
+
+  $file = './'. drupal_get_path('module', $module) ."/$name.$type";
+
+  if (is_file($file)) {
+    require_once $file;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Load an include file for each of the modules that have been enabled in
+ * the system table.
+ */
+function module_load_all_includes($type, $name = NULL) {
+  $modules = module_list();
+  foreach ($modules as $module) {
+    module_load_include($type, $module, $name);
+  }
+}
+
+/**
+ * Enable a given list of modules.
+ *
+ * @param $module_list
+ *   An array of module names.
+ */
+function module_enable($module_list) {
+  $invoke_modules = array();
+  foreach ($module_list as $module) {
+    $existing = db_fetch_object(db_query("SELECT status FROM {system} WHERE type = '%s' AND name = '%s'", 'module', $module));
+    if ($existing->status == 0) {
+      module_load_install($module);
+      db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 1, 0, 'module', $module);
+      drupal_load('module', $module);
+      $invoke_modules[] = $module;
+    }
+  }
+
+  if (!empty($invoke_modules)) {
+    // Refresh the module list to include the new enabled module.
+    module_list(TRUE, FALSE);
+    // Force to regenerate the stored list of hook implementations.
+    module_implements('', FALSE, TRUE);
+  }
+
+  foreach ($invoke_modules as $module) {
+    module_invoke($module, 'enable');
+    // Check if node_access table needs rebuilding.
+    // We check for the existence of node_access_needs_rebuild() since
+    // at install time, module_enable() could be called while node.module
+    // is not enabled yet.
+    if (function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+      node_access_needs_rebuild(TRUE);
+    }
+  }
+}
+
+/**
+ * Disable a given set of modules.
+ *
+ * @param $module_list
+ *   An array of module names.
+ */
+function module_disable($module_list) {
+  $invoke_modules = array();
+  foreach ($module_list as $module) {
+    if (module_exists($module)) {
+      // Check if node_access table needs rebuilding.
+      if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+        node_access_needs_rebuild(TRUE);
+      }
+
+      module_load_install($module);
+      module_invoke($module, 'disable');
+      db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 0, 0, 'module', $module);
+      $invoke_modules[] = $module;
+    }
+  }
+
+  if (!empty($invoke_modules)) {
+    // Refresh the module list to exclude the disabled modules.
+    module_list(TRUE, FALSE);
+    // Force to regenerate the stored list of hook implementations.
+    module_implements('', FALSE, TRUE);
+  }
+
+  // If there remains no more node_access module, rebuilding will be
+  // straightforward, we can do it right now.
+  if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) {
+    node_access_rebuild();
+  }
+}
+
+/**
+ * @defgroup hooks Hooks
+ * @{
+ * Allow modules to interact with the Drupal core.
+ *
+ * Drupal's module system is based on the concept of "hooks". A hook is a PHP
+ * function that is named foo_bar(), where "foo" is the name of the module (whose
+ * filename is thus foo.module) and "bar" is the name of the hook. Each hook has
+ * a defined set of parameters and a specified result type.
+ *
+ * To extend Drupal, a module need simply implement a hook. When Drupal wishes to
+ * allow intervention from modules, it determines which modules implement a hook
+ * and call that hook in all enabled modules that implement it.
+ *
+ * The available hooks to implement are explained here in the Hooks section of
+ * the developer documentation. The string "hook" is used as a placeholder for
+ * the module name is the hook definitions. For example, if the module file is
+ * called example.module, then hook_help() as implemented by that module would be
+ * defined as example_help().
+ */
+
+/**
+ * Determine whether a module implements a hook.
+ *
+ * @param $module
+ *   The name of the module (without the .module extension).
+ * @param $hook
+ *   The name of the hook (e.g. "help" or "menu").
+ * @return
+ *   TRUE if the module is both installed and enabled, and the hook is
+ *   implemented in that module.
+ */
+function module_hook($module, $hook) {
+  return function_exists($module .'_'. $hook);
+}
+
+/**
+ * Determine which modules are implementing a hook.
+ *
+ * @param $hook
+ *   The name of the hook (e.g. "help" or "menu").
+ * @param $sort
+ *   By default, modules are ordered by weight and filename, settings this option
+ *   to TRUE, module list will be ordered by module name.
+ * @param $refresh
+ *   For internal use only: Whether to force the stored list of hook
+ *   implementations to be regenerated (such as after enabling a new module,
+ *   before processing hook_enable).
+ * @return
+ *   An array with the names of the modules which are implementing this hook.
+ */
+function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
+  static $implementations;
+
+  if ($refresh) {
+    $implementations = array();
+    return;
+  }
+
+  if (!isset($implementations[$hook])) {
+    $implementations[$hook] = array();
+    $list = module_list(FALSE, TRUE, $sort);
+    foreach ($list as $module) {
+      if (module_hook($module, $hook)) {
+        $implementations[$hook][] = $module;
+      }
+    }
+  }
+
+  // The explicit cast forces a copy to be made. This is needed because
+  // $implementations[$hook] is only a reference to an element of
+  // $implementations and if there are nested foreaches (due to nested node
+  // API calls, for example), they would both manipulate the same array's
+  // references, which causes some modules' hooks not to be called.
+  // See also http://www.zend.com/zend/art/ref-count.php.
+  return (array)$implementations[$hook];
+}
+
+/**
+ * Invoke a hook in a particular module.
+ *
+ * @param $module
+ *   The name of the module (without the .module extension).
+ * @param $hook
+ *   The name of the hook to invoke.
+ * @param ...
+ *   Arguments to pass to the hook implementation.
+ * @return
+ *   The return value of the hook implementation.
+ */
+function module_invoke() {
+  $args = func_get_args();
+  $module = $args[0];
+  $hook = $args[1];
+  unset($args[0], $args[1]);
+  $function = $module .'_'. $hook;
+  if (module_hook($module, $hook)) {
+    return call_user_func_array($function, $args);
+  }
+}
+/**
+ * Invoke a hook in all enabled modules that implement it.
+ *
+ * @param $hook
+ *   The name of the hook to invoke.
+ * @param ...
+ *   Arguments to pass to the hook.
+ * @return
+ *   An array of return values of the hook implementations. If modules return
+ *   arrays from their implementations, those are merged into one array.
+ */
+function module_invoke_all() {
+  $args = func_get_args();
+  $hook = $args[0];
+  unset($args[0]);
+  $return = array();
+  foreach (module_implements($hook) as $module) {
+    $function = $module .'_'. $hook;
+    $result = call_user_func_array($function, $args);
+    if (isset($result) && is_array($result)) {
+      $return = array_merge_recursive($return, $result);
+    }
+    else if (isset($result)) {
+      $return[] = $result;
+    }
+  }
+
+  return $return;
+}
+
+/**
+ * @} End of "defgroup hooks".
+ */
+
+/**
+ * Array of modules required by core.
+ */
+function drupal_required_modules() {
+  return array('block', 'filter', 'node', 'system', 'user');
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/pager.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,434 @@
+<?php
+// $Id: pager.inc,v 1.63 2007/12/06 09:58:30 goba Exp $
+
+/**
+ * @file
+ * Functions to aid in presenting database results as a set of pages.
+ */
+
+/**
+ * Perform a paged database query.
+ *
+ * Use this function when doing select queries you wish to be able to page. The
+ * pager uses LIMIT-based queries to fetch only the records required to render a
+ * certain page. However, it has to learn the total number of records returned
+ * by the query to compute the number of pages (the number of records / records
+ * per page). This is done by inserting "COUNT(*)" in the original query. For
+ * example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
+ * sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
+ * node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
+ * query is accomplished using a regular expression.
+ *
+ * Unfortunately, the rewrite rule does not always work as intended for queries
+ * that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
+ * other complex queries. In those cases, you can optionally pass a query that
+ * will be used to count the records.
+ *
+ * For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
+ * GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
+ * COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
+ * COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
+ *
+ * @param $query
+ *   The SQL query that needs paging.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $count_query
+ *   An SQL query used to count matching records.
+ * @param ...
+ *   A variable number of arguments which are substituted into the query (and
+ *   the count query) using printf() syntax. Instead of a variable number of
+ *   query arguments, you may also pass a single array containing the query
+ *   arguments.
+ * @return
+ *   A database query result resource, or FALSE if the query was not executed
+ *   correctly.
+ *
+ * @ingroup database
+ */
+function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
+  global $pager_page_array, $pager_total, $pager_total_items;
+  $page = isset($_GET['page']) ? $_GET['page'] : '';
+
+  // Substitute in query arguments.
+  $args = func_get_args();
+  $args = array_slice($args, 4);
+  // Alternative syntax for '...'
+  if (isset($args[0]) && is_array($args[0])) {
+    $args = $args[0];
+  }
+
+  // Construct a count query if none was given.
+  if (!isset($count_query)) {
+    $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM ', ''), $query);
+  }
+
+  // Convert comma-separated $page to an array, used by other functions.
+  $pager_page_array = explode(',', $page);
+
+  // We calculate the total of pages as ceil(items / limit).
+  $pager_total_items[$element] = db_result(db_query($count_query, $args));
+  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
+  $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
+  return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
+}
+
+/**
+ * Compose a query string to append to pager requests.
+ *
+ * @return
+ *   A query string that consists of all components of the current page request
+ *   except for those pertaining to paging.
+ */
+function pager_get_querystring() {
+  static $string = NULL;
+  if (!isset($string)) {
+    $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE)));
+  }
+  return $string;
+}
+
+/**
+ * Format a query pager.
+ *
+ * Menu callbacks that display paged query results should call theme('pager') to
+ * retrieve a pager control so that users can view other results.
+ * Format a list of nearby pages with additional query results.
+ *
+ * @param $tags
+ *   An array of labels for the controls in the pager.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager links.
+ * @param $quantity
+ *   The number of pages in the list.
+ * @return
+ *   An HTML string that generates the query pager.
+ *
+ * @ingroup themeable
+ */
+function theme_pager($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9) {
+  global $pager_page_array, $pager_total;
+
+  // Calculate various markers within this pager piece:
+  // Middle is used to "center" pages around the current page.
+  $pager_middle = ceil($quantity / 2);
+  // current is the page we are currently paged to
+  $pager_current = $pager_page_array[$element] + 1;
+  // first is the first page listed by this pager piece (re quantity)
+  $pager_first = $pager_current - $pager_middle + 1;
+  // last is the last page listed by this pager piece (re quantity)
+  $pager_last = $pager_current + $quantity - $pager_middle;
+  // max is the maximum page number
+  $pager_max = $pager_total[$element];
+  // End of marker calculations.
+
+  // Prepare for generation loop.
+  $i = $pager_first;
+  if ($pager_last > $pager_max) {
+    // Adjust "center" if at end of query.
+    $i = $i + ($pager_max - $pager_last);
+    $pager_last = $pager_max;
+  }
+  if ($i <= 0) {
+    // Adjust "center" if at start of query.
+    $pager_last = $pager_last + (1 - $i);
+    $i = 1;
+  }
+  // End of generation loop preparation.
+
+  $li_first = theme('pager_first', (isset($tags[0]) ? $tags[0] : t('« first')), $limit, $element, $parameters);
+  $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹ previous')), $limit, $element, 1, $parameters);
+  $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next ›')), $limit, $element, 1, $parameters);
+  $li_last = theme('pager_last', (isset($tags[4]) ? $tags[4] : t('last »')), $limit, $element, $parameters);
+
+  if ($pager_total[$element] > 1) {
+    if ($li_first) {
+      $items[] = array(
+        'class' => 'pager-first',
+        'data' => $li_first,
+      );
+    }
+    if ($li_previous) {
+      $items[] = array(
+        'class' => 'pager-previous',
+        'data' => $li_previous,
+      );
+    }
+
+    // When there is more than one page, create the pager list.
+    if ($i != $pager_max) {
+      if ($i > 1) {
+        $items[] = array(
+          'class' => 'pager-ellipsis',
+          'data' => '…',
+        );
+      }
+      // Now generate the actual pager piece.
+      for (; $i <= $pager_last && $i <= $pager_max; $i++) {
+        if ($i < $pager_current) {
+          $items[] = array(
+            'class' => 'pager-item',
+            'data' => theme('pager_previous', $i, $limit, $element, ($pager_current - $i), $parameters),
+          );
+        }
+        if ($i == $pager_current) {
+          $items[] = array(
+            'class' => 'pager-current',
+            'data' => $i,
+          );
+        }
+        if ($i > $pager_current) {
+          $items[] = array(
+            'class' => 'pager-item',
+            'data' => theme('pager_next', $i, $limit, $element, ($i - $pager_current), $parameters),
+          );
+        }
+      }
+      if ($i < $pager_max) {
+        $items[] = array(
+          'class' => 'pager-ellipsis',
+          'data' => '…',
+        );
+      }
+    }
+    // End generation.
+    if ($li_next) {
+      $items[] = array(
+        'class' => 'pager-next',
+        'data' => $li_next,
+      );
+    }
+    if ($li_last) {
+      $items[] = array(
+        'class' => 'pager-last',
+        'data' => $li_last,
+      );
+    }
+    return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
+  }
+}
+
+
+/**
+ * @name Pager pieces
+ * @{
+ * Use these pieces to construct your own custom pagers in your theme. Note that
+ * you should NOT modify this file to customize your pager.
+ */
+
+/**
+ * Format a "first page" link.
+ *
+ * @param $text
+ *   The name (or image) of the link.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager links.
+ * @return
+ *   An HTML string that generates this piece of the query pager.
+ *
+ * @ingroup themeable
+ */
+function theme_pager_first($text, $limit, $element = 0, $parameters = array()) {
+  global $pager_page_array;
+  $output = '';
+
+  // If we are anywhere but the first page
+  if ($pager_page_array[$element] > 0) {
+    $output = theme('pager_link', $text, pager_load_array(0, $element, $pager_page_array), $element, $parameters);
+  }
+
+  return $output;
+}
+
+/**
+ * Format a "previous page" link.
+ *
+ * @param $text
+ *   The name (or image) of the link.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $interval
+ *   The number of pages to move backward when the link is clicked.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager links.
+ * @return
+ *   An HTML string that generates this piece of the query pager.
+ *
+ * @ingroup themeable
+ */
+function theme_pager_previous($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
+  global $pager_page_array;
+  $output = '';
+
+  // If we are anywhere but the first page
+  if ($pager_page_array[$element] > 0) {
+    $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
+
+    // If the previous page is the first page, mark the link as such.
+    if ($page_new[$element] == 0) {
+      $output = theme('pager_first', $text, $limit, $element, $parameters);
+    }
+    // The previous page is not the first page.
+    else {
+      $output = theme('pager_link', $text, $page_new, $element, $parameters);
+    }
+  }
+
+  return $output;
+}
+
+/**
+ * Format a "next page" link.
+ *
+ * @param $text
+ *   The name (or image) of the link.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $interval
+ *   The number of pages to move forward when the link is clicked.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager links.
+ * @return
+ *   An HTML string that generates this piece of the query pager.
+ *
+ * @ingroup themeable
+ */
+function theme_pager_next($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
+  global $pager_page_array, $pager_total;
+  $output = '';
+
+  // If we are anywhere but the last page
+  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
+    $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
+    // If the next page is the last page, mark the link as such.
+    if ($page_new[$element] == ($pager_total[$element] - 1)) {
+      $output = theme('pager_last', $text, $limit, $element, $parameters);
+    }
+    // The next page is not the last page.
+    else {
+      $output = theme('pager_link', $text, $page_new, $element, $parameters);
+    }
+  }
+
+  return $output;
+}
+
+/**
+ * Format a "last page" link.
+ *
+ * @param $text
+ *   The name (or image) of the link.
+ * @param $limit
+ *   The number of query results to display per page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager links.
+ * @return
+ *   An HTML string that generates this piece of the query pager.
+ *
+ * @ingroup themeable
+ */
+function theme_pager_last($text, $limit, $element = 0, $parameters = array()) {
+  global $pager_page_array, $pager_total;
+  $output = '';
+
+  // If we are anywhere but the last page
+  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
+    $output = theme('pager_link', $text, pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), $element, $parameters);
+  }
+
+  return $output;
+}
+
+
+/**
+ * Format a link to a specific query result page.
+ *
+ * @param $page_new
+ *   The first result to display on the linked page.
+ * @param $element
+ *   An optional integer to distinguish between multiple pagers on one page.
+ * @param $parameters
+ *   An associative array of query string parameters to append to the pager link.
+ * @param $attributes
+ *   An associative array of HTML attributes to apply to a pager anchor tag.
+ * @return
+ *   An HTML string that generates the link.
+ *
+ * @ingroup themeable
+ */
+function theme_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
+  $page = isset($_GET['page']) ? $_GET['page'] : '';
+  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
+    $parameters['page'] = $new_page;
+  }
+
+  $query = array();
+  if (count($parameters)) {
+    $query[] = drupal_query_string_encode($parameters, array());
+  }
+  $querystring = pager_get_querystring();
+  if ($querystring != '') {
+    $query[] = $querystring;
+  }
+
+  // Set each pager link title
+  if (!isset($attributes['title'])) {
+    static $titles = NULL;
+    if (!isset($titles)) {
+      $titles = array(
+        t('« first') => t('Go to first page'),
+        t('‹ previous') => t('Go to previous page'),
+        t('next ›') => t('Go to next page'),
+        t('last »') => t('Go to last page'),
+      );
+    }
+    if (isset($titles[$text])) {
+      $attributes['title'] = $titles[$text];
+    }
+    else if (is_numeric($text)) {
+      $attributes['title'] = t('Go to page @number', array('@number' => $text));
+    }
+  }
+
+  return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL));
+}
+
+/**
+ * @} End of "Pager pieces".
+ */
+
+/**
+ * Helper function
+ *
+ * Copies $old_array to $new_array and sets $new_array[$element] = $value
+ * Fills in $new_array[0 .. $element - 1] = 0
+ */
+function pager_load_array($value, $element, $old_array) {
+  $new_array = $old_array;
+  // Look for empty elements.
+  for ($i = 0; $i < $element; $i++) {
+    if (!$new_array[$i]) {
+      // Load found empty element with 0.
+      $new_array[$i] = 0;
+    }
+  }
+  // Update the changed element.
+  $new_array[$element] = (int)$value;
+  return $new_array;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/path.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,242 @@
+<?php
+// $Id: path.inc,v 1.19 2007/11/04 16:42:45 goba Exp $
+
+/**
+ * @file
+ * Functions to handle paths in Drupal, including path aliasing.
+ *
+ * These functions are not loaded for cached pages, but modules that need
+ * to use them in hook_init() or hook exit() can make them available, by
+ * executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);".
+ */
+
+/**
+ * Initialize the $_GET['q'] variable to the proper normal path.
+ */
+function drupal_init_path() {
+  if (!empty($_GET['q'])) {
+    $_GET['q'] = drupal_get_normal_path(trim($_GET['q'], '/'));
+  }
+  else {
+    $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
+  }
+}
+
+/**
+ * Given an alias, return its Drupal system URL if one exists. Given a Drupal
+ * system URL return one of its aliases if such a one exists. Otherwise,
+ * return FALSE.
+ *
+ * @param $action
+ *   One of the following values:
+ *   - wipe: delete the alias cache.
+ *   - alias: return an alias for a given Drupal system path (if one exists).
+ *   - source: return the Drupal system URL for a path alias (if one exists).
+ * @param $path
+ *   The path to investigate for corresponding aliases or system URLs.
+ * @param $path_language
+ *   Optional language code to search the path with. Defaults to the page language.
+ *   If there's no path defined for that language it will search paths without
+ *   language.
+ *
+ * @return
+ *   Either a Drupal system path, an aliased path, or FALSE if no path was
+ *   found.
+ */
+function drupal_lookup_path($action, $path = '', $path_language = '') {
+  global $language;
+  // $map is an array with language keys, holding arrays of Drupal paths to alias relations
+  static $map = array(), $no_src = array(), $count;
+
+  $path_language = $path_language ? $path_language : $language->language;
+
+  // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
+  if (!isset($count)) {
+    $count = db_result(db_query('SELECT COUNT(pid) FROM {url_alias}'));
+  }
+
+  if ($action == 'wipe') {
+    $map = array();
+    $no_src = array();
+  }
+  elseif ($count > 0 && $path != '') {
+    if ($action == 'alias') {
+      if (isset($map[$path_language][$path])) {
+        return $map[$path_language][$path];
+      }
+      // Get the most fitting result falling back with alias without language
+      $alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language));
+      $map[$path_language][$path] = $alias;
+      return $alias;
+    }
+    // Check $no_src for this $path in case we've already determined that there
+    // isn't a path that has this alias
+    elseif ($action == 'source' && !isset($no_src[$path_language][$path])) {
+      // Look for the value $path within the cached $map
+      $src = '';
+      if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) {
+        // Get the most fitting result falling back with alias without language
+        if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language))) {
+          $map[$path_language][$src] = $path;
+        }
+        else {
+          // We can't record anything into $map because we do not have a valid
+          // index and there is no need because we have not learned anything
+          // about any Drupal path. Thus cache to $no_src.
+          $no_src[$path_language][$path] = TRUE;
+        }
+      }
+      return $src;
+    }
+  }
+
+  return FALSE;
+}
+
+/**
+ * Given an internal Drupal path, return the alias set by the administrator.
+ *
+ * @param $path
+ *   An internal Drupal path.
+ * @param $path_language
+ *   An optional language code to look up the path in.
+ *
+ * @return
+ *   An aliased path if one was found, or the original path if no alias was
+ *   found.
+ */
+function drupal_get_path_alias($path, $path_language = '') {
+  $result = $path;
+  if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
+    $result = $alias;
+  }
+  return $result;
+}
+
+/**
+ * Given a path alias, return the internal path it represents.
+ *
+ * @param $path
+ *   A Drupal path alias.
+ * @param $path_language
+ *   An optional language code to look up the path in.
+ *
+ * @return
+ *   The internal path represented by the alias, or the original alias if no
+ *   internal path was found.
+ */
+function drupal_get_normal_path($path, $path_language = '') {
+  $result = $path;
+  if ($src = drupal_lookup_path('source', $path, $path_language)) {
+    $result = $src;
+  }
+  if (function_exists('custom_url_rewrite_inbound')) {
+    // Modules may alter the inbound request path by reference.
+    custom_url_rewrite_inbound($result, $path, $path_language);
+  }
+  return $result;
+}
+
+/**
+ * Return a component of the current Drupal path.
+ *
+ * When viewing a page at the path "admin/content/types", for example, arg(0)
+ * would return "admin", arg(1) would return "content", and arg(2) would return
+ * "types".
+ *
+ * Avoid use of this function where possible, as resulting code is hard to read.
+ * Instead, attempt to use named arguments in menu callback functions. See the
+ * explanation in menu.inc for how to construct callbacks that take arguments.
+ *
+ * @param $index
+ *   The index of the component, where each component is separated by a '/'
+ *   (forward-slash), and where the first component has an index of 0 (zero).
+ *
+ * @return
+ *   The component specified by $index, or NULL if the specified component was
+ *   not found.
+ */
+function arg($index = NULL, $path = NULL) {
+  static $arguments;
+
+  if (!isset($path)) {
+    $path = $_GET['q'];
+  }
+  if (!isset($arguments[$path])) {
+    $arguments[$path] = explode('/', $path);
+  }
+  if (!isset($index)) {
+    return $arguments[$path];
+  }
+  if (isset($arguments[$path][$index])) {
+    return $arguments[$path][$index];
+  }
+}
+
+/**
+ * Get the title of the current page, for display on the page and in the title bar.
+ *
+ * @return
+ *   The current page's title.
+ */
+function drupal_get_title() {
+  $title = drupal_set_title();
+
+  // during a bootstrap, menu.inc is not included and thus we cannot provide a title
+  if (!isset($title) && function_exists('menu_get_active_title')) {
+    $title = check_plain(menu_get_active_title());
+  }
+
+  return $title;
+}
+
+/**
+ * Set the title of the current page, for display on the page and in the title bar.
+ *
+ * @param $title
+ *   Optional string value to assign to the page title; or if set to NULL
+ *   (default), leaves the current title unchanged.
+ *
+ * @return
+ *   The updated title of the current page.
+ */
+function drupal_set_title($title = NULL) {
+  static $stored_title;
+
+  if (isset($title)) {
+    $stored_title = $title;
+  }
+  return $stored_title;
+}
+
+/**
+ * Check if the current page is the front page.
+ *
+ * @return
+ *   Boolean value: TRUE if the current page is the front page; FALSE if otherwise.
+ */
+function drupal_is_front_page() {
+  // As drupal_init_path updates $_GET['q'] with the 'site_frontpage' path,
+  // we can check it against the 'site_frontpage' variable.
+  return $_GET['q'] == drupal_get_normal_path(variable_get('site_frontpage', 'node'));
+}
+
+/**
+ * Check if a path matches any pattern in a set of patterns.
+ *
+ * @param $path
+ *   The path to match.
+ * @param $patterns
+ *   String containing a set of patterns separated by \n, \r or \r\n.
+ *
+ * @return
+ *   Boolean value: TRUE if the path matches a pattern, FALSE otherwise.
+ */
+function drupal_match_path($path, $patterns) {
+  static $regexps;
+
+  if (!isset($regexps[$patterns])) {
+    $regexps[$patterns] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($patterns, '/')) .')$/';
+  }
+  return preg_match($regexps[$patterns], $path);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/session.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,176 @@
+<?php
+// $Id: session.inc,v 1.44.2.1 2008/02/07 11:58:40 goba Exp $
+
+/**
+ * @file
+ * User session handling functions.
+ */
+
+function sess_open($save_path, $session_name) {
+  return TRUE;
+}
+
+function sess_close() {
+  return TRUE;
+}
+
+function sess_read($key) {
+  global $user;
+
+  // Write and Close handlers are called after destructing objects since PHP 5.0.5
+  // Thus destructors can use sessions but session handler can't use objects.
+  // So we are moving session closure before destructing objects.
+  register_shutdown_function('session_write_close');
+
+  // Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers).
+  if (!isset($_COOKIE[session_name()])) {
+    $user = drupal_anonymous_user();
+    return '';
+  }
+
+  // Otherwise, if the session is still active, we have a record of the client's session in the database.
+  $user = db_fetch_object(db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = '%s'", $key));
+
+  // We found the client's session record and they are an authenticated user
+  if ($user && $user->uid > 0) {
+    // This is done to unserialize the data member of $user
+    $user = drupal_unpack($user);
+
+    // Add roles element to $user
+    $user->roles = array();
+    $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
+    $result = db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d", $user->uid);
+    while ($role = db_fetch_object($result)) {
+      $user->roles[$role->rid] = $role->name;
+    }
+  }
+  // We didn't find the client's record (session has expired), or they are an anonymous user.
+  else {
+    $session = isset($user->session) ? $user->session : '';
+    $user = drupal_anonymous_user($session);
+  }
+
+  return $user->session;
+}
+
+function sess_write($key, $value) {
+  global $user;
+
+  // If saving of session data is disabled or if the client doesn't have a session,
+  // and one isn't being created ($value), do nothing.
+  if (!session_save_session() || (empty($_COOKIE[session_name()]) && empty($value))) {
+    return TRUE;
+  }
+
+  $result = db_result(db_query("SELECT COUNT(*) FROM {sessions} WHERE sid = '%s'", $key));
+
+  if (!$result) {
+    // Only save session data when when the browser sends a cookie. This keeps
+    // crawlers out of session table. This reduces memory and server load,
+    // and gives more useful statistics. We can't eliminate anonymous session
+    // table rows without breaking throttle module and "Who's Online" block.
+    if ($user->uid || $value || count($_COOKIE)) {
+      db_query("INSERT INTO {sessions} (sid, uid, cache, hostname, session, timestamp) VALUES ('%s', %d, %d, '%s', '%s', %d)", $key, $user->uid, isset($user->cache) ? $user->cache : '', ip_address(), $value, time());
+    }
+  }
+  else {
+    db_query("UPDATE {sessions} SET uid = %d, cache = %d, hostname = '%s', session = '%s', timestamp = %d WHERE sid = '%s'", $user->uid, isset($user->cache) ? $user->cache : '', ip_address(), $value, time(), $key);
+
+    // Last access time is updated no more frequently than once every 180 seconds.
+    // This reduces contention in the users table.
+    if ($user->uid && time() - $user->access > variable_get('session_write_interval', 180)) {
+      db_query("UPDATE {users} SET access = %d WHERE uid = %d", time(), $user->uid);
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Called when an anonymous user becomes authenticated or vice-versa.
+ */
+function sess_regenerate() {
+  $old_session_id = session_id();
+
+  // We code around http://bugs.php.net/bug.php?id=32802 by destroying
+  // the session cookie by setting expiration in the past (a negative
+  // value).  This issue only arises in PHP versions before 4.4.0,
+  // regardless of the Drupal configuration.
+  // TODO: remove this when we require at least PHP 4.4.0
+  if (isset($_COOKIE[session_name()])) {
+    setcookie(session_name(), '', time() - 42000, '/');
+  }
+
+  session_regenerate_id();
+
+  db_query("UPDATE {sessions} SET sid = '%s' WHERE sid = '%s'", session_id(), $old_session_id);
+}
+
+/**
+ * Counts how many users have sessions. Can count either anonymous sessions, authenticated sessions, or both.
+ *
+ * @param int $timestamp
+ *   A Unix timestamp representing a point of time in the past.
+ *   The default is 0, which counts all existing sessions.
+ * @param int $anonymous
+ *   TRUE counts only anonymous users.
+ *   FALSE counts only authenticated users.
+ *   Any other value will return the count of both authenticated and anonymous users.
+ * @return  int
+ *   The number of users with sessions.
+ */
+function sess_count($timestamp = 0, $anonymous = true) {
+  $query = $anonymous ? ' AND uid = 0' : ' AND uid > 0';
+  return db_result(db_query('SELECT COUNT(sid) AS count FROM {sessions} WHERE timestamp >= %d'. $query, $timestamp));
+}
+
+/**
+ * Called by PHP session handling with the PHP session ID to end a user's session.
+ *
+ * @param  string $sid
+ *   the session id
+ */
+function sess_destroy_sid($sid) {
+  db_query("DELETE FROM {sessions} WHERE sid = '%s'", $sid);
+}
+
+/**
+ * End a specific user's session
+ *
+ * @param  string $uid
+ *   the user id
+ */
+function sess_destroy_uid($uid) {
+  db_query('DELETE FROM {sessions} WHERE uid = %d', $uid);
+}
+
+function sess_gc($lifetime) {
+  // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
+  // value. For example, if you want user sessions to stay in your database
+  // for three weeks before deleting them, you need to set gc_maxlifetime
+  // to '1814400'. At that value, only after a user doesn't log in after
+  // three weeks (1814400 seconds) will his/her session be removed.
+  db_query("DELETE FROM {sessions} WHERE timestamp < %d", time() - $lifetime);
+
+  return TRUE;
+}
+
+/**
+ * Determine whether to save session data of the current request.
+ *
+ * This function allows the caller to temporarily disable writing of session data,
+ * should the request end while performing potentially dangerous operations, such as
+ * manipulating the global $user object.  See http://drupal.org/node/218104 for usage
+ *
+ * @param $status
+ *   Disables writing of session data when FALSE, (re-)enables writing when TRUE.
+ * @return
+ *   FALSE if writing session data has been disabled. Otherwise, TRUE.
+ */
+function session_save_session($status = NULL) {
+  static $save_session = TRUE;
+  if (isset($status)) {
+    $save_session = $status;
+  }
+  return ($save_session);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/tablesort.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,200 @@
+<?php
+// $Id: tablesort.inc,v 1.47 2008/01/04 09:31:48 goba Exp $
+
+/**
+ * @file
+ * Functions to aid in the creation of sortable tables.
+ *
+ * All tables created with a call to theme('table') have the option of having
+ * column headers that the user can click on to sort the table by that column.
+ */
+
+/**
+ * Initialize the table sort context.
+ */
+function tablesort_init($header) {
+  $ts = tablesort_get_order($header);
+  $ts['sort'] = tablesort_get_sort($header);
+  $ts['query_string'] = tablesort_get_querystring();
+  return $ts;
+}
+
+/**
+ * Create an SQL sort clause.
+ *
+ * This function produces the ORDER BY clause to insert in your SQL queries,
+ * assuring that the returned database table rows match the sort order chosen
+ * by the user.
+ *
+ * @param $header
+ *   An array of column headers in the format described in theme_table().
+ * @param $before
+ *   An SQL string to insert after ORDER BY and before the table sorting code.
+ *   Useful for sorting by important attributes like "sticky" first.
+ * @return
+ *   An SQL string to append to the end of a query.
+ *
+ * @ingroup database
+ */
+function tablesort_sql($header, $before = '') {
+  $ts = tablesort_init($header);
+  if ($ts['sql']) {
+    // Based on code from db_escape_table(), but this can also contain a dot.
+    $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
+
+    // Sort order can only be ASC or DESC.
+    $sort = drupal_strtoupper($ts['sort']);
+    $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
+
+    return " ORDER BY $before $field $sort";
+  }
+}
+
+/**
+ * Format a column header.
+ *
+ * If the cell in question is the column header for the current sort criterion,
+ * it gets special formatting. All possible sort criteria become links.
+ *
+ * @param $cell
+ *   The cell to format.
+ * @param $header
+ *   An array of column headers in the format described in theme_table().
+ * @param $ts
+ *   The current table sort context as returned from tablesort_init().
+ * @return
+ *   A properly formatted cell, ready for _theme_table_cell().
+ */
+function tablesort_header($cell, $header, $ts) {
+  // Special formatting for the currently sorted column header.
+  if (is_array($cell) && isset($cell['field'])) {
+    $title = t('sort by @s', array('@s' => $cell['data']));
+    if ($cell['data'] == $ts['name']) {
+      $ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
+      if (isset($cell['class'])) {
+        $cell['class'] .= ' active';
+      }
+      else {
+        $cell['class'] = 'active';
+      }
+      $image = theme('tablesort_indicator', $ts['sort']);
+    }
+    else {
+      // If the user clicks a different header, we want to sort ascending initially.
+      $ts['sort'] = 'asc';
+      $image = '';
+    }
+
+    if (!empty($ts['query_string'])) {
+      $ts['query_string'] = '&'. $ts['query_string'];
+    }
+    $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => 'sort='. $ts['sort'] .'&order='. urlencode($cell['data']) . $ts['query_string'], 'html' => TRUE));
+
+    unset($cell['field'], $cell['sort']);
+  }
+  return $cell;
+}
+
+/**
+ * Format a table cell.
+ *
+ * Adds a class attribute to all cells in the currently active column.
+ *
+ * @param $cell
+ *   The cell to format.
+ * @param $header
+ *   An array of column headers in the format described in theme_table().
+ * @param $ts
+ *   The current table sort context as returned from tablesort_init().
+ * @param $i
+ *   The index of the cell's table column.
+ * @return
+ *   A properly formatted cell, ready for _theme_table_cell().
+ */
+function tablesort_cell($cell, $header, $ts, $i) {
+  if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) {
+    if (is_array($cell)) {
+      if (isset($cell['class'])) {
+        $cell['class'] .= ' active';
+      }
+      else {
+        $cell['class'] = 'active';
+      }
+    }
+    else {
+      $cell = array('data' => $cell, 'class' => 'active');
+    }
+  }
+  return $cell;
+}
+
+/**
+ * Compose a query string to append to table sorting requests.
+ *
+ * @return
+ *   A query string that consists of all components of the current page request
+ *   except for those pertaining to table sorting.
+ */
+function tablesort_get_querystring() {
+  return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE)));
+}
+
+/**
+ * Determine the current sort criterion.
+ *
+ * @param $headers
+ *   An array of column headers in the format described in theme_table().
+ * @return
+ *   An associative array describing the criterion, containing the keys:
+ *   - "name": The localized title of the table column.
+ *   - "sql": The name of the database field to sort on.
+ */
+function tablesort_get_order($headers) {
+  $order = isset($_GET['order']) ? $_GET['order'] : '';
+  foreach ($headers as $header) {
+    if (isset($header['data']) && $order == $header['data']) {
+      return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
+    }
+
+    if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
+      $default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
+    }
+  }
+
+  if (isset($default)) {
+    return $default;
+  }
+  else {
+    // The first column specified is initial 'order by' field unless otherwise specified
+    if (is_array($headers[0])) {
+      $headers[0] += array('data' => NULL, 'field' => NULL);
+      return array('name' => $headers[0]['data'], 'sql' => $headers[0]['field']);
+    }
+    else {
+      return array('name' => $headers[0]);
+    }
+  }
+}
+
+/**
+ * Determine the current sort direction.
+ *
+ * @param $headers
+ *   An array of column headers in the format described in theme_table().
+ * @return
+ *   The current sort direction ("asc" or "desc").
+ */
+function tablesort_get_sort($headers) {
+  if (isset($_GET['sort'])) {
+    return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
+  }
+  // User has not specified a sort. Use default if specified; otherwise use "asc".
+  else {
+    foreach ($headers as $header) {
+      if (is_array($header) && array_key_exists('sort', $header)) {
+        return $header['sort'];
+      }
+    }
+  }
+  return 'asc';
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/theme.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1922 @@
+<?php
+// $Id: theme.inc,v 1.415 2008/01/27 19:47:06 goba Exp $
+
+/**
+ * @file
+ * The theme system, which controls the output of Drupal.
+ *
+ * The theme system allows for nearly all output of the Drupal system to be
+ * customized by user themes.
+ *
+ * @see <a href="http://drupal.org/node/253">Theme system</a>
+ * @see themeable
+ */
+
+/**
+ * @name Content markers
+ * @{
+ * Markers used by theme_mark() and node_mark() to designate content.
+ * @see theme_mark(), node_mark()
+ */
+define('MARK_READ',    0);
+define('MARK_NEW',     1);
+define('MARK_UPDATED', 2);
+/**
+ * @} End of "Content markers".
+ */
+
+/**
+ * Initialize the theme system by loading the theme.
+ */
+function init_theme() {
+  global $theme, $user, $custom_theme, $theme_key;
+
+  // If $theme is already set, assume the others are set, too, and do nothing
+  if (isset($theme)) {
+    return;
+  }
+
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
+  $themes = list_themes();
+
+  // Only select the user selected theme if it is available in the
+  // list of enabled themes.
+  $theme = !empty($user->theme) && !empty($themes[$user->theme]->status) ? $user->theme : variable_get('theme_default', 'garland');
+
+  // Allow modules to override the present theme... only select custom theme
+  // if it is available in the list of installed themes.
+  $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme;
+
+  // Store the identifier for retrieving theme settings with.
+  $theme_key = $theme;
+
+  // Find all our ancestor themes and put them in an array.
+  $base_theme = array();
+  $ancestor = $theme;
+  while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+    $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme];
+    $ancestor = $themes[$ancestor]->base_theme;
+  }
+  _init_theme($themes[$theme], array_reverse($base_theme));
+}
+
+/**
+ * Initialize the theme system given already loaded information. This
+ * function is useful to initialize a theme when no database is present.
+ *
+ * @param $theme
+ *   An object with the following information:
+ *     filename
+ *       The .info file for this theme. The 'path' to
+ *       the theme will be in this file's directory. (Required)
+ *     owner
+ *       The path to the .theme file or the .engine file to load for
+ *       the theme. (Required)
+ *     stylesheet
+ *       The primary stylesheet for the theme. (Optional)
+ *     engine
+ *       The name of theme engine to use. (Optional)
+ * @param $base_theme
+ *    An optional array of objects that represent the 'base theme' if the
+ *    theme is meant to be derivative of another theme. It requires
+ *    the same information as the $theme object. It should be in
+ *    'oldest first' order, meaning the top level of the chain will
+ *    be first.
+ * @param $registry_callback
+ *   The callback to invoke to set the theme registry.
+ */
+function _init_theme($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {
+  global $theme_info, $base_theme_info, $theme_engine, $theme_path;
+  $theme_info = $theme;
+  $base_theme_info = $base_theme;
+
+  $theme_path = dirname($theme->filename);
+
+  // Prepare stylesheets from this theme as well as all ancestor themes.
+  // We work it this way so that we can have child themes override parent
+  // theme stylesheets easily.
+  $final_stylesheets = array();
+
+  // Grab stylesheets from base theme
+  foreach ($base_theme as $base) {
+    if (!empty($base->stylesheets)) {
+      foreach ($base->stylesheets as $media => $stylesheets) {
+        foreach ($stylesheets as $name => $stylesheet) {
+          $final_stylesheets[$media][$name] = $stylesheet;
+        }
+      }
+    }
+  }
+
+  // Add stylesheets used by this theme.
+  if (!empty($theme->stylesheets)) {
+    foreach ($theme->stylesheets as $media => $stylesheets) {
+      foreach ($stylesheets as $name => $stylesheet) {
+        $final_stylesheets[$media][$name] = $stylesheet;
+      }
+    }
+  }
+
+  // And now add the stylesheets properly
+  foreach ($final_stylesheets as $media => $stylesheets) {
+    foreach ($stylesheets as $stylesheet) {
+      drupal_add_css($stylesheet, 'theme', $media);
+    }
+  }
+
+  // Do basically the same as the above for scripts
+  $final_scripts = array();
+
+  // Grab scripts from base theme
+  foreach ($base_theme as $base) {
+    if (!empty($base->scripts)) {
+      foreach ($base->scripts as $name => $script) {
+        $final_scripts[$name] = $script;
+      }
+    }
+  }
+
+  // Add scripts used by this theme.
+  if (!empty($theme->scripts)) {
+    foreach ($theme->scripts as $name => $script) {
+      $final_scripts[$name] = $script;
+    }
+  }
+
+  // Add scripts used by this theme.
+  foreach ($final_scripts as $script) {
+    drupal_add_js($script, 'theme');
+  }
+
+  $theme_engine = NULL;
+
+  // Initialize the theme.
+  if (isset($theme->engine)) {
+    // Include the engine.
+    include_once './'. $theme->owner;
+
+    $theme_engine = $theme->engine;
+    if (function_exists($theme_engine .'_init')) {
+      foreach ($base_theme as $base) {
+        call_user_func($theme_engine .'_init', $base);
+      }
+      call_user_func($theme_engine .'_init', $theme);
+    }
+  }
+  else {
+    // include non-engine theme files
+    foreach ($base_theme as $base) {
+      // Include the theme file or the engine.
+      if (!empty($base->owner)) {
+        include_once './'. $base->owner;
+      }
+    }
+    // and our theme gets one too.
+    if (!empty($theme->owner)) {
+      include_once './'. $theme->owner;
+    }
+  }
+
+  $registry_callback($theme, $base_theme, $theme_engine);
+}
+
+/**
+ * Retrieve the stored theme registry. If the theme registry is already
+ * in memory it will be returned; otherwise it will attempt to load the
+ * registry from cache. If this fails, it will construct the registry and
+ * cache it.
+ */
+function theme_get_registry($registry = NULL) {
+  static $theme_registry = NULL;
+  if (isset($registry)) {
+    $theme_registry = $registry;
+  }
+
+  return $theme_registry;
+}
+
+/**
+ * Store the theme registry in memory.
+ */
+function _theme_set_registry($registry) {
+  // Pass through for setting of static variable.
+  return theme_get_registry($registry);
+}
+
+/**
+ * Get the theme_registry cache from the database; if it doesn't exist, build
+ * it.
+ *
+ * @param $theme
+ *   The loaded $theme object.
+ * @param $base_theme
+ *   An array of loaded $theme objects representing the ancestor themes in
+ *   oldest first order.
+ * @param theme_engine
+ *   The name of the theme engine.
+ */
+function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
+  // Check the theme registry cache; if it exists, use it.
+  $cache = cache_get("theme_registry:$theme->name", 'cache');
+  if (isset($cache->data)) {
+    $registry = $cache->data;
+  }
+  else {
+    // If not, build one and cache it.
+    $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
+    _theme_save_registry($theme, $registry);
+  }
+  _theme_set_registry($registry);
+}
+
+/**
+ * Write the theme_registry cache into the database.
+ */
+function _theme_save_registry($theme, $registry) {
+  cache_set("theme_registry:$theme->name", $registry);
+}
+
+/**
+ * Force the system to rebuild the theme registry; this should be called
+ * when modules are added to the system, or when a dynamic system needs
+ * to add more theme hooks.
+ */
+function drupal_rebuild_theme_registry() {
+  cache_clear_all('theme_registry', 'cache', TRUE);
+}
+
+/**
+ * Process a single invocation of the theme hook. $type will be one
+ * of 'module', 'theme_engine' or 'theme' and it tells us some
+ * important information.
+ *
+ * Because $cache is a reference, the cache will be continually
+ * expanded upon; new entries will replace old entries in the
+ * array_merge, but we are careful to ensure some data is carried
+ * forward, such as the arguments a theme hook needs.
+ *
+ * An override flag can be set for preprocess functions. When detected the
+ * cached preprocessors for the hook will not be merged with the newly set.
+ * This can be useful to themes and theme engines by giving them more control
+ * over how and when the preprocess functions are run.
+ */
+function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
+  $function = $name .'_theme';
+  if (function_exists($function)) {
+    $result = $function($cache, $type, $theme, $path);
+
+    foreach ($result as $hook => $info) {
+      $result[$hook]['type'] = $type;
+      $result[$hook]['theme path'] = $path;
+      // if function and file are left out, default to standard naming
+      // conventions.
+      if (!isset($info['template']) && !isset($info['function'])) {
+        $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook;
+      }
+      // If a path is set in the info, use what was set. Otherwise use the
+      // default path. This is mostly so system.module can declare theme
+      // functions on behalf of core .include files.
+      // All files are included to be safe. Conditionally included
+      // files can prevent them from getting registered.
+      if (isset($info['file']) && !isset($info['path'])) {
+        $result[$hook]['file'] = $path .'/'. $info['file'];
+        include_once($result[$hook]['file']);
+      }
+      elseif (isset($info['file']) && isset($info['path'])) {
+        include_once($info['path'] .'/'. $info['file']);
+      }
+
+      if (isset($info['template']) && !isset($info['path'])) {
+        $result[$hook]['template'] = $path .'/'. $info['template'];
+      }
+      // If 'arguments' have been defined previously, carry them forward.
+      // This should happen if a theme overrides a Drupal defined theme
+      // function, for example.
+      if (!isset($info['arguments']) && isset($cache[$hook])) {
+        $result[$hook]['arguments'] = $cache[$hook]['arguments'];
+      }
+      // Likewise with theme paths. These are used for template naming suggestions.
+      // Theme implementations can occur in multiple paths. Suggestions should follow.
+      if (!isset($info['theme paths']) && isset($cache[$hook])) {
+        $result[$hook]['theme paths'] = $cache[$hook]['theme paths'];
+      }
+      // Check for sub-directories.
+      $result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path;
+
+      // Check for default _preprocess_ functions. Ensure arrayness.
+      if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
+        $info['preprocess functions'] = array();
+        $prefixes = array();
+        if ($type == 'module') {
+          // Default preprocessor prefix.
+          $prefixes[] = 'template';
+          // Add all modules so they can intervene with their own preprocessors. This allows them
+          // to provide preprocess functions even if they are not the owner of the current hook.
+          $prefixes += module_list();
+        }
+        elseif ($type == 'theme_engine') {
+          // Theme engines get an extra set that come before the normally named preprocessors.
+          $prefixes[] = $name .'_engine';
+          // The theme engine also registers on behalf of the theme. The theme or engine name can be used.
+          $prefixes[] = $name;
+          $prefixes[] = $theme;
+        }
+        else {
+          // This applies when the theme manually registers their own preprocessors.
+          $prefixes[] = $name;
+        }
+
+        foreach ($prefixes as $prefix) {
+          if (function_exists($prefix .'_preprocess')) {
+            $info['preprocess functions'][] = $prefix .'_preprocess';
+          }
+          if (function_exists($prefix .'_preprocess_'. $hook)) {
+            $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook;
+          }
+        }
+      }
+      // Check for the override flag and prevent the cached preprocess functions from being used.
+      // This allows themes or theme engines to remove preprocessors set earlier in the registry build.
+      if (!empty($info['override preprocess functions'])) {
+        // Flag not needed inside the registry.
+        unset($result[$hook]['override preprocess functions']);
+      }
+      elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
+        $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
+      }
+      $result[$hook]['preprocess functions'] = $info['preprocess functions'];
+    }
+
+    // Merge the newly created theme hooks into the existing cache.
+    $cache = array_merge($cache, $result);
+  }
+}
+
+/**
+ * Rebuild the hook theme_registry cache.
+ *
+ * @param $theme
+ *   The loaded $theme object.
+ * @param $base_theme
+ *   An array of loaded $theme objects representing the ancestor themes in
+ *   oldest first order.
+ * @param theme_engine
+ *   The name of the theme engine.
+ */
+function _theme_build_registry($theme, $base_theme, $theme_engine) {
+  $cache = array();
+  // First, process the theme hooks advertised by modules. This will
+  // serve as the basic registry.
+  foreach (module_implements('theme') as $module) {
+    _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
+  }
+
+  // Process each base theme.
+  foreach ($base_theme as $base) {
+    // If the theme uses a theme engine, process its hooks.
+    $base_path = dirname($base->filename);
+    if ($theme_engine) {
+      _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);
+    }
+    _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);
+  }
+
+  // And then the same thing, but for the theme.
+  if ($theme_engine) {
+    _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));
+  }
+
+  // Finally, hooks provided by the theme itself.
+  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
+
+  // Let modules alter the registry
+  drupal_alter('theme_registry', $cache);
+  return $cache;
+}
+
+/**
+ * Provides a list of currently available themes.
+ *
+ * If the database is active then it will be retrieved from the database.
+ * Otherwise it will retrieve a new list.
+ *
+ * @param $refresh
+ *   Whether to reload the list of themes from the database.
+ * @return
+ *   An array of the currently available themes.
+ */
+function list_themes($refresh = FALSE) {
+  static $list = array();
+
+  if ($refresh) {
+    $list = array();
+  }
+
+  if (empty($list)) {
+    $list = array();
+    $themes = array();
+    // Extract from the database only when it is available.
+    // Also check that the site is not in the middle of an install or update.
+    if (db_is_active() && !defined('MAINTENANCE_MODE')) {
+      $result = db_query("SELECT * FROM {system} WHERE type = '%s'", 'theme');
+      while ($theme = db_fetch_object($result)) {
+        if (file_exists($theme->filename)) {
+          $theme->info = unserialize($theme->info);
+          $themes[] = $theme;
+        }
+      }
+    }
+    else {
+      // Scan the installation when the database should not be read.
+      $themes = _system_theme_data();
+    }
+
+    foreach ($themes as $theme) {
+      foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
+        foreach ($stylesheets as $stylesheet => $path) {
+          if (file_exists($path)) {
+            $theme->stylesheets[$media][$stylesheet] = $path;
+          }
+        }
+      }
+      foreach ($theme->info['scripts'] as $script => $path) {
+        if (file_exists($path)) {
+          $theme->scripts[$script] = $path;
+        }
+      }
+      if (isset($theme->info['engine'])) {
+        $theme->engine = $theme->info['engine'];
+      }
+      if (isset($theme->info['base theme'])) {
+        $theme->base_theme = $theme->info['base theme'];
+      }
+      // Status is normally retrieved from the database. Add zero values when
+      // read from the installation directory to prevent notices.
+      if (!isset($theme->status)) {
+        $theme->status = 0;
+      }
+      $list[$theme->name] = $theme;
+    }
+  }
+
+  return $list;
+}
+
+/**
+ * Generate the themed output.
+ *
+ * All requests for theme hooks must go through this function. It examines
+ * the request and routes it to the appropriate theme function. The theme
+ * registry is checked to determine which implementation to use, which may
+ * be a function or a template.
+ *
+ * If the implementation is a function, it is executed and its return value
+ * passed along.
+ *
+ * If the implementation is a template, the arguments are converted to a
+ * $variables array. This array is then modified by the module implementing
+ * the hook, theme engine (if applicable) and the theme. The following
+ * functions may be used to modify the $variables array. They are processed in
+ * this order when available:
+ *
+ * - template_preprocess(&$variables)
+ *   This sets a default set of variables for all template implementations.
+ *
+ * - template_preprocess_HOOK(&$variables)
+ *   This is the first preprocessor called specific to the hook; it should be
+ *   implemented by the module that registers it.
+ *
+ * - MODULE_preprocess(&$variables)
+ *   This will be called for all templates; it should only be used if there
+ *   is a real need. It's purpose is similar to template_preprocess().
+ *
+ * - MODULE_preprocess_HOOK(&$variables)
+ *   This is for modules that want to alter or provide extra variables for
+ *   theming hooks not registered to itself. For example, if a module named
+ *   "foo" wanted to alter the $submitted variable for the hook "node" a
+ *   preprocess function of foo_preprocess_node() can be created to intercept
+ *   and alter the variable.
+ *
+ * - ENGINE_engine_preprocess(&$variables)
+ *   This function should only be implemented by theme engines and exists
+ *   so that it can set necessary variables for all hooks.
+ *
+ * - ENGINE_engine_preprocess_HOOK(&$variables)
+ *   This is the same as the previous function, but it is called for a single
+ *   theming hook.
+ *
+ * - ENGINE_preprocess(&$variables)
+ *   This is meant to be used by themes that utilize a theme engine. It is
+ *   provided so that the preprocessor is not locked into a specific theme.
+ *   This makes it easy to share and transport code but theme authors must be
+ *   careful to prevent fatal re-declaration errors when using sub-themes that
+ *   have their own preprocessor named exactly the same as its base theme. In
+ *   the default theme engine (PHPTemplate), sub-themes will load their own
+ *   template.php file in addition to the one used for its parent theme. This
+ *   increases the risk for these errors. A good practice is to use the engine
+ *   name for the base theme and the theme name for the sub-themes to minimize
+ *   this possibility.
+ *
+ * - ENGINE_preprocess_HOOK(&$variables)
+ *   The same applies from the previous function, but it is called for a
+ *   specific hook.
+ *
+ * - THEME_preprocess(&$variables)
+ *   These functions are based upon the raw theme; they should primarily be
+ *   used by themes that do not use an engine or by sub-themes. It serves the
+ *   same purpose as ENGINE_preprocess().
+ *
+ * - THEME_preprocess_HOOK(&$variables)
+ *   The same applies from the previous function, but it is called for a
+ *   specific hook.
+ *
+ * There are two special variables that these hooks can set:
+ *   'template_file' and 'template_files'. These will be merged together
+ *   to form a list of 'suggested' alternate template files to use, in
+ *   reverse order of priority. template_file will always be a higher
+ *   priority than items in template_files. theme() will then look for these
+ *   files, one at a time, and use the first one
+ *   that exists.
+ * @param $hook
+ *   The name of the theme function to call. May be an array, in which
+ *   case the first hook that actually has an implementation registered
+ *   will be used. This can be used to choose 'fallback' theme implementations,
+ *   so that if the specific theme hook isn't implemented anywhere, a more
+ *   generic one will be used. This can allow themes to create specific theme
+ *   implementations for named objects.
+ * @param ...
+ *   Additional arguments to pass along to the theme function.
+ * @return
+ *   An HTML string that generates the themed output.
+ */
+function theme() {
+  $args = func_get_args();
+  $hook = array_shift($args);
+
+  static $hooks = NULL;
+  if (!isset($hooks)) {
+    init_theme();
+    $hooks = theme_get_registry();
+  }
+
+  if (is_array($hook)) {
+    foreach ($hook as $candidate) {
+      if (isset($hooks[$candidate])) {
+        break;
+      }
+    }
+    $hook = $candidate;
+  }
+
+  if (!isset($hooks[$hook])) {
+    return;
+  }
+
+  $info = $hooks[$hook];
+  global $theme_path;
+  $temp = $theme_path;
+  // point path_to_theme() to the currently used theme path:
+  $theme_path = $hooks[$hook]['theme path'];
+
+  // Include a file if the theme function or preprocess function is held elsewhere.
+  if (!empty($info['file'])) {
+    $include_file = $info['file'];
+    if (isset($info['path'])) {
+      $include_file = $info['path'] .'/'. $include_file;
+    }
+    include_once($include_file);
+  }
+  if (isset($info['function'])) {
+    // The theme call is a function.
+    $output = call_user_func_array($info['function'], $args);
+  }
+  else {
+    // The theme call is a template.
+    $variables = array(
+      'template_files' => array()
+    );
+    if (!empty($info['arguments'])) {
+      $count = 0;
+      foreach ($info['arguments'] as $name => $default) {
+        $variables[$name] = isset($args[$count]) ? $args[$count] : $default;
+        $count++;
+      }
+    }
+
+    // default render function and extension.
+    $render_function = 'theme_render_template';
+    $extension = '.tpl.php';
+
+    // Run through the theme engine variables, if necessary
+    global $theme_engine;
+    if (isset($theme_engine)) {
+      // If theme or theme engine is implementing this, it may have
+      // a different extension and a different renderer.
+      if ($hooks[$hook]['type'] != 'module') {
+        if (function_exists($theme_engine .'_render_template')) {
+          $render_function = $theme_engine .'_render_template';
+        }
+        $extension_function = $theme_engine .'_extension';
+        if (function_exists($extension_function)) {
+          $extension = $extension_function();
+        }
+      }
+    }
+
+    if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
+      // This construct ensures that we can keep a reference through
+      // call_user_func_array.
+      $args = array(&$variables, $hook);
+      foreach ($info['preprocess functions'] as $preprocess_function) {
+        if (function_exists($preprocess_function)) {
+          call_user_func_array($preprocess_function, $args);
+        }
+      }
+    }
+
+    // Get suggestions for alternate templates out of the variables
+    // that were set. This lets us dynamically choose a template
+    // from a list. The order is FILO, so this array is ordered from
+    // least appropriate first to most appropriate last.
+    $suggestions = array();
+
+    if (isset($variables['template_files'])) {
+      $suggestions = $variables['template_files'];
+    }
+    if (isset($variables['template_file'])) {
+      $suggestions[] = $variables['template_file'];
+    }
+
+    if ($suggestions) {
+      $template_file = drupal_discover_template($info['theme paths'], $suggestions, $extension);
+    }
+
+    if (empty($template_file)) {
+      $template_file = $hooks[$hook]['template'] . $extension;
+      if (isset($hooks[$hook]['path'])) {
+        $template_file = $hooks[$hook]['path'] .'/'. $template_file;
+      }
+    }
+    $output = $render_function($template_file, $variables);
+  }
+  // restore path_to_theme()
+  $theme_path = $temp;
+  return $output;
+}
+
+/**
+ * Choose which template file to actually render. These are all suggested
+ * templates from themes and modules. Theming implementations can occur on
+ * multiple levels. All paths are checked to account for this.
+ */
+function drupal_discover_template($paths, $suggestions, $extension = '.tpl.php') {
+  global $theme_engine;
+
+  // Loop through all paths and suggestions in FIFO order.
+  $suggestions = array_reverse($suggestions);
+  $paths = array_reverse($paths);
+  foreach ($suggestions as $suggestion) {
+    if (!empty($suggestion)) {
+      foreach ($paths as $path) {
+        if (file_exists($file = $path .'/'. $suggestion . $extension)) {
+          return $file;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Return the path to the currently selected theme.
+ */
+function path_to_theme() {
+  global $theme_path;
+
+  if (!isset($theme_path)) {
+    init_theme();
+  }
+
+  return $theme_path;
+}
+
+/**
+ * Find overridden theme functions. Called by themes and/or theme engines to
+ * easily discover theme functions.
+ *
+ * @param $cache
+ *   The existing cache of theme hooks to test against.
+ * @param $prefixes
+ *   An array of prefixes to test, in reverse order of importance.
+ *
+ * @return $templates
+ *   The functions found, suitable for returning from hook_theme;
+ */
+function drupal_find_theme_functions($cache, $prefixes) {
+  $templates = array();
+  $functions = get_defined_functions();
+
+  foreach ($cache as $hook => $info) {
+    foreach ($prefixes as $prefix) {
+      if (!empty($info['pattern'])) {
+        $matches = preg_grep('/^'. $prefix .'_'. $info['pattern'] .'/', $functions['user']);
+        if ($matches) {
+          foreach ($matches as $match) {
+            $new_hook = str_replace($prefix .'_', '', $match);
+            $templates[$new_hook] = array(
+              'function' => $match,
+              'arguments' => $info['arguments'],
+            );
+          }
+        }
+      }
+      if (function_exists($prefix .'_'. $hook)) {
+        $templates[$hook] = array(
+          'function' => $prefix .'_'. $hook,
+        );
+      }
+    }
+  }
+
+  return $templates;
+}
+
+/**
+ * Find overridden theme templates. Called by themes and/or theme engines to
+ * easily discover templates.
+ *
+ * @param $cache
+ *   The existing cache of theme hooks to test against.
+ * @param $extension
+ *   The extension that these templates will have.
+ * @param $path
+ *   The path to search.
+ */
+function drupal_find_theme_templates($cache, $extension, $path) {
+  $templates = array();
+
+  // Collect paths to all sub-themes grouped by base themes. These will be
+  // used for filtering. This allows base themes to have sub-themes in its
+  // folder hierarchy without affecting the base themes template discovery.
+  $theme_paths = array();
+  foreach (list_themes() as $theme_info) {
+    if (!empty($theme_info->base_theme)) {
+      $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename);
+    }
+  }
+  foreach ($theme_paths as $basetheme => $subthemes) {
+    foreach ($subthemes as $subtheme => $subtheme_path) {
+      if (isset($theme_paths[$subtheme])) {
+        $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]);
+      }
+    }
+  }
+  global $theme;
+  $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array();
+
+  // Escape the periods in the extension.
+  $regex = str_replace('.', '\.', $extension) .'$';
+  // Because drupal_system_listing works the way it does, we check for real
+  // templates separately from checking for patterns.
+  $files = drupal_system_listing($regex, $path, 'name', 0);
+  foreach ($files as $template => $file) {
+    // Ignore sub-theme templates for the current theme.
+    if (strpos($file->filename, str_replace($subtheme_paths, '', $file->filename)) !== 0) {
+      continue;
+    }
+    // Chop off the remaining extensions if there are any. $template already
+    // has the rightmost extension removed, but there might still be more,
+    // such as with .tpl.php, which still has .tpl in $template at this point.
+    if (($pos = strpos($template, '.')) !== FALSE) {
+      $template = substr($template, 0, $pos);
+    }
+    // Transform - in filenames to _ to match function naming scheme
+    // for the purposes of searching.
+    $hook = strtr($template, '-', '_');
+    if (isset($cache[$hook])) {
+      $templates[$hook] = array(
+        'template' => $template,
+        'path' => dirname($file->filename),
+      );
+    }
+  }
+
+  $patterns = array_keys($files);
+
+  foreach ($cache as $hook => $info) {
+    if (!empty($info['pattern'])) {
+      // Transform _ in pattern to - to match file naming scheme
+      // for the purposes of searching.
+      $pattern = strtr($info['pattern'], '_', '-');
+
+      $matches = preg_grep('/^'. $pattern .'/', $patterns);
+      if ($matches) {
+        foreach ($matches as $match) {
+          $file = substr($match, 0, strpos($match, '.'));
+          // Put the underscores back in for the hook name and register this pattern.
+          $templates[strtr($file, '-', '_')] = array(
+            'template' => $file,
+            'path' => dirname($files[$match]->filename),
+            'arguments' => $info['arguments'],
+          );
+        }
+      }
+    }
+  }
+  return $templates;
+}
+
+/**
+ * Retrieve an associative array containing the settings for a theme.
+ *
+ * The final settings are arrived at by merging the default settings,
+ * the site-wide settings, and the settings defined for the specific theme.
+ * If no $key was specified, only the site-wide theme defaults are retrieved.
+ *
+ * The default values for each of settings are also defined in this function.
+ * To add new settings, add their default values here, and then add form elements
+ * to system_theme_settings() in system.module.
+ *
+ * @param $key
+ *  The template/style value for a given theme.
+ *
+ * @return
+ *   An associative array containing theme settings.
+ */
+function theme_get_settings($key = NULL) {
+  $defaults = array(
+    'mission'                       =>  '',
+    'default_logo'                  =>  1,
+    'logo_path'                     =>  '',
+    'default_favicon'               =>  1,
+    'favicon_path'                  =>  '',
+    'primary_links'                 =>  1,
+    'secondary_links'               =>  1,
+    'toggle_logo'                   =>  1,
+    'toggle_favicon'                =>  1,
+    'toggle_name'                   =>  1,
+    'toggle_search'                 =>  1,
+    'toggle_slogan'                 =>  0,
+    'toggle_mission'                =>  1,
+    'toggle_node_user_picture'      =>  0,
+    'toggle_comment_user_picture'   =>  0,
+    'toggle_primary_links'          =>  1,
+    'toggle_secondary_links'        =>  1,
+  );
+
+  if (module_exists('node')) {
+    foreach (node_get_types() as $type => $name) {
+      $defaults['toggle_node_info_'. $type] = 1;
+    }
+  }
+  $settings = array_merge($defaults, variable_get('theme_settings', array()));
+
+  if ($key) {
+    $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array()));
+  }
+
+  // Only offer search box if search.module is enabled.
+  if (!module_exists('search') || !user_access('search content')) {
+    $settings['toggle_search'] = 0;
+  }
+
+  return $settings;
+}
+
+/**
+ * Retrieve a setting for the current theme.
+ * This function is designed for use from within themes & engines
+ * to determine theme settings made in the admin interface.
+ *
+ * Caches values for speed (use $refresh = TRUE to refresh cache)
+ *
+ * @param $setting_name
+ *  The name of the setting to be retrieved.
+ *
+ * @param $refresh
+ *  Whether to reload the cache of settings.
+ *
+ * @return
+ *   The value of the requested setting, NULL if the setting does not exist.
+ */
+function theme_get_setting($setting_name, $refresh = FALSE) {
+  global $theme_key;
+  static $settings;
+
+  if (empty($settings) || $refresh) {
+    $settings = theme_get_settings($theme_key);
+
+    $themes = list_themes();
+    $theme_object = $themes[$theme_key];
+
+    if ($settings['mission'] == '') {
+      $settings['mission'] = variable_get('site_mission', '');
+    }
+
+    if (!$settings['toggle_mission']) {
+      $settings['mission'] = '';
+    }
+
+    if ($settings['toggle_logo']) {
+      if ($settings['default_logo']) {
+        $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
+      }
+      elseif ($settings['logo_path']) {
+        $settings['logo'] = base_path() . $settings['logo_path'];
+      }
+    }
+
+    if ($settings['toggle_favicon']) {
+      if ($settings['default_favicon']) {
+        if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) {
+          $settings['favicon'] = base_path() . $favicon;
+        }
+        else {
+          $settings['favicon'] = base_path() .'misc/favicon.ico';
+        }
+      }
+      elseif ($settings['favicon_path']) {
+        $settings['favicon'] = base_path() . $settings['favicon_path'];
+      }
+      else {
+        $settings['toggle_favicon'] = FALSE;
+      }
+    }
+  }
+
+  return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
+}
+
+/**
+ * Render a system default template, which is essentially a PHP template.
+ *
+ * @param $file
+ *   The filename of the template to render.
+ * @param $variables
+ *   A keyed array of variables that will appear in the output.
+ *
+ * @return
+ *   The output generated by the template.
+ */
+function theme_render_template($file, $variables) {
+  extract($variables, EXTR_SKIP);  // Extract the variables to a local namespace
+  ob_start();                      // Start output buffering
+  include "./$file";               // Include the file
+  $contents = ob_get_contents();   // Get the contents of the buffer
+  ob_end_clean();                  // End buffering and discard
+  return $contents;                // Return the contents
+}
+
+/**
+ * @defgroup themeable Default theme implementations
+ * @{
+ * Functions and templates that present output to the user, and can be
+ * implemented by themes.
+ *
+ * Drupal's presentation layer is a pluggable system known as the theme
+ * layer. Each theme can take control over most of Drupal's output, and
+ * has complete control over the CSS.
+ *
+ * Inside Drupal, the theme layer is utilized by the use of the theme()
+ * function, which is passed the name of a component (the theme hook)
+ * and several arguments. For example, theme('table', $header, $rows);
+ * Additionally, the theme() function can take an array of theme
+ * hooks, which can be used to provide 'fallback' implementations to
+ * allow for more specific control of output. For example, the function:
+ * theme(array('table__foo', 'table'), $header, $rows) would look to see if
+ * 'table__foo' is registered anywhere; if it is not, it would 'fall back'
+ * to the generic 'table' implementation. This can be used to attach specific
+ * theme functions to named objects, allowing the themer more control over
+ * specific types of output.
+ *
+ * As of Drupal 6, every theme hook is required to be registered by the
+ * module that owns it, so that Drupal can tell what to do with it and
+ * to make it simple for themes to identify and override the behavior
+ * for these calls.
+ *
+ * The theme hooks are registered via hook_theme(), which returns an
+ * array of arrays with information about the hook. It describes the
+ * arguments the function or template will need, and provides
+ * defaults for the template in case they are not filled in. If the default
+ * implementation is a function, by convention it is named theme_HOOK().
+ *
+ * Each module should provide a default implementation for themes that
+ * it registers. This implementation may be either a function or a template;
+ * if it is a function it must be specified via hook_theme(). By convention,
+ * default implementations of theme hooks are named theme_HOOK. Default
+ * template implementations are stored in the module directory.
+ *
+ * Drupal's default template renderer is a simple PHP parsing engine that
+ * includes the template and stores the output. Drupal's theme engines
+ * can provide alternate template engines, such as XTemplate, Smarty and
+ * PHPTal. The most common template engine is PHPTemplate (included with
+ * Drupal and implemented in phptemplate.engine, which uses Drupal's default
+ * template renderer.
+ *
+ * In order to create theme-specific implementations of these hooks,
+ * themes can implement their own version of theme hooks, either as functions
+ * or templates. These implementations will be used instead of the default
+ * implementation. If using a pure .theme without an engine, the .theme is
+ * required to implement its own version of hook_theme() to tell Drupal what
+ * it is implementing; themes utilizing an engine will have their well-named
+ * theming functions automatically registered for them. While this can vary
+ * based upon the theme engine, the standard set by phptemplate is that theme
+ * functions should be named either phptemplate_HOOK or THEMENAME_HOOK. For
+ * example, for Drupal's default theme (Garland) to implement the 'table' hook,
+ * the phptemplate.engine would find phptemplate_table() or garland_table().
+ * The ENGINE_HOOK() syntax is preferred, as this can be used by sub-themes
+ * (which are themes that share code but use different stylesheets).
+ *
+ * The theme system is described and defined in theme.inc.
+ *
+ * @see theme()
+ * @see hook_theme()
+ */
+
+/**
+ * Formats text for emphasized display in a placeholder inside a sentence.
+ * Used automatically by t().
+ *
+ * @param $text
+ *   The text to format (plain-text).
+ * @return
+ *   The formatted text (html).
+ */
+function theme_placeholder($text) {
+  return '<em>'. check_plain($text) .'</em>';
+}
+
+/**
+ * Return a themed set of status and/or error messages. The messages are grouped
+ * by type.
+ *
+ * @param $display
+ *   (optional) Set to 'status' or 'error' to display only messages of that type.
+ *
+ * @return
+ *   A string containing the messages.
+ */
+function theme_status_messages($display = NULL) {
+  $output = '';
+  foreach (drupal_get_messages($display) as $type => $messages) {
+    $output .= "<div class=\"messages $type\">\n";
+    if (count($messages) > 1) {
+      $output .= " <ul>\n";
+      foreach ($messages as $message) {
+        $output .= '  <li>'. $message ."</li>\n";
+      }
+      $output .= " </ul>\n";
+    }
+    else {
+      $output .= $messages[0];
+    }
+    $output .= "</div>\n";
+  }
+  return $output;
+}
+
+/**
+ * Return a themed set of links.
+ *
+ * @param $links
+ *   A keyed array of links to be themed.
+ * @param $attributes
+ *   A keyed array of attributes
+ * @return
+ *   A string containing an unordered list of links.
+ */
+function theme_links($links, $attributes = array('class' => 'links')) {
+  $output = '';
+
+  if (count($links) > 0) {
+    $output = '<ul'. drupal_attributes($attributes) .'>';
+
+    $num_links = count($links);
+    $i = 1;
+
+    foreach ($links as $key => $link) {
+      $class = $key;
+
+      // Add first, last and active classes to the list of links to help out themers.
+      if ($i == 1) {
+        $class .= ' first';
+      }
+      if ($i == $num_links) {
+        $class .= ' last';
+      }
+      if (isset($link['href']) && $link['href'] == $_GET['q']) {
+        $class .= ' active';
+      }
+      $output .= '<li class="'. $class .'">';
+
+      if (isset($link['href'])) {
+        // Pass in $link as $options, they share the same keys.
+        $output .= l($link['title'], $link['href'], $link);
+      }
+      else if (!empty($link['title'])) {
+        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
+        if (empty($link['html'])) {
+          $link['title'] = check_plain($link['title']);
+        }
+        $span_attributes = '';
+        if (isset($link['attributes'])) {
+          $span_attributes = drupal_attributes($link['attributes']);
+        }
+        $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
+      }
+
+      $i++;
+      $output .= "</li>\n";
+    }
+
+    $output .= '</ul>';
+  }
+
+  return $output;
+}
+
+/**
+ * Return a themed image.
+ *
+ * @param $path
+ *   Either the path of the image file (relative to base_path()) or a full URL.
+ * @param $alt
+ *   The alternative text for text-based browsers.
+ * @param $title
+ *   The title text is displayed when the image is hovered in some popular browsers.
+ * @param $attributes
+ *   Associative array of attributes to be placed in the img tag.
+ * @param $getsize
+ *   If set to TRUE, the image's dimension are fetched and added as width/height attributes.
+ * @return
+ *   A string containing the image tag.
+ */
+function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
+  if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
+    $attributes = drupal_attributes($attributes);
+    $url = (url($path) == $path) ? $path : (base_path() . $path);
+    return '<img src="'. check_url($url) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. (isset($image_attributes) ? $image_attributes : '') . $attributes .' />';
+  }
+}
+
+/**
+ * Return a themed breadcrumb trail.
+ *
+ * @param $breadcrumb
+ *   An array containing the breadcrumb links.
+ * @return a string containing the breadcrumb output.
+ */
+function theme_breadcrumb($breadcrumb) {
+  if (!empty($breadcrumb)) {
+    return '<div class="breadcrumb">'. implode(' » ', $breadcrumb) .'</div>';
+  }
+}
+
+/**
+ * Return a themed help message.
+ *
+ * @return a string containing the helptext for the current page.
+ */
+function theme_help() {
+  if ($help = menu_get_active_help()) {
+    return '<div class="help">'. $help .'</div>';
+  }
+}
+
+/**
+ * Return a themed submenu, typically displayed under the tabs.
+ *
+ * @param $links
+ *   An array of links.
+ */
+function theme_submenu($links) {
+  return '<div class="submenu">'. implode(' | ', $links) .'</div>';
+}
+
+/**
+ * Return a themed table.
+ *
+ * @param $header
+ *   An array containing the table headers. Each element of the array can be
+ *   either a localized string or an associative array with the following keys:
+ *   - "data": The localized title of the table column.
+ *   - "field": The database field represented in the table column (required if
+ *     user is to be able to sort on this column).
+ *   - "sort": A default sort order for this column ("asc" or "desc").
+ *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
+ * @param $rows
+ *   An array of table rows. Every row is an array of cells, or an associative
+ *   array with the following keys:
+ *   - "data": an array of cells
+ *   - Any HTML attributes, such as "class", to apply to the table row.
+ *
+ *   Each cell can be either a string or an associative array with the following keys:
+ *   - "data": The string to display in the table cell.
+ *   - "header": Indicates this cell is a header.
+ *   - Any HTML attributes, such as "colspan", to apply to the table cell.
+ *
+ *   Here's an example for $rows:
+ *   @verbatim
+ *   $rows = array(
+ *     // Simple row
+ *     array(
+ *       'Cell 1', 'Cell 2', 'Cell 3'
+ *     ),
+ *     // Row with attributes on the row and some of its cells.
+ *     array(
+ *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
+ *     )
+ *   );
+ *   @endverbatim
+ *
+ * @param $attributes
+ *   An array of HTML attributes to apply to the table tag.
+ * @param $caption
+ *   A localized string to use for the <caption> tag.
+ * @return
+ *   An HTML string representing the table.
+ */
+function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
+
+  // Add sticky headers, if applicable.
+  if (count($header)) {
+    drupal_add_js('misc/tableheader.js');
+    // Add 'sticky-enabled' class to the table to identify it for JS.
+    // This is needed to target tables constructed by this function.
+    $attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] .' sticky-enabled');
+  }
+
+  $output = '<table'. drupal_attributes($attributes) .">\n";
+
+  if (isset($caption)) {
+    $output .= '<caption>'. $caption ."</caption>\n";
+  }
+
+  // Format the table header:
+  if (count($header)) {
+    $ts = tablesort_init($header);
+    // HTML requires that the thead tag has tr tags in it follwed by tbody
+    // tags. Using ternary operator to check and see if we have any rows.
+    $output .= (count($rows) ? ' <thead><tr>' : ' <tr>');
+    foreach ($header as $cell) {
+      $cell = tablesort_header($cell, $header, $ts);
+      $output .= _theme_table_cell($cell, TRUE);
+    }
+    // Using ternary operator to close the tags based on whether or not there are rows
+    $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n");
+  }
+  else {
+    $ts = array();
+  }
+
+  // Format the table rows:
+  if (count($rows)) {
+    $output .= "<tbody>\n";
+    $flip = array('even' => 'odd', 'odd' => 'even');
+    $class = 'even';
+    foreach ($rows as $number => $row) {
+      $attributes = array();
+
+      // Check if we're dealing with a simple or complex row
+      if (isset($row['data'])) {
+        foreach ($row as $key => $value) {
+          if ($key == 'data') {
+            $cells = $value;
+          }
+          else {
+            $attributes[$key] = $value;
+          }
+        }
+      }
+      else {
+        $cells = $row;
+      }
+      if (count($cells)) {
+        // Add odd/even class
+        $class = $flip[$class];
+        if (isset($attributes['class'])) {
+          $attributes['class'] .= ' '. $class;
+        }
+        else {
+          $attributes['class'] = $class;
+        }
+
+        // Build row
+        $output .= ' <tr'. drupal_attributes($attributes) .'>';
+        $i = 0;
+        foreach ($cells as $cell) {
+          $cell = tablesort_cell($cell, $header, $ts, $i++);
+          $output .= _theme_table_cell($cell);
+        }
+        $output .= " </tr>\n";
+      }
+    }
+    $output .= "</tbody>\n";
+  }
+
+  $output .= "</table>\n";
+  return $output;
+}
+
+/**
+ * Returns a header cell for tables that have a select all functionality.
+ */
+function theme_table_select_header_cell() {
+  drupal_add_js('misc/tableselect.js');
+
+  return array('class' => 'select-all');
+}
+
+/**
+ * Return a themed sort icon.
+ *
+ * @param $style
+ *   Set to either asc or desc. This sets which icon to show.
+ * @return
+ *   A themed sort icon.
+ */
+function theme_tablesort_indicator($style) {
+  if ($style == "asc") {
+    return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending'));
+  }
+  else {
+    return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending'));
+  }
+}
+
+/**
+ * Return a themed box.
+ *
+ * @param $title
+ *   The subject of the box.
+ * @param $content
+ *   The content of the box.
+ * @param $region
+ *   The region in which the box is displayed.
+ * @return
+ *   A string containing the box output.
+ */
+function theme_box($title, $content, $region = 'main') {
+  $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>';
+  return $output;
+}
+
+/**
+ * Return a themed marker, useful for marking new or updated
+ * content.
+ *
+ * @param $type
+ *   Number representing the marker type to display
+ * @see MARK_NEW, MARK_UPDATED, MARK_READ
+ * @return
+ *   A string containing the marker.
+ */
+function theme_mark($type = MARK_NEW) {
+  global $user;
+  if ($user->uid) {
+    if ($type == MARK_NEW) {
+      return ' <span class="marker">'. t('new') .'</span>';
+    }
+    else if ($type == MARK_UPDATED) {
+      return ' <span class="marker">'. t('updated') .'</span>';
+    }
+  }
+}
+
+/**
+ * Return a themed list of items.
+ *
+ * @param $items
+ *   An array of items to be displayed in the list. If an item is a string,
+ *   then it is used as is. If an item is an array, then the "data" element of
+ *   the array is used as the contents of the list item. If an item is an array
+ *   with a "children" element, those children are displayed in a nested list.
+ *   All other elements are treated as attributes of the list item element.
+ * @param $title
+ *   The title of the list.
+ * @param $attributes
+ *   The attributes applied to the list element.
+ * @param $type
+ *   The type of list to return (e.g. "ul", "ol")
+ * @return
+ *   A string containing the list output.
+ */
+function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL) {
+  $output = '<div class="item-list">';
+  if (isset($title)) {
+    $output .= '<h3>'. $title .'</h3>';
+  }
+
+  if (!empty($items)) {
+    $output .= "<$type". drupal_attributes($attributes) .'>';
+    $num_items = count($items);
+    foreach ($items as $i => $item) {
+      $attributes = array();
+      $children = array();
+      if (is_array($item)) {
+        foreach ($item as $key => $value) {
+          if ($key == 'data') {
+            $data = $value;
+          }
+          elseif ($key == 'children') {
+            $children = $value;
+          }
+          else {
+            $attributes[$key] = $value;
+          }
+        }
+      }
+      else {
+        $data = $item;
+      }
+      if (count($children) > 0) {
+        $data .= theme_item_list($children, NULL, $type, $attributes); // Render nested list
+      }
+      if ($i == 0) {
+        $attributes['class'] = empty($attributes['class']) ? 'first' : ($attributes['class'] .' first');
+      }
+      if ($i == $num_items - 1) {
+        $attributes['class'] = empty($attributes['class']) ? 'last' : ($attributes['class'] .' last');
+      }
+      $output .= '<li'. drupal_attributes($attributes) .'>'. $data ."</li>\n";
+    }
+    $output .= "</$type>";
+  }
+  $output .= '</div>';
+  return $output;
+}
+
+/**
+ * Returns code that emits the 'more help'-link.
+ */
+function theme_more_help_link($url) {
+  return '<div class="more-help-link">'. t('[<a href="@link">more help...</a>]', array('@link' => check_url($url))) .'</div>';
+}
+
+/**
+ * Return code that emits an XML icon.
+ * 
+ * For most use cases, this function has been superseded by theme_feed_icon().
+ * 
+ * @see theme_feed_icon()
+ * @param $url
+ *   The url of the feed.
+ */
+function theme_xml_icon($url) {
+  if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) {
+    return '<a href="'. check_url($url) .'" class="xml-icon">'. $image .'</a>';
+  }
+}
+
+/**
+ * Return code that emits an feed icon.
+ *
+ * @param $url
+ *   The url of the feed.
+ * @param $title
+ *   A descriptive title of the feed.
+  */
+function theme_feed_icon($url, $title) {
+  if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), $title)) {
+    return '<a href="'. check_url($url) .'" class="feed-icon">'. $image .'</a>';
+  }
+}
+
+/**
+ * Returns code that emits the 'more' link used on blocks.
+ *
+ * @param $url
+ *   The url of the main page
+ * @param $title
+ *   A descriptive verb for the link, like 'Read more'
+ */
+function theme_more_link($url, $title) {
+  return '<div class="more-link">'. t('<a href="@link" title="@title">more</a>', array('@link' => check_url($url), '@title' => $title)) .'</div>';
+}
+
+/**
+ * Execute hook_footer() which is run at the end of the page right before the
+ * close of the body tag.
+ *
+ * @param $main (optional)
+ *   Whether the current page is the front page of the site.
+ * @return
+ *   A string containing the results of the hook_footer() calls.
+ */
+function theme_closure($main = 0) {
+  $footer = module_invoke_all('footer', $main);
+  return implode("\n", $footer) . drupal_get_js('footer');
+}
+
+/**
+ * Return a set of blocks available for the current user.
+ *
+ * @param $region
+ *   Which set of blocks to retrieve.
+ * @return
+ *   A string containing the themed blocks for this region.
+ */
+function theme_blocks($region) {
+  $output = '';
+
+  if ($list = block_list($region)) {
+    foreach ($list as $key => $block) {
+      // $key == <i>module</i>_<i>delta</i>
+      $output .= theme('block', $block);
+    }
+  }
+
+  // Add any content assigned to this region through drupal_set_content() calls.
+  $output .= drupal_get_content($region);
+
+  return $output;
+}
+
+/**
+ * Format a username.
+ *
+ * @param $object
+ *   The user object to format, usually returned from user_load().
+ * @return
+ *   A string containing an HTML link to the user's page if the passed object
+ *   suggests that this is a site user. Otherwise, only the username is returned.
+ */
+function theme_username($object) {
+
+  if ($object->uid && $object->name) {
+    // Shorten the name when it is too long or it will break many tables.
+    if (drupal_strlen($object->name) > 20) {
+      $name = drupal_substr($object->name, 0, 15) .'...';
+    }
+    else {
+      $name = $object->name;
+    }
+
+    if (user_access('access user profiles')) {
+      $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
+    }
+    else {
+      $output = check_plain($name);
+    }
+  }
+  else if ($object->name) {
+    // Sometimes modules display content composed by people who are
+    // not registered members of the site (e.g. mailing list or news
+    // aggregator modules). This clause enables modules to display
+    // the true author of the content.
+    if (!empty($object->homepage)) {
+      $output = l($object->name, $object->homepage, array('rel' => 'nofollow'));
+    }
+    else {
+      $output = check_plain($object->name);
+    }
+
+    $output .= ' ('. t('not verified') .')';
+  }
+  else {
+    $output = variable_get('anonymous', t('Anonymous'));
+  }
+
+  return $output;
+}
+
+/**
+ * Return a themed progress bar.
+ *
+ * @param $percent
+ *   The percentage of the progress.
+ * @param $message
+ *   A string containing information to be displayed.
+ * @return
+ *   A themed HTML string representing the progress bar.
+ */
+function theme_progress_bar($percent, $message) {
+  $output = '<div id="progress" class="progress">';
+  $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>';
+  $output .= '<div class="percentage">'. $percent .'%</div>';
+  $output .= '<div class="message">'. $message .'</div>';
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Create a standard indentation div. Used for drag and drop tables.
+ *
+ * @param $size
+ *   Optional. The number of indentations to create.
+ * @return
+ *   A string containing indentations.
+ */
+function theme_indentation($size = 1) {
+  $output = '';
+  for ($n = 0; $n < $size; $n++) {
+    $output .= '<div class="indentation">&nbsp;</div>';
+  }
+  return $output;
+}
+
+/**
+ * @} End of "defgroup themeable".
+ */
+
+function _theme_table_cell($cell, $header = FALSE) {
+  $attributes = '';
+
+  if (is_array($cell)) {
+    $data = isset($cell['data']) ? $cell['data'] : '';
+    $header |= isset($cell['header']);
+    unset($cell['data']);
+    unset($cell['header']);
+    $attributes = drupal_attributes($cell);
+  }
+  else {
+    $data = $cell;
+  }
+
+  if ($header) {
+    $output = "<th$attributes>$data</th>";
+  }
+  else {
+    $output = "<td$attributes>$data</td>";
+  }
+
+  return $output;
+}
+
+/**
+ * Adds a default set of helper variables for preprocess functions and
+ * templates. This comes in before any other preprocess function which makes
+ * it possible to be used in default theme implementations (non-overriden
+ * theme functions).
+ */
+function template_preprocess(&$variables, $hook) {
+  global $user;
+  static $count = array();
+
+  // Track run count for each hook to provide zebra striping.
+  // See "template_preprocess_block()" which provides the same feature specific to blocks.
+  $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1;
+  $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even';
+  $variables['id'] = $count[$hook]++;
+
+  // Tell all templates where they are located.
+  $variables['directory'] = path_to_theme();
+
+  // Set default variables that depend on the database.
+  $variables['is_admin']            = FALSE;
+  $variables['is_front']            = FALSE;
+  $variables['logged_in']           = FALSE;
+  if ($variables['db_is_active'] = db_is_active()  && !defined('MAINTENANCE_MODE')) {
+    // Check for administrators.
+    if (user_access('access administration pages')) {
+      $variables['is_admin'] = TRUE;
+    }
+    // Flag front page status.
+    $variables['is_front'] = drupal_is_front_page();
+    // Tell all templates by which kind of user they're viewed.
+    $variables['logged_in'] = ($user->uid > 0);
+    // Provide user object to all templates
+    $variables['user'] = $user;
+  }
+}
+
+/**
+ * Process variables for page.tpl.php
+ *
+ * Most themes utilize their own copy of page.tpl.php. The default is located
+ * inside "modules/system/page.tpl.php". Look in there for the full list of
+ * variables.
+ *
+ * Uses the arg() function to generate a series of page template suggestions
+ * based on the current path.
+ *
+ * Any changes to variables in this preprocessor should also be changed inside
+ * template_preprocess_maintenance_page() to keep all them consistent.
+ *
+ * The $variables array contains the following arguments:
+ * - $content
+ * - $show_blocks
+ *
+ * @see page.tpl.php
+ */
+function template_preprocess_page(&$variables) {
+  // Add favicon
+  if (theme_get_setting('toggle_favicon')) {
+    drupal_set_html_head('<link rel="shortcut icon" href="'. check_url(theme_get_setting('favicon')) .'" type="image/x-icon" />');
+  }
+
+  global $theme;
+  // Populate all block regions.
+  $regions = system_region_list($theme);
+  // Load all region content assigned via blocks.
+  foreach (array_keys($regions) as $region) {
+    // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE.
+    if (!(!$variables['show_blocks'] && ($region == 'left' || $region == 'right'))) {
+      $blocks = theme('blocks', $region);
+    }
+    else {
+      $blocks = '';
+    }
+    // Assign region to a region variable.
+    isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks;
+  }
+
+  // Set up layout variable.
+  $variables['layout'] = 'none';
+  if (!empty($variables['left'])) {
+    $variables['layout'] = 'left';
+  }
+  if (!empty($variables['right'])) {
+    $variables['layout'] = ($variables['layout'] == 'left') ? 'both' : 'right';
+  }
+
+  // Set mission when viewing the frontpage.
+  if (drupal_is_front_page()) {
+    $mission = filter_xss_admin(theme_get_setting('mission'));
+  }
+
+  // Construct page title
+  if (drupal_get_title()) {
+    $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal'));
+  }
+  else {
+    $head_title = array(variable_get('site_name', 'Drupal'));
+    if (variable_get('site_slogan', '')) {
+      $head_title[] = variable_get('site_slogan', '');
+    }
+  }
+  $variables['head_title']        = implode(' | ', $head_title);
+  $variables['base_path']         = base_path();
+  $variables['front_page']        = url();
+  $variables['breadcrumb']        = theme('breadcrumb', drupal_get_breadcrumb());
+  $variables['feed_icons']        = drupal_get_feeds();
+  $variables['footer_message']    = filter_xss_admin(variable_get('site_footer', FALSE));
+  $variables['head']              = drupal_get_html_head();
+  $variables['help']              = theme('help');
+  $variables['language']          = $GLOBALS['language'];
+  $variables['language']->dir     = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
+  $variables['logo']              = theme_get_setting('logo');
+  $variables['messages']          = $variables['show_messages'] ? theme('status_messages') : '';
+  $variables['mission']           = isset($mission) ? $mission : '';
+  $variables['primary_links']     = theme_get_setting('toggle_primary_links') ? menu_primary_links() : array();
+  $variables['secondary_links']   = theme_get_setting('toggle_secondary_links') ? menu_secondary_links() : array();
+  $variables['search_box']        = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : '');
+  $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
+  $variables['site_slogan']       = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
+  $variables['css']               = drupal_add_css();
+  $variables['styles']            = drupal_get_css();
+  $variables['scripts']           = drupal_get_js();
+  $variables['tabs']              = theme('menu_local_tasks');
+  $variables['title']             = drupal_get_title();
+  // Closure should be filled last.
+  $variables['closure']           = theme('closure');
+
+  if ($node = menu_get_object()) {
+    $variables['node'] = $node;
+  }
+
+  // Compile a list of classes that are going to be applied to the body element.
+  // This allows advanced theming based on context (home page, node of certain type, etc.).
+  $body_classes = array();
+  // Add a class that tells us whether we're on the front page or not.
+  $body_classes[] = $variables['is_front'] ? 'front' : 'not-front';
+  // Add a class that tells us whether the page is viewed by an authenticated user or not.
+  $body_classes[] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in';
+  // Add arg(0) to make it possible to theme the page depending on the current page
+  // type (e.g. node, admin, user, etc.). To avoid illegal characters in the class,
+  // we're removing everything disallowed. We are not using 'a-z' as that might leave
+  // in certain international characters (e.g. German umlauts).
+  $body_classes[] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', 'page-'. form_clean_id(drupal_strtolower(arg(0))));
+  // If on an individual node page, add the node type.
+  if (isset($variables['node']) && $variables['node']->type) {
+    $body_classes[] = 'node-type-'. form_clean_id($variables['node']->type);
+  }
+  // Add information about the number of sidebars.
+  if ($variables['layout'] == 'both') {
+    $body_classes[] = 'two-sidebars';
+  }
+  elseif ($variables['layout'] == 'none') {
+    $body_classes[] = 'no-sidebars';
+  }
+  else {
+    $body_classes[] = 'one-sidebar sidebar-'. $variables['layout'];
+  }
+  // Implode with spaces.
+  $variables['body_classes'] = implode(' ', $body_classes);
+
+  // Build a list of suggested template files in order of specificity. One
+  // suggestion is made for every element of the current path, though
+  // numeric elements are not carried to subsequent suggestions. For example,
+  // http://www.example.com/node/1/edit would result in the following
+  // suggestions:
+  //
+  // page-node-edit.tpl.php
+  // page-node-1.tpl.php
+  // page-node.tpl.php
+  // page.tpl.php
+  $i = 0;
+  $suggestion = 'page';
+  $suggestions = array();
+  while ($arg = arg($i++)) {
+    $suggestions[] = $suggestion .'-'. $arg;
+    if (!is_numeric($arg)) {
+      $suggestion .= '-'. $arg;
+    }
+  }
+  if (drupal_is_front_page()) {
+    $suggestions[] = 'page-front';
+  }
+
+  if ($suggestions) {
+    $variables['template_files'] = $suggestions;
+  }
+}
+
+/**
+ * Process variables for node.tpl.php
+ *
+ * Most themes utilize their own copy of node.tpl.php. The default is located
+ * inside "modules/node/node.tpl.php". Look in there for the full list of
+ * variables.
+ *
+ * The $variables array contains the following arguments:
+ * - $node
+ * - $teaser
+ * - $page
+ *
+ * @see node.tpl.php
+ */
+function template_preprocess_node(&$variables) {
+  $node = $variables['node'];
+  if (module_exists('taxonomy')) {
+    $variables['taxonomy'] = taxonomy_link('taxonomy terms', $node);
+  }
+  else {
+    $variables['taxonomy'] = array();
+  }
+
+  if ($variables['teaser'] && $node->teaser) {
+    $variables['content'] = $node->teaser;
+  }
+  elseif (isset($node->body)) {
+    $variables['content'] = $node->body;
+  }
+  else {
+    $variables['content'] = '';
+  }
+
+  $variables['date']      = format_date($node->created);
+  $variables['links']     = !empty($node->links) ? theme('links', $node->links, array('class' => 'links inline')) : '';
+  $variables['name']      = theme('username', $node);
+  $variables['node_url']  = url('node/'. $node->nid);
+  $variables['terms']     = theme('links', $variables['taxonomy'], array('class' => 'links inline'));
+  $variables['title']     = check_plain($node->title);
+
+  // Flatten the node object's member fields.
+  $variables = array_merge((array)$node, $variables);
+
+  // Display info only on certain node types.
+  if (theme_get_setting('toggle_node_info_'. $node->type)) {
+    $variables['submitted'] = theme('node_submitted', $node);
+    $variables['picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', $node) : '';
+  }
+  else {
+    $variables['submitted'] = '';
+    $variables['picture'] = '';
+  }
+  // Clean up name so there are no underscores.
+  $variables['template_files'][] = 'node-'. $node->type;
+}
+
+/**
+ * Process variables for block.tpl.php
+ *
+ * Prepare the values passed to the theme_block function to be passed
+ * into a pluggable template engine. Uses block properties to generate a
+ * series of template file suggestions. If none are found, the default
+ * block.tpl.php is used.
+ *
+ * Most themes utilize their own copy of block.tpl.php. The default is located
+ * inside "modules/system/block.tpl.php". Look in there for the full list of
+ * variables.
+ *
+ * The $variables array contains the following arguments:
+ * - $block
+ *
+ * @see block.tpl.php
+ */
+function template_preprocess_block(&$variables) {
+  static $block_counter = array();
+  // All blocks get an independent counter for each region.
+  if (!isset($block_counter[$variables['block']->region])) {
+    $block_counter[$variables['block']->region] = 1;
+  }
+  // Same with zebra striping.
+  $variables['block_zebra'] = ($block_counter[$variables['block']->region] % 2) ? 'odd' : 'even';
+  $variables['block_id'] = $block_counter[$variables['block']->region]++;
+
+  $variables['template_files'][] = 'block-'. $variables['block']->region;
+  $variables['template_files'][] = 'block-'. $variables['block']->module;
+  $variables['template_files'][] = 'block-'. $variables['block']->module .'-'. $variables['block']->delta;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/theme.maintenance.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,288 @@
+<?php
+// $Id: theme.maintenance.inc,v 1.10 2008/01/24 09:42:50 goba Exp $
+
+/**
+ * @file
+ * Theming for maintenance pages.
+ */
+
+/**
+ * Sets up the theming system for site installs, updates and when the site is
+ * in off-line mode. It also applies when the database is unavailable.
+ *
+ * Minnelli is always used for the initial install and update operations. In
+ * other cases, "settings.php" must have a "maintenance_theme" key set for the
+ * $conf variable in order to change the maintenance theme.
+ */
+function _drupal_maintenance_theme() {
+  global $theme, $theme_key;
+
+  // If $theme is already set, assume the others are set too, and do nothing.
+  if (isset($theme)) {
+    return;
+  }
+
+  require_once './includes/path.inc';
+  require_once './includes/theme.inc';
+  require_once './includes/common.inc';
+  require_once './includes/unicode.inc';
+  require_once './includes/file.inc';
+  require_once './includes/module.inc';
+  require_once './includes/database.inc';
+  unicode_check();
+
+  // Install and update pages are treated differently to prevent theming overrides.
+  if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
+    $theme = 'minnelli';
+  }
+  else {
+    // Load module basics (needed for hook invokes).
+    $module_list['system']['filename'] = 'modules/system/system.module';
+    $module_list['filter']['filename'] = 'modules/filter/filter.module';
+    module_list(TRUE, FALSE, FALSE, $module_list);
+    drupal_load('module', 'system');
+    drupal_load('module', 'filter');
+
+    $theme = variable_get('maintenance_theme', 'minnelli');
+  }
+
+  $themes = list_themes();
+
+  // Store the identifier for retrieving theme settings with.
+  $theme_key = $theme;
+
+  // Find all our ancestor themes and put them in an array.
+  $base_theme = array();
+  $ancestor = $theme;
+  while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+    $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme];
+    $ancestor = $themes[$ancestor]->base_theme;
+  }
+  _init_theme($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry');
+
+  // These are usually added from system_init() -except maintenance.css.
+  // When the database is inactive it's not called so we add it here.
+  drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
+  drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
+  drupal_add_css(drupal_get_path('module', 'system') .'/system-menus.css', 'module');
+  drupal_add_css(drupal_get_path('module', 'system') .'/maintenance.css', 'module');
+}
+
+/**
+ * This builds the registry when the site needs to bypass any database calls.
+ */
+function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
+  $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
+  _theme_set_registry($registry);
+}
+
+/**
+ * Return a themed list of maintenance tasks to perform.
+ *
+ * @ingroup themeable
+ */
+function theme_task_list($items, $active = NULL) {
+  $done = isset($items[$active]) || $active == NULL;
+  $output = '<ol class="task-list">';
+  foreach ($items as $k => $item) {
+    if ($active == $k) {
+      $class = 'active';
+      $done = false;
+    }
+    else {
+      $class = $done ? 'done' : '';
+    }
+    $output .= '<li class="'. $class .'">'. $item .'</li>';
+  }
+  $output .= '</ol>';
+  return $output;
+}
+
+/**
+ * Generate a themed installation page.
+ *
+ * Note: this function is not themeable.
+ *
+ * @param $content
+ *   The page content to show.
+ */
+function theme_install_page($content) {
+  drupal_set_header('Content-Type: text/html; charset=utf-8');
+
+  // Assign content.
+  $variables['content'] = $content;
+  // Delay setting the message variable so it can be processed below.
+  $variables['show_messages'] = FALSE;
+  // The maintenance preprocess function is recycled here.
+  template_preprocess_maintenance_page($variables);
+
+  // Special handling of error messages
+  $messages = drupal_set_message();
+  if (isset($messages['error'])) {
+    $title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process');
+    $variables['messages'] .= '<h3>'. $title .':</h3>';
+    $variables['messages'] .= theme('status_messages', 'error');
+    $variables['content'] .= '<p>'. st('Please check the error messages and <a href="!url">try again</a>.', array('!url' => request_uri())) .'</p>';
+  }
+  
+  // Special handling of warning messages
+  if (isset($messages['warning'])) {
+    $title = count($messages['warning']) > 1 ? st('The following installation warnings should be carefully reviewed') : st('The following installation warning should be carefully reviewed');
+    $variables['messages'] .= '<h4>'. $title .':</h4>';
+    $variables['messages'] .= theme('status_messages', 'warning');
+  }
+
+  // Special handling of status messages
+  if (isset($messages['status'])) {
+    $title = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored');
+    $variables['messages'] .= '<h4>'. $title .':</h4>';
+    $variables['messages'] .= theme('status_messages', 'status');
+  }
+
+  // This was called as a theme hook (not template), so we need to
+  // fix path_to_theme() for the template, to point at the actual
+  // theme rather than system module as owner of the hook.
+  global $theme_path;
+  $theme_path = 'themes/garland';
+
+  return theme_render_template('themes/garland/maintenance-page.tpl.php', $variables);
+}
+
+/**
+ * Generate a themed update page.
+ *
+ * Note: this function is not themeable.
+ *
+ * @param $content
+ *   The page content to show.
+ * @param $show_messages
+ *   Whether to output status and error messages.
+ *   FALSE can be useful to postpone the messages to a subsequent page.
+ */
+function theme_update_page($content, $show_messages = TRUE) {
+  // Set required headers.
+  drupal_set_header('Content-Type: text/html; charset=utf-8');
+
+  // Assign content and show message flag.
+  $variables['content'] = $content;
+  $variables['show_messages'] = $show_messages;
+  // The maintenance preprocess function is recycled here.
+  template_preprocess_maintenance_page($variables);
+
+  // Special handling of warning messages.
+  $messages = drupal_set_message();
+  if (isset($messages['warning'])) {
+    $title = count($messages['warning']) > 1 ? 'The following update warnings should be carefully reviewed before continuing' : 'The following update warning should be carefully reviewed before continuing';
+    $variables['messages'] .= '<h4>'. $title .':</h4>';
+    $variables['messages'] .= theme('status_messages', 'warning');
+  }
+
+  // This was called as a theme hook (not template), so we need to
+  // fix path_to_theme() for the template, to point at the actual
+  // theme rather than system module as owner of the hook.
+  global $theme_path;
+  $theme_path = 'themes/garland';
+
+  return theme_render_template('themes/garland/maintenance-page.tpl.php', $variables);
+}
+
+/**
+ * The variables generated here is a mirror of template_preprocess_page().
+ * This preprocessor will run it's course when theme_maintenance_page() is
+ * invoked. It is also used in theme_install_page() and theme_update_page() to
+ * keep all the variables consistent.
+ *
+ * An alternate template file of "maintenance-page-offline.tpl.php" can be
+ * used when the database is offline to hide errors and completely replace the
+ * content.
+ *
+ * The $variables array contains the following arguments:
+ * - $content
+ * - $show_blocks
+ *
+ * @see maintenance-page.tpl.php
+ */
+function template_preprocess_maintenance_page(&$variables) {
+  // Add favicon
+  if (theme_get_setting('toggle_favicon')) {
+    drupal_set_html_head('<link rel="shortcut icon" href="'. check_url(theme_get_setting('favicon')) .'" type="image/x-icon" />');
+  }
+
+  global $theme;
+  // Retrieve the theme data to list all available regions.
+  $theme_data = _system_theme_data();
+  $regions = $theme_data[$theme]->info['regions'];
+
+  // Get all region content set with drupal_set_content().
+  foreach (array_keys($regions) as $region) {
+    // Assign region to a region variable.
+    $region_content = drupal_get_content($region);
+    isset($variables[$region]) ? $variables[$region] .= $region_content : $variables[$region] = $region_content;
+  }
+
+  // Setup layout variable.
+  $variables['layout'] = 'none';
+  if (!empty($variables['left'])) {
+    $variables['layout'] = 'left';
+  }
+  if (!empty($variables['right'])) {
+    $variables['layout'] = ($variables['layout'] == 'left') ? 'both' : 'right';
+  }
+
+  // Construct page title
+  if (drupal_get_title()) {
+    $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal'));
+  }
+  else {
+    $head_title = array(variable_get('site_name', 'Drupal'));
+    if (variable_get('site_slogan', '')) {
+      $head_title[] = variable_get('site_slogan', '');
+    }
+  }
+  $variables['head_title']        = implode(' | ', $head_title);
+  $variables['base_path']         = base_path();
+  $variables['breadcrumb']        = '';
+  $variables['feed_icons']        = '';
+  $variables['footer_message']    = filter_xss_admin(variable_get('site_footer', FALSE));
+  $variables['head']              = drupal_get_html_head();
+  $variables['help']              = '';
+  $variables['language']          = $GLOBALS['language'];
+  $variables['language']->dir     = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
+  $variables['logo']              = theme_get_setting('logo');
+  $variables['messages']          = $variables['show_messages'] ? theme('status_messages') : '';
+  $variables['mission']           = '';
+  $variables['primary_links']     = array();
+  $variables['secondary_links']   = array();
+  $variables['search_box']        = '';
+  $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
+  $variables['site_slogan']       = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
+  $variables['css']               = drupal_add_css();
+  $variables['styles']            = drupal_get_css();
+  $variables['scripts']           = drupal_get_js();
+  $variables['tabs']              = '';
+  $variables['title']             = drupal_get_title();
+  $variables['closure']           = '';
+
+  // Compile a list of classes that are going to be applied to the body element.
+  $body_classes = array();
+  $body_classes[] = 'in-maintenance';
+  if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
+    $body_classes[] = 'db-offline';
+  }
+  if ($variables['layout'] == 'both') {
+    $body_classes[] = 'two-sidebars';
+  }
+  elseif ($variables['layout'] == 'none') {
+    $body_classes[] = 'no-sidebars';
+  }
+  else {
+    $body_classes[] = 'one-sidebar sidebar-'. $variables['layout'];
+  }
+  $variables['body_classes'] = implode(' ', $body_classes);
+
+  // Dead databases will show error messages so supplying this template will
+  // allow themers to override the page and the content completely.
+  if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
+    $variables['template_file'] = 'maintenance-page-offline';
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/unicode.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,542 @@
+<?php
+// $Id: unicode.inc,v 1.29 2007/12/28 12:02:50 dries Exp $
+
+/**
+ * Indicates an error during check for PHP unicode support.
+ */
+define('UNICODE_ERROR', -1);
+
+/**
+ * Indicates that standard PHP (emulated) unicode support is being used.
+ */
+define('UNICODE_SINGLEBYTE', 0);
+
+/**
+ * Indicates that full unicode support with the PHP mbstring extension is being
+ * used.
+ */
+define('UNICODE_MULTIBYTE', 1);
+
+/**
+ * Wrapper around _unicode_check().
+ */
+function unicode_check() {
+  list($GLOBALS['multibyte']) = _unicode_check();
+}
+
+/**
+ * Perform checks about Unicode support in PHP, and set the right settings if
+ * needed.
+ *
+ * Because Drupal needs to be able to handle text in various encodings, we do
+ * not support mbstring function overloading. HTTP input/output conversion must
+ * be disabled for similar reasons.
+ *
+ * @param $errors
+ *   Whether to report any fatal errors with form_set_error().
+ */
+function _unicode_check() {
+  // Ensure translations don't break at install time
+  $t = get_t();
+
+  // Set the standard C locale to ensure consistent, ASCII-only string handling.
+  setlocale(LC_CTYPE, 'C');
+
+  // Check for outdated PCRE library
+  // Note: we check if U+E2 is in the range U+E0 - U+E1. This test returns TRUE on old PCRE versions.
+  if (preg_match('/[à-á]/u', 'â')) {
+    return array(UNICODE_ERROR, $t('The PCRE library in your PHP installation is outdated. This will cause problems when handling Unicode text. If you are running PHP 4.3.3 or higher, make sure you are using the PCRE library supplied by PHP. Please refer to the <a href="@url">PHP PCRE documentation</a> for more information.', array('@url' => 'http://www.php.net/pcre')));
+  }
+
+  // Check for mbstring extension
+  if (!function_exists('mb_strlen')) {
+    return array(UNICODE_SINGLEBYTE, $t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="@url">PHP mbstring extension</a> for improved Unicode support.', array('@url' => 'http://www.php.net/mbstring')));
+  }
+
+  // Check mbstring configuration
+  if (ini_get('mbstring.func_overload') != 0) {
+    return array(UNICODE_ERROR, $t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+  }
+  if (ini_get('mbstring.encoding_translation') != 0) {
+    return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+  }
+  if (ini_get('mbstring.http_input') != 'pass') {
+    return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+  }
+  if (ini_get('mbstring.http_output') != 'pass') {
+    return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+  }
+
+  // Set appropriate configuration
+  mb_internal_encoding('utf-8');
+  mb_language('uni');
+  return array(UNICODE_MULTIBYTE, '');
+}
+
+/**
+ * Return Unicode library status and errors.
+ */
+function unicode_requirements() {
+  // Ensure translations don't break at install time
+  $t = get_t();
+
+  $libraries = array(
+    UNICODE_SINGLEBYTE => $t('Standard PHP'),
+    UNICODE_MULTIBYTE => $t('PHP Mbstring Extension'),
+    UNICODE_ERROR => $t('Error'),
+  );
+  $severities = array(
+    UNICODE_SINGLEBYTE => REQUIREMENT_WARNING,
+    UNICODE_MULTIBYTE => REQUIREMENT_OK,
+    UNICODE_ERROR => REQUIREMENT_ERROR,
+  );
+  list($library, $description) = _unicode_check();
+
+  $requirements['unicode'] = array(
+    'title' => $t('Unicode library'),
+    'value' => $libraries[$library],
+  );
+  if ($description) {
+    $requirements['unicode']['description'] = $description;
+  }
+
+  $requirements['unicode']['severity'] = $severities[$library];
+
+  return $requirements;
+}
+
+/**
+ * Prepare a new XML parser.
+ *
+ * This is a wrapper around xml_parser_create() which extracts the encoding from
+ * the XML data first and sets the output encoding to UTF-8. This function should
+ * be used instead of xml_parser_create(), because PHP 4's XML parser doesn't
+ * check the input encoding itself. "Starting from PHP 5, the input encoding is
+ * automatically detected, so that the encoding parameter specifies only the
+ * output encoding."
+ *
+ * This is also where unsupported encodings will be converted. Callers should
+ * take this into account: $data might have been changed after the call.
+ *
+ * @param &$data
+ *   The XML data which will be parsed later.
+ * @return
+ *   An XML parser object.
+ */
+function drupal_xml_parser_create(&$data) {
+  // Default XML encoding is UTF-8
+  $encoding = 'utf-8';
+  $bom = FALSE;
+
+  // Check for UTF-8 byte order mark (PHP5's XML parser doesn't handle it).
+  if (!strncmp($data, "\xEF\xBB\xBF", 3)) {
+    $bom = TRUE;
+    $data = substr($data, 3);
+  }
+
+  // Check for an encoding declaration in the XML prolog if no BOM was found.
+  if (!$bom && ereg('^<\?xml[^>]+encoding="([^"]+)"', $data, $match)) {
+    $encoding = $match[1];
+  }
+
+  // Unsupported encodings are converted here into UTF-8.
+  $php_supported = array('utf-8', 'iso-8859-1', 'us-ascii');
+  if (!in_array(strtolower($encoding), $php_supported)) {
+    $out = drupal_convert_to_utf8($data, $encoding);
+    if ($out !== FALSE) {
+      $encoding = 'utf-8';
+      $data = ereg_replace('^(<\?xml[^>]+encoding)="([^"]+)"', '\\1="utf-8"', $out);
+    }
+    else {
+      watchdog('php', 'Could not convert XML encoding %s to UTF-8.', array('%s' => $encoding), WATCHDOG_WARNING);
+      return 0;
+    }
+  }
+
+  $xml_parser = xml_parser_create($encoding);
+  xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8');
+  return $xml_parser;
+}
+
+/**
+ * Convert data to UTF-8
+ *
+ * Requires the iconv, GNU recode or mbstring PHP extension.
+ *
+ * @param $data
+ *   The data to be converted.
+ * @param $encoding
+ *   The encoding that the data is in
+ * @return
+ *   Converted data or FALSE.
+ */
+function drupal_convert_to_utf8($data, $encoding) {
+  if (function_exists('iconv')) {
+    $out = @iconv($encoding, 'utf-8', $data);
+  }
+  else if (function_exists('mb_convert_encoding')) {
+    $out = @mb_convert_encoding($data, 'utf-8', $encoding);
+  }
+  else if (function_exists('recode_string')) {
+    $out = @recode_string($encoding .'..utf-8', $data);
+  }
+  else {
+    watchdog('php', 'Unsupported encoding %s. Please install iconv, GNU recode or mbstring for PHP.', array('%s' => $encoding), WATCHDOG_ERROR);
+    return FALSE;
+  }
+
+  return $out;
+}
+
+/**
+ * Truncate a UTF-8-encoded string safely to a number of bytes.
+ *
+ * If the end position is in the middle of a UTF-8 sequence, it scans backwards
+ * until the beginning of the byte sequence.
+ *
+ * Use this function whenever you want to chop off a string at an unsure
+ * location. On the other hand, if you're sure that you're splitting on a
+ * character boundary (e.g. after using strpos() or similar), you can safely use
+ * substr() instead.
+ *
+ * @param $string
+ *   The string to truncate.
+ * @param $len
+ *   An upper limit on the returned string length.
+ * @return
+ *   The truncated string.
+ */
+function drupal_truncate_bytes($string, $len) {
+  if (strlen($string) <= $len) {
+    return $string;
+  }
+  if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) {
+    return substr($string, 0, $len);
+  }
+  while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0) {};
+  return substr($string, 0, $len);
+}
+
+/**
+ * Truncate a UTF-8-encoded string safely to a number of characters.
+ *
+ * @param $string
+ *   The string to truncate.
+ * @param $len
+ *   An upper limit on the returned string length.
+ * @param $wordsafe
+ *   Flag to truncate at last space within the upper limit. Defaults to FALSE.
+ * @param $dots
+ *   Flag to add trailing dots. Defaults to FALSE.
+ * @return
+ *   The truncated string.
+ */
+function truncate_utf8($string, $len, $wordsafe = FALSE, $dots = FALSE) {
+
+  if (drupal_strlen($string) <= $len) {
+    return $string;
+  }
+
+  if ($dots) {
+    $len -= 4;
+  }
+
+  if ($wordsafe) {
+    $string = drupal_substr($string, 0, $len + 1); // leave one more character
+    if ($last_space = strrpos($string, ' ')) { // space exists AND is not on position 0
+      $string = substr($string, 0, $last_space);
+    }
+    else {
+      $string = drupal_substr($string, 0, $len);
+    }
+  }
+  else {
+    $string = drupal_substr($string, 0, $len);
+  }
+
+  if ($dots) {
+    $string .= ' ...';
+  }
+
+  return $string;
+}
+
+/**
+ * Encodes MIME/HTTP header values that contain non-ASCII, UTF-8 encoded
+ * characters.
+ *
+ * For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=".
+ *
+ * See http://www.rfc-editor.org/rfc/rfc2047.txt for more information.
+ *
+ * Notes:
+ * - Only encode strings that contain non-ASCII characters.
+ * - We progressively cut-off a chunk with truncate_utf8(). This is to ensure
+ *   each chunk starts and ends on a character boundary.
+ * - Using \n as the chunk separator may cause problems on some systems and may
+ *   have to be changed to \r\n or \r.
+ */
+function mime_header_encode($string) {
+  if (preg_match('/[^\x20-\x7E]/', $string)) {
+    $chunk_size = 47; // floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
+    $len = strlen($string);
+    $output = '';
+    while ($len > 0) {
+      $chunk = drupal_truncate_bytes($string, $chunk_size);
+      $output .= ' =?UTF-8?B?'. base64_encode($chunk) ."?=\n";
+      $c = strlen($chunk);
+      $string = substr($string, $c);
+      $len -= $c;
+    }
+    return trim($output);
+  }
+  return $string;
+}
+
+/**
+ * Complement to mime_header_encode
+ */
+function mime_header_decode($header) {
+  // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace)
+  $header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', '_mime_header_decode', $header);
+  // Second step: remaining chunks (do not collapse whitespace)
+  return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', '_mime_header_decode', $header);
+}
+
+/**
+ * Helper function to mime_header_decode
+ */
+function _mime_header_decode($matches) {
+  // Regexp groups:
+  // 1: Character set name
+  // 2: Escaping method (Q or B)
+  // 3: Encoded data
+  $data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
+  if (strtolower($matches[1]) != 'utf-8') {
+    $data = drupal_convert_to_utf8($data, $matches[1]);
+  }
+  return $data;
+}
+
+/**
+ * Decode all HTML entities (including numerical ones) to regular UTF-8 bytes.
+ * Double-escaped entities will only be decoded once ("&amp;lt;" becomes "&lt;", not "<").
+ *
+ * @param $text
+ *   The text to decode entities in.
+ * @param $exclude
+ *   An array of characters which should not be decoded. For example,
+ *   array('<', '&', '"'). This affects both named and numerical entities.
+ */
+function decode_entities($text, $exclude = array()) {
+  static $table;
+  // We store named entities in a table for quick processing.
+  if (!isset($table)) {
+    // Get all named HTML entities.
+    $table = array_flip(get_html_translation_table(HTML_ENTITIES));
+    // PHP gives us ISO-8859-1 data, we need UTF-8.
+    $table = array_map('utf8_encode', $table);
+    // Add apostrophe (XML)
+    $table['&apos;'] = "'";
+  }
+  $newtable = array_diff($table, $exclude);
+
+  // Use a regexp to select all entities in one pass, to avoid decoding double-escaped entities twice.
+  return preg_replace('/&(#x?)?([A-Za-z0-9]+);/e', '_decode_entities("$1", "$2", "$0", $newtable, $exclude)', $text);
+}
+
+/**
+ * Helper function for decode_entities
+ */
+function _decode_entities($prefix, $codepoint, $original, &$table, &$exclude) {
+  // Named entity
+  if (!$prefix) {
+    if (isset($table[$original])) {
+      return $table[$original];
+    }
+    else {
+      return $original;
+    }
+  }
+  // Hexadecimal numerical entity
+  if ($prefix == '#x') {
+    $codepoint = base_convert($codepoint, 16, 10);
+  }
+  // Decimal numerical entity (strip leading zeros to avoid PHP octal notation)
+  else {
+    $codepoint = preg_replace('/^0+/', '', $codepoint);
+  }
+  // Encode codepoint as UTF-8 bytes
+  if ($codepoint < 0x80) {
+    $str = chr($codepoint);
+  }
+  else if ($codepoint < 0x800) {
+    $str = chr(0xC0 | ($codepoint >> 6))
+         . chr(0x80 | ($codepoint & 0x3F));
+  }
+  else if ($codepoint < 0x10000) {
+    $str = chr(0xE0 | ( $codepoint >> 12))
+         . chr(0x80 | (($codepoint >> 6) & 0x3F))
+         . chr(0x80 | ( $codepoint       & 0x3F));
+  }
+  else if ($codepoint < 0x200000) {
+    $str = chr(0xF0 | ( $codepoint >> 18))
+         . chr(0x80 | (($codepoint >> 12) & 0x3F))
+         . chr(0x80 | (($codepoint >> 6)  & 0x3F))
+         . chr(0x80 | ( $codepoint        & 0x3F));
+  }
+  // Check for excluded characters
+  if (in_array($str, $exclude)) {
+    return $original;
+  }
+  else {
+    return $str;
+  }
+}
+
+/**
+ * Count the amount of characters in a UTF-8 string. This is less than or
+ * equal to the byte count.
+ */
+function drupal_strlen($text) {
+  global $multibyte;
+  if ($multibyte == UNICODE_MULTIBYTE) {
+    return mb_strlen($text);
+  }
+  else {
+    // Do not count UTF-8 continuation bytes.
+    return strlen(preg_replace("/[\x80-\xBF]/", '', $text));
+  }
+}
+
+/**
+ * Uppercase a UTF-8 string.
+ */
+function drupal_strtoupper($text) {
+  global $multibyte;
+  if ($multibyte == UNICODE_MULTIBYTE) {
+    return mb_strtoupper($text);
+  }
+  else {
+    // Use C-locale for ASCII-only uppercase
+    $text = strtoupper($text);
+    // Case flip Latin-1 accented letters
+    $text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '_unicode_caseflip', $text);
+    return $text;
+  }
+}
+
+/**
+ * Lowercase a UTF-8 string.
+ */
+function drupal_strtolower($text) {
+  global $multibyte;
+  if ($multibyte == UNICODE_MULTIBYTE) {
+    return mb_strtolower($text);
+  }
+  else {
+    // Use C-locale for ASCII-only lowercase
+    $text = strtolower($text);
+    // Case flip Latin-1 accented letters
+    $text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '_unicode_caseflip', $text);
+    return $text;
+  }
+}
+
+/**
+ * Helper function for case conversion of Latin-1.
+ * Used for flipping U+C0-U+DE to U+E0-U+FD and back.
+ */
+function _unicode_caseflip($matches) {
+  return $matches[0][0] . chr(ord($matches[0][1]) ^ 32);
+}
+
+/**
+ * Capitalize the first letter of a UTF-8 string.
+ */
+function drupal_ucfirst($text) {
+  // Note: no mbstring equivalent!
+  return drupal_strtoupper(drupal_substr($text, 0, 1)) . drupal_substr($text, 1);
+}
+
+/**
+ * Cut off a piece of a string based on character indices and counts. Follows
+ * the same behavior as PHP's own substr() function.
+ *
+ * Note that for cutting off a string at a known character/substring
+ * location, the usage of PHP's normal strpos/substr is safe and
+ * much faster.
+ */
+function drupal_substr($text, $start, $length = NULL) {
+  global $multibyte;
+  if ($multibyte == UNICODE_MULTIBYTE) {
+    return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
+  }
+  else {
+    $strlen = strlen($text);
+    // Find the starting byte offset
+    $bytes = 0;
+    if ($start > 0) {
+      // Count all the continuation bytes from the start until we have found
+      // $start characters
+      $bytes = -1; $chars = -1;
+      while ($bytes < $strlen && $chars < $start) {
+        $bytes++;
+        $c = ord($text[$bytes]);
+        if ($c < 0x80 || $c >= 0xC0) {
+          $chars++;
+        }
+      }
+    }
+    else if ($start < 0) {
+      // Count all the continuation bytes from the end until we have found
+      // abs($start) characters
+      $start = abs($start);
+      $bytes = $strlen; $chars = 0;
+      while ($bytes > 0 && $chars < $start) {
+        $bytes--;
+        $c = ord($text[$bytes]);
+        if ($c < 0x80 || $c >= 0xC0) {
+          $chars++;
+        }
+      }
+    }
+    $istart = $bytes;
+
+    // Find the ending byte offset
+    if ($length === NULL) {
+      $bytes = $strlen - 1;
+    }
+    else if ($length > 0) {
+      // Count all the continuation bytes from the starting index until we have
+      // found $length + 1 characters. Then backtrack one byte.
+      $bytes = $istart; $chars = 0;
+      while ($bytes < $strlen && $chars < $length) {
+        $bytes++;
+        $c = ord($text[$bytes]);
+        if ($c < 0x80 || $c >= 0xC0) {
+          $chars++;
+        }
+      }
+      $bytes--;
+    }
+    else if ($length < 0) {
+      // Count all the continuation bytes from the end until we have found
+      // abs($length) characters
+      $length = abs($length);
+      $bytes = $strlen - 1; $chars = 0;
+      while ($bytes >= 0 && $chars < $length) {
+        $c = ord($text[$bytes]);
+        if ($c < 0x80 || $c >= 0xC0) {
+          $chars++;
+        }
+        $bytes--;
+      }
+    }
+    $iend = $bytes;
+
+    return substr($text, $istart, max(0, $iend - $istart + 1));
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/xmlrpc.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,477 @@
+<?php
+// $Id: xmlrpc.inc,v 1.47 2008/01/09 21:52:43 goba Exp $
+
+/**
+ * @file
+ * Drupal XML-RPC library. Based on the IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002-2005
+ * Version 1.7 (beta) - Simon Willison, 23rd May 2005
+ * Site:   http://scripts.incutio.com/xmlrpc/
+ * Manual: http://scripts.incutio.com/xmlrpc/manual.php
+ * This version is made available under the GNU GPL License
+ */
+
+/**
+ * Recursively turn a data structure into objects with 'data' and 'type' attributes.
+ *
+ * @param $data
+ *   The data structure.
+ * @param  $type
+ *   Optional type assign to $data.
+ * @return
+ *   Object.
+ */
+function xmlrpc_value($data, $type = FALSE) {
+  $xmlrpc_value = new stdClass();
+  $xmlrpc_value->data = $data;
+  if (!$type) {
+    $type = xmlrpc_value_calculate_type($xmlrpc_value);
+  }
+  $xmlrpc_value->type = $type;
+  if ($type == 'struct') {
+    // Turn all the values in the array into new xmlrpc_values
+    foreach ($xmlrpc_value->data as $key => $value) {
+      $xmlrpc_value->data[$key] = xmlrpc_value($value);
+    }
+  }
+  if ($type == 'array') {
+    for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) {
+      $xmlrpc_value->data[$i] = xmlrpc_value($xmlrpc_value->data[$i]);
+    }
+  }
+  return $xmlrpc_value;
+}
+
+/**
+ * Map PHP type to XML-RPC type.
+ *
+ * @param $xmlrpc_value
+ *   Variable whose type should be mapped.
+ * @return
+ *   XML-RPC type as string.
+ * @see
+ *   http://www.xmlrpc.com/spec#scalars
+ */
+function xmlrpc_value_calculate_type(&$xmlrpc_value) {
+  // http://www.php.net/gettype: Never use gettype() to test for a certain type [...] Instead, use the is_* functions.
+  if (is_bool($xmlrpc_value->data)) {
+    return 'boolean';
+  }
+  if (is_double($xmlrpc_value->data)) {
+    return 'double';
+  }
+  if (is_int($xmlrpc_value->data)) {
+      return 'int';
+  }
+  if (is_array($xmlrpc_value->data)) {
+    // empty or integer-indexed arrays are 'array', string-indexed arrays 'struct'
+    return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct';
+  }
+  if (is_object($xmlrpc_value->data)) {
+    if ($xmlrpc_value->data->is_date) {
+      return 'date';
+    }
+    if ($xmlrpc_value->data->is_base64) {
+      return 'base64';
+    }
+    $xmlrpc_value->data = get_object_vars($xmlrpc_value->data);
+    return 'struct';
+  }
+  // default
+  return 'string';
+}
+
+/**
+ * Generate XML representing the given value.
+ *
+ * @param $xmlrpc_value
+ * @return
+ *   XML representation of value.
+ */
+function xmlrpc_value_get_xml($xmlrpc_value) {
+  switch ($xmlrpc_value->type) {
+    case 'boolean':
+      return '<boolean>'. (($xmlrpc_value->data) ? '1' : '0') .'</boolean>';
+      break;
+    case 'int':
+      return '<int>'. $xmlrpc_value->data .'</int>';
+      break;
+    case 'double':
+      return '<double>'. $xmlrpc_value->data .'</double>';
+      break;
+    case 'string':
+      // Note: we don't escape apostrophes because of the many blogging clients
+      // that don't support numerical entities (and XML in general) properly.
+      return '<string>'. htmlspecialchars($xmlrpc_value->data) .'</string>';
+      break;
+    case 'array':
+      $return = '<array><data>'."\n";
+      foreach ($xmlrpc_value->data as $item) {
+        $return .= '  <value>'. xmlrpc_value_get_xml($item) ."</value>\n";
+      }
+      $return .= '</data></array>';
+      return $return;
+      break;
+    case 'struct':
+      $return = '<struct>'."\n";
+      foreach ($xmlrpc_value->data as $name => $value) {
+        $return .= "  <member><name>". check_plain($name) ."</name><value>";
+        $return .= xmlrpc_value_get_xml($value) ."</value></member>\n";
+      }
+      $return .= '</struct>';
+      return $return;
+      break;
+    case 'date':
+      return xmlrpc_date_get_xml($xmlrpc_value->data);
+      break;
+    case 'base64':
+      return xmlrpc_base64_get_xml($xmlrpc_value->data);
+      break;
+  }
+  return FALSE;
+}
+
+/**
+ * Construct an object representing an XML-RPC message.
+ *
+ * @param $message
+ *   String containing XML as defined at http://www.xmlrpc.com/spec
+ * @return
+ *   Object
+ */
+function xmlrpc_message($message) {
+  $xmlrpc_message = new stdClass();
+  $xmlrpc_message->array_structs = array();   // The stack used to keep track of the current array/struct
+  $xmlrpc_message->array_structs_types = array(); // The stack used to keep track of if things are structs or array
+  $xmlrpc_message->current_struct_name = array();  // A stack as well
+  $xmlrpc_message->message = $message;
+  return $xmlrpc_message;
+}
+
+/**
+ * Parse an XML-RPC message. If parsing fails, the faultCode and faultString
+ * will be added to the message object.
+ *
+ * @param $xmlrpc_message
+ *   Object generated by xmlrpc_message()
+ * @return
+ *   TRUE if parsing succeeded; FALSE otherwise
+ */
+function xmlrpc_message_parse(&$xmlrpc_message) {
+  // First remove the XML declaration
+  $xmlrpc_message->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $xmlrpc_message->message);
+  if (trim($xmlrpc_message->message) == '') {
+    return FALSE;
+  }
+  $xmlrpc_message->_parser = xml_parser_create();
+  // Set XML parser to take the case of tags into account.
+  xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE);
+  // Set XML parser callback functions
+  xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close');
+  xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata');
+  xmlrpc_message_set($xmlrpc_message);
+  if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) {
+    return FALSE;
+  }
+  xml_parser_free($xmlrpc_message->_parser);
+  // Grab the error messages, if any
+  $xmlrpc_message = xmlrpc_message_get();
+  if ($xmlrpc_message->messagetype == 'fault') {
+    $xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode'];
+    $xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString'];
+  }
+  return TRUE;
+}
+
+/**
+ * Store a copy of the $xmlrpc_message object temporarily.
+ *
+ * @param $value
+ *   Object
+ * @return
+ *   The most recently stored $xmlrpc_message
+ */
+function xmlrpc_message_set($value = NULL) {
+  static $xmlrpc_message;
+  if ($value) {
+    $xmlrpc_message = $value;
+  }
+  return $xmlrpc_message;
+}
+
+function xmlrpc_message_get() {
+  return xmlrpc_message_set();
+}
+
+function xmlrpc_message_tag_open($parser, $tag, $attr) {
+  $xmlrpc_message = xmlrpc_message_get();
+  $xmlrpc_message->current_tag_contents = '';
+  $xmlrpc_message->last_open = $tag;
+  switch ($tag) {
+    case 'methodCall':
+    case 'methodResponse':
+    case 'fault':
+      $xmlrpc_message->messagetype = $tag;
+      break;
+    // Deal with stacks of arrays and structs
+    case 'data':
+      $xmlrpc_message->array_structs_types[] = 'array';
+      $xmlrpc_message->array_structs[] = array();
+      break;
+    case 'struct':
+      $xmlrpc_message->array_structs_types[] = 'struct';
+      $xmlrpc_message->array_structs[] = array();
+      break;
+  }
+  xmlrpc_message_set($xmlrpc_message);
+}
+
+function xmlrpc_message_cdata($parser, $cdata) {
+  $xmlrpc_message = xmlrpc_message_get();
+  $xmlrpc_message->current_tag_contents .= $cdata;
+  xmlrpc_message_set($xmlrpc_message);
+}
+
+function xmlrpc_message_tag_close($parser, $tag) {
+  $xmlrpc_message = xmlrpc_message_get();
+  $value_flag = FALSE;
+  switch ($tag) {
+    case 'int':
+    case 'i4':
+      $value = (int)trim($xmlrpc_message->current_tag_contents);
+      $value_flag = TRUE;
+      break;
+    case 'double':
+      $value = (double)trim($xmlrpc_message->current_tag_contents);
+      $value_flag = TRUE;
+      break;
+    case 'string':
+      $value = $xmlrpc_message->current_tag_contents;
+      $value_flag = TRUE;
+      break;
+    case 'dateTime.iso8601':
+      $value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents));
+      // $value = $iso->getTimestamp();
+      $value_flag = TRUE;
+      break;
+    case 'value':
+      // If no type is indicated, the type is string
+      // We take special care for empty values
+      if (trim($xmlrpc_message->current_tag_contents) != '' || $xmlrpc_message->last_open == 'value') {
+        $value = (string)$xmlrpc_message->current_tag_contents;
+        $value_flag = TRUE;
+      }
+      unset($xmlrpc_message->last_open);
+      break;
+    case 'boolean':
+      $value = (boolean)trim($xmlrpc_message->current_tag_contents);
+      $value_flag = TRUE;
+      break;
+    case 'base64':
+      $value = base64_decode(trim($xmlrpc_message->current_tag_contents));
+      $value_flag = TRUE;
+      break;
+    // Deal with stacks of arrays and structs
+    case 'data':
+    case 'struct':
+      $value = array_pop($xmlrpc_message->array_structs );
+      array_pop($xmlrpc_message->array_structs_types);
+      $value_flag = TRUE;
+      break;
+    case 'member':
+      array_pop($xmlrpc_message->current_struct_name);
+      break;
+    case 'name':
+      $xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents);
+      break;
+    case 'methodName':
+      $xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents);
+      break;
+  }
+  if ($value_flag) {
+    if (count($xmlrpc_message->array_structs ) > 0) {
+      // Add value to struct or array
+      if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types)-1] == 'struct') {
+        // Add to struct
+        $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name)-1]] = $value;
+      }
+      else {
+        // Add to array
+        $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][] = $value;
+      }
+    }
+    else {
+      // Just add as a parameter
+      $xmlrpc_message->params[] = $value;
+    }
+  }
+  if (!in_array($tag, array("data", "struct", "member"))) {
+    $xmlrpc_message->current_tag_contents = '';
+  }
+  xmlrpc_message_set($xmlrpc_message);
+}
+
+/**
+ * Construct an object representing an XML-RPC request
+ *
+ * @param $method
+ *   The name of the method to be called
+ * @param $args
+ *   An array of parameters to send with the method.
+ * @return
+ *   Object
+ */
+function xmlrpc_request($method, $args) {
+  $xmlrpc_request = new stdClass();
+  $xmlrpc_request->method = $method;
+  $xmlrpc_request->args = $args;
+  $xmlrpc_request->xml = <<<EOD
+<?xml version="1.0"?>
+<methodCall>
+<methodName>{$xmlrpc_request->method}</methodName>
+<params>
+
+EOD;
+  foreach ($xmlrpc_request->args as $arg) {
+    $xmlrpc_request->xml .= '<param><value>';
+    $v = xmlrpc_value($arg);
+    $xmlrpc_request->xml .= xmlrpc_value_get_xml($v);
+    $xmlrpc_request->xml .= "</value></param>\n";
+  }
+  $xmlrpc_request->xml .= '</params></methodCall>';
+  return $xmlrpc_request;
+}
+
+
+function xmlrpc_error($code = NULL, $message = NULL) {
+  static $xmlrpc_error;
+  if (isset($code)) {
+    $xmlrpc_error = new stdClass();
+    $xmlrpc_error->is_error = TRUE;
+    $xmlrpc_error->code = $code;
+    $xmlrpc_error->message = $message;
+    module_invoke('system', 'check_http_request');
+  }
+  return $xmlrpc_error;
+}
+
+function xmlrpc_error_get_xml($xmlrpc_error) {
+  return <<<EOD
+<methodResponse>
+  <fault>
+  <value>
+    <struct>
+    <member>
+      <name>faultCode</name>
+      <value><int>{$xmlrpc_error->code}</int></value>
+    </member>
+    <member>
+      <name>faultString</name>
+      <value><string>{$xmlrpc_error->message}</string></value>
+    </member>
+    </struct>
+  </value>
+  </fault>
+</methodResponse>
+
+EOD;
+}
+
+function xmlrpc_date($time) {
+  $xmlrpc_date = new stdClass();
+  $xmlrpc_date->is_date = TRUE;
+  // $time can be a PHP timestamp or an ISO one
+  if (is_numeric($time)) {
+    $xmlrpc_date->year = date('Y', $time);
+    $xmlrpc_date->month = date('m', $time);
+    $xmlrpc_date->day = date('d', $time);
+    $xmlrpc_date->hour = date('H', $time);
+    $xmlrpc_date->minute = date('i', $time);
+    $xmlrpc_date->second = date('s', $time);
+    $xmlrpc_date->iso8601 = date('Ymd\TH:i:s', $time);
+  }
+  else {
+    $xmlrpc_date->iso8601 = $time;
+    $time = str_replace(array('-', ':'), '', $time);
+    $xmlrpc_date->year = substr($time, 0, 4);
+    $xmlrpc_date->month = substr($time, 4, 2);
+    $xmlrpc_date->day = substr($time, 6, 2);
+    $xmlrpc_date->hour = substr($time, 9, 2);
+    $xmlrpc_date->minute = substr($time, 11, 2);
+    $xmlrpc_date->second = substr($time, 13, 2);
+  }
+  return $xmlrpc_date;
+}
+
+function xmlrpc_date_get_xml($xmlrpc_date) {
+  return '<dateTime.iso8601>'. $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day .'T'. $xmlrpc_date->hour .':'. $xmlrpc_date->minute .':'. $xmlrpc_date->second .'</dateTime.iso8601>';
+}
+
+function xmlrpc_base64($data) {
+  $xmlrpc_base64 = new stdClass();
+  $xmlrpc_base64->is_base64 = TRUE;
+  $xmlrpc_base64->data = $data;
+  return $xmlrpc_base64;
+}
+
+function xmlrpc_base64_get_xml($xmlrpc_base64) {
+  return '<base64>'. base64_encode($xmlrpc_base64->data) .'</base64>';
+}
+
+/**
+ * Execute an XML remote procedural call. This is private function; call xmlrpc()
+ * in common.inc instead of this function.
+ *
+ * @return
+ *   A $xmlrpc_message object if the call succeeded; FALSE if the call failed
+ */
+function _xmlrpc() {
+  $args = func_get_args();
+  $url = array_shift($args);
+  if (is_array($args[0])) {
+    $method = 'system.multicall';
+    $multicall_args = array();
+    foreach ($args[0] as $call) {
+      $multicall_args[] = array('methodName' => array_shift($call), 'params' => $call);
+    }
+    $args = array($multicall_args);
+  }
+  else {
+    $method = array_shift($args);
+  }
+  $xmlrpc_request = xmlrpc_request($method, $args);
+  $result = drupal_http_request($url, array("Content-Type" => "text/xml"), 'POST', $xmlrpc_request->xml);
+  if ($result->code != 200) {
+    xmlrpc_error($result->code, $result->error);
+    return FALSE;
+  }
+  $message = xmlrpc_message($result->data);
+  // Now parse what we've got back
+  if (!xmlrpc_message_parse($message)) {
+    // XML error
+    xmlrpc_error(-32700, t('Parse error. Not well formed'));
+    return FALSE;
+  }
+  // Is the message a fault?
+  if ($message->messagetype == 'fault') {
+    xmlrpc_error($message->fault_code, $message->fault_string);
+    return FALSE;
+  }
+  // Message must be OK
+  return $message->params[0];
+}
+
+/**
+ * Returns the last XML-RPC client error number
+ */
+function xmlrpc_errno() {
+  $error = xmlrpc_error();
+  return $error->code;
+}
+
+/**
+ * Returns the last XML-RPC client error message
+ */
+function xmlrpc_error_msg() {
+  $error = xmlrpc_error();
+  return $error->message;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/xmlrpcs.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,313 @@
+<?php
+// $Id: xmlrpcs.inc,v 1.24 2007/12/31 08:54:36 dries Exp $
+
+/**
+ * The main entry point for XML-RPC requests.
+ *
+ * @param $callbacks
+ *   Array of external XML-RPC method names with the callbacks they map to.
+ */
+function xmlrpc_server($callbacks) {
+  $xmlrpc_server = new stdClass();
+  // Define built-in XML-RPC method names
+  $defaults = array(
+      'system.multicall' => 'xmlrpc_server_multicall',
+    array(
+      'system.methodSignature',
+      'xmlrpc_server_method_signature',
+      array('array', 'string'),
+      'Returns an array describing the return type and required parameters of a method.'
+    ),
+    array(
+      'system.getCapabilities',
+      'xmlrpc_server_get_capabilities',
+      array('struct'),
+      'Returns a struct describing the XML-RPC specifications supported by this server.'
+    ),
+    array(
+      'system.listMethods',
+      'xmlrpc_server_list_methods',
+      array('array'),
+      'Returns an array of available methods on this server.'),
+    array(
+      'system.methodHelp',
+      'xmlrpc_server_method_help',
+      array('string', 'string'),
+      'Returns a documentation string for the specified method.')
+  );
+  // We build an array of all method names by combining the built-ins
+  // with those defined by modules implementing the _xmlrpc hook.
+  // Built-in methods are overridable.
+  foreach (array_merge($defaults, (array)$callbacks) as $key => $callback) {
+    // we could check for is_array($callback)
+    if (is_int($key)) {
+      $method = $callback[0];
+      $xmlrpc_server->callbacks[$method] = $callback[1];
+      $xmlrpc_server->signatures[$method] = $callback[2];
+      $xmlrpc_server->help[$method] = $callback[3];
+    }
+    else {
+      $xmlrpc_server->callbacks[$key] = $callback;
+      $xmlrpc_server->signatures[$key] = '';
+      $xmlrpc_server->help[$key] = '';
+    }
+  }
+
+  $data = file_get_contents('php://input');
+  if (!$data) {
+    die('XML-RPC server accepts POST requests only.');
+  }
+  $xmlrpc_server->message = xmlrpc_message($data);
+  if (!xmlrpc_message_parse($xmlrpc_server->message)) {
+    xmlrpc_server_error(-32700, t('Parse error. Request not well formed.'));
+  }
+  if ($xmlrpc_server->message->messagetype != 'methodCall') {
+    xmlrpc_server_error(-32600, t('Server error. Invalid XML-RPC. Request must be a methodCall.'));
+  }
+  xmlrpc_server_set($xmlrpc_server);
+  $result = xmlrpc_server_call($xmlrpc_server, $xmlrpc_server->message->methodname, $xmlrpc_server->message->params);
+
+  if ($result->is_error) {
+    xmlrpc_server_error($result);
+  }
+  // Encode the result
+  $r = xmlrpc_value($result);
+  // Create the XML
+  $xml = '
+<methodResponse>
+  <params>
+  <param>
+    <value>'.
+    xmlrpc_value_get_xml($r)
+    .'</value>
+  </param>
+  </params>
+</methodResponse>
+
+';
+  // Send it
+  xmlrpc_server_output($xml);
+}
+
+/**
+ * Throw an XML-RPC error.
+ *
+ * @param $error
+ *   an error object OR integer error code
+ * @param $message
+ *   description of error, used only if integer error code was passed
+ */
+function xmlrpc_server_error($error, $message = FALSE) {
+  if ($message && !is_object($error)) {
+    $error = xmlrpc_error($error, $message);
+  }
+  xmlrpc_server_output(xmlrpc_error_get_xml($error));
+}
+
+function xmlrpc_server_output($xml) {
+  $xml = '<?xml version="1.0"?>'."\n". $xml;
+  header('Connection: close');
+  header('Content-Length: '. strlen($xml));
+  header('Content-Type: text/xml');
+  header('Date: '. date('r'));
+  echo $xml;
+  exit;
+}
+
+/**
+ * Store a copy of the request temporarily.
+ *
+ * @param $xmlrpc_server
+ *   Request object created by xmlrpc_server().
+ */
+function xmlrpc_server_set($xmlrpc_server = NULL) {
+  static $server;
+  if (!isset($server)) {
+    $server = $xmlrpc_server;
+  }
+  return $server;
+}
+
+// Retrieve the stored request.
+function xmlrpc_server_get() {
+  return xmlrpc_server_set();
+}
+
+/**
+ * Dispatch the request and any parameters to the appropriate handler.
+ *
+ * @param $xmlrpc_server
+ * @param $methodname
+ *   The external XML-RPC method name, e.g. 'system.methodHelp'
+ * @param $args
+ *   Array containing any parameters that were sent along with the request.
+ */
+function xmlrpc_server_call($xmlrpc_server, $methodname, $args) {
+  // Make sure parameters are in an array
+  if ($args && !is_array($args)) {
+    $args = array($args);
+  }
+  // Has this method been mapped to a Drupal function by us or by modules?
+  if (!isset($xmlrpc_server->callbacks[$methodname])) {
+    return xmlrpc_error(-32601, t('Server error. Requested method %methodname not specified.', array("%methodname" => $xmlrpc_server->message->methodname)));
+  }
+  $method = $xmlrpc_server->callbacks[$methodname];
+  $signature = $xmlrpc_server->signatures[$methodname];
+
+  // If the method has a signature, validate the request against the signature
+  if (is_array($signature)) {
+    $ok = TRUE;
+    $return_type = array_shift($signature);
+    // Check the number of arguments
+    if (count($args) != count($signature)) {
+      return xmlrpc_error(-32602, t('Server error. Wrong number of method parameters.'));
+    }
+    // Check the argument types
+    foreach ($signature as $key => $type) {
+      $arg = $args[$key];
+      switch ($type) {
+        case 'int':
+        case 'i4':
+          if (is_array($arg) || !is_int($arg)) {
+            $ok = FALSE;
+          }
+          break;
+        case 'base64':
+        case 'string':
+          if (!is_string($arg)) {
+            $ok = FALSE;
+          }
+          break;
+        case 'boolean':
+          if ($arg !== FALSE && $arg !== TRUE) {
+            $ok = FALSE;
+          }
+          break;
+        case 'float':
+        case 'double':
+          if (!is_float($arg)) {
+            $ok = FALSE;
+          }
+          break;
+        case 'date':
+        case 'dateTime.iso8601':
+          if (!$arg->is_date) {
+            $ok = FALSE;
+          }
+          break;
+      }
+      if (!$ok) {
+        return xmlrpc_error(-32602, t('Server error. Invalid method parameters.'));
+      }
+    }
+  }
+
+  if (!function_exists($method)) {
+    return xmlrpc_error(-32601, t('Server error. Requested function %method does not exist.', array("%method" => $method)));
+  }
+  // Call the mapped function
+  return call_user_func_array($method, $args);
+}
+
+function xmlrpc_server_multicall($methodcalls) {
+  // See http://www.xmlrpc.com/discuss/msgReader$1208
+  $return = array();
+  $xmlrpc_server = xmlrpc_server_get();
+  foreach ($methodcalls as $call) {
+    $ok = TRUE;
+    if (!isset($call['methodName']) || !isset($call['params'])) {
+      $result = xmlrpc_error(3, t('Invalid syntax for system.multicall.'));
+      $ok = FALSE;
+    }
+    $method = $call['methodName'];
+    $params = $call['params'];
+    if ($method == 'system.multicall') {
+      $result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.'));
+    }
+    elseif ($ok) {
+      $result = xmlrpc_server_call($xmlrpc_server, $method, $params);
+    }
+    if ($result->is_error) {
+      $return[] = array(
+        'faultCode' => $result->code,
+        'faultString' => $result->message
+      );
+    }
+    else {
+      $return[] = $result;
+    }
+  }
+  return $return;
+}
+
+
+/**
+ * XML-RPC method system.listMethods maps to this function.
+ */
+function xmlrpc_server_list_methods() {
+  $xmlrpc_server = xmlrpc_server_get();
+  return array_keys($xmlrpc_server->callbacks);
+}
+
+/**
+ * XML-RPC method system.getCapabilities maps to this function.
+ * See http://groups.yahoo.com/group/xml-rpc/message/2897
+ */
+function xmlrpc_server_get_capabilities() {
+  return array(
+    'xmlrpc' => array(
+      'specUrl' => 'http://www.xmlrpc.com/spec',
+      'specVersion' => 1
+    ),
+    'faults_interop' => array(
+      'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
+      'specVersion' => 20010516
+    ),
+    'system.multicall' => array(
+      'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
+      'specVersion' => 1
+    ),
+    'introspection' => array(
+    'specUrl' => 'http://scripts.incutio.com/xmlrpc/introspection.html',
+    'specVersion' => 1
+    )
+  );
+}
+
+/**
+ * XML-RPC method system.methodSignature maps to this function.
+ *
+ * @param $methodname
+ *   Name of method for which we return a method signature.
+ * @return array
+ *   An array of types representing the method signature of the
+ *   function that the methodname maps to. The methodSignature of
+ *   this function is 'array', 'string' because it takes an array
+ *   and returns a string.
+ */
+function xmlrpc_server_method_signature($methodname) {
+  $xmlrpc_server = xmlrpc_server_get();
+  if (!isset($xmlrpc_server->callbacks[$methodname])) {
+    return xmlrpc_error(-32601, t('Server error. Requested method %methodname not specified.', array("%methodname" => $methodname)));
+  }
+  if (!is_array($xmlrpc_server->signatures[$methodname])) {
+    return xmlrpc_error(-32601, t('Server error. Requested method %methodname signature not specified.', array("%methodname" => $methodname)));
+  }
+  // We array of types
+  $return = array();
+  foreach ($xmlrpc_server->signatures[$methodname] as $type) {
+    $return[] = $type;
+  }
+  return $return;
+}
+
+/**
+ * XML-RPC method system.methodHelp maps to this function.
+ *
+ * @param $method
+ *   Name of method for which we return a help string.
+ */
+function xmlrpc_server_method_help($method) {
+  $xmlrpc_server = xmlrpc_server_get();
+  return $xmlrpc_server->help[$method];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/index.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,39 @@
+<?php
+// $Id: index.php,v 1.94 2007/12/26 08:46:48 dries Exp $
+
+/**
+ * @file
+ * The PHP page that serves all page requests on a Drupal installation.
+ *
+ * The routines here dispatch control to the appropriate handler, which then
+ * prints the appropriate page.
+ *
+ * All Drupal code is released under the GNU General Public License.
+ * See COPYRIGHT.txt and LICENSE.txt.
+ */
+
+require_once './includes/bootstrap.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+
+$return = menu_execute_active_handler();
+
+// Menu status constants are integers; page content is a string.
+if (is_int($return)) {
+  switch ($return) {
+    case MENU_NOT_FOUND:
+      drupal_not_found();
+      break;
+    case MENU_ACCESS_DENIED:
+      drupal_access_denied();
+      break;
+    case MENU_SITE_OFFLINE:
+      drupal_site_offline();
+      break;
+  }
+}
+elseif (isset($return)) {
+  // Print any value (including an empty string) except NULL or undefined:
+  print theme('page', $return);
+}
+
+drupal_page_footer();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1152 @@
+<?php
+// $Id: install.php,v 1.113.2.2 2008/02/08 22:00:45 goba Exp $
+
+require_once './includes/install.inc';
+
+define('MAINTENANCE_MODE', 'install');
+
+/**
+ * The Drupal installation happens in a series of steps. We begin by verifying
+ * that the current environment meets our minimum requirements. We then go
+ * on to verify that settings.php is properly configured. From there we
+ * connect to the configured database and verify that it meets our minimum
+ * requirements. Finally we can allow the user to select an installation
+ * profile and complete the installation process.
+ *
+ * @param $phase
+ *   The installation phase we should proceed to.
+ */
+function install_main() {
+  require_once './includes/bootstrap.inc';
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+  // This must go after drupal_bootstrap(), which unsets globals!
+  global $profile, $install_locale, $conf;
+
+  require_once './modules/system/system.install';
+  require_once './includes/file.inc';
+
+  // Ensure correct page headers are sent (e.g. caching)
+  drupal_page_header();
+
+  // Set up $language, so t() caller functions will still work.
+  drupal_init_language();
+
+  // Load module basics (needed for hook invokes).
+  include_once './includes/module.inc';
+  $module_list['system']['filename'] = 'modules/system/system.module';
+  $module_list['filter']['filename'] = 'modules/filter/filter.module';
+  module_list(TRUE, FALSE, FALSE, $module_list);
+  drupal_load('module', 'system');
+  drupal_load('module', 'filter');
+
+  // Set up theme system for the maintenance page.
+  drupal_maintenance_theme();
+
+  // Check existing settings.php.
+  $verify = install_verify_settings();
+
+  if ($verify) {
+    // Since we have a database connection, we use the normal cache system.
+    // This is important, as the installer calls into the Drupal system for
+    // the clean URL checks, so we should maintain the cache properly.
+    require_once './includes/cache.inc';
+    $conf['cache_inc'] = './includes/cache.inc';
+
+    // Establish a connection to the database.
+    require_once './includes/database.inc';
+    db_set_active();
+
+    // Check if Drupal is installed.
+    $task = install_verify_drupal();
+    if ($task == 'done') {
+      install_already_done_error();
+    }
+  }
+  else {
+    // Since no persistent storage is available yet, and functions that check
+    // for cached data will fail, we temporarily replace the normal cache
+    // system with a stubbed-out version that short-circuits the actual
+    // caching process and avoids any errors.
+    require_once './includes/cache-install.inc';
+    $conf['cache_inc'] = './includes/cache-install.inc';
+
+    $task = NULL;
+  }
+
+  // Decide which profile to use.
+  if (!empty($_GET['profile'])) {
+    $profile = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['profile']);
+  }
+  elseif ($profile = install_select_profile()) {
+    install_goto("install.php?profile=$profile");
+  }
+  else {
+    install_no_profile_error();
+  }
+
+  // Load the profile.
+  require_once "./profiles/$profile/$profile.profile";
+
+  // Locale selection
+  if (!empty($_GET['locale'])) {
+    $install_locale = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['locale']);
+  }
+  elseif (($install_locale = install_select_locale($profile)) !== FALSE) {
+    install_goto("install.php?profile=$profile&locale=$install_locale");
+  }
+
+  // Tasks come after the database is set up
+  if (!$task) {
+    // Check the installation requirements for Drupal and this profile.
+    install_check_requirements($profile, $verify);
+
+    // Verify existence of all required modules.
+    $modules = drupal_verify_profile($profile, $install_locale);
+
+    // If any error messages are set now, it means a requirement problem.
+    $messages = drupal_set_message();
+    if (!empty($messages['error'])) {
+      install_task_list('requirements');
+      drupal_set_title(st('Requirements problem'));
+      print theme('install_page', '');
+      exit;
+    }
+
+    // Change the settings.php information if verification failed earlier.
+    // Note: will trigger a redirect if database credentials change.
+    if (!$verify) {
+      install_change_settings($profile, $install_locale);
+    }
+
+    // Install system.module.
+    drupal_install_system();
+    // Save the list of other modules to install for the 'profile-install'
+    // task. variable_set() can be used now that system.module is installed
+    // and drupal is bootstrapped.
+    variable_set('install_profile_modules', array_diff($modules, array('system')));
+  }
+
+  // The database is set up, turn to further tasks.
+  install_tasks($profile, $task);
+}
+
+/**
+ * Verify if Drupal is installed.
+ */
+function install_verify_drupal() {
+  // Read the variable manually using the @ so we don't trigger an error if it fails.
+  $result = @db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task');
+  if ($result) {
+    return unserialize(db_result($result));
+  }
+}
+
+/**
+ * Verify existing settings.php
+ */
+function install_verify_settings() {
+  global $db_prefix, $db_type, $db_url;
+
+  // Verify existing settings (if any).
+  if (!empty($db_url)) {
+    // We need this because we want to run form_get_errors.
+    include_once './includes/form.inc';
+
+    $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
+    $db_user = urldecode($url['user']);
+    $db_pass = isset($url['pass']) ? urldecode($url['pass']) : NULL;
+    $db_host = urldecode($url['host']);
+    $db_port = isset($url['port']) ? urldecode($url['port']) : '';
+    $db_path = ltrim(urldecode($url['path']), '/');
+    $settings_file = './'. conf_path(FALSE, TRUE) .'/settings.php';
+
+    $form_state = array();
+    _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, $form_state);
+    if (!form_get_errors()) {
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Configure and rewrite settings.php.
+ */
+function install_change_settings($profile = 'default', $install_locale = '') {
+  global $db_url, $db_type, $db_prefix;
+
+  $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
+  $db_user = isset($url['user']) ? urldecode($url['user']) : '';
+  $db_pass = isset($url['pass']) ? urldecode($url['pass']) : '';
+  $db_host = isset($url['host']) ? urldecode($url['host']) : '';
+  $db_port = isset($url['port']) ? urldecode($url['port']) : '';
+  $db_path = ltrim(urldecode($url['path']), '/');
+  $conf_path = './'. conf_path(FALSE, TRUE);
+  $settings_file = $conf_path .'/settings.php';
+
+  // We always need this because we want to run form_get_errors.
+  include_once './includes/form.inc';
+  install_task_list('database');
+
+  if ($db_url == 'mysql://username:password@localhost/databasename') {
+    $db_user = $db_pass = $db_path = '';
+  }
+  elseif (!empty($db_url)) {
+    // Do not install over a configured settings.php.
+    install_already_done_error();
+  }
+
+  $output = drupal_get_form('install_settings_form', $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path);
+  drupal_set_title(st('Database configuration'));
+  print theme('install_page', $output);
+  exit;
+}
+
+
+/**
+ * Form API array definition for install_settings.
+ */
+function install_settings_form(&$form_state, $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path) {
+  if (empty($db_host)) {
+    $db_host = 'localhost';
+  }
+  $db_types = drupal_detect_database_types();
+
+  // If both 'mysql' and 'mysqli' are available, we disable 'mysql':
+  if (isset($db_types['mysqli'])) {
+    unset($db_types['mysql']);
+  }
+
+  if (count($db_types) == 0) {
+    $form['no_db_types'] = array(
+      '#value' => st('Your web server does not appear to support any common database types. Check with your hosting provider to see if they offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array('@drupal-databases' => 'http://drupal.org/node/270#database')),
+    );
+  }
+  else {
+    $form['basic_options'] = array(
+      '#type' => 'fieldset',
+      '#title' => st('Basic options'),
+      '#description' => '<p>'. st('To set up your @drupal database, enter the following information.', array('@drupal' => drupal_install_profile_name())) .'</p>',
+    );
+
+    if (count($db_types) > 1) {
+      $form['basic_options']['db_type'] = array(
+        '#type' => 'radios',
+        '#title' => st('Database type'),
+        '#required' => TRUE,
+        '#options' => $db_types,
+        '#default_value' => ($db_type ? $db_type : current($db_types)),
+        '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_name())),
+      );
+      $db_path_description = st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_name()));
+    }
+    else {
+      if (count($db_types) == 1) {
+        $db_types = array_values($db_types);
+        $form['basic_options']['db_type'] = array(
+          '#type' => 'hidden',
+          '#value' => $db_types[0],
+        );
+        $db_path_description = st('The name of the %db_type database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('%db_type' => $db_types[0], '@drupal' => drupal_install_profile_name()));
+      }
+    }
+
+    // Database name
+    $form['basic_options']['db_path'] = array(
+      '#type' => 'textfield',
+      '#title' => st('Database name'),
+      '#default_value' => $db_path,
+      '#size' => 45,
+      '#maxlength' => 45,
+      '#required' => TRUE,
+      '#description' => $db_path_description
+    );
+
+    // Database username
+    $form['basic_options']['db_user'] = array(
+      '#type' => 'textfield',
+      '#title' => st('Database username'),
+      '#default_value' => $db_user,
+      '#size' => 45,
+      '#maxlength' => 45,
+      '#required' => TRUE,
+    );
+
+    // Database username
+    $form['basic_options']['db_pass'] = array(
+      '#type' => 'password',
+      '#title' => st('Database password'),
+      '#default_value' => $db_pass,
+      '#size' => 45,
+      '#maxlength' => 45,
+    );
+
+    $form['advanced_options'] = array(
+      '#type' => 'fieldset',
+      '#title' => st('Advanced options'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider.")
+    );
+
+    // Database host
+    $form['advanced_options']['db_host'] = array(
+      '#type' => 'textfield',
+      '#title' => st('Database host'),
+      '#default_value' => $db_host,
+      '#size' => 45,
+      '#maxlength' => 45,
+      '#required' => TRUE,
+      '#description' => st('If your database is located on a different server, change this.'),
+    );
+
+    // Database port
+    $form['advanced_options']['db_port'] = array(
+      '#type' => 'textfield',
+      '#title' => st('Database port'),
+      '#default_value' => $db_port,
+      '#size' => 45,
+      '#maxlength' => 45,
+      '#description' => st('If your database server is listening to a non-standard port, enter its number.'),
+    );
+
+    // Table prefix
+    $prefix = ($profile == 'default') ? 'drupal_' : $profile .'_';
+    $form['advanced_options']['db_prefix'] = array(
+      '#type' => 'textfield',
+      '#title' => st('Table prefix'),
+      '#default_value' => $db_prefix,
+      '#size' => 45,
+      '#maxlength' => 45,
+      '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_name(), '%prefix' => $prefix)),
+    );
+
+    $form['save'] = array(
+      '#type' => 'submit',
+      '#value' => st('Save and continue'),
+    );
+
+    $form['errors'] = array();
+    $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
+    $form['_db_url'] = array('#type' => 'value');
+    $form['#action'] = "install.php?profile=$profile". ($install_locale ? "&locale=$install_locale" : '');
+    $form['#redirect'] = FALSE;
+  }
+  return $form;
+}
+
+/**
+ * Form API validate for install_settings form.
+ */
+function install_settings_form_validate($form, &$form_state) {
+  global $db_url;
+  _install_settings_form_validate($form_state['values']['db_prefix'], $form_state['values']['db_type'], $form_state['values']['db_user'], $form_state['values']['db_pass'], $form_state['values']['db_host'], $form_state['values']['db_port'], $form_state['values']['db_path'], $form_state['values']['settings_file'], $form_state, $form);
+}
+
+/**
+ * Helper function for install_settings_validate.
+ */
+function _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, &$form_state, $form = NULL) {
+  global $db_url;
+
+  // Verify the table prefix
+  if (!empty($db_prefix) && is_string($db_prefix) && !preg_match('/^[A-Za-z0-9_.]+$/', $db_prefix)) {
+    form_set_error('db_prefix', st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $db_prefix)), 'error');
+  }
+
+  if (!empty($db_port) && !is_numeric($db_port)) {
+    form_set_error('db_port', st('Database port must be a number.'));
+  }
+
+  // Check database type
+  if (!isset($form)) {
+    $_db_url = is_array($db_url) ? $db_url['default'] : $db_url;
+    $db_type = substr($_db_url, 0, strpos($_db_url, '://'));
+  }
+  $databases = drupal_detect_database_types();
+  if (!in_array($db_type, $databases)) {
+    form_set_error('db_type', st("In your %settings_file file you have configured @drupal to use a %db_type server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%db_type' => $db_type)));
+  }
+  else {
+    // Verify
+    $db_url = $db_type .'://'. urlencode($db_user) . ($db_pass ? ':'. urlencode($db_pass) : '') .'@'. ($db_host ? urlencode($db_host) : 'localhost') . ($db_port ? ":$db_port" : '') .'/'. urlencode($db_path);
+    if (isset($form)) {
+      form_set_value($form['_db_url'], $db_url, $form_state);
+    }
+    $success = array();
+
+    $function = 'drupal_test_'. $db_type;
+    if (!$function($db_url, $success)) {
+      if (isset($success['CONNECT'])) {
+        form_set_error('db_type', st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($success, ', '))));
+      }
+      else {
+        form_set_error('db_type', '');
+      }
+    }
+  }
+}
+
+/**
+ * Form API submit for install_settings form.
+ */
+function install_settings_form_submit($form, &$form_state) {
+  global $profile, $install_locale;
+
+  // Update global settings array and save
+  $settings['db_url'] = array(
+    'value'    => $form_state['values']['_db_url'],
+    'required' => TRUE,
+  );
+  $settings['db_prefix'] = array(
+    'value'    => $form_state['values']['db_prefix'],
+    'required' => TRUE,
+  );
+  drupal_rewrite_settings($settings);
+
+  // Continue to install profile step
+  install_goto("install.php?profile=$profile". ($install_locale ? "&locale=$install_locale" : ''));
+}
+
+/**
+ * Find all .profile files.
+ */
+function install_find_profiles() {
+  return file_scan_directory('./profiles', '\.profile$', array('.', '..', 'CVS'), 0, TRUE, 'name', 0);
+}
+
+/**
+ * Allow admin to select which profile to install.
+ *
+ * @return
+ *   The selected profile.
+ */
+function install_select_profile() {
+  include_once './includes/form.inc';
+
+  $profiles = install_find_profiles();
+  // Don't need to choose profile if only one available.
+  if (sizeof($profiles) == 1) {
+    $profile = array_pop($profiles);
+    require_once $profile->filename;
+    return $profile->name;
+  }
+  elseif (sizeof($profiles) > 1) {
+    foreach ($profiles as $profile) {
+      if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) {
+        return $profile->name;
+      }
+    }
+
+    install_task_list('profile-select');
+
+    drupal_set_title(st('Select an installation profile'));
+    print theme('install_page', drupal_get_form('install_select_profile_form', $profiles));
+    exit;
+  }
+}
+
+/**
+ * Form API array definition for the profile selection form.
+ */
+function install_select_profile_form(&$form_state, $profiles) {
+  foreach ($profiles as $profile) {
+    include_once($profile->filename);
+    // Load profile details.
+    $function = $profile->name .'_profile_details';
+    if (function_exists($function)) {
+      $details = $function();
+    }
+    // If set, used defined name. Otherwise use file name.
+    $name = isset($details['name']) ? $details['name'] : $profile->name;
+    $form['profile'][$name] = array(
+      '#type' => 'radio',
+      '#value' => 'default',
+      '#return_value' => $profile->name,
+      '#title' => $name,
+      '#description' => isset($details['description']) ? $details['description'] : '',
+      '#parents' => array('profile'),
+    );
+  }
+  $form['submit'] =  array(
+    '#type' => 'submit',
+    '#value' => st('Save and continue'),
+  );
+  return $form;
+}
+
+/**
+ * Find all .po files for the current profile.
+ */
+function install_find_locales($profilename) {
+  $locales = file_scan_directory('./profiles/'. $profilename .'/translations', '\.po$', array('.', '..', 'CVS'), 0, FALSE);
+  array_unshift($locales, (object) array('name' => 'en'));
+  return $locales;
+}
+
+/**
+ * Allow admin to select which locale to use for the current profile.
+ *
+ * @return
+ *   The selected language.
+ */
+function install_select_locale($profilename) {
+  include_once './includes/file.inc';
+  include_once './includes/form.inc';
+
+  // Find all available locales.
+  $locales = install_find_locales($profilename);
+
+  // If only the built-in (English) language is available,
+  // and we are using the default profile, inform the user
+  // that the installer can be localized. Otherwise we assume
+  // the user know what he is doing.
+  if (count($locales) == 1) {
+    if ($profilename == 'default') {
+      install_task_list('locale-select');
+      drupal_set_title(st('Choose language'));
+      if (!empty($_GET['localize'])) {
+        $output = '<p>'. st('With the addition of an appropriate translation package, this installer is capable of proceeding in another language of your choice. To install and use Drupal in a language other than English:') .'</p>';
+        $output .= '<ul><li>'. st('Determine if <a href="@translations" target="_blank">a translation of this Drupal version</a> is available in your language of choice. A translation is provided via a translation package; each translation package enables the display of a specific version of Drupal in a specific language. Not all languages are available for every version of Drupal.', array('@translations' => 'http://drupal.org/project/translations')) .'</li>';
+        $output .= '<li>'. st('If an alternative translation package of your choice is available, download and extract its contents to your Drupal root directory.') .'</li>';
+        $output .= '<li>'. st('Return to choose language using the second link below and select your desired language from the displayed list. Reloading the page allows the list to automatically adjust to the presence of new translation packages.') .'</li>';
+        $output .= '</ul><p>'. st('Alternatively, to install and use Drupal in English, or to defer the selection of an alternative language until after installation, select the first link below.') .'</p>';
+        $output .= '<p>'. st('How should the installation continue?') .'</p>';
+        $output .= '<ul><li><a href="install.php?profile='. $profilename .'&amp;locale=en">'. st('Continue installation in English') .'</a></li><li><a href="install.php?profile='. $profilename .'">'. st('Return to choose a language') .'</a></li></ul>';
+      }
+      else {
+        $output = '<ul><li><a href="install.php?profile='. $profilename .'&amp;locale=en">'. st('Install Drupal in English') .'</a></li><li><a href="install.php?profile='. $profilename .'&amp;localize=true">'. st('Learn how to install Drupal in other languages') .'</a></li></ul>';
+      }
+      print theme('install_page', $output);
+      exit;
+    }
+    // One language, but not the default profile, assume
+    // the user knows what he is doing.
+    return FALSE;
+  }
+  else {
+    // Allow profile to pre-select the language, skipping the selection.
+    $function = $profilename .'_profile_details';
+    if (function_exists($function)) {
+      $details = $function();
+      if (isset($details['language'])) {
+        foreach ($locales as $locale) {
+          if ($details['language'] == $locale->name) {
+            return $locale->name;
+          }
+        }
+      }
+    }
+
+    foreach ($locales as $locale) {
+      if ($_POST['locale'] == $locale->name) {
+        return $locale->name;
+      }
+    }
+
+    install_task_list('locale-select');
+
+    drupal_set_title(st('Choose language'));
+    print theme('install_page', drupal_get_form('install_select_locale_form', $locales));
+    exit;
+  }
+}
+
+/**
+ * Form API array definition for language selection.
+ */
+function install_select_locale_form(&$form_state, $locales) {
+  include_once './includes/locale.inc';
+  $languages = _locale_get_predefined_list();
+  foreach ($locales as $locale) {
+    // Try to use verbose locale name
+    $name = $locale->name;
+    if (isset($languages[$name])) {
+      $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' '. st('(@language)', array('@language' => $languages[$name][1])) : '');
+    }
+    $form['locale'][$locale->name] = array(
+      '#type' => 'radio',
+      '#return_value' => $locale->name,
+      '#default_value' => ($locale->name == 'en' ? TRUE : FALSE),
+      '#title' => $name . ($locale->name == 'en' ? ' '. st('(built-in)') : ''),
+      '#parents' => array('locale')
+    );
+  }
+  $form['submit'] =  array(
+    '#type' => 'submit',
+    '#value' => st('Select language'),
+  );
+  return $form;
+}
+
+/**
+ * Show an error page when there are no profiles available.
+ */
+function install_no_profile_error() {
+  install_task_list('profile-select');
+  drupal_set_title(st('No profiles available'));
+  print theme('install_page', '<p>'. st('We were unable to find any installer profiles. Installer profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.') .'</p>');
+  exit;
+}
+
+
+/**
+ * Show an error page when Drupal has already been installed.
+ */
+function install_already_done_error() {
+  global $base_url;
+
+  drupal_set_title(st('Drupal already installed'));
+  print theme('install_page', st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url)));
+  exit;
+}
+
+/**
+ * Tasks performed after the database is initialized.
+ */
+function install_tasks($profile, $task) {
+  global $base_url, $install_locale;
+
+  // Bootstrap newly installed Drupal, while preserving existing messages.
+  $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : '';
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+  $_SESSION['messages'] = $messages;
+
+  // URL used to direct page requests.
+  $url = $base_url .'/install.php?locale='. $install_locale .'&profile='. $profile;
+
+  // Build a page for final tasks.
+  if (empty($task)) {
+    variable_set('install_task', 'profile-install');
+    $task = 'profile-install';
+  }
+
+  // We are using a list of if constructs here to allow for
+  // passing from one task to the other in the same request.
+
+  // Install profile modules.
+  if ($task == 'profile-install') {
+    $modules = variable_get('install_profile_modules', array());
+    $files = module_rebuild_cache();
+    variable_del('install_profile_modules');
+    $operations = array();
+    foreach ($modules as $module) {
+      $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name']));
+    }
+    $batch = array(
+      'operations' => $operations,
+      'finished' => '_install_profile_batch_finished',
+      'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_name())),
+      'error_message' => st('The installation has encountered an error.'),
+    );
+    // Start a batch, switch to 'profile-install-batch' task. We need to
+    // set the variable here, because batch_process() redirects.
+    variable_set('install_task', 'profile-install-batch');
+    batch_set($batch);
+    batch_process($url, $url);
+  }
+  // We are running a batch install of the profile's modules.
+  // This might run in multiple HTTP requests, constantly redirecting
+  // to the same address, until the batch finished callback is invoked
+  // and the task advances to 'locale-initial-import'.
+  if ($task == 'profile-install-batch') {
+    include_once 'includes/batch.inc';
+    $output = _batch_page();
+  }
+
+  // Import interface translations for the enabled modules.
+  if ($task == 'locale-initial-import') {
+    if (!empty($install_locale) && ($install_locale != 'en')) {
+      include_once 'includes/locale.inc';
+      // Enable installation language as default site language.
+      locale_add_language($install_locale, NULL, NULL, NULL, NULL, NULL, 1, TRUE);
+      // Collect files to import for this language.
+      $batch = locale_batch_by_language($install_locale, '_install_locale_initial_batch_finished');
+      if (!empty($batch)) {
+        // Remember components we cover in this batch set.
+        variable_set('install_locale_batch_components', $batch['#components']);
+        // Start a batch, switch to 'locale-batch' task. We need to
+        // set the variable here, because batch_process() redirects.
+        variable_set('install_task', 'locale-initial-batch');
+        batch_set($batch);
+        batch_process($url, $url);
+      }
+    }
+    // Found nothing to import or not foreign language, go to next task.
+    $task = 'configure';
+  }
+  if ($task == 'locale-initial-batch') {
+    include_once 'includes/batch.inc';
+    include_once 'includes/locale.inc';
+    $output = _batch_page();
+  }
+
+  if ($task == 'configure') {
+    if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) {
+      // Site already configured: This should never happen, means re-running
+      // the installer, possibly by an attacker after the 'install_task' variable
+      // got accidentally blown somewhere. Stop it now.
+      install_already_done_error();
+    }
+    $form = drupal_get_form('install_configure_form', $url);
+
+    if (!variable_get('site_name', FALSE) && !variable_get('site_mail', FALSE)) {
+      // Not submitted yet: Prepare to display the form.
+      $output = $form;
+      drupal_set_title(st('Configure site'));
+
+      // Warn about settings.php permissions risk
+      $settings_dir = './'. conf_path();
+      $settings_file = $settings_dir .'/settings.php';
+      if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($settings_dir, FILE_NOT_WRITABLE, 'dir')) {
+        drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, please consult the <a href="@handbook_url">on-line handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/getting-started')), 'error');
+      }
+      else {
+        drupal_set_message(st('All necessary changes to %dir and %file have been made. They have been set to read-only for security.', array('%dir' => $settings_dir, '%file' => $settings_file)));
+      }
+
+      // Add JavaScript validation.
+      _user_password_dynamic_validation();
+      drupal_add_js(drupal_get_path('module', 'system') .'/system.js', 'module');
+      // We add these strings as settings because JavaScript translation does not
+      // work on install time.
+      drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail')), 'cleanURL' => array('success' => st('Your server has been successfully tested to support this feature.'), 'failure' => st('Your system configuration does not currently support this feature. The <a href="http://drupal.org/node/15365">handbook page on Clean URLs</a> has additional troubleshooting information.'), 'testing' => st('Testing clean URLs...'))), 'setting');
+      drupal_add_js('
+// Global Killswitch
+if (Drupal.jsEnabled) {
+  $(document).ready(function() {
+    Drupal.cleanURLsInstallCheck();
+    Drupal.setDefaultTimezone();
+  });
+}', 'inline');
+      // Build menu to allow clean URL check.
+      menu_rebuild();
+    }
+
+    else {
+      $task = 'profile';
+    }
+  }
+
+  // If found an unknown task or the 'profile' task, which is
+  // reserved for profiles, hand over the control to the profile,
+  // so it can run any number of custom tasks it defines.
+  if (!in_array($task, install_reserved_tasks())) {
+    $function = $profile .'_profile_tasks';
+    if (function_exists($function)) {
+      // The profile needs to run more code, maybe even more tasks.
+      // $task is sent through as a reference and may be changed!
+      $output = $function($task, $url);
+    }
+
+    // If the profile doesn't move on to a new task we assume
+    // that it is done.
+    if ($task == 'profile') {
+      $task = 'profile-finished';
+    }
+  }
+
+  // Profile custom tasks are done, so let the installer regain
+  // control and proceed with importing the remaining translations.
+  if ($task == 'profile-finished') {
+    if (!empty($install_locale) && ($install_locale != 'en')) {
+      include_once 'includes/locale.inc';
+      // Collect files to import for this language. Skip components
+      // already covered in the initial batch set.
+      $batch = locale_batch_by_language($install_locale, '_install_locale_remaining_batch_finished', variable_get('install_locale_batch_components', array()));
+      // Remove temporary variable.
+      variable_del('install_locale_batch_components');
+      if (!empty($batch)) {
+        // Start a batch, switch to 'locale-remaining-batch' task. We need to
+        // set the variable here, because batch_process() redirects.
+        variable_set('install_task', 'locale-remaining-batch');
+        batch_set($batch);
+        batch_process($url, $url);
+      }
+    }
+    // Found nothing to import or not foreign language, go to next task.
+    $task = 'finished';
+  }
+  if ($task == 'locale-remaining-batch') {
+    include_once 'includes/batch.inc';
+    include_once 'includes/locale.inc';
+    $output = _batch_page();
+  }
+
+  // Display a 'finished' page to user.
+  if ($task == 'finished') {
+    drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
+    $messages = drupal_set_message();
+    $output = '<p>'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'</p>';
+    $output .= '<p>'. (isset($messages['error']) ? st('Please review the messages above before continuing on to <a href="@url">your new site</a>.', array('@url' => url(''))) : st('You may now visit <a href="@url">your new site</a>.', array('@url' => url('')))) .'</p>';
+    $task = 'done';
+  }
+
+  // The end of the install process. Remember profile used.
+  if ($task == 'done') {
+    // Rebuild menu to get content type links registered by the profile,
+    // and possibly any other menu items created through the tasks.
+    menu_rebuild();
+
+    // Register actions declared by any modules.
+    actions_synchronize();
+
+    // Randomize query-strings on css/js files, to hide the fact that
+    // this is a new install, not upgraded yet.
+    _drupal_flush_css_js();
+
+    variable_set('install_profile', $profile);
+  }
+
+  // Set task for user, and remember the task in the database.
+  install_task_list($task);
+  variable_set('install_task', $task);
+
+  // Output page, if some output was required. Otherwise it is possible
+  // that we are printing a JSON page and theme output should not be there.
+  if (isset($output)) {
+    print theme('maintenance_page', $output);
+  }
+}
+
+/**
+ * Batch callback for batch installation of modules.
+ */
+function _install_module_batch($module, $module_name, &$context) {
+  _drupal_install_module($module);
+  // We enable the installed module right away, so that the module will be
+  // loaded by drupal_bootstrap in subsequent batch requests, and other
+  // modules possibly depending on it can safely perform their installation
+  // steps.
+  module_enable(array($module));
+  $context['results'][] = $module;
+  $context['message'] = 'Installed '. $module_name .' module.';
+}
+
+/**
+ * Finished callback for the modules install batch.
+ *
+ * Advance installer task to language import.
+ */
+function _install_profile_batch_finished($success, $results) {
+  variable_set('install_task', 'locale-initial-import');
+}
+
+/**
+ * Finished callback for the first locale import batch.
+ *
+ * Advance installer task to the configure screen.
+ */
+function _install_locale_initial_batch_finished($success, $results) {
+  variable_set('install_task', 'configure');
+}
+
+/**
+ * Finished callback for the second locale import batch.
+ *
+ * Advance installer task to the finished screen.
+ */
+function _install_locale_remaining_batch_finished($success, $results) {
+  variable_set('install_task', 'finished');
+}
+
+/**
+ * The list of reserved tasks to run in the installer.
+ */
+function install_reserved_tasks() {
+  return array('configure', 'profile-install', 'profile-install-batch', 'locale-initial-import', 'locale-initial-batch', 'profile-finished', 'locale-remaining-batch', 'finished', 'done');
+}
+
+/**
+ * Check installation requirements and report any errors.
+ */
+function install_check_requirements($profile, $verify) {
+
+  // If Drupal is not set up already, we need to create a settings file.
+  if (!$verify) {
+    $writable = FALSE;
+    $conf_path = './'. conf_path(FALSE, TRUE);
+    $settings_file = $conf_path .'/settings.php';
+    $file = $conf_path;
+    // Verify that the directory exists.
+    if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) {
+      // Check to see if a settings.php already exists.
+      if (drupal_verify_install_file($settings_file, FILE_EXIST)) {
+        // If it does, make sure it is writable.
+        $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE);
+        $file = $settings_file;
+      }
+      else {
+        // If not, make sure the directory is.
+        $writable = drupal_verify_install_file($conf_path, FILE_READABLE|FILE_WRITABLE, 'dir');
+      }
+    }
+
+    if (!$writable) {
+      drupal_set_message(st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, please consult the <a href="@handbook_url">on-line handbook</a>.', array('@drupal' => drupal_install_profile_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'error');
+    }
+  }
+
+  // Check the other requirements.
+  $requirements = drupal_check_profile($profile);
+  $severity = drupal_requirements_severity($requirements);
+
+  // If there are issues, report them.
+  if ($severity == REQUIREMENT_ERROR) {
+
+    foreach ($requirements as $requirement) {
+      if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
+        $message = $requirement['description'];
+        if (isset($requirement['value']) && $requirement['value']) {
+          $message .= ' ('. st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')';
+        }
+        drupal_set_message($message, 'error');
+      }
+    }
+  }
+  if ($severity == REQUIREMENT_WARNING) {
+
+    foreach ($requirements as $requirement) {
+      if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_WARNING) {
+        $message = $requirement['description'];
+        if (isset($requirement['value']) && $requirement['value']) {
+          $message .= ' ('. st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')';
+        }
+        drupal_set_message($message, 'warning');
+      }
+    }
+  } 
+}
+
+/**
+ * Add the installation task list to the current page.
+ */
+function install_task_list($active = NULL) {
+  // Default list of tasks.
+  $tasks = array(
+    'profile-select'        => st('Choose profile'),
+    'locale-select'         => st('Choose language'),
+    'requirements'          => st('Verify requirements'),
+    'database'              => st('Set up database'),
+    'profile-install-batch' => st('Install profile'),
+    'locale-initial-batch'  => st('Set up translations'),
+    'configure'             => st('Configure site'),
+  );
+
+  $profiles = install_find_profiles();
+  $profile = isset($_GET['profile']) && isset($profiles[$_GET['profile']]) ? $_GET['profile'] : '.';
+  $locales = install_find_locales($profile);
+
+  // If we have only one profile, remove 'Choose profile'
+  // and rename 'Install profile'.
+  if (count($profiles) == 1) {
+    unset($tasks['profile-select']);
+    $tasks['profile-install-batch'] = st('Install site');
+  }
+
+  // Add tasks defined by the profile.
+  if ($profile) {
+    $function = $profile .'_profile_task_list';
+    if (function_exists($function)) {
+      $result = $function();
+      if (is_array($result)) {
+        $tasks += $result;
+      }
+    }
+  }
+
+  if (count($locales) < 2 || empty($_GET['locale']) || $_GET['locale'] == 'en') {
+    // If not required, remove translation import from the task list.
+    unset($tasks['locale-initial-batch']);
+  }
+  else {
+    // If required, add remaining translations import task.
+    $tasks += array('locale-remaining-batch' => st('Finish translations'));
+  }
+
+  // Add finished step as the last task.
+  $tasks += array(
+    'finished'     => st('Finished')
+  );
+
+  // Let the theming function know that 'finished' and 'done'
+  // include everything, so every step is completed.
+  if (in_array($active, array('finished', 'done'))) {
+    $active = NULL;
+  }
+  drupal_set_content('left', theme_task_list($tasks, $active));
+}
+
+/**
+ * Form API array definition for site configuration.
+ */
+function install_configure_form(&$form_state, $url) {
+
+  $form['intro'] = array(
+    '#value' => st('To configure your website, please provide the following information.'),
+    '#weight' => -10,
+  );
+  $form['site_information'] = array(
+    '#type' => 'fieldset',
+    '#title' => st('Site information'),
+    '#collapsible' => FALSE,
+  );
+  $form['site_information']['site_name'] = array(
+    '#type' => 'textfield',
+    '#title' => st('Site name'),
+    '#required' => TRUE,
+    '#weight' => -20,
+  );
+  $form['site_information']['site_mail'] = array(
+    '#type' => 'textfield',
+    '#title' => st('Site e-mail address'),
+    '#default_value' => ini_get('sendmail_from'),
+    '#description' => st("The <em>From</em> address in automated e-mails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this e-mail being flagged as spam.)"),
+    '#required' => TRUE,
+    '#weight' => -15,
+  );
+  $form['admin_account'] = array(
+    '#type' => 'fieldset',
+    '#title' => st('Administrator account'),
+    '#collapsible' => FALSE,
+  );
+  $form['admin_account']['account']['#tree'] = TRUE;
+  $form['admin_account']['markup'] = array(
+    '#value' => '<p class="description">'. st('The administrator account has complete access to the site; it will automatically be granted all permissions and can perform any administrative activity. This will be the only account that can perform certain activities, so keep its credentials safe.') .'</p>',
+    '#weight' => -10,
+  );
+
+  $form['admin_account']['account']['name'] = array('#type' => 'textfield',
+    '#title' => st('Username'),
+    '#maxlength' => USERNAME_MAX_LENGTH,
+    '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'),
+    '#required' => TRUE,
+    '#weight' => -10,
+  );
+
+  $form['admin_account']['account']['mail'] = array('#type' => 'textfield',
+    '#title' => st('E-mail address'),
+    '#maxlength' => EMAIL_MAX_LENGTH,
+    '#description' => st('All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
+    '#required' => TRUE,
+    '#weight' => -5,
+  );
+  $form['admin_account']['account']['pass'] = array(
+    '#type' => 'password_confirm',
+    '#required' => TRUE,
+    '#size' => 25,
+    '#weight' => 0,
+  );
+
+  $form['server_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => st('Server settings'),
+    '#collapsible' => FALSE,
+  );
+  $form['server_settings']['date_default_timezone'] = array(
+    '#type' => 'select',
+    '#title' => st('Default time zone'),
+    '#default_value' => 0,
+    '#options' => _system_zonelist(),
+    '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'),
+    '#weight' => 5,
+  );
+
+  $form['server_settings']['clean_url'] = array(
+    '#type' => 'radios',
+    '#title' => st('Clean URLs'),
+    '#default_value' => 0,
+    '#options' => array(0 => st('Disabled'), 1 => st('Enabled')),
+    '#description' => st('This option makes Drupal emit "clean" URLs (i.e. without <code>?q=</code> in the URL).'),
+    '#disabled' => TRUE,
+    '#prefix' => '<div id="clean-url" class="install">',
+    '#suffix' => '</div>',
+    '#weight' => 10,
+  );
+
+  $form['server_settings']['update_status_module'] = array(
+    '#type' => 'checkboxes',
+    '#title' => st('Update notifications'),
+    '#options' => array(1 => st('Check for updates automatically')),
+    '#default_value' => array(1),
+    '#description' => st('With this option enabled, Drupal will notify you when new releases are available. This will significantly enhance your site\'s security and is <strong>highly recommended</strong>. This requires your site to periodically send anonymous information on its installed components to <a href="@drupal">drupal.org</a>. For more information please see the <a href="@update">update notification information</a>.', array('@drupal' => 'http://drupal.org', '@update' => 'http://drupal.org/handbook/modules/update')),
+    '#weight' => 15,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => st('Save and continue'),
+    '#weight' => 15,
+  );
+  $form['#action'] = $url;
+  $form['#redirect'] = FALSE;
+
+  // Allow the profile to alter this form. $form_state isn't available
+  // here, but to conform to the hook_form_alter() signature, we pass
+  // an empty array.
+  $hook_form_alter = $_GET['profile'] .'_form_alter';
+  if (function_exists($hook_form_alter)) {
+    $hook_form_alter($form, array(), 'install_configure');
+  }
+  return $form;
+}
+
+/**
+ * Form API validate for the site configuration form.
+ */
+function install_configure_form_validate($form, &$form_state) {
+  if ($error = user_validate_name($form_state['values']['account']['name'])) {
+    form_error($form['admin_account']['account']['name'], $error);
+  }
+  if ($error = user_validate_mail($form_state['values']['account']['mail'])) {
+    form_error($form['admin_account']['account']['mail'], $error);
+  }
+  if ($error = user_validate_mail($form_state['values']['site_mail'])) {
+    form_error($form['site_information']['site_mail'], $error);
+  }
+}
+
+/**
+ * Form API submit for the site configuration form.
+ */
+function install_configure_form_submit($form, &$form_state) {
+  global $user;
+
+  variable_set('site_name', $form_state['values']['site_name']);
+  variable_set('site_mail', $form_state['values']['site_mail']);
+  variable_set('date_default_timezone', $form_state['values']['date_default_timezone']);
+
+  // Enable update.module if this option was selected.
+  if ($form_state['values']['update_status_module'][1]) {
+    drupal_install_modules(array('update'));
+  }
+
+  // Turn this off temporarily so that we can pass a password through.
+  variable_set('user_email_verification', FALSE);
+  $form_state['old_values'] = $form_state['values'];
+  $form_state['values'] = $form_state['values']['account'];
+
+  // We precreated user 1 with placeholder values. Let's save the real values.
+  $account = user_load(1);
+  $merge_data = array('init' => $form_state['values']['mail'], 'roles' => array(), 'status' => 1);
+  user_save($account, array_merge($form_state['values'], $merge_data));
+  // Log in the first user.
+  user_authenticate($form_state['values']);
+  $form_state['values'] = $form_state['old_values'];
+  unset($form_state['old_values']);
+  variable_set('user_email_verification', TRUE);
+
+  if (isset($form_state['values']['clean_url'])) {
+    variable_set('clean_url', $form_state['values']['clean_url']);
+  }
+  // The user is now logged in, but has no session ID yet, which
+  // would be required later in the request, so remember it.
+  $user->sid = session_id();
+
+  // Record when this install ran.
+  variable_set('install_time', time());
+}
+
+// Start the installer.
+install_main();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/ahah.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,225 @@
+// $Id: ahah.js,v 1.7.2.1 2008/02/11 14:46:27 goba Exp $
+
+/**
+ * Provides AJAX-like page updating via AHAH (Asynchronous HTML and HTTP).
+ *
+ * AHAH is a method of making a request via Javascript while viewing an HTML
+ * page. The request returns a small chunk of HTML, which is then directly
+ * injected into the page.
+ *
+ * Drupal uses this file to enhance form elements with #ahah[path] and
+ * #ahah[wrapper] properties. If set, this file will automatically be included
+ * to provide AHAH capabilities.
+ */
+
+/**
+ * Attaches the ahah behavior to each ahah form element.
+ */
+Drupal.behaviors.ahah = function(context) {
+  for (var base in Drupal.settings.ahah) {
+    if (!$('#'+ base + '.ahah-processed').size()) {
+      var element_settings = Drupal.settings.ahah[base];
+
+      $(element_settings.selector).each(function() {
+        element_settings.element = this;
+        var ahah = new Drupal.ahah(base, element_settings);
+      });
+
+      $('#'+ base).addClass('ahah-processed');
+    }
+  }
+};
+
+/**
+ * AHAH object.
+ */
+Drupal.ahah = function(base, element_settings) {
+  // Set the properties for this object.
+  this.element = element_settings.element;
+  this.selector = element_settings.selector;
+  this.event = element_settings.event;
+  this.keypress = element_settings.keypress;
+  this.url = element_settings.url;
+  this.wrapper = '#'+ element_settings.wrapper;
+  this.effect = element_settings.effect;
+  this.method = element_settings.method;
+  this.progress = element_settings.progress;
+  this.button = element_settings.button || { };
+
+  if (this.effect == 'none') {
+    this.showEffect = 'show';
+    this.hideEffect = 'hide';
+    this.showSpeed = '';
+  }
+  else if (this.effect == 'fade') {
+    this.showEffect = 'fadeIn';
+    this.hideEffect = 'fadeOut';
+    this.showSpeed = 'slow';
+  }
+  else {
+    this.showEffect = this.effect + 'Toggle';
+    this.hideEffect = this.effect + 'Toggle';
+    this.showSpeed = 'slow';
+  }
+
+  // Record the form action and target, needed for iFrame file uploads.
+  var form = $(this.element).parents('form');
+  this.form_action = form.attr('action');
+  this.form_target = form.attr('target');
+  this.form_encattr = form.attr('encattr');
+
+  // Set the options for the ajaxSubmit function.
+  // The 'this' variable will not persist inside of the options object.
+  var ahah = this;
+  var options = {
+    url: ahah.url,
+    data: ahah.button,
+    beforeSubmit: function(form_values, element_settings, options) {
+      return ahah.beforeSubmit(form_values, element_settings, options);
+    },
+    success: function(response, status) {
+      // Sanity check for browser support (object expected).
+      // When using iFrame uploads, responses must be returned as a string.
+      if (typeof(response) == 'string') {
+        response = Drupal.parseJson(response);
+      }
+      return ahah.success(response, status);
+    },
+    complete: function(response, status) {
+      if (status == 'error' || status == 'parsererror') {
+        return ahah.error(response, ahah.url);
+      }
+    },
+    dataType: 'json',
+    type: 'POST'
+  };
+
+  // Bind the ajaxSubmit function to the element event.
+  $(element_settings.element).bind(element_settings.event, function() {
+    $(element_settings.element).parents('form').ajaxSubmit(options);
+    return false;
+  });
+  // If necessary, enable keyboard submission so that AHAH behaviors
+  // can be triggered through keyboard input as well as e.g. a mousedown
+  // action.
+  if (element_settings.keypress) {
+    $(element_settings.element).keypress(function(event) {
+      // Detect enter key.
+      if (event.keyCode == 13) {
+        $(element_settings.element).trigger(element_settings.event);
+        return false;
+      }
+    });
+  }
+};
+
+/**
+ * Handler for the form redirection submission.
+ */
+Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) {
+  // Disable the element that received the change.
+  $(this.element).addClass('progress-disabled').attr('disabled', true);
+
+  // Insert progressbar or throbber.
+  if (this.progress.type == 'bar') {
+    var progressBar = new Drupal.progressBar('ahah-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
+    if (this.progress.message) {
+      progressBar.setProgress(-1, this.progress.message);
+    }
+    if (this.progress.url) {
+      progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
+    }
+    this.progress.element = $(progressBar.element).addClass('ahah-progress ahah-progress-bar');
+    this.progress.object = progressBar;
+    $(this.element).after(this.progress.element);
+  }
+  else if (this.progress.type == 'throbber') {
+    this.progress.element = $('<div class="ahah-progress ahah-progress-throbber"><div class="throbber">&nbsp;</div></div>');
+    if (this.progress.message) {
+      $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>')
+    }
+    $(this.element).after(this.progress.element);
+  }
+};
+
+/**
+ * Handler for the form redirection completion.
+ */
+Drupal.ahah.prototype.success = function (response, status) {
+  var wrapper = $(this.wrapper);
+  var form = $(this.element).parents('form');
+  // Manually insert HTML into the jQuery object, using $() directly crashes
+  // Safari with long string lengths. http://dev.jquery.com/ticket/1152
+  var new_content = $('<div></div>').html(response.data);
+
+  // Restore the previous action and target to the form.
+  form.attr('action', this.form_action);
+  this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target');
+  this.form_encattr ? form.attr('target', this.form_encattr) : form.removeAttr('encattr');
+
+  // Remove the progress element.
+  if (this.progress.element) {
+    $(this.progress.element).remove();
+  }
+  if (this.progress.object) {
+    this.progress.object.stopMonitoring();
+  }
+  $(this.element).removeClass('progress-disabled').attr('disabled', false);
+
+  // Add the new content to the page.
+  Drupal.freezeHeight();
+  if (this.method == 'replace') {
+    wrapper.empty().append(new_content);
+  }
+  else {
+    wrapper[this.method](new_content);
+  }
+
+  // Immediately hide the new content if we're using any effects.
+  if (this.showEffect != 'show') {
+    new_content.hide();
+  }
+
+  // Determine what effect use and what content will receive the effect, then
+  // show the new content. For browser compatibility, Safari is excluded from
+  // using effects on table rows.
+  if (($.browser.safari && $("tr.ahah-new-content", new_content).size() > 0)) {
+    new_content.show();
+  }
+  else if ($('.ahah-new-content', new_content).size() > 0) {
+    $('.ahah-new-content', new_content).hide();
+    new_content.show();
+    $(".ahah-new-content", new_content)[this.showEffect](this.showSpeed);
+  }
+  else if (this.showEffect != 'show') {
+    new_content[this.showEffect](this.showSpeed);
+  }
+
+  // Attach all javascript behaviors to the new content, if it was successfully
+  // added to the page, this if statement allows #ahah[wrapper] to be optional.
+  if (new_content.parents('html').length > 0) {
+    Drupal.attachBehaviors(new_content);
+  }
+
+  Drupal.unfreezeHeight();
+};
+
+/**
+ * Handler for the form redirection error.
+ */
+Drupal.ahah.prototype.error = function (response, uri) {
+  alert(Drupal.ahahError(response, uri));
+  // Resore the previous action and target to the form.
+  $(this.element).parent('form').attr( { action: this.form_action, target: this.form_target} );
+  // Remove the progress element.
+  if (this.progress.element) {
+    $(this.progress.element).remove();
+  }
+  if (this.progress.object) {
+    this.progress.object.stopMonitoring();
+  }
+  // Undo hide.
+  $(this.wrapper).show();
+  // Re-enable the element.
+  $(this.element).removeClass('progess-disabled').attr('disabled', false);
+};
Binary file misc/arrow-asc.png has changed
Binary file misc/arrow-desc.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/autocomplete.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,298 @@
+// $Id: autocomplete.js,v 1.23 2008/01/04 11:53:21 goba Exp $
+
+/**
+ * Attaches the autocomplete behavior to all required fields
+ */
+Drupal.behaviors.autocomplete = function (context) {
+  var acdb = [];
+  $('input.autocomplete:not(.autocomplete-processed)', context).each(function () {
+    var uri = this.value;
+    if (!acdb[uri]) {
+      acdb[uri] = new Drupal.ACDB(uri);
+    }
+    var input = $('#' + this.id.substr(0, this.id.length - 13))
+      .attr('autocomplete', 'OFF')[0];
+    $(input.form).submit(Drupal.autocompleteSubmit);
+    new Drupal.jsAC(input, acdb[uri]);
+    $(this).addClass('autocomplete-processed');
+  });
+};
+
+/**
+ * Prevents the form from submitting if the suggestions popup is open
+ * and closes the suggestions popup when doing so.
+ */
+Drupal.autocompleteSubmit = function () {
+  return $('#autocomplete').each(function () {
+    this.owner.hidePopup();
+  }).size() == 0;
+};
+
+/**
+ * An AutoComplete object
+ */
+Drupal.jsAC = function (input, db) {
+  var ac = this;
+  this.input = input;
+  this.db = db;
+
+  $(this.input)
+    .keydown(function (event) { return ac.onkeydown(this, event); })
+    .keyup(function (event) { ac.onkeyup(this, event); })
+    .blur(function () { ac.hidePopup(); ac.db.cancel(); });
+
+};
+
+/**
+ * Handler for the "keydown" event
+ */
+Drupal.jsAC.prototype.onkeydown = function (input, e) {
+  if (!e) {
+    e = window.event;
+  }
+  switch (e.keyCode) {
+    case 40: // down arrow
+      this.selectDown();
+      return false;
+    case 38: // up arrow
+      this.selectUp();
+      return false;
+    default: // all other keys
+      return true;
+  }
+};
+
+/**
+ * Handler for the "keyup" event
+ */
+Drupal.jsAC.prototype.onkeyup = function (input, e) {
+  if (!e) {
+    e = window.event;
+  }
+  switch (e.keyCode) {
+    case 16: // shift
+    case 17: // ctrl
+    case 18: // alt
+    case 20: // caps lock
+    case 33: // page up
+    case 34: // page down
+    case 35: // end
+    case 36: // home
+    case 37: // left arrow
+    case 38: // up arrow
+    case 39: // right arrow
+    case 40: // down arrow
+      return true;
+
+    case 9:  // tab
+    case 13: // enter
+    case 27: // esc
+      this.hidePopup(e.keyCode);
+      return true;
+
+    default: // all other keys
+      if (input.value.length > 0)
+        this.populatePopup();
+      else
+        this.hidePopup(e.keyCode);
+      return true;
+  }
+};
+
+/**
+ * Puts the currently highlighted suggestion into the autocomplete field
+ */
+Drupal.jsAC.prototype.select = function (node) {
+  this.input.value = node.autocompleteValue;
+};
+
+/**
+ * Highlights the next suggestion
+ */
+Drupal.jsAC.prototype.selectDown = function () {
+  if (this.selected && this.selected.nextSibling) {
+    this.highlight(this.selected.nextSibling);
+  }
+  else {
+    var lis = $('li', this.popup);
+    if (lis.size() > 0) {
+      this.highlight(lis.get(0));
+    }
+  }
+};
+
+/**
+ * Highlights the previous suggestion
+ */
+Drupal.jsAC.prototype.selectUp = function () {
+  if (this.selected && this.selected.previousSibling) {
+    this.highlight(this.selected.previousSibling);
+  }
+};
+
+/**
+ * Highlights a suggestion
+ */
+Drupal.jsAC.prototype.highlight = function (node) {
+  if (this.selected) {
+    $(this.selected).removeClass('selected');
+  }
+  $(node).addClass('selected');
+  this.selected = node;
+};
+
+/**
+ * Unhighlights a suggestion
+ */
+Drupal.jsAC.prototype.unhighlight = function (node) {
+  $(node).removeClass('selected');
+  this.selected = false;
+};
+
+/**
+ * Hides the autocomplete suggestions
+ */
+Drupal.jsAC.prototype.hidePopup = function (keycode) {
+  // Select item if the right key or mousebutton was pressed
+  if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
+    this.input.value = this.selected.autocompleteValue;
+  }
+  // Hide popup
+  var popup = this.popup;
+  if (popup) {
+    this.popup = null;
+    $(popup).fadeOut('fast', function() { $(popup).remove(); });
+  }
+  this.selected = false;
+};
+
+/**
+ * Positions the suggestions popup and starts a search
+ */
+Drupal.jsAC.prototype.populatePopup = function () {
+  // Show popup
+  if (this.popup) {
+    $(this.popup).remove();
+  }
+  this.selected = false;
+  this.popup = document.createElement('div');
+  this.popup.id = 'autocomplete';
+  this.popup.owner = this;
+  $(this.popup).css({
+    marginTop: this.input.offsetHeight +'px',
+    width: (this.input.offsetWidth - 4) +'px',
+    display: 'none'
+  });
+  $(this.input).before(this.popup);
+
+  // Do search
+  this.db.owner = this;
+  this.db.search(this.input.value);
+};
+
+/**
+ * Fills the suggestion popup with any matches received
+ */
+Drupal.jsAC.prototype.found = function (matches) {
+  // If no value in the textfield, do not show the popup.
+  if (!this.input.value.length) {
+    return false;
+  }
+
+  // Prepare matches
+  var ul = document.createElement('ul');
+  var ac = this;
+  for (key in matches) {
+    var li = document.createElement('li');
+    $(li)
+      .html('<div>'+ matches[key] +'</div>')
+      .mousedown(function () { ac.select(this); })
+      .mouseover(function () { ac.highlight(this); })
+      .mouseout(function () { ac.unhighlight(this); });
+    li.autocompleteValue = key;
+    $(ul).append(li);
+  }
+
+  // Show popup with matches, if any
+  if (this.popup) {
+    if (ul.childNodes.length > 0) {
+      $(this.popup).empty().append(ul).show();
+    }
+    else {
+      $(this.popup).css({visibility: 'hidden'});
+      this.hidePopup();
+    }
+  }
+};
+
+Drupal.jsAC.prototype.setStatus = function (status) {
+  switch (status) {
+    case 'begin':
+      $(this.input).addClass('throbbing');
+      break;
+    case 'cancel':
+    case 'error':
+    case 'found':
+      $(this.input).removeClass('throbbing');
+      break;
+  }
+};
+
+/**
+ * An AutoComplete DataBase object
+ */
+Drupal.ACDB = function (uri) {
+  this.uri = uri;
+  this.delay = 300;
+  this.cache = {};
+};
+
+/**
+ * Performs a cached and delayed search
+ */
+Drupal.ACDB.prototype.search = function (searchString) {
+  var db = this;
+  this.searchString = searchString;
+
+  // See if this key has been searched for before
+  if (this.cache[searchString]) {
+    return this.owner.found(this.cache[searchString]);
+  }
+
+  // Initiate delayed search
+  if (this.timer) {
+    clearTimeout(this.timer);
+  }
+  this.timer = setTimeout(function() {
+    db.owner.setStatus('begin');
+
+    // Ajax GET request for autocompletion
+    $.ajax({
+      type: "GET",
+      url: db.uri +'/'+ Drupal.encodeURIComponent(searchString),
+      dataType: 'json',
+      success: function (matches) {
+        if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
+          db.cache[searchString] = matches;
+          // Verify if these are still the matches the user wants to see
+          if (db.searchString == searchString) {
+            db.owner.found(matches);
+          }
+          db.owner.setStatus('found');
+        }
+      },
+      error: function (xmlhttp) {
+        alert(Drupal.ahahError(xmlhttp, db.uri));
+      }
+    });
+  }, this.delay);
+};
+
+/**
+ * Cancels the current autocomplete request
+ */
+Drupal.ACDB.prototype.cancel = function() {
+  if (this.owner) this.owner.setStatus('cancel');
+  if (this.timer) clearTimeout(this.timer);
+  this.searchString = '';
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/batch.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,38 @@
+// $Id: batch.js,v 1.4 2007/10/21 18:59:01 goba Exp $
+
+/**
+ * Attaches the batch behavior to progress bars.
+ */
+Drupal.behaviors.batch = function (context) {
+  // This behavior attaches by ID, so is only valid once on a page.
+  if ($('#progress.batch-processed').size()) {
+    return;
+  }
+  $('#progress', context).addClass('batch-processed').each(function () {
+    var holder = this;
+    var uri = Drupal.settings.batch.uri;
+    var initMessage = Drupal.settings.batch.initMessage;
+    var errorMessage = Drupal.settings.batch.errorMessage;
+
+    // Success: redirect to the summary.
+    var updateCallback = function (progress, status, pb) {
+      if (progress == 100) {
+        pb.stopMonitoring();
+        window.location = uri+'&op=finished';
+      }
+    };
+
+    var errorCallback = function (pb) {
+      var div = document.createElement('p');
+      div.className = 'error';
+      $(div).html(errorMessage);
+      $(holder).prepend(div);
+      $('#wait').hide();
+    };
+
+    var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback);
+    progress.setProgress(-1, initMessage);
+    $(holder).append(progress.element);
+    progress.startMonitoring(uri+'&op=do', 10);
+  });
+};
Binary file misc/blog.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/collapse.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,77 @@
+// $Id: collapse.js,v 1.17 2008/01/29 10:58:25 goba Exp $
+
+/**
+ * Toggle the visibility of a fieldset using smooth animations
+ */
+Drupal.toggleFieldset = function(fieldset) {
+  if ($(fieldset).is('.collapsed')) {
+    // Action div containers are processed separately because of a IE bug
+    // that alters the default submit button behavior.
+    var content = $('> div:not(.action)', fieldset);
+    $(fieldset).removeClass('collapsed');
+    content.hide();
+    content.slideDown( {
+      duration: 'fast',
+      easing: 'linear',
+      complete: function() {
+        Drupal.collapseScrollIntoView(this.parentNode);
+        this.parentNode.animating = false;
+        $('div.action', fieldset).show();
+      },
+      step: function() {
+        // Scroll the fieldset into view
+        Drupal.collapseScrollIntoView(this.parentNode);
+      }
+    });
+  }
+  else {
+    $('div.action', fieldset).hide();
+    var content = $('> div:not(.action)', fieldset).slideUp('fast', function() {
+      $(this.parentNode).addClass('collapsed');
+      this.parentNode.animating = false;
+    });
+  }
+};
+
+/**
+ * Scroll a given fieldset into view as much as possible.
+ */
+Drupal.collapseScrollIntoView = function (node) {
+  var h = self.innerHeight || document.documentElement.clientHeight || $('body')[0].clientHeight || 0;
+  var offset = self.pageYOffset || document.documentElement.scrollTop || $('body')[0].scrollTop || 0;
+  var posY = $(node).offset().top;
+  var fudge = 55;
+  if (posY + node.offsetHeight + fudge > h + offset) {
+    if (node.offsetHeight > h) {
+      window.scrollTo(0, posY);
+    } else {
+      window.scrollTo(0, posY + node.offsetHeight - h + fudge);
+    }
+  }
+};
+
+Drupal.behaviors.collapse = function (context) {
+  $('fieldset.collapsible > legend:not(.collapse-processed)', context).each(function() {
+    var fieldset = $(this.parentNode);
+    // Expand if there are errors inside
+    if ($('input.error, textarea.error, select.error', fieldset).size() > 0) {
+      fieldset.removeClass('collapsed');
+    }
+
+    // Turn the legend into a clickable link and wrap the contents of the fieldset
+    // in a div for easier animation
+    var text = this.innerHTML;
+      $(this).empty().append($('<a href="#">'+ text +'</a>').click(function() {
+        var fieldset = $(this).parents('fieldset:first')[0];
+        // Don't animate multiple times
+        if (!fieldset.animating) {
+          fieldset.animating = true;
+          Drupal.toggleFieldset(fieldset);
+        }
+        return false;
+      }))
+      .after($('<div class="fieldset-wrapper"></div>')
+      .append(fieldset.children(':not(legend):not(.action)')))
+      .addClass('collapse-processed');
+  });
+};
Binary file misc/draggable.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/drupal.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,295 @@
+// $Id: drupal.js,v 1.41.2.1 2008/02/06 12:18:04 goba Exp $
+
+var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} };
+
+/**
+ * Set the variable that indicates if JavaScript behaviors should be applied
+ */
+Drupal.jsEnabled = document.getElementsByTagName && document.createElement && document.createTextNode && document.documentElement && document.getElementById;
+
+/**
+ * Attach all registered behaviors to a page element.
+ *
+ * Behaviors are event-triggered actions that attach to page elements, enhancing
+ * default non-Javascript UIs. Behaviors are registered in the Drupal.behaviors
+ * object as follows:
+ * @code
+ *    Drupal.behaviors.behaviorName = function () {
+ *      ...
+ *    };
+ * @endcode
+ *
+ * Drupal.attachBehaviors is added below to the jQuery ready event and so
+ * runs on initial page load. Developers implementing AHAH/AJAX in their
+ * solutions should also call this function after new page content has been
+ * loaded, feeding in an element to be processed, in order to attach all
+ * behaviors to the new content.
+ *
+ * Behaviors should use a class in the form behaviorName-processed to ensure
+ * the behavior is attached only once to a given element. (Doing so enables
+ * the reprocessing of given elements, which may be needed on occasion despite
+ * the ability to limit behavior attachment to a particular element.)
+ *
+ * @param context
+ *   An element to attach behaviors to. If none is given, the document element
+ *   is used.
+ */
+Drupal.attachBehaviors = function(context) {
+  context = context || document;
+  if (Drupal.jsEnabled) {
+    // Execute all of them.
+    jQuery.each(Drupal.behaviors, function() {
+      this(context);
+    });
+  }
+};
+
+/**
+ * Encode special characters in a plain-text string for display as HTML.
+ */
+Drupal.checkPlain = function(str) {
+  str = String(str);
+  var replace = { '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' };
+  for (var character in replace) {
+    str = str.replace(character, replace[character]);
+  }
+  return str;
+};
+
+/**
+ * Translate strings to the page language or a given language.
+ *
+ * See the documentation of the server-side t() function for further details.
+ *
+ * @param str
+ *   A string containing the English string to translate.
+ * @param args
+ *   An object of replacements pairs to make after translation. Incidences
+ *   of any key in this array are replaced with the corresponding value.
+ *   Based on the first character of the key, the value is escaped and/or themed:
+ *    - !variable: inserted as is
+ *    - @variable: escape plain text to HTML (Drupal.checkPlain)
+ *    - %variable: escape text and theme as a placeholder for user-submitted
+ *      content (checkPlain + Drupal.theme('placeholder'))
+ * @return
+ *   The translated string.
+ */
+Drupal.t = function(str, args) {
+  // Fetch the localized version of the string.
+  if (Drupal.locale.strings && Drupal.locale.strings[str]) {
+    str = Drupal.locale.strings[str];
+  }
+
+  if (args) {
+    // Transform arguments before inserting them
+    for (var key in args) {
+      switch (key.charAt(0)) {
+        // Escaped only
+        case '@':
+          args[key] = Drupal.checkPlain(args[key]);
+        break;
+        // Pass-through
+        case '!':
+          break;
+        // Escaped and placeholder
+        case '%':
+        default:
+          args[key] = Drupal.theme('placeholder', args[key]);
+          break;
+      }
+      str = str.replace(key, args[key]);
+    }
+  }
+  return str;
+};
+
+/**
+ * Format a string containing a count of items.
+ *
+ * This function ensures that the string is pluralized correctly. Since Drupal.t() is
+ * called by this function, make sure not to pass already-localized strings to it.
+ *
+ * See the documentation of the server-side format_plural() function for further details.
+ *
+ * @param count
+ *   The item count to display.
+ * @param singular
+ *   The string for the singular case. Please make sure it is clear this is
+ *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
+ *   Do not use @count in the singular string.
+ * @param plural
+ *   The string for the plural case. Please make sure it is clear this is plural,
+ *   to ease translation. Use @count in place of the item count, as in "@count
+ *   new comments".
+ * @param args
+ *   An object of replacements pairs to make after translation. Incidences
+ *   of any key in this array are replaced with the corresponding value.
+ *   Based on the first character of the key, the value is escaped and/or themed:
+ *    - !variable: inserted as is
+ *    - @variable: escape plain text to HTML (Drupal.checkPlain)
+ *    - %variable: escape text and theme as a placeholder for user-submitted
+ *      content (checkPlain + Drupal.theme('placeholder'))
+ *   Note that you do not need to include @count in this array.
+ *   This replacement is done automatically for the plural case.
+ * @return
+ *   A translated string.
+ */
+Drupal.formatPlural = function(count, singular, plural, args) {
+  var args = args || {};
+  args['@count'] = count;
+  // Determine the index of the plural form.
+  var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1);
+
+  if (index == 0) {
+    return Drupal.t(singular, args);
+  }
+  else if (index == 1) {
+    return Drupal.t(plural, args);
+  }
+  else {
+    args['@count['+ index +']'] = args['@count'];
+    delete args['@count'];
+    return Drupal.t(plural.replace('@count', '@count['+ index +']'));
+  }
+};
+
+/**
+ * Generate the themed representation of a Drupal object.
+ *
+ * All requests for themed output must go through this function. It examines
+ * the request and routes it to the appropriate theme function. If the current
+ * theme does not provide an override function, the generic theme function is
+ * called.
+ *
+ * For example, to retrieve the HTML that is output by theme_placeholder(text),
+ * call Drupal.theme('placeholder', text).
+ *
+ * @param func
+ *   The name of the theme function to call.
+ * @param ...
+ *   Additional arguments to pass along to the theme function.
+ * @return
+ *   Any data the theme function returns. This could be a plain HTML string,
+ *   but also a complex object.
+ */
+Drupal.theme = function(func) {
+  for (var i = 1, args = []; i < arguments.length; i++) {
+    args.push(arguments[i]);
+  }
+
+  return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args);
+};
+
+/**
+ * Parse a JSON response.
+ *
+ * The result is either the JSON object, or an object with 'status' 0 and 'data' an error message.
+ */
+Drupal.parseJson = function (data) {
+  if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
+    return { status: 0, data: data.length ? data : Drupal.t('Unspecified error') };
+  }
+  return eval('(' + data + ');');
+};
+
+/**
+ * Freeze the current body height (as minimum height). Used to prevent
+ * unnecessary upwards scrolling when doing DOM manipulations.
+ */
+Drupal.freezeHeight = function () {
+  Drupal.unfreezeHeight();
+  var div = document.createElement('div');
+  $(div).css({
+    position: 'absolute',
+    top: '0px',
+    left: '0px',
+    width: '1px',
+    height: $('body').css('height')
+  }).attr('id', 'freeze-height');
+  $('body').append(div);
+};
+
+/**
+ * Unfreeze the body height
+ */
+Drupal.unfreezeHeight = function () {
+  $('#freeze-height').remove();
+};
+
+/**
+ * Wrapper to address the mod_rewrite url encoding bug
+ * (equivalent of drupal_urlencode() in PHP).
+ */
+Drupal.encodeURIComponent = function (item, uri) {
+  uri = uri || location.href;
+  item = encodeURIComponent(item).replace(/%2F/g, '/');
+  return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
+};
+
+/**
+ * Get the text selection in a textarea.
+ */
+Drupal.getSelection = function (element) {
+  if (typeof(element.selectionStart) != 'number' && document.selection) {
+    // The current selection
+    var range1 = document.selection.createRange();
+    var range2 = range1.duplicate();
+    // Select all text.
+    range2.moveToElementText(element);
+    // Now move 'dummy' end point to end point of original range.
+    range2.setEndPoint('EndToEnd', range1);
+    // Now we can calculate start and end points.
+    var start = range2.text.length - range1.text.length;
+    var end = start + range1.text.length;
+    return { 'start': start, 'end': end };
+  }
+  return { 'start': element.selectionStart, 'end': element.selectionEnd };
+};
+
+/**
+ * Build an error message from ahah response.
+ */
+Drupal.ahahError = function(xmlhttp, uri) {
+  if (xmlhttp.status == 200) {
+    if (jQuery.trim($(xmlhttp.responseText).text())) {
+      var message = Drupal.t("An error occurred. \n@uri\n@text", {'@uri': uri, '@text': xmlhttp.responseText });
+    }
+    else {
+      var message = Drupal.t("An error occurred. \n@uri\n(no information available).", {'@uri': uri, '@text': xmlhttp.responseText });
+    }
+  }
+  else {
+    var message = Drupal.t("An HTTP error @status occurred. \n@uri", {'@uri': uri, '@status': xmlhttp.status });
+  }
+  return message;
+}
+
+// Global Killswitch on the <html> element
+if (Drupal.jsEnabled) {
+  // Global Killswitch on the <html> element
+  document.documentElement.className = 'js';
+  // 'js enabled' cookie
+  document.cookie = 'has_js=1; path=/';
+  // Attach all behaviors.
+  $(document).ready(function() {
+    Drupal.attachBehaviors(this);
+  });
+}
+
+/**
+ * The default themes.
+ */
+Drupal.theme.prototype = {
+
+  /**
+   * Formats text for emphasized display in a placeholder inside a sentence.
+   *
+   * @param str
+   *   The text to format (plain-text).
+   * @return
+   *   The formatted text (html).
+   */
+  placeholder: function(str) {
+    return '<em>' + Drupal.checkPlain(str) + '</em>';
+  }
+};
Binary file misc/druplicon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/farbtastic/farbtastic.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,35 @@
+/* $Id: farbtastic.css,v 1.3 2007/04/13 07:33:23 dries Exp $ */
+
+.farbtastic {
+  position: relative;
+}
+.farbtastic * {
+  position: absolute;
+  cursor: crosshair;
+}
+.farbtastic, .farbtastic .wheel {
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .color, .farbtastic .overlay {
+  top: 47px;
+  left: 47px;
+  width: 101px;
+  height: 101px;
+}
+.farbtastic .wheel {
+  background: url(wheel.png) no-repeat;
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .overlay {
+  background: url(mask.png) no-repeat;
+}
+.farbtastic .marker {
+  width: 17px;
+  height: 17px;
+  margin: -8px 0 0 -8px;
+  overflow: hidden;
+  background: url(marker.png) no-repeat;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/farbtastic/farbtastic.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,329 @@
+// $Id: farbtastic.js,v 1.4 2007/06/01 09:05:45 unconed Exp $
+// Farbtastic 1.2
+
+jQuery.fn.farbtastic = function (callback) {
+  $.farbtastic(this, callback);
+  return this;
+};
+
+jQuery.farbtastic = function (container, callback) {
+  var container = $(container).get(0);
+  return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback));
+};
+
+jQuery._farbtastic = function (container, callback) {
+  // Store farbtastic object
+  var fb = this;
+
+  // Insert markup
+  $(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>');
+  var e = $('.farbtastic', container);
+  fb.wheel = $('.wheel', container).get(0);
+  // Dimensions
+  fb.radius = 84;
+  fb.square = 100;
+  fb.width = 194;
+
+  // Fix background PNGs in IE6
+  if (navigator.appVersion.match(/MSIE [0-6]\./)) {
+    $('*', e).each(function () {
+      if (this.currentStyle.backgroundImage != 'none') {
+        var image = this.currentStyle.backgroundImage;
+        image = this.currentStyle.backgroundImage.substring(5, image.length - 2);
+        $(this).css({
+          'backgroundImage': 'none',
+          'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
+        });
+      }
+    });
+  }
+
+  /**
+   * Link to the given element(s) or callback.
+   */
+  fb.linkTo = function (callback) {
+    // Unbind previous nodes
+    if (typeof fb.callback == 'object') {
+      $(fb.callback).unbind('keyup', fb.updateValue);
+    }
+
+    // Reset color
+    fb.color = null;
+
+    // Bind callback or elements
+    if (typeof callback == 'function') {
+      fb.callback = callback;
+    }
+    else if (typeof callback == 'object' || typeof callback == 'string') {
+      fb.callback = $(callback);
+      fb.callback.bind('keyup', fb.updateValue);
+      if (fb.callback.get(0).value) {
+        fb.setColor(fb.callback.get(0).value);
+      }
+    }
+    return this;
+  };
+  fb.updateValue = function (event) {
+    if (this.value && this.value != fb.color) {
+      fb.setColor(this.value);
+    }
+  };
+
+  /**
+   * Change color with HTML syntax #123456
+   */
+  fb.setColor = function (color) {
+    var unpack = fb.unpack(color);
+    if (fb.color != color && unpack) {
+      fb.color = color;
+      fb.rgb = unpack;
+      fb.hsl = fb.RGBToHSL(fb.rgb);
+      fb.updateDisplay();
+    }
+    return this;
+  };
+
+  /**
+   * Change color with HSL triplet [0..1, 0..1, 0..1]
+   */
+  fb.setHSL = function (hsl) {
+    fb.hsl = hsl;
+    fb.rgb = fb.HSLToRGB(hsl);
+    fb.color = fb.pack(fb.rgb);
+    fb.updateDisplay();
+    return this;
+  };
+
+  /////////////////////////////////////////////////////
+
+  /**
+   * Retrieve the coordinates of the given event relative to the center
+   * of the widget.
+   */
+  fb.widgetCoords = function (event) {
+    var x, y;
+    var el = event.target || event.srcElement;
+    var reference = fb.wheel;
+
+    if (typeof event.offsetX != 'undefined') {
+      // Use offset coordinates and find common offsetParent
+      var pos = { x: event.offsetX, y: event.offsetY };
+
+      // Send the coordinates upwards through the offsetParent chain.
+      var e = el;
+      while (e) {
+        e.mouseX = pos.x;
+        e.mouseY = pos.y;
+        pos.x += e.offsetLeft;
+        pos.y += e.offsetTop;
+        e = e.offsetParent;
+      }
+
+      // Look for the coordinates starting from the wheel widget.
+      var e = reference;
+      var offset = { x: 0, y: 0 };
+      while (e) {
+        if (typeof e.mouseX != 'undefined') {
+          x = e.mouseX - offset.x;
+          y = e.mouseY - offset.y;
+          break;
+        }
+        offset.x += e.offsetLeft;
+        offset.y += e.offsetTop;
+        e = e.offsetParent;
+      }
+
+      // Reset stored coordinates
+      e = el;
+      while (e) {
+        e.mouseX = undefined;
+        e.mouseY = undefined;
+        e = e.offsetParent;
+      }
+    }
+    else {
+      // Use absolute coordinates
+      var pos = fb.absolutePosition(reference);
+      x = (event.pageX || 0*(event.clientX + $('html').get(0).scrollLeft)) - pos.x;
+      y = (event.pageY || 0*(event.clientY + $('html').get(0).scrollTop)) - pos.y;
+    }
+    // Subtract distance to middle
+    return { x: x - fb.width / 2, y: y - fb.width / 2 };
+  };
+
+  /**
+   * Mousedown handler
+   */
+  fb.mousedown = function (event) {
+    // Capture mouse
+    if (!document.dragging) {
+      $(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup);
+      document.dragging = true;
+    }
+
+    // Check which area is being dragged
+    var pos = fb.widgetCoords(event);
+    fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square;
+
+    // Process
+    fb.mousemove(event);
+    return false;
+  };
+
+  /**
+   * Mousemove handler
+   */
+  fb.mousemove = function (event) {
+    // Get coordinates relative to color picker center
+    var pos = fb.widgetCoords(event);
+
+    // Set new HSL parameters
+    if (fb.circleDrag) {
+      var hue = Math.atan2(pos.x, -pos.y) / 6.28;
+      if (hue < 0) hue += 1;
+      fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]);
+    }
+    else {
+      var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5));
+      var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5));
+      fb.setHSL([fb.hsl[0], sat, lum]);
+    }
+    return false;
+  };
+
+  /**
+   * Mouseup handler
+   */
+  fb.mouseup = function () {
+    // Uncapture mouse
+    $(document).unbind('mousemove', fb.mousemove);
+    $(document).unbind('mouseup', fb.mouseup);
+    document.dragging = false;
+  };
+
+  /**
+   * Update the markers and styles
+   */
+  fb.updateDisplay = function () {
+    // Markers
+    var angle = fb.hsl[0] * 6.28;
+    $('.h-marker', e).css({
+      left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px',
+      top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'
+    });
+
+    $('.sl-marker', e).css({
+      left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px',
+      top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'
+    });
+
+    // Saturation/Luminance gradient
+    $('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])));
+
+    // Linked elements or callback
+    if (typeof fb.callback == 'object') {
+      // Set background/foreground color
+      $(fb.callback).css({
+        backgroundColor: fb.color,
+        color: fb.hsl[2] > 0.5 ? '#000' : '#fff'
+      });
+
+      // Change linked value
+      $(fb.callback).each(function() {
+        if (this.value && this.value != fb.color) {
+          this.value = fb.color;
+        }
+      });
+    }
+    else if (typeof fb.callback == 'function') {
+      fb.callback.call(fb, fb.color);
+    }
+  };
+
+  /**
+   * Get absolute position of element
+   */
+  fb.absolutePosition = function (el) {
+    var r = { x: el.offsetLeft, y: el.offsetTop };
+    // Resolve relative to offsetParent
+    if (el.offsetParent) {
+      var tmp = fb.absolutePosition(el.offsetParent);
+      r.x += tmp.x;
+      r.y += tmp.y;
+    }
+    return r;
+  };
+
+  /* Various color utility functions */
+  fb.pack = function (rgb) {
+    var r = Math.round(rgb[0] * 255);
+    var g = Math.round(rgb[1] * 255);
+    var b = Math.round(rgb[2] * 255);
+    return '#' + (r < 16 ? '0' : '') + r.toString(16) +
+           (g < 16 ? '0' : '') + g.toString(16) +
+           (b < 16 ? '0' : '') + b.toString(16);
+  };
+
+  fb.unpack = function (color) {
+    if (color.length == 7) {
+      return [parseInt('0x' + color.substring(1, 3)) / 255,
+        parseInt('0x' + color.substring(3, 5)) / 255,
+        parseInt('0x' + color.substring(5, 7)) / 255];
+    }
+    else if (color.length == 4) {
+      return [parseInt('0x' + color.substring(1, 2)) / 15,
+        parseInt('0x' + color.substring(2, 3)) / 15,
+        parseInt('0x' + color.substring(3, 4)) / 15];
+    }
+  };
+
+  fb.HSLToRGB = function (hsl) {
+    var m1, m2, r, g, b;
+    var h = hsl[0], s = hsl[1], l = hsl[2];
+    m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
+    m1 = l * 2 - m2;
+    return [this.hueToRGB(m1, m2, h+0.33333),
+        this.hueToRGB(m1, m2, h),
+        this.hueToRGB(m1, m2, h-0.33333)];
+  };
+
+  fb.hueToRGB = function (m1, m2, h) {
+    h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
+    if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
+    if (h * 2 < 1) return m2;
+    if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
+    return m1;
+  };
+
+  fb.RGBToHSL = function (rgb) {
+    var min, max, delta, h, s, l;
+    var r = rgb[0], g = rgb[1], b = rgb[2];
+    min = Math.min(r, Math.min(g, b));
+    max = Math.max(r, Math.max(g, b));
+    delta = max - min;
+    l = (min + max) / 2;
+    s = 0;
+    if (l > 0 && l < 1) {
+      s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
+    }
+    h = 0;
+    if (delta > 0) {
+      if (max == r && max != g) h += (g - b) / delta;
+      if (max == g && max != b) h += (2 + (b - r) / delta);
+      if (max == b && max != r) h += (4 + (r - g) / delta);
+      h /= 6;
+    }
+    return [h, s, l];
+  };
+
+  // Install mousedown handler (the others are set on the document on-demand)
+  $('*', e).mousedown(fb.mousedown);
+
+    // Init color
+  fb.setColor('#000000');
+
+  // Set linked elements/callback
+  if (callback) {
+    fb.linkTo(callback);
+  }
+};
\ No newline at end of file
Binary file misc/farbtastic/marker.png has changed
Binary file misc/farbtastic/mask.png has changed
Binary file misc/farbtastic/wheel.png has changed
Binary file misc/favicon.ico has changed
Binary file misc/feed.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/form.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,10 @@
+// $Id: form.js,v 1.1 2007/09/12 18:29:32 goba Exp $
+
+Drupal.behaviors.multiselectSelector = function() {
+  // Automatically selects the right radio button in a multiselect control.
+  $('.multiselect select:not(.multiselectSelector-processed)')
+    .addClass('multiselectSelector-processed').change(function() {
+      $('.multiselect input:radio[value="'+ this.id.substr(5) +'"]')
+        .attr('checked', true);
+  });
+};
Binary file misc/forum-closed.png has changed
Binary file misc/forum-default.png has changed
Binary file misc/forum-hot-new.png has changed
Binary file misc/forum-hot.png has changed
Binary file misc/forum-new.png has changed
Binary file misc/forum-sticky.png has changed
Binary file misc/grippie.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/jquery.form.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,13 @@
+// $Id: jquery.form.js,v 1.2 2007/11/19 10:05:48 goba Exp $
+
+/*
+ * jQuery Form Plugin
+ * version: 2.01 (10/31/2007)
+ * @requires jQuery v1.1 or later
+ *
+ * Examples at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(5($){$.7.1j=5(o){2(P o==\'5\')o={L:o};o=$.2h({1h:4.X(\'2i\')||1E.2u.3D(),I:4.X(\'2g\')||\'29\'},o||{});3 p={};$.M.N(\'R.2P.2L\',[4,o,p]);2(p.1Q)6 4;3 a=4.1z(o.2r);2(o.V){H(3 n 3u o.V)a.C({z:n,A:o.V[n]})}2(o.28&&o.28(a,4,o)===E)6 4;$.M.N(\'R.K.36\',[a,4,o,p]);2(p.1Q)6 4;3 q=$.1x(a);2(o.I.31()==\'29\'){o.1h+=(o.1h.2Z(\'?\')>=0?\'&\':\'?\')+q;o.V=B}8 o.V=q;3 r=4,U=[];2(o.1r)U.C(5(){r.1r()});2(o.1o)U.C(5(){r.1o()});2(!o.18&&o.14){3 u=o.L||5(){};U.C(5(a){2(4.1N)$(o.14).X("1M",a).1N().D(u,1L);8 $(o.14).2t(a).D(u,1L)})}8 2(o.L)U.C(o.L);o.L=5(a,b){H(3 i=0,F=U.G;i<F;i++)U[i](a,b,r)};3 v=$(\'19:3v\',4).15();3 w=E;H(3 j=0;j<v.G;j++)2(v[j])w=T;2(o.2f||w){2($.1i.3o&&o.2a)$.3l(o.2a,1l);8 1l()}8 $.3h(o);$.M.N(\'R.K.3f\',[4,o]);6 4;5 1l(){3 d=r[0];3 f=$.2h({},$.39,o);3 h=\'35\'+$.7.1j.1a++;3 i=$(\'<2f 33="\'+h+\'" z="\'+h+\'" />\');3 j=i[0];3 k=$.1i.20&&1E.20.30()<9;2($.1i.1X||k)j.2Y=\'2W:E;1w.2U("");\';i.2S({2R:\'2Q\',23:\'-24\',1R:\'-24\'});3 l={Z:B,1b:B,2K:0,2J:\'n/a\',2H:5(){},2F:5(){},2E:5(){}};3 g=f.2B;2(g&&!$.1O++)$.M.N("2x");2(g)$.M.N("2w",[l,f]);3 m=0;3 n=0;1f(5(){i.2v(\'1n\');j.1K?j.1K(\'1J\',12):j.2s(\'1I\',12,E);3 a=d.1H?\'1H\':\'2q\';3 t=r.X(\'14\');r.X({14:h,2g:\'3C\',2i:f.1h});d[a]=\'3B/R-V\';2(f.1G)1f(5(){n=T;12()},f.1G);d.K();r.X(\'14\',t)},10);5 12(){2(m++)6;j.2o?j.2o(\'1J\',12):j.3A(\'1I\',12,E);3 a=T;3z{2(n)3x\'1G\';3 b,O;O=j.2n?j.2n.1w:j.2l?j.2l:j.1w;l.Z=O.1n?O.1n.1M:B;l.1b=O.2k?O.2k:O;2(f.18==\'2j\'||f.18==\'3s\'){3 c=O.1D(\'1C\')[0];b=c?c.A:l.Z;2(f.18==\'2j\')3r("V = "+b);8 $.3q(b)}8 2(f.18==\'2m\'){b=l.1b;2(!b&&l.Z!=B)b=2d(l.Z)}8{b=l.Z}}3p(e){a=E;$.3n(f,l,\'2b\',e)}2(a){f.L(b,\'L\');2(g)$.M.N("3m",[l,f])}2(g)$.M.N("3k",[l,f]);2(g&&!--$.1O)$.M.N("3j");2(f.27)f.27(l,a?\'L\':\'2b\');1f(5(){i.3i();l.1b=B},3g)};5 2d(s,a){2(1E.26){a=25 26(\'3d.3c\');a.3b=\'E\';a.3a(s)}8 a=(25 38()).37(s,\'1A/2m\');6(a&&a.22&&a.22.1e!=\'34\')?a:B}}};$.7.1j.1a=0;$.7.W=5(a){6 4.21().K(1m).D(5(){4.1u=$.7.W.1a++;$.7.W.1t[4.1u]=a;$(":K,19:Y",4).1Z(1s)})};$.7.W.1a=1;$.7.W.1t={};5 1s(e){3 a=4.R;a.Q=4;2(4.I==\'Y\'){2(e.1Y!=S){a.11=e.1Y;a.16=e.2X}8 2(P $.7.1U==\'5\'){3 b=$(4).1U();a.11=e.1V-b.1R;a.16=e.1W-b.23}8{a.11=e.1V-4.2V;a.16=e.1W-4.32}}1f(5(){a.Q=a.11=a.16=B},10)};5 1m(){3 a=4.1u;3 b=$.7.W.1t[a];$(4).1j(b);6 E};$.7.21=5(){4.1T(\'K\',1m);6 4.D(5(){$(":K,19:Y",4).1T(\'1Z\',1s)})};$.7.1z=5(b){3 a=[];2(4.G==0)6 a;3 c=4[0];3 d=b?c.1D(\'*\'):c.2T;2(!d)6 a;H(3 i=0,F=d.G;i<F;i++){3 e=d[i];3 n=e.z;2(!n)1v;2(b&&c.Q&&e.I=="Y"){2(!e.1d&&c.Q==e)a.C({z:n+\'.x\',A:c.11},{z:n+\'.y\',A:c.16});1v}3 v=$.15(e,T);2(v&&v.1c==1g){H(3 j=0,1S=v.G;j<1S;j++)a.C({z:n,A:v[j]})}8 2(v!==B&&P v!=\'S\')a.C({z:n,A:v})}2(!b&&c.Q){3 f=c.1D("19");H(3 i=0,F=f.G;i<F;i++){3 g=f[i];3 n=g.z;2(n&&!g.1d&&g.I=="Y"&&c.Q==g)a.C({z:n+\'.x\',A:c.11},{z:n+\'.y\',A:c.16})}}6 a};$.7.2O=5(a){6 $.1x(4.1z(a))};$.7.2N=5(b){3 a=[];4.D(5(){3 n=4.z;2(!n)6;3 v=$.15(4,b);2(v&&v.1c==1g){H(3 i=0,F=v.G;i<F;i++)a.C({z:n,A:v[i]})}8 2(v!==B&&P v!=\'S\')a.C({z:4.z,A:v})});6 $.1x(a)};$.7.15=5(a){H(3 b=[],i=0,F=4.G;i<F;i++){3 c=4[i];3 v=$.15(c,a);2(v===B||P v==\'S\'||(v.1c==1g&&!v.G))1v;v.1c==1g?$.3e(b,v):b.C(v)}6 b};$.15=5(b,c){3 n=b.z,t=b.I,13=b.1e.1F();2(P c==\'S\')c=T;2(c&&(!n||b.1d||t==\'17\'||t==\'2M\'||(t==\'1q\'||t==\'1B\')&&!b.1p||(t==\'K\'||t==\'Y\')&&b.R&&b.R.Q!=b||13==\'J\'&&b.1y==-1))6 B;2(13==\'J\'){3 d=b.1y;2(d<0)6 B;3 a=[],1k=b.2I;3 e=(t==\'J-2e\');3 f=(e?d+1:1k.G);H(3 i=(e?d:0);i<f;i++){3 g=1k[i];2(g.2c){3 v=$.1i.1X&&!(g.2G[\'A\'].3t)?g.1A:g.A;2(e)6 v;a.C(v)}}6 a}6 b.A};$.7.1o=5(){6 4.D(5(){$(\'19,J,1C\',4).2p()})};$.7.2p=$.7.2D=5(){6 4.D(5(){3 t=4.I,13=4.1e.1F();2(t==\'1A\'||t==\'3w\'||13==\'1C\')4.A=\'\';8 2(t==\'1q\'||t==\'1B\')4.1p=E;8 2(13==\'J\')4.1y=-1})};$.7.1r=5(){6 4.D(5(){2(P 4.17==\'5\'||(P 4.17==\'2C\'&&!4.17.3y))4.17()})};$.7.2A=5(b){2(b==S)b=T;6 4.D(5(){4.1d=!b})};$.7.J=5(b){2(b==S)b=T;6 4.D(5(){3 t=4.I;2(t==\'1q\'||t==\'1B\')4.1p=b;8 2(4.1e.1F()==\'1P\'){3 a=$(4).2z(\'J\');2(b&&a[0]&&a[0].I==\'J-2e\'){a.2y(\'1P\').J(E)}4.2c=b}})}})(3E);',62,227,'||if|var|this|function|return|fn|else|||||||||||||||||||||||||||name|value|null|push|each|false|max|length|for|type|select|submit|success|event|trigger|doc|typeof|clk|form|undefined|true|callbacks|data|ajaxForm|attr|image|responseText||clk_x|cb|tag|target|fieldValue|clk_y|reset|dataType|input|counter|responseXML|constructor|disabled|tagName|setTimeout|Array|url|browser|ajaxSubmit|ops|fileUpload|submitHandler|body|clearForm|checked|checkbox|resetForm|clickHandler|optionHash|formPluginId|continue|document|param|selectedIndex|formToArray|text|radio|textarea|getElementsByTagName|window|toLowerCase|timeout|encoding|load|onload|attachEvent|arguments|innerHTML|evalScripts|active|option|veto|left|jmax|unbind|offset|pageX|pageY|msie|offsetX|click|opera|ajaxFormUnbind|documentElement|top|1000px|new|ActiveXObject|complete|beforeSubmit|GET|closeKeepAlive|error|selected|toXml|one|iframe|method|extend|action|json|XMLDocument|contentDocument|xml|contentWindow|detachEvent|clearFields|enctype|semantic|addEventListener|html|location|appendTo|ajaxSend|ajaxStart|find|parent|enable|global|object|clearInputs|setRequestHeader|getResponseHeader|attributes|getAllResponseHeaders|options|statusText|status|serialize|button|fieldSerialize|formSerialize|pre|absolute|position|css|elements|write|offsetLeft|javascript|offsetY|src|indexOf|version|toUpperCase|offsetTop|id|parsererror|jqFormIO|validate|parseFromString|DOMParser|ajaxSettings|loadXML|async|XMLDOM|Microsoft|merge|notify|100|ajax|remove|ajaxStop|ajaxComplete|get|ajaxSuccess|handleError|safari|catch|globalEval|eval|script|specified|in|file|password|throw|nodeType|try|removeEventListener|multipart|POST|toString|jQuery'.split('|'),0,{}))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/jquery.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,13 @@
+// $Id: jquery.js,v 1.12.2.1 2008/02/06 12:18:04 goba Exp $
+
+/*
+ * jQuery 1.2.3 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008)
+ * Rev: 4663
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(J(){7(1e.3N)L w=1e.3N;L E=1e.3N=J(a,b){K 1B E.2l.4T(a,b)};7(1e.$)L D=1e.$;1e.$=E;L u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/;L G=/^.[^:#\\[\\.]*$/;E.1n=E.2l={4T:J(d,b){d=d||T;7(d.15){6[0]=d;6.M=1;K 6}N 7(1o d=="25"){L c=u.2O(d);7(c&&(c[1]||!b)){7(c[1])d=E.4a([c[1]],b);N{L a=T.5J(c[3]);7(a)7(a.2w!=c[3])K E().2s(d);N{6[0]=a;6.M=1;K 6}N d=[]}}N K 1B E(b).2s(d)}N 7(E.1q(d))K 1B E(T)[E.1n.21?"21":"3U"](d);K 6.6E(d.1k==1M&&d||(d.5h||d.M&&d!=1e&&!d.15&&d[0]!=10&&d[0].15)&&E.2I(d)||[d])},5h:"1.2.3",87:J(){K 6.M},M:0,22:J(a){K a==10?E.2I(6):6[a]},2F:J(b){L a=E(b);a.54=6;K a},6E:J(a){6.M=0;1M.2l.1g.1i(6,a);K 6},R:J(a,b){K E.R(6,a,b)},4X:J(b){L a=-1;6.R(J(i){7(6==b)a=i});K a},1J:J(c,a,b){L d=c;7(c.1k==4e)7(a==10)K 6.M&&E[b||"1J"](6[0],c)||10;N{d={};d[c]=a}K 6.R(J(i){Q(c 1p d)E.1J(b?6.W:6,c,E.1l(6,d[c],b,i,c))})},1j:J(b,a){7((b==\'27\'||b==\'1R\')&&2M(a)<0)a=10;K 6.1J(b,a,"2o")},1u:J(b){7(1o b!="3V"&&b!=V)K 6.4x().3t((6[0]&&6[0].2i||T).5r(b));L a="";E.R(b||6,J(){E.R(6.3p,J(){7(6.15!=8)a+=6.15!=1?6.6K:E.1n.1u([6])})});K a},5m:J(b){7(6[0])E(b,6[0].2i).5k().3o(6[0]).2c(J(){L a=6;2b(a.1C)a=a.1C;K a}).3t(6);K 6},8w:J(a){K 6.R(J(){E(6).6z().5m(a)})},8p:J(a){K 6.R(J(){E(6).5m(a)})},3t:J(){K 6.3O(18,P,S,J(a){7(6.15==1)6.38(a)})},6q:J(){K 6.3O(18,P,P,J(a){7(6.15==1)6.3o(a,6.1C)})},6o:J(){K 6.3O(18,S,S,J(a){6.1a.3o(a,6)})},5a:J(){K 6.3O(18,S,P,J(a){6.1a.3o(a,6.2B)})},3h:J(){K 6.54||E([])},2s:J(b){L c=E.2c(6,J(a){K E.2s(b,a)});K 6.2F(/[^+>] [^+>]/.17(b)||b.1f("..")>-1?E.57(c):c)},5k:J(e){L f=6.2c(J(){7(E.14.1d&&!E.3E(6)){L a=6.69(P),4Y=T.3s("1x");4Y.38(a);K E.4a([4Y.3d])[0]}N K 6.69(P)});L d=f.2s("*").4R().R(J(){7(6[F]!=10)6[F]=V});7(e===P)6.2s("*").4R().R(J(i){7(6.15==3)K;L c=E.O(6,"2R");Q(L a 1p c)Q(L b 1p c[a])E.16.1b(d[i],a,c[a][b],c[a][b].O)});K f},1E:J(b){K 6.2F(E.1q(b)&&E.3y(6,J(a,i){K b.1P(a,i)})||E.3e(b,6))},56:J(b){7(b.1k==4e)7(G.17(b))K 6.2F(E.3e(b,6,P));N b=E.3e(b,6);L a=b.M&&b[b.M-1]!==10&&!b.15;K 6.1E(J(){K a?E.33(6,b)<0:6!=b})},1b:J(a){K!a?6:6.2F(E.37(6.22(),a.1k==4e?E(a).22():a.M!=10&&(!a.12||E.12(a,"3u"))?a:[a]))},3H:J(a){K a?E.3e(a,6).M>0:S},7j:J(a){K 6.3H("."+a)},5O:J(b){7(b==10){7(6.M){L c=6[0];7(E.12(c,"2k")){L e=c.3T,5I=[],11=c.11,2X=c.U=="2k-2X";7(e<0)K V;Q(L i=2X?e:0,2f=2X?e+1:11.M;i<2f;i++){L d=11[i];7(d.2p){b=E.14.1d&&!d.9J.1A.9y?d.1u:d.1A;7(2X)K b;5I.1g(b)}}K 5I}N K(6[0].1A||"").1r(/\\r/g,"")}K 10}K 6.R(J(){7(6.15!=1)K;7(b.1k==1M&&/5u|5t/.17(6.U))6.3k=(E.33(6.1A,b)>=0||E.33(6.31,b)>=0);N 7(E.12(6,"2k")){L a=b.1k==1M?b:[b];E("98",6).R(J(){6.2p=(E.33(6.1A,a)>=0||E.33(6.1u,a)>=0)});7(!a.M)6.3T=-1}N 6.1A=b})},3q:J(a){K a==10?(6.M?6[0].3d:V):6.4x().3t(a)},6S:J(a){K 6.5a(a).1V()},6Z:J(i){K 6.2K(i,i+1)},2K:J(){K 6.2F(1M.2l.2K.1i(6,18))},2c:J(b){K 6.2F(E.2c(6,J(a,i){K b.1P(a,i,a)}))},4R:J(){K 6.1b(6.54)},O:J(d,b){L a=d.23(".");a[1]=a[1]?"."+a[1]:"";7(b==V){L c=6.5n("8P"+a[1]+"!",[a[0]]);7(c==10&&6.M)c=E.O(6[0],d);K c==V&&a[1]?6.O(a[0]):c}N K 6.1N("8K"+a[1]+"!",[a[0],b]).R(J(){E.O(6,d,b)})},35:J(a){K 6.R(J(){E.35(6,a)})},3O:J(g,f,h,d){L e=6.M>1,3n;K 6.R(J(){7(!3n){3n=E.4a(g,6.2i);7(h)3n.8D()}L b=6;7(f&&E.12(6,"1O")&&E.12(3n[0],"4v"))b=6.3S("1U")[0]||6.38(6.2i.3s("1U"));L c=E([]);E.R(3n,J(){L a=e?E(6).5k(P)[0]:6;7(E.12(a,"1m")){c=c.1b(a)}N{7(a.15==1)c=c.1b(E("1m",a).1V());d.1P(b,a)}});c.R(6A)})}};E.2l.4T.2l=E.2l;J 6A(i,a){7(a.3Q)E.3P({1c:a.3Q,3l:S,1H:"1m"});N E.5g(a.1u||a.6x||a.3d||"");7(a.1a)a.1a.34(a)}E.1s=E.1n.1s=J(){L b=18[0]||{},i=1,M=18.M,5c=S,11;7(b.1k==8d){5c=b;b=18[1]||{};i=2}7(1o b!="3V"&&1o b!="J")b={};7(M==1){b=6;i=0}Q(;i<M;i++)7((11=18[i])!=V)Q(L a 1p 11){7(b===11[a])6w;7(5c&&11[a]&&1o 11[a]=="3V"&&b[a]&&!11[a].15)b[a]=E.1s(b[a],11[a]);N 7(11[a]!=10)b[a]=11[a]}K b};L F="3N"+(1B 3v()).3L(),6t=0,5b={};L H=/z-?4X|86-?84|1w|6k|7Z-?1R/i;E.1s({7Y:J(a){1e.$=D;7(a)1e.3N=w;K E},1q:J(a){K!!a&&1o a!="25"&&!a.12&&a.1k!=1M&&/J/i.17(a+"")},3E:J(a){K a.1F&&!a.1h||a.28&&a.2i&&!a.2i.1h},5g:J(a){a=E.3g(a);7(a){L b=T.3S("6f")[0]||T.1F,1m=T.3s("1m");1m.U="1u/4m";7(E.14.1d)1m.1u=a;N 1m.38(T.5r(a));b.38(1m);b.34(1m)}},12:J(b,a){K b.12&&b.12.2E()==a.2E()},1T:{},O:J(c,d,b){c=c==1e?5b:c;L a=c[F];7(!a)a=c[F]=++6t;7(d&&!E.1T[a])E.1T[a]={};7(b!=10)E.1T[a][d]=b;K d?E.1T[a][d]:a},35:J(c,b){c=c==1e?5b:c;L a=c[F];7(b){7(E.1T[a]){2V E.1T[a][b];b="";Q(b 1p E.1T[a])1Q;7(!b)E.35(c)}}N{1S{2V c[F]}1X(e){7(c.52)c.52(F)}2V E.1T[a]}},R:J(c,a,b){7(b){7(c.M==10){Q(L d 1p c)7(a.1i(c[d],b)===S)1Q}N Q(L i=0,M=c.M;i<M;i++)7(a.1i(c[i],b)===S)1Q}N{7(c.M==10){Q(L d 1p c)7(a.1P(c[d],d,c[d])===S)1Q}N Q(L i=0,M=c.M,1A=c[0];i<M&&a.1P(1A,i,1A)!==S;1A=c[++i]){}}K c},1l:J(b,a,c,i,d){7(E.1q(a))a=a.1P(b,i);K a&&a.1k==51&&c=="2o"&&!H.17(d)?a+"2S":a},1t:{1b:J(c,b){E.R((b||"").23(/\\s+/),J(i,a){7(c.15==1&&!E.1t.3Y(c.1t,a))c.1t+=(c.1t?" ":"")+a})},1V:J(c,b){7(c.15==1)c.1t=b!=10?E.3y(c.1t.23(/\\s+/),J(a){K!E.1t.3Y(b,a)}).6a(" "):""},3Y:J(b,a){K E.33(a,(b.1t||b).3X().23(/\\s+/))>-1}},68:J(b,c,a){L e={};Q(L d 1p c){e[d]=b.W[d];b.W[d]=c[d]}a.1P(b);Q(L d 1p c)b.W[d]=e[d]},1j:J(d,e,c){7(e=="27"||e=="1R"){L b,46={43:"4W",4U:"1Z",19:"3D"},3c=e=="27"?["7O","7M"]:["7J","7I"];J 5E(){b=e=="27"?d.7H:d.7F;L a=0,2N=0;E.R(3c,J(){a+=2M(E.2o(d,"7E"+6,P))||0;2N+=2M(E.2o(d,"2N"+6+"5X",P))||0});b-=24.7C(a+2N)}7(E(d).3H(":4d"))5E();N E.68(d,46,5E);K 24.2f(0,b)}K E.2o(d,e,c)},2o:J(e,k,j){L d;J 3x(b){7(!E.14.2d)K S;L a=T.4c.4K(b,V);K!a||a.4M("3x")==""}7(k=="1w"&&E.14.1d){d=E.1J(e.W,"1w");K d==""?"1":d}7(E.14.2z&&k=="19"){L c=e.W.50;e.W.50="0 7r 7o";e.W.50=c}7(k.1D(/4g/i))k=y;7(!j&&e.W&&e.W[k])d=e.W[k];N 7(T.4c&&T.4c.4K){7(k.1D(/4g/i))k="4g";k=k.1r(/([A-Z])/g,"-$1").2h();L h=T.4c.4K(e,V);7(h&&!3x(e))d=h.4M(k);N{L f=[],2C=[];Q(L a=e;a&&3x(a);a=a.1a)2C.4J(a);Q(L i=0;i<2C.M;i++)7(3x(2C[i])){f[i]=2C[i].W.19;2C[i].W.19="3D"}d=k=="19"&&f[2C.M-1]!=V?"2H":(h&&h.4M(k))||"";Q(L i=0;i<f.M;i++)7(f[i]!=V)2C[i].W.19=f[i]}7(k=="1w"&&d=="")d="1"}N 7(e.4n){L g=k.1r(/\\-(\\w)/g,J(a,b){K b.2E()});d=e.4n[k]||e.4n[g];7(!/^\\d+(2S)?$/i.17(d)&&/^\\d/.17(d)){L l=e.W.26,3K=e.3K.26;e.3K.26=e.4n.26;e.W.26=d||0;d=e.W.7f+"2S";e.W.26=l;e.3K.26=3K}}K d},4a:J(l,h){L k=[];h=h||T;7(1o h.3s==\'10\')h=h.2i||h[0]&&h[0].2i||T;E.R(l,J(i,d){7(!d)K;7(d.1k==51)d=d.3X();7(1o d=="25"){d=d.1r(/(<(\\w+)[^>]*?)\\/>/g,J(b,a,c){K c.1D(/^(aa|a6|7e|a5|4D|7a|a0|3m|9W|9U|9S)$/i)?b:a+"></"+c+">"});L f=E.3g(d).2h(),1x=h.3s("1x");L e=!f.1f("<9P")&&[1,"<2k 74=\'74\'>","</2k>"]||!f.1f("<9M")&&[1,"<73>","</73>"]||f.1D(/^<(9G|1U|9E|9B|9x)/)&&[1,"<1O>","</1O>"]||!f.1f("<4v")&&[2,"<1O><1U>","</1U></1O>"]||(!f.1f("<9w")||!f.1f("<9v"))&&[3,"<1O><1U><4v>","</4v></1U></1O>"]||!f.1f("<7e")&&[2,"<1O><1U></1U><6V>","</6V></1O>"]||E.14.1d&&[1,"1x<1x>","</1x>"]||[0,"",""];1x.3d=e[1]+d+e[2];2b(e[0]--)1x=1x.5o;7(E.14.1d){L g=!f.1f("<1O")&&f.1f("<1U")<0?1x.1C&&1x.1C.3p:e[1]=="<1O>"&&f.1f("<1U")<0?1x.3p:[];Q(L j=g.M-1;j>=0;--j)7(E.12(g[j],"1U")&&!g[j].3p.M)g[j].1a.34(g[j]);7(/^\\s/.17(d))1x.3o(h.5r(d.1D(/^\\s*/)[0]),1x.1C)}d=E.2I(1x.3p)}7(d.M===0&&(!E.12(d,"3u")&&!E.12(d,"2k")))K;7(d[0]==10||E.12(d,"3u")||d.11)k.1g(d);N k=E.37(k,d)});K k},1J:J(d,e,c){7(!d||d.15==3||d.15==8)K 10;L f=E.3E(d)?{}:E.46;7(e=="2p"&&E.14.2d)d.1a.3T;7(f[e]){7(c!=10)d[f[e]]=c;K d[f[e]]}N 7(E.14.1d&&e=="W")K E.1J(d.W,"9u",c);N 7(c==10&&E.14.1d&&E.12(d,"3u")&&(e=="9r"||e=="9o"))K d.9m(e).6K;N 7(d.28){7(c!=10){7(e=="U"&&E.12(d,"4D")&&d.1a)6Q"U 9i 9h\'t 9g 9e";d.9b(e,""+c)}7(E.14.1d&&/6O|3Q/.17(e)&&!E.3E(d))K d.4z(e,2);K d.4z(e)}N{7(e=="1w"&&E.14.1d){7(c!=10){d.6k=1;d.1E=(d.1E||"").1r(/6M\\([^)]*\\)/,"")+(2M(c).3X()=="96"?"":"6M(1w="+c*6L+")")}K d.1E&&d.1E.1f("1w=")>=0?(2M(d.1E.1D(/1w=([^)]*)/)[1])/6L).3X():""}e=e.1r(/-([a-z])/95,J(a,b){K b.2E()});7(c!=10)d[e]=c;K d[e]}},3g:J(a){K(a||"").1r(/^\\s+|\\s+$/g,"")},2I:J(b){L a=[];7(1o b!="93")Q(L i=0,M=b.M;i<M;i++)a.1g(b[i]);N a=b.2K(0);K a},33:J(b,a){Q(L i=0,M=a.M;i<M;i++)7(a[i]==b)K i;K-1},37:J(a,b){7(E.14.1d){Q(L i=0;b[i];i++)7(b[i].15!=8)a.1g(b[i])}N Q(L i=0;b[i];i++)a.1g(b[i]);K a},57:J(a){L c=[],2r={};1S{Q(L i=0,M=a.M;i<M;i++){L b=E.O(a[i]);7(!2r[b]){2r[b]=P;c.1g(a[i])}}}1X(e){c=a}K c},3y:J(c,a,d){L b=[];Q(L i=0,M=c.M;i<M;i++)7(!d&&a(c[i],i)||d&&!a(c[i],i))b.1g(c[i]);K b},2c:J(d,a){L c=[];Q(L i=0,M=d.M;i<M;i++){L b=a(d[i],i);7(b!==V&&b!=10){7(b.1k!=1M)b=[b];c=c.71(b)}}K c}});L v=8Y.8W.2h();E.14={5K:(v.1D(/.+(?:8T|8S|8R|8O)[\\/: ]([\\d.]+)/)||[])[1],2d:/77/.17(v),2z:/2z/.17(v),1d:/1d/.17(v)&&!/2z/.17(v),48:/48/.17(v)&&!/(8L|77)/.17(v)};L y=E.14.1d?"6H":"75";E.1s({8I:!E.14.1d||T.6F=="79",46:{"Q":"8F","8E":"1t","4g":y,75:y,6H:y,3d:"3d",1t:"1t",1A:"1A",2Y:"2Y",3k:"3k",8C:"8B",2p:"2p",8A:"8z",3T:"3T",6C:"6C",28:"28",12:"12"}});E.R({6B:J(a){K a.1a},8y:J(a){K E.4u(a,"1a")},8x:J(a){K E.2Z(a,2,"2B")},8v:J(a){K E.2Z(a,2,"4t")},8u:J(a){K E.4u(a,"2B")},8t:J(a){K E.4u(a,"4t")},8s:J(a){K E.5i(a.1a.1C,a)},8r:J(a){K E.5i(a.1C)},6z:J(a){K E.12(a,"8q")?a.8o||a.8n.T:E.2I(a.3p)}},J(c,d){E.1n[c]=J(b){L a=E.2c(6,d);7(b&&1o b=="25")a=E.3e(b,a);K 6.2F(E.57(a))}});E.R({6y:"3t",8m:"6q",3o:"6o",8l:"5a",8k:"6S"},J(c,b){E.1n[c]=J(){L a=18;K 6.R(J(){Q(L i=0,M=a.M;i<M;i++)E(a[i])[b](6)})}});E.R({8j:J(a){E.1J(6,a,"");7(6.15==1)6.52(a)},8i:J(a){E.1t.1b(6,a)},8h:J(a){E.1t.1V(6,a)},8g:J(a){E.1t[E.1t.3Y(6,a)?"1V":"1b"](6,a)},1V:J(a){7(!a||E.1E(a,[6]).r.M){E("*",6).1b(6).R(J(){E.16.1V(6);E.35(6)});7(6.1a)6.1a.34(6)}},4x:J(){E(">*",6).1V();2b(6.1C)6.34(6.1C)}},J(a,b){E.1n[a]=J(){K 6.R(b,18)}});E.R(["8f","5X"],J(i,c){L b=c.2h();E.1n[b]=J(a){K 6[0]==1e?E.14.2z&&T.1h["5e"+c]||E.14.2d&&1e["8e"+c]||T.6F=="79"&&T.1F["5e"+c]||T.1h["5e"+c]:6[0]==T?24.2f(24.2f(T.1h["5d"+c],T.1F["5d"+c]),24.2f(T.1h["5L"+c],T.1F["5L"+c])):a==10?(6.M?E.1j(6[0],b):V):6.1j(b,a.1k==4e?a:a+"2S")}});L C=E.14.2d&&4s(E.14.5K)<8c?"(?:[\\\\w*4r-]|\\\\\\\\.)":"(?:[\\\\w\\8b-\\8a*4r-]|\\\\\\\\.)",6v=1B 4q("^>\\\\s*("+C+"+)"),6u=1B 4q("^("+C+"+)(#)("+C+"+)"),6s=1B 4q("^([#.]?)("+C+"*)");E.1s({6r:{"":J(a,i,m){K m[2]=="*"||E.12(a,m[2])},"#":J(a,i,m){K a.4z("2w")==m[2]},":":{89:J(a,i,m){K i<m[3]-0},88:J(a,i,m){K i>m[3]-0},2Z:J(a,i,m){K m[3]-0==i},6Z:J(a,i,m){K m[3]-0==i},3j:J(a,i){K i==0},3J:J(a,i,m,r){K i==r.M-1},6n:J(a,i){K i%2==0},6l:J(a,i){K i%2},"3j-4p":J(a){K a.1a.3S("*")[0]==a},"3J-4p":J(a){K E.2Z(a.1a.5o,1,"4t")==a},"83-4p":J(a){K!E.2Z(a.1a.5o,2,"4t")},6B:J(a){K a.1C},4x:J(a){K!a.1C},82:J(a,i,m){K(a.6x||a.81||E(a).1u()||"").1f(m[3])>=0},4d:J(a){K"1Z"!=a.U&&E.1j(a,"19")!="2H"&&E.1j(a,"4U")!="1Z"},1Z:J(a){K"1Z"==a.U||E.1j(a,"19")=="2H"||E.1j(a,"4U")=="1Z"},80:J(a){K!a.2Y},2Y:J(a){K a.2Y},3k:J(a){K a.3k},2p:J(a){K a.2p||E.1J(a,"2p")},1u:J(a){K"1u"==a.U},5u:J(a){K"5u"==a.U},5t:J(a){K"5t"==a.U},59:J(a){K"59"==a.U},3I:J(a){K"3I"==a.U},58:J(a){K"58"==a.U},6j:J(a){K"6j"==a.U},6i:J(a){K"6i"==a.U},2G:J(a){K"2G"==a.U||E.12(a,"2G")},4D:J(a){K/4D|2k|6h|2G/i.17(a.12)},3Y:J(a,i,m){K E.2s(m[3],a).M},7X:J(a){K/h\\d/i.17(a.12)},7W:J(a){K E.3y(E.3G,J(b){K a==b.Y}).M}}},6g:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1B 4q("^([:.#]*)("+C+"+)")],3e:J(a,c,b){L d,2m=[];2b(a&&a!=d){d=a;L f=E.1E(a,c,b);a=f.t.1r(/^\\s*,\\s*/,"");2m=b?c=f.r:E.37(2m,f.r)}K 2m},2s:J(t,p){7(1o t!="25")K[t];7(p&&p.15!=1&&p.15!=9)K[];p=p||T;L d=[p],2r=[],3J,12;2b(t&&3J!=t){L r=[];3J=t;t=E.3g(t);L o=S;L g=6v;L m=g.2O(t);7(m){12=m[1].2E();Q(L i=0;d[i];i++)Q(L c=d[i].1C;c;c=c.2B)7(c.15==1&&(12=="*"||c.12.2E()==12))r.1g(c);d=r;t=t.1r(g,"");7(t.1f(" ")==0)6w;o=P}N{g=/^([>+~])\\s*(\\w*)/i;7((m=g.2O(t))!=V){r=[];L l={};12=m[2].2E();m=m[1];Q(L j=0,3f=d.M;j<3f;j++){L n=m=="~"||m=="+"?d[j].2B:d[j].1C;Q(;n;n=n.2B)7(n.15==1){L h=E.O(n);7(m=="~"&&l[h])1Q;7(!12||n.12.2E()==12){7(m=="~")l[h]=P;r.1g(n)}7(m=="+")1Q}}d=r;t=E.3g(t.1r(g,""));o=P}}7(t&&!o){7(!t.1f(",")){7(p==d[0])d.4l();2r=E.37(2r,d);r=d=[p];t=" "+t.6e(1,t.M)}N{L k=6u;L m=k.2O(t);7(m){m=[0,m[2],m[3],m[1]]}N{k=6s;m=k.2O(t)}m[2]=m[2].1r(/\\\\/g,"");L f=d[d.M-1];7(m[1]=="#"&&f&&f.5J&&!E.3E(f)){L q=f.5J(m[2]);7((E.14.1d||E.14.2z)&&q&&1o q.2w=="25"&&q.2w!=m[2])q=E(\'[@2w="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.12(q,m[3]))?[q]:[]}N{Q(L i=0;d[i];i++){L a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];7(a=="*"&&d[i].12.2h()=="3V")a="3m";r=E.37(r,d[i].3S(a))}7(m[1]==".")r=E.55(r,m[2]);7(m[1]=="#"){L e=[];Q(L i=0;r[i];i++)7(r[i].4z("2w")==m[2]){e=[r[i]];1Q}r=e}d=r}t=t.1r(k,"")}}7(t){L b=E.1E(t,r);d=r=b.r;t=E.3g(b.t)}}7(t)d=[];7(d&&p==d[0])d.4l();2r=E.37(2r,d);K 2r},55:J(r,m,a){m=" "+m+" ";L c=[];Q(L i=0;r[i];i++){L b=(" "+r[i].1t+" ").1f(m)>=0;7(!a&&b||a&&!b)c.1g(r[i])}K c},1E:J(t,r,h){L d;2b(t&&t!=d){d=t;L p=E.6g,m;Q(L i=0;p[i];i++){m=p[i].2O(t);7(m){t=t.7V(m[0].M);m[2]=m[2].1r(/\\\\/g,"");1Q}}7(!m)1Q;7(m[1]==":"&&m[2]=="56")r=G.17(m[3])?E.1E(m[3],r,P).r:E(r).56(m[3]);N 7(m[1]==".")r=E.55(r,m[2],h);N 7(m[1]=="["){L g=[],U=m[3];Q(L i=0,3f=r.M;i<3f;i++){L a=r[i],z=a[E.46[m[2]]||m[2]];7(z==V||/6O|3Q|2p/.17(m[2]))z=E.1J(a,m[2])||\'\';7((U==""&&!!z||U=="="&&z==m[5]||U=="!="&&z!=m[5]||U=="^="&&z&&!z.1f(m[5])||U=="$="&&z.6e(z.M-m[5].M)==m[5]||(U=="*="||U=="~=")&&z.1f(m[5])>=0)^h)g.1g(a)}r=g}N 7(m[1]==":"&&m[2]=="2Z-4p"){L e={},g=[],17=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.2O(m[3]=="6n"&&"2n"||m[3]=="6l"&&"2n+1"||!/\\D/.17(m[3])&&"7U+"+m[3]||m[3]),3j=(17[1]+(17[2]||1))-0,d=17[3]-0;Q(L i=0,3f=r.M;i<3f;i++){L j=r[i],1a=j.1a,2w=E.O(1a);7(!e[2w]){L c=1;Q(L n=1a.1C;n;n=n.2B)7(n.15==1)n.4k=c++;e[2w]=P}L b=S;7(3j==0){7(j.4k==d)b=P}N 7((j.4k-d)%3j==0&&(j.4k-d)/3j>=0)b=P;7(b^h)g.1g(j)}r=g}N{L f=E.6r[m[1]];7(1o f=="3V")f=f[m[2]];7(1o f=="25")f=6c("S||J(a,i){K "+f+";}");r=E.3y(r,J(a,i){K f(a,i,m,r)},h)}}K{r:r,t:t}},4u:J(b,c){L d=[];L a=b[c];2b(a&&a!=T){7(a.15==1)d.1g(a);a=a[c]}K d},2Z:J(a,e,c,b){e=e||1;L d=0;Q(;a;a=a[c])7(a.15==1&&++d==e)1Q;K a},5i:J(n,a){L r=[];Q(;n;n=n.2B){7(n.15==1&&(!a||n!=a))r.1g(n)}K r}});E.16={1b:J(f,i,g,e){7(f.15==3||f.15==8)K;7(E.14.1d&&f.53!=10)f=1e;7(!g.2D)g.2D=6.2D++;7(e!=10){L h=g;g=J(){K h.1i(6,18)};g.O=e;g.2D=h.2D}L j=E.O(f,"2R")||E.O(f,"2R",{}),1v=E.O(f,"1v")||E.O(f,"1v",J(){L a;7(1o E=="10"||E.16.5f)K a;a=E.16.1v.1i(18.3R.Y,18);K a});1v.Y=f;E.R(i.23(/\\s+/),J(c,b){L a=b.23(".");b=a[0];g.U=a[1];L d=j[b];7(!d){d=j[b]={};7(!E.16.2y[b]||E.16.2y[b].4j.1P(f)===S){7(f.3F)f.3F(b,1v,S);N 7(f.6b)f.6b("4i"+b,1v)}}d[g.2D]=g;E.16.2a[b]=P});f=V},2D:1,2a:{},1V:J(e,h,f){7(e.15==3||e.15==8)K;L i=E.O(e,"2R"),29,4X;7(i){7(h==10||(1o h=="25"&&h.7T(0)=="."))Q(L g 1p i)6.1V(e,g+(h||""));N{7(h.U){f=h.2q;h=h.U}E.R(h.23(/\\s+/),J(b,a){L c=a.23(".");a=c[0];7(i[a]){7(f)2V i[a][f.2D];N Q(f 1p i[a])7(!c[1]||i[a][f].U==c[1])2V i[a][f];Q(29 1p i[a])1Q;7(!29){7(!E.16.2y[a]||E.16.2y[a].4h.1P(e)===S){7(e.67)e.67(a,E.O(e,"1v"),S);N 7(e.66)e.66("4i"+a,E.O(e,"1v"))}29=V;2V i[a]}}})}Q(29 1p i)1Q;7(!29){L d=E.O(e,"1v");7(d)d.Y=V;E.35(e,"2R");E.35(e,"1v")}}},1N:J(g,c,d,f,h){c=E.2I(c||[]);7(g.1f("!")>=0){g=g.2K(0,-1);L a=P}7(!d){7(6.2a[g])E("*").1b([1e,T]).1N(g,c)}N{7(d.15==3||d.15==8)K 10;L b,29,1n=E.1q(d[g]||V),16=!c[0]||!c[0].36;7(16)c.4J(6.4Z({U:g,2L:d}));c[0].U=g;7(a)c[0].65=P;7(E.1q(E.O(d,"1v")))b=E.O(d,"1v").1i(d,c);7(!1n&&d["4i"+g]&&d["4i"+g].1i(d,c)===S)b=S;7(16)c.4l();7(h&&E.1q(h)){29=h.1i(d,b==V?c:c.71(b));7(29!==10)b=29}7(1n&&f!==S&&b!==S&&!(E.12(d,\'a\')&&g=="4V")){6.5f=P;1S{d[g]()}1X(e){}}6.5f=S}K b},1v:J(c){L a;c=E.16.4Z(c||1e.16||{});L b=c.U.23(".");c.U=b[0];L f=E.O(6,"2R")&&E.O(6,"2R")[c.U],42=1M.2l.2K.1P(18,1);42.4J(c);Q(L j 1p f){L d=f[j];42[0].2q=d;42[0].O=d.O;7(!b[1]&&!c.65||d.U==b[1]){L e=d.1i(6,42);7(a!==S)a=e;7(e===S){c.36();c.44()}}}7(E.14.1d)c.2L=c.36=c.44=c.2q=c.O=V;K a},4Z:J(c){L a=c;c=E.1s({},a);c.36=J(){7(a.36)a.36();a.7S=S};c.44=J(){7(a.44)a.44();a.7R=P};7(!c.2L)c.2L=c.7Q||T;7(c.2L.15==3)c.2L=a.2L.1a;7(!c.4S&&c.5w)c.4S=c.5w==c.2L?c.7P:c.5w;7(c.64==V&&c.63!=V){L b=T.1F,1h=T.1h;c.64=c.63+(b&&b.2v||1h&&1h.2v||0)-(b.62||0);c.7N=c.7L+(b&&b.2x||1h&&1h.2x||0)-(b.60||0)}7(!c.3c&&((c.4f||c.4f===0)?c.4f:c.5Z))c.3c=c.4f||c.5Z;7(!c.7b&&c.5Y)c.7b=c.5Y;7(!c.3c&&c.2G)c.3c=(c.2G&1?1:(c.2G&2?3:(c.2G&4?2:0)));K c},2y:{21:{4j:J(){5M();K},4h:J(){K}},3C:{4j:J(){7(E.14.1d)K S;E(6).2j("4P",E.16.2y.3C.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4P",E.16.2y.3C.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3C";K E.16.1v.1i(6,18)}},3B:{4j:J(){7(E.14.1d)K S;E(6).2j("4O",E.16.2y.3B.2q);K P},4h:J(){7(E.14.1d)K S;E(6).3w("4O",E.16.2y.3B.2q);K P},2q:J(a){7(I(a,6))K P;18[0].U="3B";K E.16.1v.1i(6,18)}}}};E.1n.1s({2j:J(c,a,b){K c=="4H"?6.2X(c,a,b):6.R(J(){E.16.1b(6,c,b||a,b&&a)})},2X:J(d,b,c){K 6.R(J(){E.16.1b(6,d,J(a){E(6).3w(a);K(c||b).1i(6,18)},c&&b)})},3w:J(a,b){K 6.R(J(){E.16.1V(6,a,b)})},1N:J(c,a,b){K 6.R(J(){E.16.1N(c,a,6,P,b)})},5n:J(c,a,b){7(6[0])K E.16.1N(c,a,6[0],S,b);K 10},2g:J(){L b=18;K 6.4V(J(a){6.4N=0==6.4N?1:0;a.36();K b[6.4N].1i(6,18)||S})},7D:J(a,b){K 6.2j(\'3C\',a).2j(\'3B\',b)},21:J(a){5M();7(E.2Q)a.1P(T,E);N E.3A.1g(J(){K a.1P(6,E)});K 6}});E.1s({2Q:S,3A:[],21:J(){7(!E.2Q){E.2Q=P;7(E.3A){E.R(E.3A,J(){6.1i(T)});E.3A=V}E(T).5n("21")}}});L x=S;J 5M(){7(x)K;x=P;7(T.3F&&!E.14.2z)T.3F("5W",E.21,S);7(E.14.1d&&1e==3b)(J(){7(E.2Q)K;1S{T.1F.7B("26")}1X(3a){3z(18.3R,0);K}E.21()})();7(E.14.2z)T.3F("5W",J(){7(E.2Q)K;Q(L i=0;i<T.4L.M;i++)7(T.4L[i].2Y){3z(18.3R,0);K}E.21()},S);7(E.14.2d){L a;(J(){7(E.2Q)K;7(T.39!="5V"&&T.39!="1y"){3z(18.3R,0);K}7(a===10)a=E("W, 7a[7A=7z]").M;7(T.4L.M!=a){3z(18.3R,0);K}E.21()})()}E.16.1b(1e,"3U",E.21)}E.R(("7y,7x,3U,7w,5d,4H,4V,7v,"+"7G,7u,7t,4P,4O,7s,2k,"+"58,7K,7q,7p,3a").23(","),J(i,b){E.1n[b]=J(a){K a?6.2j(b,a):6.1N(b)}});L I=J(a,c){L b=a.4S;2b(b&&b!=c)1S{b=b.1a}1X(3a){b=c}K b==c};E(1e).2j("4H",J(){E("*").1b(T).3w()});E.1n.1s({3U:J(g,d,c){7(E.1q(g))K 6.2j("3U",g);L e=g.1f(" ");7(e>=0){L i=g.2K(e,g.M);g=g.2K(0,e)}c=c||J(){};L f="4Q";7(d)7(E.1q(d)){c=d;d=V}N{d=E.3m(d);f="61"}L h=6;E.3P({1c:g,U:f,1H:"3q",O:d,1y:J(a,b){7(b=="1W"||b=="5U")h.3q(i?E("<1x/>").3t(a.4b.1r(/<1m(.|\\s)*?\\/1m>/g,"")).2s(i):a.4b);h.R(c,[a.4b,b,a])}});K 6},7n:J(){K E.3m(6.5T())},5T:J(){K 6.2c(J(){K E.12(6,"3u")?E.2I(6.7m):6}).1E(J(){K 6.31&&!6.2Y&&(6.3k||/2k|6h/i.17(6.12)||/1u|1Z|3I/i.17(6.U))}).2c(J(i,c){L b=E(6).5O();K b==V?V:b.1k==1M?E.2c(b,J(a,i){K{31:c.31,1A:a}}):{31:c.31,1A:b}}).22()}});E.R("5S,6d,5R,6D,5Q,6m".23(","),J(i,o){E.1n[o]=J(f){K 6.2j(o,f)}});L B=(1B 3v).3L();E.1s({22:J(d,b,a,c){7(E.1q(b)){a=b;b=V}K E.3P({U:"4Q",1c:d,O:b,1W:a,1H:c})},7l:J(b,a){K E.22(b,V,a,"1m")},7k:J(c,b,a){K E.22(c,b,a,"3i")},7i:J(d,b,a,c){7(E.1q(b)){a=b;b={}}K E.3P({U:"61",1c:d,O:b,1W:a,1H:c})},85:J(a){E.1s(E.4I,a)},4I:{2a:P,U:"4Q",2U:0,5P:"4o/x-7h-3u-7g",5N:P,3l:P,O:V,6p:V,3I:V,49:{3M:"4o/3M, 1u/3M",3q:"1u/3q",1m:"1u/4m, 4o/4m",3i:"4o/3i, 1u/4m",1u:"1u/a7",4G:"*/*"}},4F:{},3P:J(s){L f,2W=/=\\?(&|$)/g,1z,O;s=E.1s(P,s,E.1s(P,{},E.4I,s));7(s.O&&s.5N&&1o s.O!="25")s.O=E.3m(s.O);7(s.1H=="4E"){7(s.U.2h()=="22"){7(!s.1c.1D(2W))s.1c+=(s.1c.1D(/\\?/)?"&":"?")+(s.4E||"7d")+"=?"}N 7(!s.O||!s.O.1D(2W))s.O=(s.O?s.O+"&":"")+(s.4E||"7d")+"=?";s.1H="3i"}7(s.1H=="3i"&&(s.O&&s.O.1D(2W)||s.1c.1D(2W))){f="4E"+B++;7(s.O)s.O=(s.O+"").1r(2W,"="+f+"$1");s.1c=s.1c.1r(2W,"="+f+"$1");s.1H="1m";1e[f]=J(a){O=a;1W();1y();1e[f]=10;1S{2V 1e[f]}1X(e){}7(h)h.34(g)}}7(s.1H=="1m"&&s.1T==V)s.1T=S;7(s.1T===S&&s.U.2h()=="22"){L i=(1B 3v()).3L();L j=s.1c.1r(/(\\?|&)4r=.*?(&|$)/,"$a4="+i+"$2");s.1c=j+((j==s.1c)?(s.1c.1D(/\\?/)?"&":"?")+"4r="+i:"")}7(s.O&&s.U.2h()=="22"){s.1c+=(s.1c.1D(/\\?/)?"&":"?")+s.O;s.O=V}7(s.2a&&!E.5H++)E.16.1N("5S");7((!s.1c.1f("a3")||!s.1c.1f("//"))&&s.1H=="1m"&&s.U.2h()=="22"){L h=T.3S("6f")[0];L g=T.3s("1m");g.3Q=s.1c;7(s.7c)g.a2=s.7c;7(!f){L l=S;g.9Z=g.9Y=J(){7(!l&&(!6.39||6.39=="5V"||6.39=="1y")){l=P;1W();1y();h.34(g)}}}h.38(g);K 10}L m=S;L k=1e.78?1B 78("9X.9V"):1B 76();k.9T(s.U,s.1c,s.3l,s.6p,s.3I);1S{7(s.O)k.4C("9R-9Q",s.5P);7(s.5C)k.4C("9O-5A-9N",E.4F[s.1c]||"9L, 9K 9I 9H 5z:5z:5z 9F");k.4C("X-9C-9A","76");k.4C("9z",s.1H&&s.49[s.1H]?s.49[s.1H]+", */*":s.49.4G)}1X(e){}7(s.6Y)s.6Y(k);7(s.2a)E.16.1N("6m",[k,s]);L c=J(a){7(!m&&k&&(k.39==4||a=="2U")){m=P;7(d){6I(d);d=V}1z=a=="2U"&&"2U"||!E.6X(k)&&"3a"||s.5C&&E.6J(k,s.1c)&&"5U"||"1W";7(1z=="1W"){1S{O=E.6W(k,s.1H)}1X(e){1z="5x"}}7(1z=="1W"){L b;1S{b=k.5q("6U-5A")}1X(e){}7(s.5C&&b)E.4F[s.1c]=b;7(!f)1W()}N E.5v(s,k,1z);1y();7(s.3l)k=V}};7(s.3l){L d=53(c,13);7(s.2U>0)3z(J(){7(k){k.9t();7(!m)c("2U")}},s.2U)}1S{k.9s(s.O)}1X(e){E.5v(s,k,V,e)}7(!s.3l)c();J 1W(){7(s.1W)s.1W(O,1z);7(s.2a)E.16.1N("5Q",[k,s])}J 1y(){7(s.1y)s.1y(k,1z);7(s.2a)E.16.1N("5R",[k,s]);7(s.2a&&!--E.5H)E.16.1N("6d")}K k},5v:J(s,a,b,e){7(s.3a)s.3a(a,b,e);7(s.2a)E.16.1N("6D",[a,s,e])},5H:0,6X:J(r){1S{K!r.1z&&9q.9p=="59:"||(r.1z>=6T&&r.1z<9n)||r.1z==6R||r.1z==9l||E.14.2d&&r.1z==10}1X(e){}K S},6J:J(a,c){1S{L b=a.5q("6U-5A");K a.1z==6R||b==E.4F[c]||E.14.2d&&a.1z==10}1X(e){}K S},6W:J(r,b){L c=r.5q("9k-U");L d=b=="3M"||!b&&c&&c.1f("3M")>=0;L a=d?r.9j:r.4b;7(d&&a.1F.28=="5x")6Q"5x";7(b=="1m")E.5g(a);7(b=="3i")a=6c("("+a+")");K a},3m:J(a){L s=[];7(a.1k==1M||a.5h)E.R(a,J(){s.1g(3r(6.31)+"="+3r(6.1A))});N Q(L j 1p a)7(a[j]&&a[j].1k==1M)E.R(a[j],J(){s.1g(3r(j)+"="+3r(6))});N s.1g(3r(j)+"="+3r(a[j]));K s.6a("&").1r(/%20/g,"+")}});E.1n.1s({1G:J(c,b){K c?6.2e({1R:"1G",27:"1G",1w:"1G"},c,b):6.1E(":1Z").R(J(){6.W.19=6.5s||"";7(E.1j(6,"19")=="2H"){L a=E("<"+6.28+" />").6y("1h");6.W.19=a.1j("19");7(6.W.19=="2H")6.W.19="3D";a.1V()}}).3h()},1I:J(b,a){K b?6.2e({1R:"1I",27:"1I",1w:"1I"},b,a):6.1E(":4d").R(J(){6.5s=6.5s||E.1j(6,"19");6.W.19="2H"}).3h()},6N:E.1n.2g,2g:J(a,b){K E.1q(a)&&E.1q(b)?6.6N(a,b):a?6.2e({1R:"2g",27:"2g",1w:"2g"},a,b):6.R(J(){E(6)[E(6).3H(":1Z")?"1G":"1I"]()})},9f:J(b,a){K 6.2e({1R:"1G"},b,a)},9d:J(b,a){K 6.2e({1R:"1I"},b,a)},9c:J(b,a){K 6.2e({1R:"2g"},b,a)},9a:J(b,a){K 6.2e({1w:"1G"},b,a)},99:J(b,a){K 6.2e({1w:"1I"},b,a)},97:J(c,a,b){K 6.2e({1w:a},c,b)},2e:J(l,k,j,h){L i=E.6P(k,j,h);K 6[i.2P===S?"R":"2P"](J(){7(6.15!=1)K S;L g=E.1s({},i);L f=E(6).3H(":1Z"),4A=6;Q(L p 1p l){7(l[p]=="1I"&&f||l[p]=="1G"&&!f)K E.1q(g.1y)&&g.1y.1i(6);7(p=="1R"||p=="27"){g.19=E.1j(6,"19");g.32=6.W.32}}7(g.32!=V)6.W.32="1Z";g.40=E.1s({},l);E.R(l,J(c,a){L e=1B E.2t(4A,g,c);7(/2g|1G|1I/.17(a))e[a=="2g"?f?"1G":"1I":a](l);N{L b=a.3X().1D(/^([+-]=)?([\\d+-.]+)(.*)$/),1Y=e.2m(P)||0;7(b){L d=2M(b[2]),2A=b[3]||"2S";7(2A!="2S"){4A.W[c]=(d||1)+2A;1Y=((d||1)/e.2m(P))*1Y;4A.W[c]=1Y+2A}7(b[1])d=((b[1]=="-="?-1:1)*d)+1Y;e.45(1Y,d,2A)}N e.45(1Y,a,"")}});K P})},2P:J(a,b){7(E.1q(a)||(a&&a.1k==1M)){b=a;a="2t"}7(!a||(1o a=="25"&&!b))K A(6[0],a);K 6.R(J(){7(b.1k==1M)A(6,a,b);N{A(6,a).1g(b);7(A(6,a).M==1)b.1i(6)}})},94:J(b,c){L a=E.3G;7(b)6.2P([]);6.R(J(){Q(L i=a.M-1;i>=0;i--)7(a[i].Y==6){7(c)a[i](P);a.72(i,1)}});7(!c)6.5p();K 6}});L A=J(b,c,a){7(!b)K 10;c=c||"2t";L q=E.O(b,c+"2P");7(!q||a)q=E.O(b,c+"2P",a?E.2I(a):[]);K q};E.1n.5p=J(a){a=a||"2t";K 6.R(J(){L q=A(6,a);q.4l();7(q.M)q[0].1i(6)})};E.1s({6P:J(b,a,c){L d=b&&b.1k==92?b:{1y:c||!c&&a||E.1q(b)&&b,2u:b,3Z:c&&a||a&&a.1k!=91&&a};d.2u=(d.2u&&d.2u.1k==51?d.2u:{90:8Z,9D:6T}[d.2u])||8X;d.5y=d.1y;d.1y=J(){7(d.2P!==S)E(6).5p();7(E.1q(d.5y))d.5y.1i(6)};K d},3Z:{70:J(p,n,b,a){K b+a*p},5j:J(p,n,b,a){K((-24.8V(p*24.8U)/2)+0.5)*a+b}},3G:[],3W:V,2t:J(b,c,a){6.11=c;6.Y=b;6.1l=a;7(!c.47)c.47={}}});E.2t.2l={4y:J(){7(6.11.30)6.11.30.1i(6.Y,[6.2J,6]);(E.2t.30[6.1l]||E.2t.30.4G)(6);7(6.1l=="1R"||6.1l=="27")6.Y.W.19="3D"},2m:J(a){7(6.Y[6.1l]!=V&&6.Y.W[6.1l]==V)K 6.Y[6.1l];L r=2M(E.1j(6.Y,6.1l,a));K r&&r>-8Q?r:2M(E.2o(6.Y,6.1l))||0},45:J(c,b,d){6.5B=(1B 3v()).3L();6.1Y=c;6.3h=b;6.2A=d||6.2A||"2S";6.2J=6.1Y;6.4B=6.4w=0;6.4y();L e=6;J t(a){K e.30(a)}t.Y=6.Y;E.3G.1g(t);7(E.3W==V){E.3W=53(J(){L a=E.3G;Q(L i=0;i<a.M;i++)7(!a[i]())a.72(i--,1);7(!a.M){6I(E.3W);E.3W=V}},13)}},1G:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1G=P;6.45(0,6.2m());7(6.1l=="27"||6.1l=="1R")6.Y.W[6.1l]="8N";E(6.Y).1G()},1I:J(){6.11.47[6.1l]=E.1J(6.Y.W,6.1l);6.11.1I=P;6.45(6.2m(),0)},30:J(a){L t=(1B 3v()).3L();7(a||t>6.11.2u+6.5B){6.2J=6.3h;6.4B=6.4w=1;6.4y();6.11.40[6.1l]=P;L b=P;Q(L i 1p 6.11.40)7(6.11.40[i]!==P)b=S;7(b){7(6.11.19!=V){6.Y.W.32=6.11.32;6.Y.W.19=6.11.19;7(E.1j(6.Y,"19")=="2H")6.Y.W.19="3D"}7(6.11.1I)6.Y.W.19="2H";7(6.11.1I||6.11.1G)Q(L p 1p 6.11.40)E.1J(6.Y.W,p,6.11.47[p])}7(b&&E.1q(6.11.1y))6.11.1y.1i(6.Y);K S}N{L n=t-6.5B;6.4w=n/6.11.2u;6.4B=E.3Z[6.11.3Z||(E.3Z.5j?"5j":"70")](6.4w,n,0,1,6.11.2u);6.2J=6.1Y+((6.3h-6.1Y)*6.4B);6.4y()}K P}};E.2t.30={2v:J(a){a.Y.2v=a.2J},2x:J(a){a.Y.2x=a.2J},1w:J(a){E.1J(a.Y.W,"1w",a.2J)},4G:J(a){a.Y.W[a.1l]=a.2J+a.2A}};E.1n.5L=J(){L b=0,3b=0,Y=6[0],5l;7(Y)8M(E.14){L d=Y.1a,41=Y,1K=Y.1K,1L=Y.2i,5D=2d&&4s(5K)<8J&&!/a1/i.17(v),2T=E.1j(Y,"43")=="2T";7(Y.6G){L c=Y.6G();1b(c.26+24.2f(1L.1F.2v,1L.1h.2v),c.3b+24.2f(1L.1F.2x,1L.1h.2x));1b(-1L.1F.62,-1L.1F.60)}N{1b(Y.5G,Y.5F);2b(1K){1b(1K.5G,1K.5F);7(48&&!/^t(8H|d|h)$/i.17(1K.28)||2d&&!5D)2N(1K);7(!2T&&E.1j(1K,"43")=="2T")2T=P;41=/^1h$/i.17(1K.28)?41:1K;1K=1K.1K}2b(d&&d.28&&!/^1h|3q$/i.17(d.28)){7(!/^8G|1O.*$/i.17(E.1j(d,"19")))1b(-d.2v,-d.2x);7(48&&E.1j(d,"32")!="4d")2N(d);d=d.1a}7((5D&&(2T||E.1j(41,"43")=="4W"))||(48&&E.1j(41,"43")!="4W"))1b(-1L.1h.5G,-1L.1h.5F);7(2T)1b(24.2f(1L.1F.2v,1L.1h.2v),24.2f(1L.1F.2x,1L.1h.2x))}5l={3b:3b,26:b}}J 2N(a){1b(E.2o(a,"a8",P),E.2o(a,"a9",P))}J 1b(l,t){b+=4s(l)||0;3b+=4s(t)||0}K 5l}})();',62,631,'||||||this|if||||||||||||||||||||||||||||||||||||||function|return|var|length|else|data|true|for|each|false|document|type|null|style||elem||undefined|options|nodeName||browser|nodeType|event|test|arguments|display|parentNode|add|url|msie|window|indexOf|push|body|apply|css|constructor|prop|script|fn|typeof|in|isFunction|replace|extend|className|text|handle|opacity|div|complete|status|value|new|firstChild|match|filter|documentElement|show|dataType|hide|attr|offsetParent|doc|Array|trigger|table|call|break|height|try|cache|tbody|remove|success|catch|start|hidden||ready|get|split|Math|string|left|width|tagName|ret|global|while|map|safari|animate|max|toggle|toLowerCase|ownerDocument|bind|select|prototype|cur||curCSS|selected|handler|done|find|fx|duration|scrollLeft|id|scrollTop|special|opera|unit|nextSibling|stack|guid|toUpperCase|pushStack|button|none|makeArray|now|slice|target|parseFloat|border|exec|queue|isReady|events|px|fixed|timeout|delete|jsre|one|disabled|nth|step|name|overflow|inArray|removeChild|removeData|preventDefault|merge|appendChild|readyState|error|top|which|innerHTML|multiFilter|rl|trim|end|json|first|checked|async|param|elems|insertBefore|childNodes|html|encodeURIComponent|createElement|append|form|Date|unbind|color|grep|setTimeout|readyList|mouseleave|mouseenter|block|isXMLDoc|addEventListener|timers|is|password|last|runtimeStyle|getTime|xml|jQuery|domManip|ajax|src|callee|getElementsByTagName|selectedIndex|load|object|timerId|toString|has|easing|curAnim|offsetChild|args|position|stopPropagation|custom|props|orig|mozilla|accepts|clean|responseText|defaultView|visible|String|charCode|float|teardown|on|setup|nodeIndex|shift|javascript|currentStyle|application|child|RegExp|_|parseInt|previousSibling|dir|tr|state|empty|update|getAttribute|self|pos|setRequestHeader|input|jsonp|lastModified|_default|unload|ajaxSettings|unshift|getComputedStyle|styleSheets|getPropertyValue|lastToggle|mouseout|mouseover|GET|andSelf|relatedTarget|init|visibility|click|absolute|index|container|fix|outline|Number|removeAttribute|setInterval|prevObject|classFilter|not|unique|submit|file|after|windowData|deep|scroll|client|triggered|globalEval|jquery|sibling|swing|clone|results|wrapAll|triggerHandler|lastChild|dequeue|getResponseHeader|createTextNode|oldblock|checkbox|radio|handleError|fromElement|parsererror|old|00|Modified|startTime|ifModified|safari2|getWH|offsetTop|offsetLeft|active|values|getElementById|version|offset|bindReady|processData|val|contentType|ajaxSuccess|ajaxComplete|ajaxStart|serializeArray|notmodified|loaded|DOMContentLoaded|Width|ctrlKey|keyCode|clientTop|POST|clientLeft|clientX|pageX|exclusive|detachEvent|removeEventListener|swap|cloneNode|join|attachEvent|eval|ajaxStop|substr|head|parse|textarea|reset|image|zoom|odd|ajaxSend|even|before|username|prepend|expr|quickClass|uuid|quickID|quickChild|continue|textContent|appendTo|contents|evalScript|parent|defaultValue|ajaxError|setArray|compatMode|getBoundingClientRect|styleFloat|clearInterval|httpNotModified|nodeValue|100|alpha|_toggle|href|speed|throw|304|replaceWith|200|Last|colgroup|httpData|httpSuccess|beforeSend|eq|linear|concat|splice|fieldset|multiple|cssFloat|XMLHttpRequest|webkit|ActiveXObject|CSS1Compat|link|metaKey|scriptCharset|callback|col|pixelLeft|urlencoded|www|post|hasClass|getJSON|getScript|elements|serialize|black|keyup|keypress|solid|change|mousemove|mouseup|dblclick|resize|focus|blur|stylesheet|rel|doScroll|round|hover|padding|offsetHeight|mousedown|offsetWidth|Bottom|Top|keydown|clientY|Right|pageY|Left|toElement|srcElement|cancelBubble|returnValue|charAt|0n|substring|animated|header|noConflict|line|enabled|innerText|contains|only|weight|ajaxSetup|font|size|gt|lt|uFFFF|u0128|417|Boolean|inner|Height|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|prependTo|contentWindow|contentDocument|wrap|iframe|children|siblings|prevAll|nextAll|prev|wrapInner|next|parents|maxLength|maxlength|readOnly|readonly|reverse|class|htmlFor|inline|able|boxModel|522|setData|compatible|with|1px|ie|getData|10000|ra|it|rv|PI|cos|userAgent|400|navigator|600|slow|Function|Object|array|stop|ig|NaN|fadeTo|option|fadeOut|fadeIn|setAttribute|slideToggle|slideUp|changed|slideDown|be|can|property|responseXML|content|1223|getAttributeNode|300|method|protocol|location|action|send|abort|cssText|th|td|cap|specified|Accept|With|colg|Requested|fast|tfoot|GMT|thead|1970|Jan|attributes|01|Thu|leg|Since|If|opt|Type|Content|embed|open|area|XMLHTTP|hr|Microsoft|onreadystatechange|onload|meta|adobeair|charset|http|1_|img|br|plain|borderLeftWidth|borderTopWidth|abbr'.split('|'),0,{}))
\ No newline at end of file
Binary file misc/menu-collapsed-rtl.png has changed
Binary file misc/menu-collapsed.png has changed
Binary file misc/menu-expanded.png has changed
Binary file misc/menu-leaf.png has changed
Binary file misc/powered-black-135x42.png has changed
Binary file misc/powered-black-80x15.png has changed
Binary file misc/powered-black-88x31.png has changed
Binary file misc/powered-blue-135x42.png has changed
Binary file misc/powered-blue-80x15.png has changed
Binary file misc/powered-blue-88x31.png has changed
Binary file misc/powered-gray-135x42.png has changed
Binary file misc/powered-gray-80x15.png has changed
Binary file misc/powered-gray-88x31.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/print-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+/* $Id: print-rtl.css,v 1.1 2007/06/10 08:55:55 goba Exp $ */
+
+body {
+  direction: rtl;
+}
+th {
+  text-align: right;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/print.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,26 @@
+/* $Id: print.css,v 1.6 2007/06/10 08:55:55 goba Exp $ */
+
+body {
+  margin: 1em;
+  background-color: #fff;
+}
+th {
+  text-align: left; /* LTR */
+  color: #006;
+  border-bottom: 1px solid #ccc;
+}
+tr.odd {
+  background-color: #ddd;
+}
+tr.even {
+  background-color: #fff;
+}
+td {
+  padding: 5px;
+}
+#menu {
+  visibility: hidden;
+}
+#main {
+  margin: 1em;
+}
Binary file misc/progress.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/progress.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,107 @@
+// $Id: progress.js,v 1.20 2008/01/04 11:53:21 goba Exp $
+
+/**
+ * A progressbar object. Initialized with the given id. Must be inserted into
+ * the DOM afterwards through progressBar.element.
+ *
+ * method is the function which will perform the HTTP request to get the
+ * progress bar state. Either "GET" or "POST".
+ *
+ * e.g. pb = new progressBar('myProgressBar');
+ *      some_element.appendChild(pb.element);
+ */
+Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
+  var pb = this;
+  this.id = id;
+  this.method = method || "GET";
+  this.updateCallback = updateCallback;
+  this.errorCallback = errorCallback;
+
+  this.element = document.createElement('div');
+  this.element.id = id;
+  this.element.className = 'progress';
+  $(this.element).html('<div class="bar"><div class="filled"></div></div>'+
+                       '<div class="percentage"></div>'+
+                       '<div class="message">&nbsp;</div>');
+};
+
+/**
+ * Set the percentage and status message for the progressbar.
+ */
+Drupal.progressBar.prototype.setProgress = function (percentage, message) {
+  if (percentage >= 0 && percentage <= 100) {
+    $('div.filled', this.element).css('width', percentage +'%');
+    $('div.percentage', this.element).html(percentage +'%');
+  }
+  $('div.message', this.element).html(message);
+  if (this.updateCallback) {
+    this.updateCallback(percentage, message, this);
+  }
+};
+
+/**
+ * Start monitoring progress via Ajax.
+ */
+Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
+  this.delay = delay;
+  this.uri = uri;
+  this.sendPing();
+};
+
+/**
+ * Stop monitoring progress via Ajax.
+ */
+Drupal.progressBar.prototype.stopMonitoring = function () {
+  clearTimeout(this.timer);
+  // This allows monitoring to be stopped from within the callback
+  this.uri = null;
+};
+
+/**
+ * Request progress data from server.
+ */
+Drupal.progressBar.prototype.sendPing = function () {
+  if (this.timer) {
+    clearTimeout(this.timer);
+  }
+  if (this.uri) {
+    var pb = this;
+    // When doing a post request, you need non-null data. Otherwise a
+    // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
+    $.ajax({
+      type: this.method,
+      url: this.uri,
+      data: '',
+      dataType: 'json',
+      success: function (progress) {
+        // Display errors
+        if (progress.status == 0) {
+          pb.displayError(progress.data);
+          return;
+        }
+        // Update display
+        pb.setProgress(progress.percentage, progress.message);
+        // Schedule next timer
+        pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay);
+      },
+      error: function (xmlhttp) {
+        pb.displayError(Drupal.ahahError(xmlhttp, pb.uri));
+      }
+    });
+  }
+};
+
+/**
+ * Display errors on the page.
+ */
+Drupal.progressBar.prototype.displayError = function (string) {
+  var error = document.createElement('div');
+  error.className = 'error';
+  error.innerHTML = string;
+
+  $(this.element).before(error).hide();
+
+  if (this.errorCallback) {
+    this.errorCallback(this);
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/tabledrag.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1088 @@
+// $Id: tabledrag.js,v 1.13.2.1 2008/02/08 18:54:10 goba Exp $
+
+/**
+ * Drag and drop table rows with field manipulation.
+ *
+ * Using the drupal_add_tabledrag() function, any table with weights or parent
+ * relationships may be made into draggable tables. Columns containing a field
+ * may optionally be hidden, providing a better user experience.
+ *
+ * Created tableDrag instances may be modified with custom behaviors by
+ * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
+ * See blocks.js for an example of adding additional functionality to tableDrag.
+ */
+Drupal.behaviors.tableDrag = function(context) {
+  for (var base in Drupal.settings.tableDrag) {
+    if (!$('#' + base + '.tabledrag-processed', context).size()) {
+      var tableSettings = Drupal.settings.tableDrag[base];
+
+      $('#' + base).filter(':not(.tabledrag-processed)').each(function() {
+        // Create the new tableDrag instance. Save in the Drupal variable
+        // to allow other scripts access to the object.
+        Drupal.tableDrag[base] = new Drupal.tableDrag(this, tableSettings);
+      });
+
+      $('#' + base).addClass('tabledrag-processed');
+    }
+  }
+};
+
+/**
+ * Constructor for the tableDrag object. Provides table and field manipulation.
+ *
+ * @param table
+ *   DOM object for the table to be made draggable.
+ * @param tableSettings
+ *   Settings for the table added via drupal_add_dragtable().
+ */
+Drupal.tableDrag = function(table, tableSettings) {
+  var self = this;
+
+  // Required object variables.
+  this.table = table;
+  this.tableSettings = tableSettings;
+  this.dragObject = null; // Used to hold information about a current drag operation.
+  this.rowObject = null; // Provides operations for row manipulation.
+  this.oldRowElement = null; // Remember the previous element.
+  this.oldY = 0; // Used to determine up or down direction from last mouse move.
+  this.changed = false; // Whether anything in the entire table has changed.
+  this.maxDepth = 0; // Maximum amount of allowed parenting.
+  this.rtl = $(this.table).css('direction') == 'rtl' ? -1 : 1; // Direction of the table.
+
+  // Configure the scroll settings.
+  this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
+  this.scrollInterval = null;
+  this.scrollY = 0;
+  this.windowHeight = 0;
+
+  // Check this table's settings to see if there are parent relationships in
+  // this table. For efficiency, large sections of code can be skipped if we
+  // don't need to track horizontal movement and indentations.
+  this.indentEnabled = false;
+  for (group in tableSettings) {
+    for (n in tableSettings[group]) {
+      if (tableSettings[group][n]['relationship'] == 'parent') {
+        this.indentEnabled = true;
+      }
+      if (tableSettings[group][n]['limit'] > 0) {
+        this.maxDepth = tableSettings[group][n]['limit'];
+      }
+    }
+  }
+  if (this.indentEnabled) {
+    this.indentCount = 1; // Total width of indents, set in makeDraggable.
+    // Find the width of indentations to measure mouse movements against.
+    // Because the table doesn't need to start with any indentations, we
+    // manually create an empty div, check it's width, then remove.
+    var indent = $(Drupal.theme('tableDragIndentation')).appendTo('body');
+    this.indentAmount = parseInt(indent.css('width'));
+    indent.remove();
+  }
+
+  // Make each applicable row draggable.
+  $('tr.draggable', table).each(function() { self.makeDraggable(this); });
+
+  // Hide columns containing affected form elements.
+  this.hideColumns();
+
+  // Add mouse bindings to the document. The self variable is passed along
+  // as event handlers do not have direct access to the tableDrag object.
+  $(document).bind('mousemove', function(event) { self.dragRow(event, self); return false; });
+  $(document).bind('mouseup', function(event) { self.dropRow(event, self); });
+};
+
+/**
+ * Hide the columns containing form elements according to the settings for
+ * this tableDrag instance.
+ */
+Drupal.tableDrag.prototype.hideColumns = function() {
+  for (var group in this.tableSettings) {
+    // Find the first field in this group.
+    for (var d in this.tableSettings[group]) {
+      if ($('.' + this.tableSettings[group][d]['target'], this.table).size()) {
+        var hidden = this.tableSettings[group][d]['hidden'];
+        var field = $('.' + this.tableSettings[group][d]['target'] + ':first', this.table);
+        var cell = field.parents('td:first, th:first');
+        break;
+      }
+    }
+    // Hide the column containing this field.
+    if (hidden && cell[0] && cell.css('display') != 'none') {
+      // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based.
+      var columnIndex = $('td', field.parents('tr:first')).index(cell.get(0)) + 1;
+      var headerIndex = $('td:not(:hidden)', field.parents('tr:first')).index(cell.get(0)) + 1;
+      $('tbody tr', this.table).each(function() {
+        // Find and hide the cell in the table body.
+        var cell = $('td:nth-child('+ columnIndex +')', this);
+        if (cell.size()) {
+          cell.css('display', 'none');
+        }
+        // We might be dealing with a row spanning the entire table.
+        // Reduce the colspan on the first cell to prevent the cell from
+        // overshooting the table.
+        else {
+          cell = $('td:first', this);
+          cell.attr('colspan', cell.attr('colspan') - 1);
+        }
+      });
+      $('thead tr', this.table).each(function() {
+        // Remove table header cells entirely (Safari doesn't hide properly).
+        var th = $('th:nth-child('+ headerIndex +')', this);
+        if (th.size()) {
+          th.remove();
+        }
+      });
+    }
+  }
+};
+
+/**
+ * Find the target used within a particular row and group.
+ */
+Drupal.tableDrag.prototype.rowSettings = function(group, row) {
+  var field = $('.' + group, row);
+  for (delta in this.tableSettings[group]) {
+    var targetClass = this.tableSettings[group][delta]['target'];
+    if (field.is('.' + targetClass)) {
+      // Return a copy of the row settings.
+      var rowSettings = new Object();
+      for (var n in this.tableSettings[group][delta]) {
+        rowSettings[n] = this.tableSettings[group][delta][n];
+      }
+      return rowSettings;
+    }
+  }
+};
+
+/**
+ * Take an item and add event handlers to make it become draggable.
+ */
+Drupal.tableDrag.prototype.makeDraggable = function(item) {
+  var self = this;
+
+  // Create the handle.
+  var handle = $('<a href="#" class="tabledrag-handle"><div class="handle">&nbsp;</div></a>').attr('title', Drupal.t('Drag to re-order'));
+  // Insert the handle after indentations (if any).
+  if ($('td:first .indentation:last', item).after(handle).size()) {
+    // Update the total width of indentation in this entire table.
+    self.indentCount = Math.max($('.indentation', item).size(), self.indentCount);
+  }
+  else {
+    $('td:first', item).prepend(handle);
+  }
+
+  // Add hover action for the handle.
+  handle.hover(function() {
+    self.dragObject == null ? $(this).addClass('tabledrag-handle-hover') : null;
+  }, function() {
+    self.dragObject == null ? $(this).removeClass('tabledrag-handle-hover') : null;
+  });
+
+  // Add the mousedown action for the handle.
+  handle.mousedown(function(event) {
+    // Create a new dragObject recording the event information.
+    self.dragObject = new Object();
+    self.dragObject.initMouseOffset = self.getMouseOffset(item, event);
+    self.dragObject.initMouseCoords = self.mouseCoords(event);
+    if (self.indentEnabled) {
+      self.dragObject.indentMousePos = self.dragObject.initMouseCoords;
+    }
+
+    // If there's a lingering row object from the keyboard, remove its focus.
+    if (self.rowObject) {
+      $('a.tabledrag-handle', self.rowObject.element).blur();
+    }
+
+    // Create a new rowObject for manipulation of this row.
+    self.rowObject = new self.row(item, 'mouse', self.indentEnabled, self.maxDepth, true);
+
+    // Save the position of the table.
+    self.table.topY = self.getPosition(self.table).y;
+    self.table.bottomY = self.table.topY + self.table.offsetHeight;
+
+    // Add classes to the handle and row.
+    $(this).addClass('tabledrag-handle-hover');
+    $(item).addClass('drag');
+
+    // Set the document to use the move cursor during drag.
+    $('body').addClass('drag');
+    if (self.oldRowElement) {
+      $(self.oldRowElement).removeClass('drag-previous');
+    }
+
+    // Hack for IE6 that flickers uncontrollably if select lists are moved.
+    if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
+      $('select', this.table).css('display', 'none');
+    }
+
+    // Hack for Konqueror, prevent the blur handler from firing.
+    // Konqueror always gives links focus, even after returning false on mousedown.
+    self.safeBlur = false;
+
+    // Call optional placeholder function.
+    self.onDrag();
+    return false;
+  });
+
+  // Prevent the anchor tag from jumping us to the top of the page.
+  handle.click(function() {
+    return false;
+  });
+
+  // Similar to the hover event, add a class when the handle is focused.
+  handle.focus(function() {
+    $(this).addClass('tabledrag-handle-hover');
+    self.safeBlur = true;
+  });
+
+  // Remove the handle class on blur and fire the same function as a mouseup.
+  handle.blur(function(event) {
+    $(this).removeClass('tabledrag-handle-hover');
+    if (self.rowObject && self.safeBlur) {
+      self.dropRow(event, self);
+    }
+  });
+
+  // Add arrow-key support to the handle.
+  handle.keydown(function(event) {
+    // If a rowObject doesn't yet exist and this isn't the tab key.
+    if (event.keyCode != 9 && !self.rowObject) {
+      self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);
+    }
+
+    var keyChange = false;
+    switch (event.keyCode) {
+      case 37: // Left arrow.
+      case 63234: // Safari left arrow.
+        keyChange = true;
+        self.rowObject.indent(-1 * self.rtl);
+        break;
+      case 38: // Up arrow.
+      case 63232: // Safari up arrow.
+        var previousRow = $(self.rowObject.element).prev('tr').get(0);
+        while (previousRow && $(previousRow).is(':hidden')) {
+          previousRow = $(previousRow).prev('tr').get(0);
+        }
+        if (previousRow) {
+          self.safeBlur = false; // Do not allow the onBlur cleanup.
+          self.rowObject.direction = 'up';
+          keyChange = true;
+
+          if ($(item).is('.tabledrag-root')) {
+            // Swap with the previous top-level row..
+            var groupHeight = 0;
+            while (previousRow && $('.indentation', previousRow).size()) {
+              previousRow = $(previousRow).prev('tr').get(0);
+              groupHeight += $(previousRow).is(':hidden') ? 0 : previousRow.offsetHeight;
+            }
+            if (previousRow) {
+              self.rowObject.swap('before', previousRow);
+              // No need to check for indentation, 0 is the only valid one.
+              window.scrollBy(0, -groupHeight);
+            }
+          }
+          else if (self.table.tBodies[0].rows[0] != previousRow || $(previousRow).is('.draggable')) {
+            // Swap with the previous row (unless previous row is the first one
+            // and undraggable).
+            self.rowObject.swap('before', previousRow);
+            self.rowObject.interval = null;
+            self.rowObject.indent(0);
+            window.scrollBy(0, -parseInt(item.offsetHeight));
+          }
+          handle.get(0).focus(); // Regain focus after the DOM manipulation.
+        }
+        break;
+      case 39: // Right arrow.
+      case 63235: // Safari right arrow.
+        keyChange = true;
+        self.rowObject.indent(1 * self.rtl);
+        break;
+      case 40: // Down arrow.
+      case 63233: // Safari down arrow.
+        var nextRow = $(self.rowObject.group).filter(':last').next('tr').get(0);
+        while (nextRow && $(nextRow).is(':hidden')) {
+          nextRow = $(nextRow).next('tr').get(0);
+        }
+        if (nextRow) {
+          self.safeBlur = false; // Do not allow the onBlur cleanup.
+          self.rowObject.direction = 'down';
+          keyChange = true;
+
+          if ($(item).is('.tabledrag-root')) {
+            // Swap with the next group (necessarily a top-level one).
+            var groupHeight = 0;
+            nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);
+            if (nextGroup) {
+              $(nextGroup.group).each(function () {groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight});
+              nextGroupRow = $(nextGroup.group).filter(':last').get(0);
+              self.rowObject.swap('after', nextGroupRow);
+              // No need to check for indentation, 0 is the only valid one.
+              window.scrollBy(0, parseInt(groupHeight));
+            }
+          }
+          else {
+            // Swap with the next row.
+            self.rowObject.swap('after', nextRow);
+            self.rowObject.interval = null;
+            self.rowObject.indent(0);
+            window.scrollBy(0, parseInt(item.offsetHeight));
+          }
+          handle.get(0).focus(); // Regain focus after the DOM manipulation.
+        }
+        break;
+    }
+
+    if (self.rowObject && self.rowObject.changed == true) {
+      $(item).addClass('drag');
+      if (self.oldRowElement) {
+        $(self.oldRowElement).removeClass('drag-previous');
+      }
+      self.oldRowElement = item;
+      self.restripeTable();
+      self.onDrag();
+    }
+
+    // Returning false if we have an arrow key to prevent scrolling.
+    if (keyChange) {
+      return false;
+    }
+  });
+
+  // Compatibility addition, return false on keypress to prevent unwanted scrolling.
+  // IE and Safari will supress scrolling on keydown, but all other browsers
+  // need to return false on keypress. http://www.quirksmode.org/js/keys.html
+  handle.keypress(function(event) {
+    switch (event.keyCode) {
+      case 37: // Left arrow.
+      case 38: // Up arrow.
+      case 39: // Right arrow.
+      case 40: // Down arrow.
+        return false;
+    }
+  });
+};
+
+/**
+ * Mousemove event handler, bound to document.
+ */
+Drupal.tableDrag.prototype.dragRow = function(event, self) {
+  if (self.dragObject) {
+    self.currentMouseCoords = self.mouseCoords(event);
+
+    var y = self.currentMouseCoords.y - self.dragObject.initMouseOffset.y;
+    var x = self.currentMouseCoords.x - self.dragObject.initMouseOffset.x;
+
+    // Check for row swapping and vertical scrolling.
+    if (y != self.oldY) {
+      self.rowObject.direction = y > self.oldY ? 'down' : 'up';
+      self.oldY = y; // Update the old value.
+
+      // Check if the window should be scrolled (and how fast).
+      var scrollAmount = self.checkScroll(self.currentMouseCoords.y);
+      // Stop any current scrolling.
+      clearInterval(self.scrollInterval);
+      // Continue scrolling if the mouse has moved in the scroll direction.
+      if (scrollAmount > 0 && self.rowObject.direction == 'down' || scrollAmount < 0 && self.rowObject.direction == 'up') {
+        self.setScroll(scrollAmount);
+      }
+
+      // If we have a valid target, perform the swap and restripe the table.
+      var currentRow = self.findDropTargetRow(x, y);
+      if (currentRow) {
+        if (self.rowObject.direction == 'down') {
+          self.rowObject.swap('after', currentRow, self);
+        }
+        else {
+          self.rowObject.swap('before', currentRow, self);
+        }
+        self.restripeTable();
+      }
+    }
+
+    // Similar to row swapping, handle indentations.
+    if (self.indentEnabled) {
+      var xDiff = self.currentMouseCoords.x - self.dragObject.indentMousePos.x;
+      // Set the number of indentations the mouse has been moved left or right.
+      var indentDiff = parseInt(xDiff / self.indentAmount * self.rtl);
+      // Indent the row with our estimated diff, which may be further
+      // restricted according to the rows around this row.
+      var indentChange = self.rowObject.indent(indentDiff);
+      // Update table and mouse indentations.
+      self.dragObject.indentMousePos.x += self.indentAmount * indentChange * self.rtl;
+      self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
+    }
+  }
+};
+
+/**
+ * Mouseup event handler, bound to document.
+ * Blur event handler, bound to drag handle for keyboard support.
+ */
+Drupal.tableDrag.prototype.dropRow = function(event, self) {
+  // Drop row functionality shared between mouseup and blur events.
+  if (self.rowObject != null) {
+    var droppedRow = self.rowObject.element;
+    // The row is already in the right place so we just release it.
+    if (self.rowObject.changed == true) {
+      // Update the fields in the dropped row.
+      self.updateFields(droppedRow);
+
+      // If a setting exists for affecting the entire group, update all the
+      // fields in the entire dragged group.
+      for (var group in self.tableSettings) {
+        var rowSettings = self.rowSettings(group, droppedRow);
+        if (rowSettings.relationship == 'group') {
+          for (n in self.rowObject.children) {
+            self.updateField(self.rowObject.children[n], group);
+          }
+        }
+      }
+
+      self.rowObject.markChanged();
+      if (self.changed == false) {
+        $(Drupal.theme('tableDragChangedWarning')).insertAfter(self.table).hide().fadeIn('slow');
+        self.changed = true;
+      }
+    }
+
+    if (self.indentEnabled) {
+      self.rowObject.removeIndentClasses();
+    }
+    if (self.oldRowElement) {
+      $(self.oldRowElement).removeClass('drag-previous');
+    }
+    $(droppedRow).removeClass('drag').addClass('drag-previous');
+    self.oldRowElement = droppedRow;
+    self.onDrop();
+    self.rowObject = null;
+  }
+
+  // Functionality specific only to mouseup event.
+  if (self.dragObject != null) {
+    $('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover');
+
+    self.dragObject = null;
+    $('body').removeClass('drag');
+    clearInterval(self.scrollInterval);
+
+    // Hack for IE6 that flickers uncontrollably if select lists are moved.
+    if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
+      $('select', this.table).css('display', 'block');
+    }
+  }
+};
+
+/**
+ * Get the position of an element by adding up parent offsets in the DOM tree.
+ */
+Drupal.tableDrag.prototype.getPosition = function(element){
+  var left = 0;
+  var top  = 0;
+  // Because Safari doesn't report offsetHeight on table rows, but does on table
+  // cells, grab the firstChild of the row and use that instead.
+  // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari
+  if (element.offsetHeight == 0) {
+    element = element.firstChild; // a table cell
+  }
+
+  while (element.offsetParent){
+    left   += element.offsetLeft;
+    top    += element.offsetTop;
+    element = element.offsetParent;
+  }
+
+  left += element.offsetLeft;
+  top  += element.offsetTop;
+
+  return {x:left, y:top};
+};
+
+/**
+ * Get the mouse coordinates from the event (allowing for browser differences).
+ */
+Drupal.tableDrag.prototype.mouseCoords = function(event){
+  if (event.pageX || event.pageY) {
+    return {x:event.pageX, y:event.pageY};
+  }
+  return {
+    x:event.clientX + document.body.scrollLeft - document.body.clientLeft,
+    y:event.clientY + document.body.scrollTop  - document.body.clientTop
+  };
+};
+
+/**
+ * Given a target element and a mouse event, get the mouse offset from that
+ * element. To do this we need the element's position and the mouse position.
+ */
+Drupal.tableDrag.prototype.getMouseOffset = function(target, event) {
+  var docPos   = this.getPosition(target);
+  var mousePos = this.mouseCoords(event);
+  return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
+};
+
+/**
+ * Find the row the mouse is currently over. This row is then taken and swapped
+ * with the one being dragged.
+ *
+ * @param x
+ *   The x coordinate of the mouse on the page (not the screen).
+ * @param y
+ *   The y coordinate of the mouse on the page (not the screen).
+ */
+Drupal.tableDrag.prototype.findDropTargetRow = function(x, y) {
+  var rows = this.table.tBodies[0].rows;
+  for (var n=0; n<rows.length; n++) {
+    var row = rows[n];
+    var indentDiff = 0;
+    // Safari fix see Drupal.tableDrag.prototype.getPosition()
+    if (row.offsetHeight == 0) {
+      var rowY = this.getPosition(row.firstChild).y;
+      var rowHeight = parseInt(row.firstChild.offsetHeight)/2;
+    }
+    // Other browsers.
+    else {
+      var rowY = this.getPosition(row).y;
+      var rowHeight = parseInt(row.offsetHeight)/2;
+    }
+
+    // Because we always insert before, we need to offset the height a bit.
+    if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {
+      if (this.indentEnabled) {
+        // Check that this row is not a child of the row being dragged.
+        for (n in this.rowObject.group) {
+          if (this.rowObject.group[n] == row) {
+            return null;
+          }
+        }
+      }
+      // Check that swapping with this row is allowed.
+      if (!this.rowObject.isValidSwap(row)) {
+        return null;
+      }
+
+      // We may have found the row the mouse just passed over, but it doesn't
+      // take into account hidden rows. Skip backwards until we find a draggable
+      // row.
+      while ($(row).is(':hidden') && $(row).prev('tr').is(':hidden')) {
+        row = $(row).prev('tr').get(0);
+      }
+      return row;
+    }
+  }
+  return null;
+};
+
+/**
+ * After the row is dropped, update the table fields according to the settings
+ * set for this table.
+ *
+ * @param changedRow
+ *   DOM object for the row that was just dropped.
+ */
+Drupal.tableDrag.prototype.updateFields = function(changedRow) {
+  for (var group in this.tableSettings) {
+    // Each group may have a different setting for relationship, so we find
+    // the source rows for each seperately.
+    this.updateField(changedRow, group);
+  }
+};
+
+/**
+ * After the row is dropped, update a single table field according to specific
+ * settings.
+ *
+ * @param changedRow
+ *   DOM object for the row that was just dropped.
+ * @param group
+ *   The settings group on which field updates will occur.
+ */
+Drupal.tableDrag.prototype.updateField = function(changedRow, group) {
+  var rowSettings = this.rowSettings(group, changedRow);
+
+  // Set the row as it's own target.
+  if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
+    var sourceRow = changedRow;
+  }
+  // Siblings are easy, check previous and next rows.
+  else if (rowSettings.relationship == 'sibling') {
+    var previousRow = $(changedRow).prev('tr').get(0);
+    var nextRow = $(changedRow).next('tr').get(0);
+    var sourceRow = changedRow;
+    if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
+      if (this.indentEnabled) {
+        if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
+          sourceRow = previousRow;
+        }
+      }
+      else {
+        sourceRow = previousRow;
+      }
+    }
+    else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
+      if (this.indentEnabled) {
+        if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
+          sourceRow = nextRow;
+        }
+      }
+      else {
+        sourceRow = nextRow;
+      }
+    }
+  }
+  // Parents, look up the tree until we find a field not in this group.
+  // Go up as many parents as indentations in the changed row.
+  else if (rowSettings.relationship == 'parent') {
+    var previousRow = $(changedRow).prev('tr');
+    while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
+      previousRow = previousRow.prev('tr');
+    }
+    // If we found a row.
+    if (previousRow.length) {
+      sourceRow = previousRow[0];
+    }
+    // Otherwise we went all the way to the left of the table without finding
+    // a parent, meaning this item has been placed at the root level.
+    else {
+      // Use the first row in the table as source, because it's garanteed to
+      // be at the root level. Find the first item, then compare this row
+      // against it as a sibling.
+      sourceRow = $('tr.draggable:first').get(0);
+      if (sourceRow == this.rowObject.element) {
+        sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+      }
+      var useSibling = true;
+    }
+  }
+
+  // Because we may have moved the row from one category to another,
+  // take a look at our sibling and borrow its sources and targets.
+  this.copyDragClasses(sourceRow, changedRow, group);
+  rowSettings = this.rowSettings(group, changedRow);
+
+  // In the case that we're looking for a parent, but the row is at the top
+  // of the tree, copy our sibling's values.
+  if (useSibling) {
+    rowSettings.relationship = 'sibling';
+    rowSettings.source = rowSettings.target;
+  }
+
+  var targetClass = '.' + rowSettings.target;
+  var targetElement = $(targetClass, changedRow).get(0);
+
+  // Check if a target element exists in this row.
+  if (targetElement) {
+    var sourceClass = '.' + rowSettings.source;
+    var sourceElement = $(sourceClass, sourceRow).get(0);
+    switch (rowSettings.action) {
+      case 'depth':
+        // Get the depth of the target row.
+        targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
+        break;
+      case 'match':
+        // Update the value.
+        targetElement.value = sourceElement.value;
+        break;
+      case 'order':
+        var siblings = this.rowObject.findSiblings(rowSettings);
+        if ($(targetElement).is('select')) {
+          // Get a list of acceptable values.
+          var values = new Array();
+          $('option', targetElement).each(function() {
+            values.push(this.value);
+          });
+          var maxVal = values[values.length - 1];
+          // Populate the values in the siblings.
+          $(targetClass, siblings).each(function() {
+            // If there are more items than possible values, assign the maximum value to the row. 
+            if (values.length > 0) {
+              this.value = values.shift();
+            }
+            else {
+              this.value = maxVal;
+            }
+          });
+        }
+        else {
+          // Assume a numeric input field.
+          var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
+          $(targetClass, siblings).each(function() {
+            this.value = weight;
+            weight++;
+          });
+        }
+        break;
+    }
+  }
+};
+
+/**
+ * Copy all special tableDrag classes from one row's form elements to a
+ * different one, removing any special classes that the destination row
+ * may have had.
+ */
+Drupal.tableDrag.prototype.copyDragClasses = function(sourceRow, targetRow, group) {
+  var sourceElement = $('.' + group, sourceRow);
+  var targetElement = $('.' + group, targetRow);
+  if (sourceElement.length && targetElement.length) {
+    targetElement[0].className = sourceElement[0].className;
+  }
+};
+
+Drupal.tableDrag.prototype.checkScroll = function(cursorY) {
+  var de  = document.documentElement;
+  var b  = document.body;
+
+  var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth != 0 ? de.clientHeight : b.offsetHeight);
+  var scrollY = this.scrollY = (document.all ? (!de.scrollTop ? b.scrollTop : de.scrollTop) : (window.pageYOffset ? window.pageYOffset : window.scrollY));
+  var trigger = this.scrollSettings.trigger;
+  var delta = 0;
+
+  // Return a scroll speed relative to the edge of the screen.
+  if (cursorY - scrollY > windowHeight - trigger) {
+    delta = trigger / (windowHeight + scrollY - cursorY);
+    delta = (delta > 0 && delta < trigger) ? delta : trigger;
+    return delta * this.scrollSettings.amount;
+  }
+  else if (cursorY - scrollY < trigger) {
+    delta = trigger / (cursorY - scrollY);
+    delta = (delta > 0 && delta < trigger) ? delta : trigger;
+    return -delta * this.scrollSettings.amount;
+  }
+};
+
+Drupal.tableDrag.prototype.setScroll = function(scrollAmount) {
+  var self = this;
+
+  this.scrollInterval = setInterval(function() {
+    // Update the scroll values stored in the object.
+    self.checkScroll(self.currentMouseCoords.y);
+    var aboveTable = self.scrollY > self.table.topY;
+    var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;
+    if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {
+      window.scrollBy(0, scrollAmount);
+    }
+  }, this.scrollSettings.interval);
+};
+
+Drupal.tableDrag.prototype.restripeTable = function() {
+  // :even and :odd are reversed because jquery counts from 0 and
+  // we count from 1, so we're out of sync.
+  $('tr.draggable', this.table)
+    .filter(':odd').filter('.odd')
+      .removeClass('odd').addClass('even')
+    .end().end()
+    .filter(':even').filter('.even')
+      .removeClass('even').addClass('odd');
+};
+
+/**
+ * Stub function. Allows a custom handler when a row begins dragging.
+ */
+Drupal.tableDrag.prototype.onDrag = function() {
+  return null;
+};
+
+/**
+ * Stub function. Allows a custom handler when a row is dropped.
+ */
+Drupal.tableDrag.prototype.onDrop = function() {
+  return null;
+};
+
+/**
+ * Constructor to make a new object to manipulate a table row.
+ *
+ * @param tableRow
+ *   The DOM element for the table row we will be manipulating.
+ * @param method
+ *   The method in which this row is being moved. Either 'keyboard' or 'mouse'.
+ * @param indentEnabled
+ *   Whether the containing table uses indentations. Used for optimizations.
+ * @param maxDepth
+ *   The maximum amount of indentations this row may contain.
+ * @param addClasses
+ *   Whether we want to add classes to this row to indicate child relationships.
+ */
+Drupal.tableDrag.prototype.row = function(tableRow, method, indentEnabled, maxDepth, addClasses) {
+  this.element = tableRow;
+  this.method = method;
+  this.group = new Array(tableRow);
+  this.groupDepth = $('.indentation', tableRow).size();
+  this.changed = false;
+  this.table = $(tableRow).parents('table:first').get(0);
+  this.indentEnabled = indentEnabled;
+  this.maxDepth = maxDepth;
+  this.direction = ''; // Direction the row is being moved.
+
+  if (this.indentEnabled) {
+    this.indents = $('.indentation', tableRow).size();
+    this.children = this.findChildren(addClasses);
+    this.group = $.merge(this.group, this.children);
+    // Find the depth of this entire group.
+    for (var n = 0; n < this.group.length; n++) {
+      this.groupDepth = Math.max($('.indentation', this.group[n]).size(), this.groupDepth);
+    }
+  }
+};
+
+/**
+ * Find all children of rowObject by indentation.
+ *
+ * @param addClasses
+ *   Whether we want to add classes to this row to indicate child relationships.
+ */
+Drupal.tableDrag.prototype.row.prototype.findChildren = function(addClasses) {
+  var parentIndentation = this.indents;
+  var currentRow = $(this.element, this.table).next('tr.draggable');
+  var rows = new Array();
+  var child = 0;
+  while (currentRow.length) {
+    var rowIndentation = $('.indentation', currentRow).length;
+    // A greater indentation indicates this is a child.
+    if (rowIndentation > parentIndentation) {
+      child++;
+      rows.push(currentRow[0]);
+      if (addClasses) {
+        $('.indentation', currentRow).each(function(indentNum) {
+          if (child == 1 && (indentNum == parentIndentation)) {
+            $(this).addClass('tree-child-first');
+          }
+          if (indentNum == parentIndentation) {
+            $(this).addClass('tree-child');
+          }
+          else if (indentNum > parentIndentation) {
+            $(this).addClass('tree-child-horizontal');
+          }
+        });
+      }
+    }
+    else {
+      break;
+    }
+    currentRow = currentRow.next('tr.draggable');
+  }
+  if (addClasses && rows.length) {
+    $('.indentation:nth-child(' + (parentIndentation + 1) + ')', rows[rows.length - 1]).addClass('tree-child-last');
+  }
+  return rows;
+};
+
+/**
+ * Ensure that two rows are allowed to be swapped.
+ *
+ * @param row
+ *   DOM object for the row being considered for swapping.
+ */
+Drupal.tableDrag.prototype.row.prototype.isValidSwap = function(row) {
+  if (this.indentEnabled) {
+    var prevRow, nextRow;
+    if (this.direction == 'down') {
+      prevRow = row;
+      nextRow = $(row).next('tr').get(0);
+    }
+    else {
+      prevRow = $(row).prev('tr').get(0);
+      nextRow = row;
+    }
+    this.interval = this.validIndentInterval(prevRow, nextRow);
+
+    // We have an invalid swap if the valid indentations interval is empty.
+    if (this.interval.min > this.interval.max) {
+      return false;
+    }
+  }
+
+  // Do not let an un-draggable first row have anything put before it.
+  if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) {
+    return false;
+  }
+
+  return true;
+};
+
+/**
+ * Perform the swap between two rows.
+ *
+ * @param position
+ *   Whether the swap will occur 'before' or 'after' the given row.
+ * @param row
+ *   DOM element what will be swapped with the row group.
+ */
+Drupal.tableDrag.prototype.row.prototype.swap = function(position, row) {
+  $(row)[position](this.group);
+  this.changed = true;
+  this.onSwap(row);
+};
+
+/**
+ * Determine the valid indentations interval for the row at a given position
+ * in the table.
+ *
+ * @param prevRow
+ *   DOM object for the row before the tested position
+ *   (or null for first position in the table).
+ * @param nextRow
+ *   DOM object for the row after the tested position
+ *   (or null for last position in the table).
+ */
+Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {
+  var minIndent, maxIndent;
+
+  // Minimum indentation:
+  // Do not orphan the next row.
+  minIndent = nextRow ? $('.indentation', nextRow).size() : 0;
+
+  // Maximum indentation:
+  if (!prevRow || $(this.element).is('.tabledrag-root')) {
+    // Do not indent the first row in the table or 'root' rows..
+    maxIndent = 0;
+  }
+  else {
+    // Do not go deeper than as a child of the previous row.
+    maxIndent = $('.indentation', prevRow).size() + ($(prevRow).is('.tabledrag-leaf') ? 0 : 1);
+    // Limit by the maximum allowed depth for the table.
+    if (this.maxDepth) {
+      maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));
+    }
+  }
+
+  return {'min':minIndent, 'max':maxIndent};
+}
+
+/**
+ * Indent a row within the legal bounds of the table.
+ *
+ * @param indentDiff
+ *   The number of additional indentations proposed for the row (can be
+ *   positive or negative). This number will be adjusted to nearest valid
+ *   indentation level for the row.
+ */
+Drupal.tableDrag.prototype.row.prototype.indent = function(indentDiff) {
+  // Determine the valid indentations interval if not available yet.
+  if (!this.interval) {
+    prevRow = $(this.element).prev('tr').get(0);
+    nextRow = $(this.group).filter(':last').next('tr').get(0);
+    this.interval = this.validIndentInterval(prevRow, nextRow);
+  }
+
+  // Adjust to the nearest valid indentation.
+  var indent = this.indents + indentDiff;
+  indent = Math.max(indent, this.interval.min);
+  indent = Math.min(indent, this.interval.max);
+  indentDiff = indent - this.indents;
+
+  for (var n = 1; n <= Math.abs(indentDiff); n++) {
+    // Add or remove indentations.
+    if (indentDiff < 0) {
+      $('.indentation:first', this.group).remove();
+      this.indents--;
+    }
+    else {
+      $('td:first', this.group).prepend(Drupal.theme('tableDragIndentation'));
+      this.indents++;
+    }
+  }
+  if (indentDiff) {
+    // Update indentation for this row.
+    this.changed = true;
+    this.groupDepth += indentDiff;
+    this.onIndent();
+  }
+
+  return indentDiff;
+};
+
+/**
+ * Find all siblings for a row, either according to its subgroup or indentation.
+ * Note that the passed in row is included in the list of siblings.
+ *
+ * @param settings
+ *   The field settings we're using to identify what constitutes a sibling.
+ */
+Drupal.tableDrag.prototype.row.prototype.findSiblings = function(rowSettings) {
+  var siblings = new Array();
+  var directions = new Array('prev', 'next');
+  var rowIndentation = this.indents;
+  for (var d in directions) {
+    var checkRow = $(this.element)[directions[d]]();
+    while (checkRow.length) {
+      // Check that the sibling contains a similar target field.
+      if ($('.' + rowSettings.target, checkRow)) {
+        // Either add immediately if this is a flat table, or check to ensure
+        // that this row has the same level of indentaiton.
+        if (this.indentEnabled) {
+          var checkRowIndentation = $('.indentation', checkRow).length
+        }
+
+        if (!(this.indentEnabled) || (checkRowIndentation == rowIndentation)) {
+          siblings.push(checkRow[0]);
+        }
+        else if (checkRowIndentation < rowIndentation) {
+          // No need to keep looking for siblings when we get to a parent.
+          break;
+        }
+      }
+      else {
+        break;
+      }
+      checkRow = $(checkRow)[directions[d]]();
+    }
+    // Since siblings are added in reverse order for previous, reverse the
+    // completed list of previous siblings. Add the current row and continue.
+    if (directions[d] == 'prev') {
+      siblings.reverse();
+      siblings.push(this.element);
+    }
+  }
+  return siblings;
+};
+
+/**
+ * Remove indentation helper classes from the current row group.
+ */
+Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function() {
+  for (n in this.children) {
+    $('.indentation', this.children[n])
+      .removeClass('tree-child')
+      .removeClass('tree-child-first')
+      .removeClass('tree-child-last')
+      .removeClass('tree-child-horizontal');
+  }
+};
+
+/**
+ * Add an asterisk or other marker to the changed row.
+ */
+Drupal.tableDrag.prototype.row.prototype.markChanged = function() {
+  var marker = Drupal.theme('tableDragChangedMarker');
+  var cell = $('td:first', this.element);
+  if ($('span.tabledrag-changed', cell).length == 0) {
+    cell.append(marker);
+  }
+};
+
+/**
+ * Stub function. Allows a custom handler when a row is indented.
+ */
+Drupal.tableDrag.prototype.row.prototype.onIndent = function() {
+  return null;
+};
+
+/**
+ * Stub function. Allows a custom handler when a row is swapped.
+ */
+Drupal.tableDrag.prototype.row.prototype.onSwap = function(swappedRow) {
+  return null;
+};
+
+Drupal.theme.prototype.tableDragChangedMarker = function () {
+  return '<span class="warning tabledrag-changed">*</span>';
+};
+
+Drupal.theme.prototype.tableDragIndentation = function () {
+  return '<div class="indentation">&nbsp;</div>';
+};
+
+Drupal.theme.prototype.tableDragChangedWarning = function () {
+  return '<div class="warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("Changes made in this table will not be saved until the form is submitted.") + '</div>';
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/tableheader.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,100 @@
+// $Id: tableheader.js,v 1.16 2008/01/30 10:17:39 goba Exp $
+
+Drupal.tableHeaderDoScroll = function() {
+  if (typeof(Drupal.tableHeaderOnScroll)=='function') {
+    Drupal.tableHeaderOnScroll();
+  }
+};
+
+Drupal.behaviors.tableHeader = function (context) {
+  // This breaks in anything less than IE 7. Prevent it from running.
+  if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 7) {
+    return;
+  }
+
+  // Keep track of all cloned table headers.
+  var headers = [];
+
+  $('table.sticky-enabled thead:not(.tableHeader-processed)', context).each(function () {
+    // Clone thead so it inherits original jQuery properties.
+    var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('<table class="sticky-header"></table>').parent().css({
+      position: 'fixed',
+      top: '0px'
+    });
+
+    headerClone = $(headerClone)[0];
+    headers.push(headerClone);
+
+    // Store parent table.
+    var table = $(this).parent('table')[0];
+    headerClone.table = table;
+    // Finish initialzing header positioning.
+    tracker(headerClone);
+
+    $(table).addClass('sticky-table');
+    $(this).addClass('tableHeader-processed');
+  });
+
+  // Track positioning and visibility.
+  function tracker(e) {
+    // Save positioning data.
+    var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
+    if (e.viewHeight != viewHeight) {
+      e.viewHeight = viewHeight;
+      e.vPosition = $(e.table).offset().top - 4;
+      e.hPosition = $(e.table).offset().left;
+      e.vLength = e.table.clientHeight - 100;
+      // Resize header and its cell widths.
+      var parentCell = $('th', e.table);
+      $('th', e).each(function(index) {
+        var cellWidth = parentCell.eq(index).css('width');
+        // Exception for IE7.
+        if (cellWidth == 'auto') {
+          cellWidth = parentCell.get(index).clientWidth +'px';
+        }
+        $(this).css('width', cellWidth);
+      });
+      $(e).css('width', $(e.table).css('width'));
+    }
+
+    // Track horizontal positioning relative to the viewport and set visibility.
+    var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft;
+    var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - e.vPosition;
+    var visState = (vOffset > 0 && vOffset < e.vLength) ? 'visible' : 'hidden';
+    $(e).css({left: -hScroll + e.hPosition +'px', visibility: visState});
+  }
+
+  // Only attach to scrollbars once, even if Drupal.attachBehaviors is called
+  //  multiple times.
+  if (!$('body').hasClass('tableHeader-processed')) {
+    $('body').addClass('tableHeader-processed');
+    $(window).scroll(Drupal.tableHeaderDoScroll);
+    $(document.documentElement).scroll(Drupal.tableHeaderDoScroll);
+  }
+
+  // Track scrolling.
+  Drupal.tableHeaderOnScroll = function() {
+    $(headers).each(function () {
+      tracker(this);
+    });
+  };
+
+  // Track resizing.
+  var time = null;
+  var resize = function () {
+    // Ensure minimum time between adjustments.
+    if (time) {
+      return;
+    }
+    time = setTimeout(function () {
+      $('table.sticky-header').each(function () {
+        // Force cell width calculation.
+        this.viewHeight = 0;
+        tracker(this);
+      });
+      // Reset timer
+      time = null;
+    }, 250);
+  };
+  $(window).resize(resize);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/tableselect.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,87 @@
+// $Id: tableselect.js,v 1.8 2007/11/19 12:15:16 goba Exp $
+
+Drupal.behaviors.tableSelect = function (context) {
+  $('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
+};
+
+Drupal.tableSelect = function() {
+  // Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
+  if ($('td input:checkbox', this).size() == 0) {
+    return;
+  }
+
+  // Keep track of the table, which checkbox is checked and alias the settings.
+  var table = this, checkboxes, lastChecked;
+  var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
+  var updateSelectAll = function(state) {
+    $('th.select-all input:checkbox', table).each(function() {
+      $(this).attr('title', state ? strings.selectNone : strings.selectAll);
+      this.checked = state;
+    });
+  };
+
+  // Find all <th> with class select-all, and insert the check all checkbox.
+  $('th.select-all', table).prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).click(function(event) {
+    if ($(event.target).is('input:checkbox')) {
+      // Loop through all checkboxes and set their state to the select all checkbox' state.
+      checkboxes.each(function() {
+        this.checked = event.target.checked;
+        // Either add or remove the selected class based on the state of the check all checkbox.
+        $(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+      });
+      // Update the title and the state of the check all box.
+      updateSelectAll(event.target.checked);
+    }
+  });
+
+  // For each of the checkboxes within the table.
+  checkboxes = $('td input:checkbox', table).click(function(e) {
+    // Either add or remove the selected class based on the state of the check all checkbox.
+    $(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+
+    // If this is a shift click, we need to highlight everything in the range.
+    // Also make sure that we are actually checking checkboxes over a range and
+    // that a checkbox has been checked or unchecked before.
+    if (e.shiftKey && lastChecked && lastChecked != e.target) {
+      // We use the checkbox's parent TR to do our range searching.
+      Drupal.tableSelectRange($(e.target).parents('tr')[0], $(lastChecked).parents('tr')[0], e.target.checked);
+    }
+
+    // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
+    updateSelectAll((checkboxes.length == $(checkboxes).filter(':checked').length));
+
+    // Keep track of the last checked checkbox.
+    lastChecked = e.target;
+  });
+  $(this).addClass('tableSelect-processed');
+};
+
+Drupal.tableSelectRange = function(from, to, state) {
+  // We determine the looping mode based on the the order of from and to.
+  var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+
+  // Traverse through the sibling nodes.
+  for (var i = from[mode]; i; i = i[mode]) {
+    // Make sure that we're only dealing with elements.
+    if (i.nodeType != 1) {
+      continue;
+    }
+
+    // Either add or remove the selected class based on the state of the target checkbox.
+    $(i)[ state ? 'addClass' : 'removeClass' ]('selected');
+    $('input:checkbox', i).each(function() {
+      this.checked = state;
+    });
+
+    if (to.nodeType) {
+      // If we are at the end of the range, stop.
+      if (i == to) {
+        break;
+      }
+    }
+    // A faster alternative to doing $(i).filter(to).length.
+    else if (jQuery.filter(to, [i]).r.length) {
+      break;
+    }
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/teaser.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,96 @@
+// $Id: teaser.js,v 1.12 2008/01/09 12:10:04 goba Exp $
+
+/**
+ * Auto-attach for teaser behavior.
+ *
+ * Note: depends on resizable textareas.
+ */
+Drupal.behaviors.teaser = function(context) {
+  // This breaks in Konqueror. Prevent it from running.
+  if (/KDE/.test(navigator.vendor)) {
+    return;
+  }
+
+  $('textarea.teaser:not(.teaser-processed)', context).each(function() {
+    var teaser = $(this).addClass('teaser-processed');
+
+    // Move teaser textarea before body, and remove its form-item wrapper.
+    var body = $('#'+ Drupal.settings.teaser[this.id]);
+    var checkbox = $('#'+ Drupal.settings.teaserCheckbox[this.id]).parent();
+    var checked = $(checkbox).children('input').attr('checked') ? true : false;
+    var parent = teaser[0].parentNode;
+    $(body).before(teaser);
+    $(parent).remove();
+
+    function trim(text) {
+      return text.replace(/^\s+/g, '').replace(/\s+$/g, '');
+    }
+
+    // Join the teaser back to the body.
+    function join_teaser() {
+      if (teaser.val()) {
+        body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val()));
+      }
+      // Empty, hide and disable teaser.
+      teaser[0].value = '';
+      $(teaser).attr('disabled', 'disabled');
+      $(teaser).parent().slideUp('fast');
+      // Change label.
+      $(this).val(Drupal.t('Split summary at cursor'));
+      // Hide separate teaser checkbox.
+      $(checkbox).hide();
+      // Force a hidden checkbox to be checked (to ensure that the body is
+      // correctly processed on form submit when teaser/body are in joined
+      // state), and remember the current checked status.
+      checked = $(checkbox).children('input').attr('checked') ? true : false;
+      $(checkbox).children('input').attr('checked', true);
+    }
+
+    // Split the teaser from the body.
+    function split_teaser() {
+      body[0].focus();
+      var selection = Drupal.getSelection(body[0]);
+      var split = selection.start;
+      var text = body.val();
+
+      // Note: using val() fails sometimes. jQuery bug?
+      teaser[0].value = trim(text.slice(0, split));
+      body[0].value = trim(text.slice(split));
+      // Reveal and enable teaser
+      $(teaser).attr('disabled', '');
+      $(teaser).parent().slideDown('fast');
+      // Change label
+      $(this).val(Drupal.t('Join summary'));
+      // Show separate teaser checkbox, restore checked value.
+      $(checkbox).show().children('input').attr('checked', checked);
+    }
+
+    // Add split/join button.
+    var button = $('<div class="teaser-button-wrapper"><input type="button" class="teaser-button" /></div>');
+    var include = $('#'+ this.id.substring(0, this.id.length - 2) +'include');
+    $(include).parent().parent().before(button);
+
+    // Extract the teaser from the body, if set. Otherwise, stay in joined mode.
+    var text = body.val().split('<!--break-->', 2);
+    if (text.length == 2) {
+      teaser[0].value = trim(text[0]);
+      body[0].value = trim(text[1]);
+      $(teaser).attr('disabled', '');
+      $('input', button).val(Drupal.t('Join summary')).toggle(join_teaser, split_teaser);
+    }
+    else {
+      $('input', button).val(Drupal.t('Split summary at cursor')).toggle(split_teaser, join_teaser);
+      $(checkbox).hide().children('input').attr('checked', true);
+    }
+
+    // Make sure that textarea.js has done its magic to ensure proper visibility state.
+    if (Drupal.behaviors.textarea && teaser.is(('.form-textarea:not(.textarea-processed)'))) {
+      Drupal.behaviors.textarea(teaser.parentNode);
+    }
+    // Set initial visibility
+    if ($(teaser).is('[@disabled]')) {
+      $(teaser).parent().hide();
+    }
+
+  });
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/textarea.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,36 @@
+// $Id: textarea.js,v 1.22 2008/01/17 19:31:56 goba Exp $
+
+Drupal.behaviors.textarea = function(context) {
+  $('textarea.resizable:not(.textarea-processed)', context).each(function() {
+    // Avoid non-processed teasers.
+    if ($(this).is(('textarea.teaser:not(.teaser-processed)'))) {
+      return false;  
+    }
+    var textarea = $(this).addClass('textarea-processed'), staticOffset = null;
+
+    // When wrapping the text area, work around an IE margin bug.  See:
+    // http://jaspan.com/ie-inherited-margin-bug-form-elements-and-haslayout
+    $(this).wrap('<div class="resizable-textarea"><span></span></div>')
+      .parent().append($('<div class="grippie"></div>').mousedown(startDrag));
+
+    var grippie = $('div.grippie', $(this).parent())[0];
+    grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) +'px';
+
+    function startDrag(e) {
+      staticOffset = textarea.height() - e.pageY;
+      textarea.css('opacity', 0.25);
+      $(document).mousemove(performDrag).mouseup(endDrag);
+      return false;
+    }
+
+    function performDrag(e) {
+      textarea.height(Math.max(32, staticOffset + e.pageY) + 'px');
+      return false;
+    }
+
+    function endDrag(e) {
+      $(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
+      textarea.css('opacity', 1);
+    }
+  });
+};
Binary file misc/throbber.gif has changed
Binary file misc/tree-bottom.png has changed
Binary file misc/tree.png has changed
Binary file misc/watchdog-error.png has changed
Binary file misc/watchdog-ok.png has changed
Binary file misc/watchdog-warning.png has changed
Binary file misc/xml.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/README.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+This directory is reserved for core module files. Custom or contributed
+modules should be placed in their own subdirectory of the sites/all/modules
+directory. For multisite installations, they can also be placed in a subdirectory
+under /sites/{sitename}/modules/, where {sitename} is the name of your site
+(e.g., www.example.com). This will allow you to more easily update Drupal core files.
+
+For more details, see: http://drupal.org/node/176043
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-feed-source.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,35 @@
+<?php
+// $Id: aggregator-feed-source.tpl.php,v 1.1 2007/09/13 08:02:38 goba Exp $
+
+/**
+ * @file aggregator-feed-source.tpl.php
+ * Default theme implementation to present the source of the feed.
+ *
+ * The contents are render above feed listings when browsing source feeds.
+ * For example, "example.com/aggregator/sources/1".
+ *
+ * Available variables:
+ * - $source_icon: Feed icon linked to the source. Rendered through
+ *   theme_feed_icon().
+ * - $source_image: Image set by the feed source.
+ * - $source_description: Description set by the feed source.
+ * - $source_url: URL to the feed source.
+ * - $last_checked: How long ago the feed was checked locally.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_feed_source()
+ */
+?>
+<div class="feed-source">
+  <?php print $source_icon; ?>
+  <?php print $source_image; ?>
+  <div class="feed-description">
+    <?php print $source_description; ?>
+  </div>
+  <div class="feed-url">
+    <em><?php print t('URL:'); ?></em> <a href="<?php print $source_url; ?>"><?php print $source_url; ?></a>
+  </div>
+  <div class="feed-updated">
+    <em><?php print t('Updated:'); ?></em> <?php print $last_checked; ?>
+  </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-item.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,46 @@
+<?php
+// $Id: aggregator-item.tpl.php,v 1.1 2007/09/13 08:02:38 goba Exp $
+
+/**
+ * @file aggregator-item.tpl.php
+ * Default theme implementation to format an individual feed item for display
+ * on the aggregator page.
+ *
+ * Available variables:
+ * - $feed_url: URL to the originating feed item.
+ * - $feed_title: Title of the feed item.
+ * - $source_url: Link to the local source section.
+ * - $source_title: Title of the remote source.
+ * - $source_date: Date the feed was posted on the remote source.
+ * - $content: Feed item content.
+ * - $categories: Linked categories assigned to the feed.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_item()
+ */
+?>
+<div class="feed-item">
+  <h3 class="feed-item-title">
+    <a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a>
+  </h3>
+
+  <div class="feed-item-meta">
+  <?php if ($source_url) : ?>
+    <a href="<?php print $source_url; ?>" class="feed-item-source"><?php print $source_title; ?></a> -
+  <?php endif; ?>
+    <span class="feed-item-date"><?php print $source_date; ?></span>
+  </div>
+
+<?php if ($content) : ?>
+  <div class="feed-item-body">
+    <?php print $content; ?>
+  </div>
+<?php endif; ?>
+
+<?php if ($categories) : ?>
+  <div class="feed-item-categories">
+    <?php print t('Categories'); ?>: <?php print implode(', ', $categories); ?>
+  </div>
+<?php endif ;?>
+
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,5 @@
+/* $Id: aggregator-rtl.css,v 1.1 2007/05/27 17:57:47 goba Exp $ */
+
+#aggregator .feed-source .feed-icon {
+  float: left;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-summary-item.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,19 @@
+<?php
+// $Id: aggregator-summary-item.tpl.php,v 1.1 2007/09/13 08:02:38 goba Exp $
+
+/**
+ * @file aggregator-summary-item.tpl.php
+ * Default theme implementation to present a linked feed item for summaries.
+ *
+ * Available variables:
+ * - $feed_url: Link to originating feed.
+ * - $feed_title: Title of feed.
+ * - $feed_age: Age of remote feed.
+ * - $source_url: Link to remote source.
+ * - $source_title: Locally set title for the source.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_summary_item()
+ */
+?>
+<a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a> <span class="age"><?php print $feed_age; ?></span><?php if ($source_url) : ?>, <span class="source"><a href="<?php print $source_url; ?>"><?php print $source_title; ?></a></span><?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-summary-items.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,24 @@
+<?php
+// $Id: aggregator-summary-items.tpl.php,v 1.1 2007/09/13 08:02:38 goba Exp $
+
+/**
+ * @file aggregator-summary-items.tpl.php
+ * Default theme implementation to present feeds as list items.
+ *
+ * Each iteration generates a single feed source or category.
+ *
+ * Available variables:
+ * - $title: Title of the feed or category.
+ * - $summary_list: Unordered list of linked feed items generated through
+ *   theme_item_list().
+ * - $source_url: URL to the local source or category.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_summary-items()
+ */
+?>
+<h2><?php print $title; ?></h2>
+<?php print $summary_list; ?>
+<div class="links">
+  <a href="<?php print $source_url; ?>"><?php print t('More'); ?></a>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator-wrapper.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,19 @@
+<?php
+// $Id: aggregator-wrapper.tpl.php,v 1.1 2007/09/13 08:02:38 goba Exp $
+
+/**
+ * @file comment-wrapper.tpl.php
+ * Default theme implementation to wrap aggregator content.
+ *
+ * Available variables:
+ * - $content: All aggregator content.
+ * - $page: Pager links rendered through theme_pager().
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_comment_wrapper()
+ */
+?>
+<div id="aggregator">
+  <?php print $content; ?>
+  <?php print $pager; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,341 @@
+<?php
+// $Id: aggregator.admin.inc,v 1.7 2008/01/10 22:47:17 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the aggregator module.
+ */
+
+/**
+ * Menu callback; displays the aggregator administration page.
+ */
+function aggregator_admin_overview() {
+  return aggregator_view();
+}
+
+/**
+ * Displays the aggregator administration page.
+ *
+ * @return
+ *   The page HTML.
+ */
+function aggregator_view() {
+  $result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.etag, f.modified, f.image, f.block ORDER BY f.title');
+
+  $output = '<h3>'. t('Feed overview') .'</h3>';
+
+  $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
+  $rows = array();
+  while ($feed = db_fetch_object($result)) {
+    $rows[] = array(l($feed->title, "aggregator/sources/$feed->fid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(time() - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - time()))) : t('never')), l(t('edit'), "admin/content/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/content/aggregator/remove/$feed->fid"), l(t('update items'), "admin/content/aggregator/update/$feed->fid"));
+  }
+  $output .= theme('table', $header, $rows);
+
+  $result = db_query('SELECT c.cid, c.title, count(ci.iid) as items FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid GROUP BY c.cid, c.title ORDER BY title');
+
+  $output .= '<h3>'. t('Category overview') .'</h3>';
+
+  $header = array(t('Title'), t('Items'), t('Operations'));
+  $rows = array();
+  while ($category = db_fetch_object($result)) {
+    $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/content/aggregator/edit/category/$category->cid"));
+  }
+  $output .= theme('table', $header, $rows);
+
+  return $output;
+}
+
+/**
+ * Form builder; Generate a form to add/edit feed sources.
+ *
+ * @ingroup forms
+ * @see aggregator_form_feed_validate()
+ * @see aggregator_form_feed_submit()
+ */
+function aggregator_form_feed(&$form_state, $edit = array('refresh' => 900, 'title' => '', 'url' => '', 'fid' => NULL)) {
+  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+
+  if ($edit['refresh'] == '') {
+    $edit['refresh'] = 3600;
+  }
+
+  $form['title'] = array('#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => $edit['title'],
+    '#maxlength' => 255,
+    '#description' => t('The name of the feed (or the name of the website providing the feed).'),
+    '#required' => TRUE,
+  );
+  $form['url'] = array('#type' => 'textfield',
+    '#title' => t('URL'),
+    '#default_value' => $edit['url'],
+    '#maxlength' => 255,
+    '#description' => t('The fully-qualified URL of the feed.'),
+    '#required' => TRUE,
+  );
+  $form['refresh'] = array('#type' => 'select',
+    '#title' => t('Update interval'),
+    '#default_value' => $edit['refresh'],
+    '#options' => $period,
+    '#description' => t('The length of time between feed updates. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
+  );
+
+  // Handling of categories:
+  $options = array();
+  $values = array();
+  $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']);
+  while ($category = db_fetch_object($categories)) {
+    $options[$category->cid] = check_plain($category->title);
+    if ($category->fid) $values[] = $category->cid;
+  }
+  if ($options) {
+    $form['category'] = array('#type' => 'checkboxes',
+      '#title' => t('Categorize news items'),
+      '#default_value' => $values,
+      '#options' => $options,
+      '#description' => t('New feed items are automatically filed in the checked categories.'),
+    );
+  }
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+  if ($edit['fid']) {
+    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['fid'] = array('#type' => 'hidden', '#value' => $edit['fid']);
+  }
+
+  return $form;
+}
+
+/**
+ * Validate aggregator_form_feed form submissions.
+ */
+function aggregator_form_feed_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Save')) {
+    // Ensure URL is valid.
+    if (!valid_url($form_state['values']['url'], TRUE)) {
+      form_set_error('url', t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
+    }
+    // Check for duplicate titles.
+    if (isset($form_state['values']['fid'])) {
+      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = '%s' OR url='%s') AND fid != %d", $form_state['values']['title'], $form_state['values']['url'], $form_state['values']['fid']);
+    }
+    else {
+      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url='%s'", $form_state['values']['title'], $form_state['values']['url']);
+    }
+    while ($feed = db_fetch_object($result)) {
+      if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
+        form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_state['values']['title'])));
+      }
+      if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
+        form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_state['values']['url'])));
+      }
+    }
+  }
+}
+
+/**
+ * Process aggregator_form_feed form submissions.
+ *
+ * @todo Add delete confirmation dialog.
+ */
+function aggregator_form_feed_submit($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Delete')) {
+    $title = $form_state['values']['title'];
+    // Unset the title:
+    unset($form_state['values']['title']);
+  }
+  aggregator_save_feed($form_state['values']);
+  if (isset($form_state['values']['fid'])) {
+    if (isset($form_state['values']['title'])) {
+      drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_state['values']['title'])));
+      if (arg(0) == 'admin') {
+        $form_state['redirect'] = 'admin/content/aggregator/';
+        return;
+      }
+      else {
+        $form_state['redirect'] = 'aggregator/sources/'. $form_state['values']['fid'];
+        return;
+      }
+    }
+    else {
+      watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $title));
+      drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
+      if (arg(0) == 'admin') {
+        $form_state['redirect'] = 'admin/content/aggregator/';
+        return;
+      }
+      else {
+        $form_state['redirect'] = 'aggregator/sources/';
+        return;
+      }
+    }
+  }
+  else {
+    watchdog('aggregator', 'Feed %feed added.', array('%feed' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
+    drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_state['values']['title'])));
+  }
+}
+
+function aggregator_admin_remove_feed($form_state, $feed) {
+  return confirm_form(
+    array(
+      'feed' => array(
+        '#type' => 'value',
+        '#value' => $feed,
+      ),
+    ),
+    t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed['title'])),
+    'admin/content/aggregator',
+    t('This action cannot be undone.'),
+    t('Remove items'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Remove all items from a feed and redirect to the overview page.
+ *
+ * @param $feed
+ *   An associative array describing the feed to be cleared.
+ */
+function aggregator_admin_remove_feed_submit($form, &$form_state) {
+  aggregator_remove($form_state['values']['feed']);
+  $form_state['redirect'] = 'admin/content/aggregator';
+}
+
+/**
+ * Menu callback; refreshes a feed, then redirects to the overview page.
+ *
+ * @param $feed
+ *   An associative array describing the feed to be refreshed.
+ */
+function aggregator_admin_refresh_feed($feed) {
+  aggregator_refresh($feed);
+  drupal_goto('admin/content/aggregator');
+}
+
+/**
+ * Form builder; Configure the aggregator system.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function aggregator_admin_settings() {
+  $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
+  $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+
+  $form['aggregator_allowed_html_tags'] = array(
+    '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255,
+    '#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
+    '#description' => t('A space-separated list of HTML tags allowed in the content of feed items. (Tags in this list are not removed by Drupal.)')
+  );
+
+  $form['aggregator_summary_items'] = array(
+    '#type' => 'select', '#title' => t('Items shown in sources and categories pages') ,
+    '#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items,
+    '#description' => t('Number of feed items displayed in feed and category summary pages.')
+  );
+
+  $form['aggregator_clear'] = array(
+    '#type' => 'select', '#title' => t('Discard items older than'),
+    '#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period,
+    '#description' => t('The length of time to retain feed items before discarding. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status')))
+  );
+
+  $form['aggregator_category_selector'] = array(
+    '#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'),
+    '#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')),
+    '#description' => t('The type of category selection widget displayed on categorization pages. (For a small number of categories, checkboxes are easier to use, while a multiple selector work well with large numbers of categories.)')
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form builder; Generate a form to add/edit/delete aggregator categories.
+ *
+ * @ingroup forms
+ * @see aggregator_form_category_validate()
+ * @see aggregator_form_category_submit()
+ */
+function aggregator_form_category(&$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) {
+  $form['title'] = array('#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => $edit['title'],
+    '#maxlength' => 64,
+    '#required' => TRUE,
+  );
+  $form['description'] = array('#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $edit['description'],
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+  if ($edit['cid']) {
+    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
+  }
+
+  return $form;
+}
+
+/**
+ * Validate aggregator_form_feed form submissions.
+ */
+function aggregator_form_category_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Save')) {
+    // Check for duplicate titles
+    if (isset($form_state['values']['cid'])) {
+      $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s' AND cid != %d", $form_state['values']['title'], $form_state['values']['cid']));
+    }
+    else {
+      $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s'", $form_state['values']['title']));
+    }
+    if ($category) {
+      form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_state['values']['title'])));
+    }
+  }
+}
+
+/**
+ * Process aggregator_form_category form submissions.
+ *
+ * @todo Add delete confirmation dialog.
+ */
+function aggregator_form_category_submit($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Delete')) {
+    $title = $form_state['values']['title'];
+    // Unset the title:
+    unset($form_state['values']['title']);
+  }
+  aggregator_save_category($form_state['values']);
+  if (isset($form_state['values']['cid'])) {
+    if (isset($form_state['values']['title'])) {
+      drupal_set_message(t('The category %category has been updated.', array('%category' => $form_state['values']['title'])));
+      if (arg(0) == 'admin') {
+        $form_state['redirect'] = 'admin/content/aggregator/';
+        return;
+      }
+      else {
+        $form_state['redirect'] = 'aggregator/categories/'. $form_state['values']['cid'];
+        return;
+      }
+    }
+    else {
+      watchdog('aggregator', 'Category %category deleted.', array('%category' => $title));
+      drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
+      if (arg(0) == 'admin') {
+        $form_state['redirect'] = 'admin/content/aggregator/';
+        return;
+      }
+      else {
+        $form_state['redirect'] = 'aggregator/categories/';
+        return;
+      }
+    }
+  }
+  else {
+    watchdog('aggregator', 'Category %category added.', array('%category' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
+    drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,38 @@
+/* $Id: aggregator.css,v 1.2 2007/05/27 17:57:47 goba Exp $ */
+
+#aggregator .feed-source .feed-title {
+  margin-top: 0;
+}
+#aggregator .feed-source .feed-image img {
+  margin-bottom: 0.75em;
+}
+#aggregator .feed-source .feed-icon {
+  float: right; /* LTR */
+  display: block;
+}
+#aggregator .feed-item {
+  margin-bottom: 1.5em;
+}
+#aggregator .feed-item-title {
+  margin-bottom: 0;
+  font-size: 1.3em;
+}
+#aggregator .feed-item-meta, #aggregator .feed-item-body {
+  margin-bottom: 0.5em;
+}
+#aggregator .feed-item-categories {
+  font-size: 0.9em;
+}
+#aggregator td {
+  vertical-align: bottom;
+}
+#aggregator td.categorize-item {
+  white-space: nowrap;
+}
+#aggregator .categorize-item .news-item .body {
+  margin-top: 0;
+}
+#aggregator .categorize-item h3 {
+  margin-bottom: 1em;
+  margin-top: 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: aggregator.info,v 1.4 2007/06/08 05:50:53 dries Exp $
+name = Aggregator
+description = "Aggregates syndicated content (RSS, RDF, and Atom feeds)."
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,241 @@
+<?php
+// $Id: aggregator.install,v 1.14 2007/12/18 12:59:20 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function aggregator_install() {
+  // Create tables.
+  drupal_install_schema('aggregator');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function aggregator_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('aggregator');
+
+  variable_del('aggregator_allowed_html_tags');
+  variable_del('aggregator_summary_items');
+  variable_del('aggregator_clear');
+  variable_del('aggregator_category_selector');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function aggregator_schema() {
+  $schema['aggregator_category'] = array(
+    'description' => t('Stores categories for aggregator feeds and feed items.'),
+    'fields' => array(
+      'cid'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique aggregator category ID.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Title of the category.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Description of the category'),
+      ),
+      'block' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The number of recent items to show within the category block.'),
+      )
+    ),
+    'primary key' => array('cid'),
+    'unique keys' => array('title' => array('title')),
+  );
+
+  $schema['aggregator_category_feed'] = array(
+    'description' => t('Bridge table; maps feeds to categories.'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The feed's {aggregator_feed}.fid."),
+      ),
+      'cid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {aggregator_category}.cid to which the feed is being assigned.'),
+      )
+    ),
+    'primary key' => array('cid', 'fid'),
+    'indexes' => array('fid' => array('fid')),
+  );
+
+  $schema['aggregator_category_item'] = array(
+    'description' => t('Bridge table; maps feed items to categories.'),
+    'fields' => array(
+      'iid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The feed item's {aggregator_item}.iid."),
+      ),
+      'cid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {aggregator_category}.cid to which the feed item is being assigned.'),
+      )
+    ),
+    'primary key' => array('cid', 'iid'),
+    'indexes' => array('iid' => array('iid')),
+  );
+
+  $schema['aggregator_feed'] = array(
+    'description' => t('Stores feeds to be parsed by the aggregator.'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique feed ID.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Title of the feed.'),
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('URL to the feed.'),
+      ),
+      'refresh' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('How often to check for new feed items, in seconds.'),
+      ),
+      'checked' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Last time feed was checked for new items, as Unix timestamp.'),
+      ),
+      'link' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The parent website of the feed; comes from the <link> element in the feed.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t("The parent website's description; comes from the <description> element in the feed."),
+      ),
+      'image' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('An image representing the feed.'),
+      ),
+      'etag' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Entity tag HTTP response header, used for validating cache.'),
+      ),
+      'modified' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('When the feed was last modified, as a Unix timestamp.'),
+      ),
+      'block' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t("Number of items to display in the feed's block."),
+      )
+    ),
+    'primary key' => array('fid'),
+    'unique keys' => array(
+      'url'  => array('url'),
+      'title' => array('title'),
+    ),
+  );
+
+  $schema['aggregator_item'] = array(
+    'description' => t('Stores the individual items imported from feeds.'),
+    'fields' => array(
+      'iid'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique ID for feed item.'),
+      ),
+      'fid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {aggregator_feed}.fid to which this item belongs.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Title of the feed item.'),
+      ),
+      'link' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Link to the feed item.'),
+      ),
+      'author' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Author of the feed item.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Body of the feed item.'),
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'description' => t('Post date of feed item, as a Unix timestamp.'),
+      ),
+      'guid' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Unique identifier for the feed item.'),
+      )
+    ),
+    'primary key' => array('iid'),
+    'indexes' => array('fid' => array('fid')),
+  );
+
+  return $schema;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,924 @@
+<?php
+// $Id: aggregator.module,v 1.374 2008/01/15 08:06:32 dries Exp $
+
+/**
+ * @file
+ * Used to aggregate syndicated content (RSS, RDF, and Atom).
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function aggregator_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#aggregator':
+      $output = '<p>'. t('The aggregator is a powerful on-site syndicator and news reader that gathers fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>';
+      $output .= '<p>'. t('Feeds contain feed items, or individual posts published by the site providing the feed. Feeds may be grouped in categories, generally by topic. Users view feed items in the <a href="@aggregator">main aggregator display</a> or by <a href="@aggregator-sources">their source</a>. Administrators can <a href="@feededit">add, edit and delete feeds</a> and choose how often to check each feed for newly updated items. The most recent items in either a feed or category can be displayed as a block through the <a href="@admin-block">blocks administration page</a>. A <a href="@aggregator-opml">machine-readable OPML file</a> of all feeds is available. A correctly configured <a href="@cron">cron maintenance task</a> is required to update feeds automatically.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@feededit' => url('admin/content/aggregator'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator/opml'), '@cron' => url('admin/reports/status'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@aggregator">Aggregator module</a>.', array('@aggregator' => 'http://drupal.org/handbook/modules/aggregator/')) .'</p>';
+      return $output;
+    case 'admin/content/aggregator':
+      $output = '<p>'. t('Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'hsttp://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>';
+      $output .= '<p>'. t('Current feeds are listed below, and <a href="@addfeed">new feeds may be added</a>. For each feed or feed category, the <em>latest items</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@addfeed' => url('admin/content/aggregator/add/feed'), '@block' => url('admin/build/block'))) .'</p>';
+      return $output;
+    case 'admin/content/aggregator/add/feed':
+      return '<p>'. t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') .'</p>';
+    case 'admin/content/aggregator/add/category':
+      return '<p>'. t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named <em>Sports</em>. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the <em>Categorize</em> page available from feed item listings). Each category provides its own feed page and block.') .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function aggregator_theme() {
+  return array(
+    'aggregator_wrapper' => array(
+      'arguments' => array('content' => NULL),
+      'file' => 'aggregator.pages.inc',
+      'template' => 'aggregator-wrapper',
+    ),
+    'aggregator_categorize_items' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'aggregator.pages.inc',
+    ),
+    'aggregator_feed_source' => array(
+      'arguments' => array('feed' => NULL),
+      'file' => 'aggregator.pages.inc',
+      'template' => 'aggregator-feed-source',
+    ),
+    'aggregator_block_item' => array(
+      'arguments' => array('item' => NULL, 'feed' => 0),
+    ),
+    'aggregator_summary_items' => array(
+      'arguments' => array('summary_items' => NULL, 'source' => NULL),
+      'file' => 'aggregator.pages.inc',
+      'template' => 'aggregator-summary-items',
+    ),
+    'aggregator_summary_item' => array(
+      'arguments' => array('item' => NULL),
+      'file' => 'aggregator.pages.inc',
+      'template' => 'aggregator-summary-item',
+    ),
+    'aggregator_item' => array(
+      'arguments' => array('item' => NULL),
+      'file' => 'aggregator.pages.inc',
+      'template' => 'aggregator-item',
+    ),
+    'aggregator_page_opml' => array(
+      'arguments' => array('feeds' => NULL),
+      'file' => 'aggregator.pages.inc',
+    ),
+    'aggregator_page_rss' => array(
+      'arguments' => array('feeds' => NULL, 'category' => NULL),
+      'file' => 'aggregator.pages.inc',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function aggregator_menu() {
+  $items['admin/content/aggregator'] = array(
+    'title' => 'Feed aggregator',
+    'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.",
+    'page callback' => 'aggregator_admin_overview',
+    'access arguments' => array('administer news feeds'),
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/add/feed'] = array(
+    'title' => 'Add feed',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_feed'),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/aggregator',
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/add/category'] = array(
+    'title' => 'Add category',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_category'),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/aggregator',
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/remove/%aggregator_feed'] = array(
+    'title' => 'Remove items',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_admin_remove_feed', 4),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/update/%aggregator_feed'] = array(
+    'title' => 'Update items',
+    'page callback' => 'aggregator_admin_refresh_feed',
+    'page arguments' => array(4),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/content/aggregator/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_admin_settings'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
+    'access arguments' => array('administer news feeds'),
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['aggregator'] = array(
+    'title' => 'Feed aggregator',
+    'page callback' => 'aggregator_page_last',
+    'access arguments' => array('access news feeds'),
+    'weight' => 5,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/sources'] = array(
+    'title' => 'Sources',
+    'page callback' => 'aggregator_page_sources',
+    'access arguments' => array('access news feeds'),
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/categories'] = array(
+    'title' => 'Categories',
+    'page callback' => 'aggregator_page_categories',
+    'access callback' => '_aggregator_has_categories',
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/rss'] = array(
+    'title' => 'RSS feed',
+    'page callback' => 'aggregator_page_rss',
+    'access arguments' => array('access news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/opml'] = array(
+    'title' => 'OPML feed',
+    'page callback' => 'aggregator_page_opml',
+    'access arguments' => array('access news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/categories/%aggregator_category'] = array(
+    'title callback' => '_aggregator_category_title',
+    'title arguments' => array(2),
+    'page callback' => 'aggregator_page_category',
+    'page arguments' => array(2),
+    'access callback' => 'user_access',
+    'access arguments' => array('access news feeds'),
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/categories/%aggregator_category/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['aggregator/categories/%aggregator_category/categorize'] = array(
+    'title' => 'Categorize',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_page_category', 2),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/categories/%aggregator_category/configure'] = array(
+    'title' => 'Configure',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_category', 2),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['aggregator/sources/%aggregator_feed'] = array(
+    'page callback' => 'aggregator_page_source',
+    'page arguments' => array(2),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/sources/%aggregator_feed/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['aggregator/sources/%aggregator_feed/categorize'] = array(
+    'title' => 'Categorize',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_page_source', 2),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'aggregator.pages.inc',
+  );
+  $items['aggregator/sources/%aggregator_feed/configure'] = array(
+    'title' => 'Configure',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_feed', 2),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/edit/feed/%aggregator_feed'] = array(
+    'title' => 'Edit feed',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_feed', 5),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.admin.inc',
+  );
+  $items['admin/content/aggregator/edit/category/%aggregator_category'] = array(
+    'title' => 'Edit category',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_category', 5),
+    'access arguments' => array('administer news feeds'),
+    'type' => MENU_CALLBACK,
+    'file' => 'aggregator.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Menu callback.
+ *
+ * @return
+ *   An aggregator category title.
+ */
+function _aggregator_category_title($category) {
+  return $category['title'];
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function aggregator_init() {
+  drupal_add_css(drupal_get_path('module', 'aggregator') .'/aggregator.css');
+}
+
+/**
+ * Find out whether there are any aggregator categories.
+ *
+ * @return
+ *   TRUE if there is at least one category and the user has access to them, FALSE otherwise.
+ */
+function _aggregator_has_categories() {
+  return user_access('access news feeds') && db_result(db_query('SELECT COUNT(*) FROM {aggregator_category}'));
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function aggregator_perm() {
+  return array('administer news feeds', 'access news feeds');
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Checks news feeds for updates once their refresh interval has elapsed.
+ */
+function aggregator_cron() {
+  $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time());
+  while ($feed = db_fetch_array($result)) {
+    aggregator_refresh($feed);
+  }
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generates blocks for the latest news items in each category and feed.
+ */
+function aggregator_block($op = 'list', $delta = 0, $edit = array()) {
+  if (user_access('access news feeds')) {
+    if ($op == 'list') {
+      $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
+      while ($category = db_fetch_object($result)) {
+        $block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
+      }
+      $result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid');
+      while ($feed = db_fetch_object($result)) {
+        $block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
+      }
+    }
+    else if ($op == 'configure') {
+      list($type, $id) = explode('-', $delta);
+      if ($type == 'category') {
+        $value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id));
+      }
+      else {
+        $value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id));
+      }
+      $form['block'] = array('#type' => 'select', '#title' => t('Number of news items in block'), '#default_value' => $value, '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
+      return $form;
+    }
+    else if ($op == 'save') {
+      list($type, $id) = explode('-', $delta);
+      if ($type == 'category') {
+        $value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id);
+      }
+      else {
+        $value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id);
+      }
+    }
+    else if ($op == 'view') {
+      list($type, $id) = explode('-', $delta);
+      switch ($type) {
+        case 'feed':
+          if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) {
+            $block['subject'] = check_plain($feed->title);
+            $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
+            $read_more = theme('more_link', url('aggregator/sources/'. $feed->fid), t("View this feed's recent news."));
+          }
+          break;
+
+        case 'category':
+          if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) {
+            $block['subject'] = check_plain($category->title);
+            $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block);
+            $read_more = theme('more_link', url('aggregator/categories/'. $category->cid), t("View this category's recent news."));
+          }
+          break;
+      }
+      $items = array();
+      while ($item = db_fetch_object($result)) {
+        $items[] = theme('aggregator_block_item', $item);
+      }
+
+      // Only display the block if there are items to show.
+      if (count($items) > 0) {
+        $block['content'] = theme('item_list', $items) . $read_more;
+      }
+    }
+    if (isset($block)) {
+      return $block;
+    }
+  }
+}
+
+/**
+ * Add/edit/delete aggregator categories.
+ *
+ * @param $edit
+ *   An associative array describing the category to be added/edited/deleted.
+ */
+function aggregator_save_category($edit) {
+  $link_path = 'aggregator/categories/';
+  if (!empty($edit['cid'])) {
+    $link_path .= $edit['cid'];
+    if (!empty($edit['title'])) {
+      db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']);
+      $op = 'update';
+    }
+    else {
+      db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']);
+      $edit['title'] = '';
+      $op = 'delete';
+    }
+  }
+  else if (!empty($edit['title'])) {
+    // A single unique id for bundles and feeds, to use in blocks
+    db_query("INSERT INTO {aggregator_category} (title, description, block) VALUES ('%s', '%s', 5)", $edit['title'], $edit['description']);
+    $link_path .= db_last_insert_id('aggregator', 'cid');
+    $op = 'insert';
+  }
+  if (isset($op)) {
+    menu_link_maintain('aggregator', $op, $link_path, $edit['title']);
+  }
+}
+
+/**
+ * Add/edit/delete an aggregator feed.
+ *
+ * @param $edit
+ *   An associative array describing the feed to be added/edited/deleted.
+ */
+function aggregator_save_feed($edit) {
+  if (!empty($edit['fid'])) {
+    // An existing feed is being modified, delete the category listings.
+    db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
+  }
+  if (!empty($edit['fid']) && !empty($edit['title'])) {
+    db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
+  }
+  else if (!empty($edit['fid'])) {
+    $items = array();
+    $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
+    while ($item = db_fetch_object($result)) {
+      $items[] = "iid = $item->iid";
+    }
+    if (!empty($items)) {
+      db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
+    }
+    db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
+    db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
+  }
+  else if (!empty($edit['title'])) {
+    db_query("INSERT INTO {aggregator_feed} (title, url, refresh, block, description, image) VALUES ('%s', '%s', %d, 5, '', '')", $edit['title'], $edit['url'], $edit['refresh']);
+    // A single unique id for bundles and feeds, to use in blocks.
+    $edit['fid'] = db_last_insert_id('aggregator_feed', 'fid');
+  }
+  if (!empty($edit['title'])) {
+    // The feed is being saved, save the categories as well.
+    if (!empty($edit['category'])) {
+      foreach ($edit['category'] as $cid => $value) {
+        if ($value) {
+          db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Removes all items from a feed.
+ *
+ * @param $feed
+ *   An associative array describing the feed to be cleared.
+ */
+function aggregator_remove($feed) {
+  $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
+  while ($item = db_fetch_object($result)) {
+    $items[] = "iid = $item->iid";
+  }
+  if (!empty($items)) {
+    db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
+  }
+  db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
+  db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
+  drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title'])));
+}
+
+/**
+ * Call-back function used by the XML parser.
+ */
+function aggregator_element_start($parser, $name, $attributes) {
+  global $item, $element, $tag, $items, $channel;
+
+  switch ($name) {
+    case 'IMAGE':
+    case 'TEXTINPUT':
+    case 'CONTENT':
+    case 'SUMMARY':
+    case 'TAGLINE':
+    case 'SUBTITLE':
+    case 'LOGO':
+    case 'INFO':
+      $element = $name;
+      break;
+    case 'ID':
+      if ($element != 'ITEM') {
+        $element = $name;
+      }
+    case 'LINK':
+      if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') {
+        if ($element == 'ITEM') {
+          $items[$item]['LINK'] = $attributes['HREF'];
+        }
+        else {
+          $channel['LINK'] = $attributes['HREF'];
+        }
+      }
+      break;
+    case 'ITEM':
+      $element = $name;
+      $item += 1;
+      break;
+    case 'ENTRY':
+      $element = 'ITEM';
+      $item += 1;
+      break;
+  }
+
+  $tag = $name;
+}
+
+/**
+ * Call-back function used by the XML parser.
+ */
+function aggregator_element_end($parser, $name) {
+  global $element;
+
+  switch ($name) {
+    case 'IMAGE':
+    case 'TEXTINPUT':
+    case 'ITEM':
+    case 'ENTRY':
+    case 'CONTENT':
+    case 'INFO':
+      $element = '';
+      break;
+    case 'ID':
+      if ($element == 'ID') {
+        $element = '';
+      }
+  }
+}
+
+/**
+ * Call-back function used by the XML parser.
+ */
+function aggregator_element_data($parser, $data) {
+  global $channel, $element, $items, $item, $image, $tag;
+  $items += array($item => array());
+  switch ($element) {
+    case 'ITEM':
+      $items[$item] += array($tag => '');
+      $items[$item][$tag] .= $data;
+      break;
+    case 'IMAGE':
+    case 'LOGO':
+      $image += array($tag => '');
+      $image[$tag] .= $data;
+      break;
+    case 'LINK':
+      if ($data) {
+        $items[$item] += array($tag => '');
+        $items[$item][$tag] .= $data;
+      }
+      break;
+    case 'CONTENT':
+      $items[$item] += array('CONTENT' => '');
+      $items[$item]['CONTENT'] .= $data;
+      break;
+    case 'SUMMARY':
+      $items[$item] += array('SUMMARY' => '');
+      $items[$item]['SUMMARY'] .= $data;
+      break;
+    case 'TAGLINE':
+    case 'SUBTITLE':
+      $channel += array('DESCRIPTION' => '');
+      $channel['DESCRIPTION'] .= $data;
+      break;
+    case 'INFO':
+    case 'ID':
+    case 'TEXTINPUT':
+      // The sub-element is not supported. However, we must recognize
+      // it or its contents will end up in the item array.
+      break;
+    default:
+      $channel += array($tag => '');
+      $channel[$tag] .= $data;
+  }
+}
+
+/**
+ * Checks a news feed for new items.
+ *
+ * @param $feed
+ *   An associative array describing the feed to be refreshed.
+ */
+function aggregator_refresh($feed) {
+  global $channel, $image;
+
+  // Generate conditional GET headers.
+  $headers = array();
+  if ($feed['etag']) {
+    $headers['If-None-Match'] = $feed['etag'];
+  }
+  if ($feed['modified']) {
+    $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT';
+  }
+
+  // Request feed.
+  $result = drupal_http_request($feed['url'], $headers);
+
+  // Process HTTP response code.
+  switch ($result->code) {
+    case 304:
+      db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
+      drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
+      break;
+    case 301:
+      $feed['url'] = $result->redirect_url;
+      watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
+      // Deliberate no break.
+    case 200:
+    case 302:
+    case 307:
+      // Filter the input data:
+      if (aggregator_parse_feed($result->data, $feed)) {
+        $modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
+
+        // Prepare the channel data.
+        foreach ($channel as $key => $value) {
+          $channel[$key] = trim($value);
+        }
+
+        // Prepare the image data (if any).
+        foreach ($image as $key => $value) {
+          $image[$key] = trim($value);
+        }
+
+        if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) {
+          // Note, we should really use theme_image() here but that only works with local images it won't work with images fetched with a URL unless PHP version > 5
+          $image = '<a href="'. check_url($image['LINK']) .'" class="feed-image"><img src="'. check_url($image['URL']) .'" alt="'. check_plain($image['TITLE']) .'" /></a>';
+        }
+        else {
+          $image = NULL;
+        }
+
+        $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
+        // Update the feed data.
+        db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel['LINK'], $channel['DESCRIPTION'], $image, $etag, $modified, $feed['fid']);
+
+        // Clear the cache.
+        cache_clear_all();
+
+        watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
+        drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
+        break;
+      }
+      $result->error = t('feed not parseable');
+      // Deliberate no break.
+    default:
+      watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error), WATCHDOG_WARNING);
+      drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error)));
+      module_invoke('system', 'check_http_request');
+  }
+}
+
+/**
+ * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
+ * functions do not handle this format.
+ * See http://www.w3.org/TR/NOTE-datetime for more information.
+ * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
+ *
+ * @param $date_str
+ *   A string with a potentially W3C DTF date.
+ * @return
+ *   A timestamp if parsed successfully or FALSE if not.
+ */
+function aggregator_parse_w3cdtf($date_str) {
+  if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
+    list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
+    // calc epoch for current date assuming GMT
+    $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
+    if ($match[10] != 'Z') { // Z is zulu time, aka GMT
+      list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
+      // zero out the variables
+      if (!$tz_hour) {
+        $tz_hour = 0;
+      }
+      if (!$tz_min) {
+        $tz_min = 0;
+      }
+      $offset_secs = (($tz_hour * 60) + $tz_min) * 60;
+      // is timezone ahead of GMT?  then subtract offset
+      if ($tz_mod == '+') {
+        $offset_secs *= -1;
+      }
+      $epoch += $offset_secs;
+    }
+    return $epoch;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Parse a feed and store its items.
+ *
+ * @param $data
+ *   The feed data.
+ * @param $feed
+ *   An associative array describing the feed to be parsed.
+ * @return
+ *   0 on error, 1 otherwise.
+ */
+function aggregator_parse_feed(&$data, $feed) {
+  global $items, $image, $channel;
+
+  // Unset the global variables before we use them:
+  unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
+  $items = array();
+  $image = array();
+  $channel = array();
+
+  // parse the data:
+  $xml_parser = drupal_xml_parser_create($data);
+  xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
+  xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
+
+  if (!xml_parse($xml_parser, $data, 1)) {
+    watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
+    drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
+    return 0;
+  }
+  xml_parser_free($xml_parser);
+
+  // We reverse the array such that we store the first item last, and the last
+  // item first. In the database, the newest item should be at the top.
+  $items = array_reverse($items);
+
+  // Initialize variables.
+  $title = $link = $author = $description = $guid = NULL;
+  foreach ($items as $item) {
+    unset($title, $link, $author, $description, $guid);
+
+    // Prepare the item:
+    foreach ($item as $key => $value) {
+      $item[$key] = trim($value);
+    }
+
+    // Resolve the item's title. If no title is found, we use up to 40
+    // characters of the description ending at a word boundary but not
+    // splitting potential entities.
+    if (!empty($item['TITLE'])) {
+      $title = $item['TITLE'];
+    }
+    elseif (!empty($item['DESCRIPTION'])) {
+      $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40));
+    }
+    else {
+      $title = '';
+    }
+
+    // Resolve the items link.
+    if (!empty($item['LINK'])) {
+      $link = $item['LINK'];
+    }
+    else {
+      $link = $feed['link'];
+    }
+    $guid = isset($item['GUID']) ? $item['GUID'] : '';
+
+    // Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag.
+    if (!empty($item['CONTENT:ENCODED'])) {
+      $item['DESCRIPTION'] = $item['CONTENT:ENCODED'];
+    }
+    else if (!empty($item['SUMMARY'])) {
+      $item['DESCRIPTION'] = $item['SUMMARY'];
+    }
+    else if (!empty($item['CONTENT'])) {
+      $item['DESCRIPTION'] = $item['CONTENT'];
+    }
+
+    // Try to resolve and parse the item's publication date. If no date is
+    // found, we use the current date instead.
+    $date = 'now';
+    foreach (array('PUBDATE', 'DC:DATE', 'DCTERMS:ISSUED', 'DCTERMS:CREATED', 'DCTERMS:MODIFIED', 'ISSUED', 'CREATED', 'MODIFIED', 'PUBLISHED', 'UPDATED') as $key) {
+      if (!empty($item[$key])) {
+        $date = $item[$key];
+        break;
+      }
+    }
+
+    $timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1.
+    if ($timestamp <= 0) {
+      $timestamp = aggregator_parse_w3cdtf($date); // Returns FALSE on failure
+      if (!$timestamp) {
+        $timestamp = time(); // better than nothing
+      }
+    }
+
+    // Save this item. Try to avoid duplicate entries as much as possible. If
+    // we find a duplicate entry, we resolve it and pass along its ID is such
+    // that we can update it if needed.
+    if (!empty($guid)) {
+      $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid));
+    }
+    else if ($link && $link != $feed['link'] && $link != $feed['url']) {
+      $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link));
+    }
+    else {
+      $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title));
+    }
+    $item += array('AUTHOR' => '', 'DESCRIPTION' => '');
+    aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid:  ''), 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid));
+  }
+
+  // Remove all items that are older than flush item timer.
+  $age = time() - variable_get('aggregator_clear', 9676800);
+  $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
+
+  $items = array();
+  $num_rows = FALSE;
+  while ($item = db_fetch_object($result)) {
+    $items[] = $item->iid;
+    $num_rows = TRUE;
+  }
+  if ($num_rows) {
+    db_query('DELETE FROM {aggregator_category_item} WHERE iid IN ('. implode(', ', $items) .')');
+    db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
+  }
+
+  return 1;
+}
+
+/**
+ * Add/edit/delete an aggregator item.
+ *
+ * @param $edit
+ *   An associative array describing the item to be added/edited/deleted.
+ */
+function aggregator_save_item($edit) {
+  if ($edit['iid'] && $edit['title']) {
+    db_query("UPDATE {aggregator_item} SET title = '%s', link = '%s', author = '%s', description = '%s', guid = '%s', timestamp = %d WHERE iid = %d", $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['guid'], $edit['timestamp'], $edit['iid']);
+  }
+  else if ($edit['iid']) {
+    db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']);
+    db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']);
+  }
+  else if ($edit['title'] && $edit['link']) {
+    db_query("INSERT INTO {aggregator_item} (fid, title, link, author, description, timestamp, guid) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s')", $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp'], $edit['guid']);
+    $edit['iid'] = db_last_insert_id('aggregator_item', 'iid');
+    // file the items in the categories indicated by the feed
+    $categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
+    while ($category = db_fetch_object($categories)) {
+      db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']);
+    }
+  }
+}
+
+/**
+ * Load an aggregator feed.
+ *
+ * @param $fid
+ *   The feed id.
+ * @return
+ *   An associative array describing the feed.
+ */
+function aggregator_feed_load($fid) {
+  static $feeds;
+  if (!isset($feeds[$fid])) {
+    $feeds[$fid] = db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid));
+  }
+  return $feeds[$fid];
+}
+
+/**
+ * Load an aggregator category.
+ *
+ * @param $cid
+ *   The category id.
+ * @return
+ *   An associative array describing the category.
+ */
+function aggregator_category_load($cid) {
+  static $categories;
+  if (!isset($categories[$cid])) {
+    $categories[$cid] = db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid));
+  }
+  return $categories[$cid];
+}
+
+/**
+ * Format an individual feed item for display in the block.
+ *
+ * @param $item
+ *   The item to be displayed.
+ * @param $feed
+ *   Not used.
+ * @return
+ *   The item HTML.
+ * @ingroup themeable
+ */
+function theme_aggregator_block_item($item, $feed = 0) {
+  global $user;
+
+  $output = '';
+  if ($user->uid && module_exists('blog') && user_access('create blog entries')) {
+    if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) {
+      $output .= '<div class="icon">'. l($image, 'node/add/blog', array('attributes' => array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), 'query' => "iid=$item->iid", 'html' => TRUE)) .'</div>';
+    }
+  }
+
+  // Display the external link to the item.
+  $output .= '<a href="'. check_url($item->link) .'">'. check_plain($item->title) ."</a>\n";
+
+  return $output;
+}
+
+/**
+ * Safely render HTML content, as allowed.
+ *
+ * @param $value
+ *   The content to be filtered.
+ * @return
+ *   The filtered content.
+ */
+function aggregator_filter_xss($value) {
+  return filter_xss($value, preg_split('/\s+|<|>/', variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY));
+}
+
+/**
+ * Helper function for drupal_map_assoc.
+ *
+ * @param $count
+ *   Items count.
+ * @return
+ *   Plural-formatted "@count items"
+ */
+function _aggregator_items($count) {
+  return format_plural($count, '1 item', '@count items');
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/aggregator/aggregator.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,492 @@
+<?php
+// $Id: aggregator.pages.inc,v 1.12 2008/01/08 10:35:40 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the aggregator module.
+ */
+
+/**
+ * Menu callback; displays the most recent items gathered from any feed.
+ *
+ * @return
+ *   The items HTML.
+ */
+function aggregator_page_last() {
+  drupal_add_feed(url('aggregator/rss'), variable_get('site_name', 'Drupal') .' '. t('aggregator'));
+
+  $items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC');
+
+  return _aggregator_page_list($items, arg(1));
+}
+
+/**
+ * Menu callback; displays all the items captured from a particular feed.
+ *
+ * If there are two arguments then this function is the categorize form.
+ *
+ * @param $arg1
+ *   If there are two arguments then $arg1 is $form_state. Otherwise, $arg1 is $feed.
+ * @param $arg2
+ *   If there are two arguments then $arg2 is feed.
+ * @return
+ *   The items HTML.
+ */
+function aggregator_page_source($arg1, $arg2 = NULL) {
+  // If there are two arguments then this function is the categorize form, and
+  // $arg1 is $form_state and $arg2 is $feed. Otherwise, $arg1 is $feed.
+  $feed = is_array($arg2) ? $arg2 : $arg1;
+  $feed = (object)$feed;
+  drupal_set_title(check_plain($feed->title));
+  $feed_source = theme('aggregator_feed_source', $feed);
+
+  // It is safe to include the fid in the query because it's loaded from the
+  // database by aggregator_feed_load.
+  $items = aggregator_feed_items_load('SELECT * FROM {aggregator_item} WHERE fid = '. $feed->fid .' ORDER BY timestamp DESC, iid DESC');
+
+  return _aggregator_page_list($items, arg(3), $feed_source);
+}
+
+/**
+ * Menu callback; displays all the items aggregated in a particular category.
+ *
+ * If there are two arguments then this function is called as a form.
+ *
+ * @param $arg1
+ *   If there are two arguments then $arg1 is $form_state. Otherwise, $arg1 is $category.
+ * @param $arg2
+ *   If there are two arguments then $arg2 is $category.
+ * @return
+ *   The items HTML.
+ */
+function aggregator_page_category($arg1, $arg2 = NULL) {
+  drupal_set_breadcrumb(array_merge(drupal_get_breadcrumb(), array(l(t('Categories'), 'aggregator/categories'))));
+  // If there are two arguments then we are called as a form, $arg1 is
+  // $form_state and $arg2 is $category. Otherwise, $arg1 is $category.
+  $category = is_array($arg2) ? $arg2 : $arg1;
+
+  drupal_add_feed(url('aggregator/rss/'. $category['cid']), variable_get('site_name', 'Drupal') .' '. t('aggregator - @title', array('@title' => $category['title'])));
+
+  // It is safe to include the cid in the query because it's loaded from the
+  // database by aggregator_category_load.
+  $items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = '. $category['cid'] .' ORDER BY timestamp DESC, i.iid DESC');
+
+  return _aggregator_page_list($items, arg(3));
+}
+
+/**
+ * Load feed items by passing a SQL query.
+ *
+ * @param $sql
+ *   The query to be executed.
+ * @return
+ *   An array of the feed items.
+ */
+function aggregator_feed_items_load($sql) {
+  $items = array();
+  if (isset($sql)) {
+    $result = pager_query($sql, 20);
+    while ($item = db_fetch_object($result)) {
+      $result_category = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = %d ORDER BY c.title', $item->iid);
+      $item->categories = array();
+      while ($item_categories = db_fetch_object($result_category)) {
+        $item->categories[] = $item_categories;
+      }
+      $items[$item->iid] = $item;
+    }
+  }
+  return $items;
+}
+
+/**
+ * Prints an aggregator page listing a number of feed items.
+ *
+ * Various menu callbacks use this function to print their feeds.
+ *
+ * @param $items
+ *   The items to be listed.
+ * @param $op
+ *   Which form should be added to the items. Only 'categorize' is now recognized.
+ * @param $feed_source
+ *   The feed source URL.
+ * @return
+ *   The items HTML.
+ */
+function _aggregator_page_list($items, $op, $feed_source = '') {
+  if (user_access('administer news feeds') && ($op == 'categorize')) {
+    // Get form data.
+    $output = aggregator_categorize_items($items, $feed_source);
+  }
+  else {
+    // Assemble themed output.
+    $output = $feed_source;
+    foreach ($items as $item) {
+      $output .= theme('aggregator_item', $item);
+    }
+    $output = theme('aggregator_wrapper', $output);
+  }
+  return $output;
+}
+
+/**
+ * Form builder; build the page list form.
+ *
+ * @param $items
+ *   An array of the feed items.
+ * @param $feed_source
+ *   The feed source URL.
+ * @return
+ *   The form structure.
+ * @ingroup forms
+ * @see aggregator_categorize_items_validate()
+ * @see aggregator_categorize_items_submit()
+ */
+function aggregator_categorize_items($items, $feed_source = '') {
+  $form['#submit'][] = 'aggregator_categorize_items_submit';
+  $form['#validate'][] = 'aggregator_categorize_items_validate';
+  $form['#theme'] = 'aggregator_categorize_items';
+  $form['feed_source'] = array('#value' => $feed_source);
+  $categories = array();
+  $done = FALSE;
+  $form['items'] = array();
+  $form['categories'] = array('#tree' => TRUE);
+  foreach ($items as $item) {
+    $form['items'][$item->iid] = array('#value' => theme('aggregator_item', $item));
+    $form['categories'][$item->iid] = array();
+    $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid);
+    $selected = array();
+    while ($category = db_fetch_object($categories_result)) {
+      if (!$done) {
+        $categories[$category->cid] = check_plain($category->title);
+      }
+      if ($category->iid) {
+        $selected[] = $category->cid;
+      }
+    }
+    $done = TRUE;
+    $form['categories'][$item->iid] = array(
+      '#type' => variable_get('aggregator_category_selector', 'checkboxes'),
+      '#default_value' => $selected,
+      '#options' => $categories,
+      '#size' => 10,
+      '#multiple' => TRUE
+    );
+  }
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save categories'));
+
+  return $form;
+}
+
+/**
+ * Validate aggregator_categorize_items form submissions.
+ */
+function aggregator_categorize_items_validate($form, &$form_state) {
+  if (!user_access('administer news feeds')) {
+    form_error($form, t('You are not allowed to categorize this feed item.'));
+  }
+}
+
+/**
+ * Process aggregator_categorize_items form submissions.
+ */
+function aggregator_categorize_items_submit($form, &$form_state) {
+  if (!empty($form_state['values']['categories'])) {
+    foreach ($form_state['values']['categories'] as $iid => $selection) {
+      db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $iid);
+      foreach ($selection as $cid) {
+        if ($cid) {
+          db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $cid, $iid);
+        }
+      }
+    }
+  }
+  drupal_set_message(t('The categories have been saved.'));
+}
+
+/**
+ * Theme the page list form for assigning categories.
+ *
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @return
+ *   The output HTML.
+ * @ingroup themeable
+ */
+function theme_aggregator_categorize_items($form) {
+  $output = drupal_render($form['feed_source']);
+  $rows = array();
+  if ($form['items']) {
+    foreach (element_children($form['items']) as $key) {
+      if (is_array($form['items'][$key])) {
+        $rows[] = array(
+          drupal_render($form['items'][$key]),
+          array('data' => drupal_render($form['categories'][$key]), 'class' => 'categorize-item'),
+        );
+      }
+    }
+  }
+  $output .= theme('table', array('', t('Categorize')), $rows);
+  $output .= drupal_render($form['submit']);
+  $output .= drupal_render($form);
+  return theme('aggregator_wrapper', $output);
+}
+
+/**
+ * Process variables for aggregator-wrapper.tpl.php.
+ *
+ * @see aggregator-wrapper.tpl.php
+ */
+function template_preprocess_aggregator_wrapper(&$variables) {
+  $variables['pager'] = theme('pager', NULL, 20, 0);
+}
+
+/**
+ * Process variables for aggregator-item.tpl.php.
+ *
+ * @see aggregator-item.tpl.php
+ */
+function template_preprocess_aggregator_item(&$variables) {
+  $item = $variables['item'];
+
+  $variables['feed_url'] = check_url($item->link);
+  $variables['feed_title'] = check_plain($item->title);
+  $variables['content'] = aggregator_filter_xss($item->description);
+
+  $variables['source_url'] = '';
+  $variables['source_title'] = '';
+  if (isset($item->ftitle) && isset($item->fid)) {
+    $variables['source_url'] = url("aggregator/sources/$item->fid");
+    $variables['source_title'] = check_plain($item->ftitle);
+  }
+  if (date('Ymd', $item->timestamp) == date('Ymd')) {
+    $variables['source_date'] = t('%ago ago', array('%ago' => format_interval(time() - $item->timestamp)));
+  }
+  else {
+    $variables['source_date'] = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i'));
+  }
+
+  $variables['categories'] = array();
+  foreach ($item->categories as $category) {
+    $variables['categories'][$category->cid] = l($category->title, 'aggregator/categories/'. $category->cid);
+  }
+}
+
+/**
+ * Menu callback; displays all the feeds used by the aggregator.
+ */
+function aggregator_page_sources() {
+  $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
+
+  $output = '';
+  while ($feed = db_fetch_object($result)) {
+    // Most recent items:
+    $summary_items = array();
+    if (variable_get('aggregator_summary_items', 3)) {
+      $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, variable_get('aggregator_summary_items', 3));
+      while ($item = db_fetch_object($items)) {
+        $summary_items[] = theme('aggregator_summary_item', $item);
+      }
+    }
+    $feed->url = url('aggregator/sources/'. $feed->fid);
+    $output .= theme('aggregator_summary_items', $summary_items, $feed);
+  }
+  $output .= theme('feed_icon', url('aggregator/opml'), t('OPML feed'));
+
+  return theme('aggregator_wrapper', $output);
+}
+
+/**
+ * Menu callback; displays all the categories used by the aggregator.
+ */
+function aggregator_page_categories() {
+  $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description');
+
+  $output = '';
+  while ($category = db_fetch_object($result)) {
+    if (variable_get('aggregator_summary_items', 3)) {
+      $summary_items = array();
+      $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = %d ORDER BY i.timestamp DESC', $category->cid, 0, variable_get('aggregator_summary_items', 3));
+      while ($item = db_fetch_object($items)) {
+        $summary_items[] = theme('aggregator_summary_item', $item);
+      }
+    }
+    $category->url = url('aggregator/categories/'. $category->cid);
+    $output .= theme('aggregator_summary_items', $summary_items, $category);
+  }
+
+  return theme('aggregator_wrapper', $output);
+}
+
+/**
+ * Menu callback; generate an RSS 0.92 feed of aggregator items or categories.
+ */
+function aggregator_page_rss() {
+  $result = NULL;
+  // arg(2) is the passed cid, only select for that category
+  if (arg(2)) {
+    $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
+    $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = %d ORDER BY timestamp DESC, i.iid DESC';
+    $result = db_query_range($sql, $category->cid, 0, variable_get('feed_default_items', 10));
+  }
+  // or, get the default aggregator items
+  else {
+    $category = NULL;
+    $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC';
+    $result = db_query_range($sql, 0, variable_get('feed_default_items', 10));
+  }
+
+  $feeds = array();
+  while ($item = db_fetch_object($result)) {
+    $feeds[] = $item;
+  }
+  return theme('aggregator_page_rss', $feeds, $category);
+}
+
+/**
+ * Theme the RSS output.
+ *
+ * @param $feeds
+ *   An array of the feeds to theme.
+ * @param $category
+ *   A common category, if any, for all the feeds.
+ * @ingroup themeable
+ */
+function theme_aggregator_page_rss($feeds, $category = NULL) {
+  drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+
+  $items = '';
+  $feed_length = variable_get('feed_item_length', 'teaser');
+  foreach ($feeds as $feed) {
+    switch ($feed_length) {
+      case 'teaser':
+        $teaser = node_teaser($feed->description);
+        if ($teaser != $feed->description) {
+          $teaser .= '<p><a href="'. check_url($feed->link) .'">'. t('read more') ."</a></p>\n";
+        }
+        $feed->description = $teaser;
+        break;
+      case 'title':
+        $feed->description = '';
+        break;
+    }
+    $items .= format_rss_item($feed->ftitle .': '. $feed->title, $feed->link, $feed->description, array('pubDate' => date('r', $feed->timestamp)));
+  }
+
+  $site_name = variable_get('site_name', 'Drupal');
+  $url = url((isset($category) ? 'aggregator/categories/'. $category->cid : 'aggregator'), array('absolute' => TRUE));
+  $description = isset($category) ? t('@site_name - aggregated feeds in category @title', array('@site_name' => $site_name, '@title' => $category->title)) : t('@site_name - aggregated feeds', array('@site_name' => $site_name));
+
+  $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+  $output .= "<rss version=\"2.0\">\n";
+  $output .= format_rss_channel(t('@site_name aggregator', array('@site_name' => $site_name)), $url, $description, $items);
+  $output .= "</rss>\n";
+
+  print $output;
+}
+
+/**
+ * Menu callback; generates an OPML representation of all feeds.
+ *
+ * @param $cid
+ *   If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported.
+ * @return
+ *   The output XML.
+ */
+function aggregator_page_opml($cid = NULL) {
+  if ($cid) {
+    $result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.fid = c.fid WHERE c.cid = %d ORDER BY title', $cid);
+  }
+  else {
+    $result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title');
+  }
+
+  $feeds = array();
+  while ($item = db_fetch_object($result)) {
+    $feeds[] = $item;
+  }
+
+  return theme('aggregator_page_opml', $feeds);
+}
+
+/**
+ * Theme the OPML feed output.
+ *
+ * @param $feeds
+ *   An array of the feeds to theme.
+ * @ingroup themeable
+ */
+function theme_aggregator_page_opml($feeds) {
+
+  drupal_set_header('Content-Type: text/xml; charset=utf-8');
+
+  $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+  $output .= "<opml version=\"1.1\">\n";
+  $output .= "<head>\n";
+  $output .= '<title>'. check_plain(variable_get('site_name', 'Drupal')) ."</title>\n";
+  $output .= '<dateModified>'. gmdate('r') ."</dateModified>\n";
+  $output .= "</head>\n";
+  $output .= "<body>\n";
+  foreach ($feeds as $feed) {
+    $output .= '<outline text="'. check_plain($feed->title) .'" xmlUrl="'. check_url($feed->url) ."\" />\n";
+  }
+  $output .= "</body>\n";
+  $output .= "</opml>\n";
+
+  print $output;
+}
+
+/**
+ * Process variables for aggregator-summary-items.tpl.php.
+ *
+ * @see aggregator-summary-item.tpl.php
+ */
+function template_preprocess_aggregator_summary_items(&$variables) {
+  $variables['title'] = check_plain($variables['source']->title);
+  $variables['summary_list'] = theme('item_list', $variables['summary_items']);
+  $variables['source_url'] = $variables['source']->url;
+}
+
+/**
+ * Process variables for aggregator-summary-item.tpl.php.
+ *
+ * @see aggregator-summary-item.tpl.php
+ */
+function template_preprocess_aggregator_summary_item(&$variables) {
+  $item = $variables['item'];
+
+  $variables['feed_url'] = check_url($item->link);
+  $variables['feed_title'] = check_plain($item->title);
+  $variables['feed_age'] = t('%age old', array('%age' => format_interval(time() - $item->timestamp)));
+
+  $variables['source_url'] = '';
+  $variables['source_title'] = '';
+  if (!empty($item->feed_link)) {
+    $variables['source_url'] = check_url($item->feed_link);
+    $variables['source_title'] = check_plain($item->feed_title);
+  }
+}
+
+/**
+ * Process variables for aggregator-feed-source.tpl.php.
+ *
+ * @see aggregator-feed-source.tpl.php
+ */
+function template_preprocess_aggregator_feed_source(&$variables) {
+  $feed = $variables['feed'];
+
+  $variables['source_icon'] = theme('feed_icon', $feed->url, t('!title feed', array('!title' => $feed->title)));
+  $variables['source_image'] = $feed->image;
+  $variables['source_description'] = aggregator_filter_xss($feed->description);
+  $variables['source_url'] = check_url(url($feed->link, array('absolute' => TRUE)));
+
+  if ($feed->checked) {
+    $variables['last_checked'] = t('@time ago', array('@time' => format_interval(time() - $feed->checked)));
+  }
+  else {
+    $variables['last_checked'] = t('never');
+  }
+
+  if (user_access('administer news feeds')) {
+    $variables['last_checked'] = l($variables['last_checked'], 'admin/content/aggregator');
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block-admin-display-form.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,76 @@
+<?php
+// $Id: block-admin-display-form.tpl.php,v 1.3 2008/01/16 22:57:26 goba Exp $
+
+/**
+ * @file block-admin-display-form.tpl.php
+ * Default theme implementation to configure blocks.
+ *
+ * Available variables:
+ * - $block_regions: An array of regions. Keyed by name with the title as value.
+ * - $block_listing: An array of blocks keyed by region and then delta.
+ * - $form_submit: Form submit button.
+ * - $throttle: TRUE or FALSE depending on throttle module being enabled.
+ *
+ * Each $block_listing[$region] contains an array of blocks for that region.
+ *
+ * Each $data in $block_listing[$region] contains:
+ * - $data->region_title: Region title for the listed block.
+ * - $data->block_title: Block title.
+ * - $data->region_select: Drop-down menu for assigning a region.
+ * - $data->weight_select: Drop-down menu for setting weights.
+ * - $data->throttle_check: Checkbox to enable throttling.
+ * - $data->configure_link: Block configuration link.
+ * - $data->delete_link: For deleting user added blocks.
+ *
+ * @see template_preprocess_block_admin_display_form()
+ * @see theme_block_admin_display()
+ */
+?>
+<?php
+  // Add table javascript.
+  drupal_add_js('misc/tableheader.js');
+  drupal_add_js(drupal_get_path('module', 'block') .'/block.js');
+  foreach ($block_regions as $region => $title) {
+    drupal_add_tabledrag('blocks', 'match', 'sibling', 'block-region-select', 'block-region-'. $region, NULL, FALSE);
+    drupal_add_tabledrag('blocks', 'order', 'sibling', 'block-weight', 'block-weight-'. $region);
+  }
+?>
+<table id="blocks" class="sticky-enabled">
+  <thead>
+    <tr>
+      <th><?php print t('Block'); ?></th>
+      <th><?php print t('Region'); ?></th>
+      <th><?php print t('Weight'); ?></th>
+      <?php if ($throttle): ?>
+        <th><?php print t('Throttle'); ?></th>
+      <?php endif; ?>
+      <th colspan="2"><?php print t('Operations'); ?></th>
+    </tr>
+  </thead>
+  <tbody>
+    <?php $row = 0; ?>
+    <?php foreach ($block_regions as $region => $title): ?>
+      <tr class="region region-<?php print $region?>">
+        <td colspan="<?php print $throttle ? '6' : '5'; ?>" class="region"><?php print $title; ?></td>
+      </tr>
+      <tr class="region-message region-<?php print $region?>-message <?php print empty($block_listing[$region]) ? 'region-empty' : 'region-populated'; ?>">
+        <td colspan="<?php print $throttle ? '6' : '5'; ?>"><em><?php print t('No blocks in this region'); ?></em></td>
+      </tr>
+      <?php foreach ($block_listing[$region] as $delta => $data): ?>
+      <tr class="draggable <?php print $row % 2 == 0 ? 'odd' : 'even'; ?><?php print $data->row_class ? ' '. $data->row_class : ''; ?>">
+        <td class="block"><?php print $data->block_title; ?></td>
+        <td><?php print $data->region_select; ?></td>
+        <td><?php print $data->weight_select; ?></td>
+        <?php if ($throttle): ?>
+          <td><?php print $data->throttle_check; ?></td>
+        <?php endif; ?>
+        <td><?php print $data->configure_link; ?></td>
+        <td><?php print $data->delete_link; ?></td>
+      </tr>
+      <?php $row++; ?>
+      <?php endforeach; ?>
+    <?php endforeach; ?>
+  </tbody>
+</table>
+
+<?php print $form_submit; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,399 @@
+<?php
+// $Id: block.admin.inc,v 1.14 2007/12/22 23:24:24 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the block module.
+ */
+
+/**
+ * Menu callback for admin/build/block.
+ */
+function block_admin_display($theme = NULL) {
+  global $custom_theme;
+
+  // If non-default theme configuration has been selected, set the custom theme.
+  $custom_theme = isset($theme) ? $theme : variable_get('theme_default', 'garland');
+
+  // Fetch and sort blocks
+  $blocks = _block_rehash();
+  usort($blocks, '_block_compare');
+
+  return drupal_get_form('block_admin_display_form', $blocks, $theme);
+}
+
+/**
+ * Generate main blocks administration form.
+ */
+function block_admin_display_form(&$form_state, $blocks, $theme = NULL) {
+  global $theme_key, $custom_theme;
+
+  // Add CSS
+  drupal_add_css(drupal_get_path('module', 'block') .'/block.css', 'module', 'all', FALSE);
+
+  // If non-default theme configuration has been selected, set the custom theme.
+  $custom_theme = isset($theme) ? $theme : variable_get('theme_default', 'garland');
+  init_theme();
+
+  $throttle = module_exists('throttle');
+  $block_regions = system_region_list($theme_key) + array(BLOCK_REGION_NONE => '<'. t('none') .'>');
+
+  // Build form tree
+  $form = array(
+    '#action' => arg(3) ? url('admin/build/block/list/'. $theme_key) : url('admin/build/block'),
+    '#tree' => TRUE,
+  );
+
+  foreach ($blocks as $i => $block) {
+    $key = $block['module'] .'_'. $block['delta'];
+    $form[$key]['module'] = array(
+      '#type' => 'value',
+      '#value' => $block['module'],
+    );
+    $form[$key]['delta'] = array(
+      '#type' => 'value',
+      '#value' => $block['delta'],
+    );
+    $form[$key]['info'] = array(
+      '#value' => check_plain($block['info'])
+    );
+    $form[$key]['theme'] = array(
+      '#type' => 'hidden',
+      '#value' => $theme_key
+    );
+    $form[$key]['weight'] = array(
+      '#type' => 'weight',
+      '#default_value' => $block['weight'],
+    );
+    $form[$key]['region'] = array(
+      '#type' => 'select',
+      '#default_value' => $block['region'],
+      '#options' => $block_regions,
+    );
+
+    if ($throttle) {
+      $form[$key]['throttle'] = array('#type' => 'checkbox', '#default_value' => isset($block['throttle']) ? $block['throttle'] : FALSE);
+    }
+    $form[$key]['configure'] = array('#value' => l(t('configure'), 'admin/build/block/configure/'. $block['module'] .'/'. $block['delta']));
+    if ($block['module'] == 'block') {
+      $form[$key]['delete'] = array('#value' => l(t('delete'), 'admin/build/block/delete/'. $block['delta']));
+    }
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save blocks'),
+  );
+
+  return $form;
+}
+
+/**
+ * Process main blocks administration form submission.
+ */
+function block_admin_display_form_submit($form, &$form_state) {
+  foreach ($form_state['values'] as $block) {
+    $block['status'] = $block['region'] != BLOCK_REGION_NONE;
+    $block['region'] = $block['status'] ? $block['region'] : '';
+    db_query("UPDATE {blocks} SET status = %d, weight = %d, region = '%s', throttle = %d WHERE module = '%s' AND delta = '%s' AND theme = '%s'", $block['status'], $block['weight'], $block['region'], isset($block['throttle']) ? $block['throttle'] : 0, $block['module'], $block['delta'], $block['theme']);
+  }
+  drupal_set_message(t('The block settings have been updated.'));
+  cache_clear_all();
+}
+
+/**
+ * Helper function for sorting blocks on admin/build/block.
+ *
+ * Active blocks are sorted by region, then by weight.
+ * Disabled blocks are sorted by name.
+ */
+function _block_compare($a, $b) {
+  global $theme_key;
+  static $regions;
+
+  // We need the region list to correctly order by region.
+  if (!isset($regions)) {
+    $regions = array_flip(array_keys(system_region_list($theme_key)));
+    $regions[BLOCK_REGION_NONE] = count($regions);
+  }
+
+  // Separate enabled from disabled.
+  $status = $b['status'] - $a['status'];
+  if ($status) {
+    return $status;
+  }
+  // Sort by region (in the order defined by theme .info file).
+  $place = $regions[$a['region']] - $regions[$b['region']];
+  if ($place) {
+    return $place;
+  }
+  // Sort by weight.
+  $weight = $a['weight'] - $b['weight'];
+  if ($weight) {
+    return $weight;
+  }
+  // Sort by title.
+  return strcmp($a['info'], $b['info']);
+}
+
+/**
+ * Menu callback; displays the block configuration form.
+ */
+function block_admin_configure(&$form_state, $module = NULL, $delta = 0) {
+
+  $form['module'] = array('#type' => 'value', '#value' => $module);
+  $form['delta'] = array('#type' => 'value', '#value' => $delta);
+
+  $edit = db_fetch_array(db_query("SELECT pages, visibility, custom, title FROM {blocks} WHERE module = '%s' AND delta = '%s'", $module, $delta));
+
+  $form['block_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Block specific settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['block_settings']['title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Block title'),
+    '#maxlength' => 64,
+    '#description' => $module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>&lt;none&gt;</em> to display no title, or leave blank to use the default block title.'),
+    '#default_value' => $edit['title'],
+    '#weight' => -18,
+  );
+
+
+  // Module-specific block configurations.
+  if ($settings = module_invoke($module, 'block', 'configure', $delta)) {
+    foreach ($settings as $k => $v) {
+      $form['block_settings'][$k] = $v;
+    }
+  }
+
+  // Get the block subject for the page title.
+  $info = module_invoke($module, 'block', 'list');
+  if (isset($info[$delta])) {
+    drupal_set_title(t("'%name' block", array('%name' => $info[$delta]['info'])));
+  }
+
+  // Standard block configurations.
+  $form['user_vis_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('User specific visibility settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['user_vis_settings']['custom'] = array(
+    '#type' => 'radios',
+    '#title' => t('Custom visibility settings'),
+    '#options' => array(
+      t('Users cannot control whether or not they see this block.'),
+      t('Show this block by default, but let individual users hide it.'),
+      t('Hide this block by default but let individual users show it.')
+    ),
+    '#description' => t('Allow individual users to customize the visibility of this block in their account settings.'),
+    '#default_value' => $edit['custom'],
+  );
+
+  // Role-based visibility settings
+  $default_role_options = array();
+  $result = db_query("SELECT rid FROM {blocks_roles} WHERE module = '%s' AND delta = '%s'", $module, $delta);
+  while ($role = db_fetch_object($result)) {
+    $default_role_options[] = $role->rid;
+  }
+  $result = db_query('SELECT rid, name FROM {role} ORDER BY name');
+  $role_options = array();
+  while ($role = db_fetch_object($result)) {
+    $role_options[$role->rid] = $role->name;
+  }
+  $form['role_vis_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Role specific visibility settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['role_vis_settings']['roles'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Show block for specific roles'),
+    '#default_value' => $default_role_options,
+    '#options' => $role_options,
+    '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
+  );
+
+  $form['page_vis_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Page specific visibility settings'),
+    '#collapsible' => TRUE,
+  );
+  $access = user_access('use PHP for block visibility');
+
+  if ($edit['visibility'] == 2 && !$access) {
+    $form['page_vis_settings'] = array();
+    $form['page_vis_settings']['visibility'] = array('#type' => 'value', '#value' => 2);
+    $form['page_vis_settings']['pages'] = array('#type' => 'value', '#value' => $edit['pages']);
+  }
+  else {
+    $options = array(t('Show on every page except the listed pages.'), t('Show on only the listed pages.'));
+    $description = t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>'));
+
+    if ($access) {
+      $options[] = t('Show if the following PHP code returns <code>TRUE</code> (PHP-mode, experts only).');
+      $description .= ' '. t('If the PHP-mode is chosen, enter PHP code between %php. Note that executing incorrect PHP-code can break your Drupal site.', array('%php' => '<?php ?>'));
+    }
+    $form['page_vis_settings']['visibility'] = array(
+      '#type' => 'radios',
+      '#title' => t('Show block on specific pages'),
+      '#options' => $options,
+      '#default_value' => $edit['visibility'],
+    );
+    $form['page_vis_settings']['pages'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Pages'),
+      '#default_value' => $edit['pages'],
+      '#description' => $description,
+    );
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save block'),
+  );
+
+  return $form;
+}
+
+function block_admin_configure_validate($form, &$form_state) {
+  if ($form_state['values']['module'] == 'block') {
+    if (empty($form_state['values']['info']) || db_result(db_query("SELECT COUNT(*) FROM {boxes} WHERE bid != %d AND info = '%s'", $form_state['values']['delta'], $form_state['values']['info']))) {
+      form_set_error('info', t('Please ensure that each block description is unique.'));
+    }
+  }
+}
+
+function block_admin_configure_submit($form, &$form_state) {
+  if (!form_get_errors()) {
+    db_query("UPDATE {blocks} SET visibility = %d, pages = '%s', custom = %d, title = '%s' WHERE module = '%s' AND delta = '%s'", $form_state['values']['visibility'], trim($form_state['values']['pages']), $form_state['values']['custom'], $form_state['values']['title'], $form_state['values']['module'], $form_state['values']['delta']);
+    db_query("DELETE FROM {blocks_roles} WHERE module = '%s' AND delta = '%s'", $form_state['values']['module'], $form_state['values']['delta']);
+    foreach (array_filter($form_state['values']['roles']) as $rid) {
+      db_query("INSERT INTO {blocks_roles} (rid, module, delta) VALUES (%d, '%s', '%s')", $rid, $form_state['values']['module'], $form_state['values']['delta']);
+    }
+    module_invoke($form_state['values']['module'], 'block', 'save', $form_state['values']['delta'], $form_state['values']);
+    drupal_set_message(t('The block configuration has been saved.'));
+    cache_clear_all();
+    $form_state['redirect'] = 'admin/build/block';
+    return;
+  }
+}
+
+/**
+ * Menu callback: display the custom block addition form.
+ */
+function block_add_block_form(&$form_state) {
+  return block_admin_configure($form_state, 'block', NULL);
+}
+
+function block_add_block_form_validate($form, &$form_state) {
+  if (empty($form_state['values']['info']) || db_result(db_query("SELECT COUNT(*) FROM {boxes} WHERE info = '%s'", $form_state['values']['info']))) {
+    form_set_error('info', t('Please ensure that each block description is unique.'));
+  }
+}
+
+/**
+ * Save the new custom block.
+ */
+function block_add_block_form_submit($form, &$form_state) {
+  db_query("INSERT INTO {boxes} (body, info, format) VALUES ('%s', '%s', %d)", $form_state['values']['body'], $form_state['values']['info'], $form_state['values']['format']);
+  $delta = db_last_insert_id('boxes', 'bid');
+
+  foreach (list_themes() as $key => $theme) {
+    if ($theme->status) {
+      db_query("INSERT INTO {blocks} (visibility, pages, custom, title, module, theme, status, weight, delta, cache) VALUES(%d, '%s', %d, '%s', '%s', '%s', %d, %d, %d, %d)", $form_state['values']['visibility'], trim($form_state['values']['pages']), $form_state['values']['custom'], $form_state['values']['title'], $form_state['values']['module'], $theme->name, 0, 0, $delta, BLOCK_NO_CACHE);
+    }
+  }
+
+  foreach (array_filter($form_state['values']['roles']) as $rid) {
+    db_query("INSERT INTO {blocks_roles} (rid, module, delta) VALUES (%d, '%s', '%s')", $rid, $form_state['values']['module'], $delta);
+  }
+
+  drupal_set_message(t('The block has been created.'));
+  cache_clear_all();
+
+  $form_state['redirect'] = 'admin/build/block';
+  return;
+}
+
+/**
+ * Menu callback; confirm deletion of custom blocks.
+ */
+function block_box_delete(&$form_state, $bid = 0) {
+  $box = block_box_get($bid);
+  $form['info'] = array('#type' => 'hidden', '#value' => $box['info'] ? $box['info'] : $box['title']);
+  $form['bid'] = array('#type' => 'hidden', '#value' => $bid);
+
+  return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $box['info'])), 'admin/build/block', '', t('Delete'), t('Cancel'));
+}
+
+/**
+ * Deletion of custom blocks.
+ */
+function block_box_delete_submit($form, &$form_state) {
+  db_query('DELETE FROM {boxes} WHERE bid = %d', $form_state['values']['bid']);
+  db_query("DELETE FROM {blocks} WHERE module = 'block' AND delta = %d", $form_state['values']['bid']);
+  drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info'])));
+  cache_clear_all();
+  $form_state['redirect'] = 'admin/build/block';
+  return;
+}
+
+/**
+ * Process variables for block-admin-display.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $form
+ *
+ * @see block-admin-display.tpl.php
+ * @see theme_block_admin_display()
+ */
+function template_preprocess_block_admin_display_form(&$variables) {
+  global $theme_key;
+
+  $block_regions = system_region_list($theme_key);
+  $variables['throttle'] = module_exists('throttle');
+  $variables['block_regions'] = $block_regions + array(BLOCK_REGION_NONE => t('Disabled'));
+
+  foreach ($block_regions as $key => $value) {
+    // Highlight regions on page to provide visual reference.
+    drupal_set_content($key, '<div class="block-region">'. $value .'</div>');
+    // Initialize an empty array for the region.
+    $variables['block_listing'][$key] = array();
+  }
+
+  // Initialize disabled blocks array.
+  $variables['block_listing'][BLOCK_REGION_NONE] = array();
+
+  // Set up to track previous region in loop.
+  $last_region = '';
+  foreach (element_children($variables['form']) as $i) {
+    $block = &$variables['form'][$i];
+
+    // Only take form elements that are blocks.
+    if (isset($block['info'])) {
+      // Fetch region for current block.
+      $region = $block['region']['#default_value'];
+
+      // Set special classes needed for table drag and drop.
+      $variables['form'][$i]['region']['#attributes']['class'] = 'block-region-select block-region-'. $region;
+      $variables['form'][$i]['weight']['#attributes']['class'] = 'block-weight block-weight-'. $region;
+
+      $variables['block_listing'][$region][$i]->row_class = isset($block['#attributes']['class']) ? $block['#attributes']['class'] : '';
+      $variables['block_listing'][$region][$i]->block_modified = isset($block['#attributes']['class']) && strpos($block['#attributes']['class'], 'block-modified') !== FALSE ? TRUE : FALSE;
+      $variables['block_listing'][$region][$i]->block_title =  drupal_render($block['info']);
+      $variables['block_listing'][$region][$i]->region_select = drupal_render($block['region']) . drupal_render($block['theme']);
+      $variables['block_listing'][$region][$i]->weight_select = drupal_render($block['weight']);
+      $variables['block_listing'][$region][$i]->throttle_check = $variables['throttle'] ? drupal_render($block['throttle']) : '';
+      $variables['block_listing'][$region][$i]->configure_link = drupal_render($block['configure']);
+      $variables['block_listing'][$region][$i]->delete_link = !empty($block['delete']) ? drupal_render($block['delete']) : '';
+      $variables['block_listing'][$region][$i]->printed = FALSE;
+
+      $last_region = $region;
+    }
+  }
+
+  $variables['form_submit'] = drupal_render($variables['form']);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,18 @@
+/* $Id: block.css,v 1.6 2007/11/14 09:49:30 dries Exp $ */
+
+#blocks td.region {
+  font-weight: bold;
+}
+#blocks tr.region-message {
+  font-weight: normal;
+  color: #999;
+}
+#blocks tr.region-populated {
+  display: none;
+}
+.block-region {
+  background-color: #ff6;
+  margin-top: 4px;
+  margin-bottom: 4px;
+  padding: 3px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: block.info,v 1.4 2007/06/08 05:50:53 dries Exp $
+name = Block
+description = Controls the boxes that are displayed around the main content.
+package = Core - required
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,179 @@
+<?php
+// $Id: block.install,v 1.8 2007/12/18 12:59:20 dries Exp $
+
+/**
+ * Implementation of hook_schema().
+ */
+function block_schema() {
+  $schema['blocks'] = array(
+    'description' => t('Stores block settings, such as region and visibility settings.'),
+    'fields' => array(
+      'bid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique block ID.'),
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("The module from which the block originates; for example, 'user' for the Who's Online block, and 'block' for any custom blocks."),
+      ),
+      'delta' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '0',
+        'description' => t('Unique ID for block within a module.'),
+      ),
+      'theme' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The theme under which the block settings apply.'),
+      ),
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Block enabled status. (1 = enabled, 0 = disabled)'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Block weight within region.'),
+      ),
+      'region' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Theme region within which the block is set.'),
+      ),
+      'custom' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Flag to indicate how users may control visibility of the block. (0 = Users cannot control, 1 = On by default, but can be hidden, 2 = Hidden by default, but can be shown)'),
+      ),
+      'throttle' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Flag to indicate whether or not to remove block when website traffic is high. (1 = throttle, 0 = do not throttle)'),
+      ),
+      'visibility' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Flag to indicate how to show blocks on pages. (0 = Show on all pages except listed pages, 1 = Show only on listed pages, 2 = Use custom PHP code to determine visibility)'),
+      ),
+      'pages' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => t('Contents of the "Pages" block; contains either a list of paths on which to include/exclude the block or PHP code, depending on "visibility" setting.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Custom title for the block. (Empty string will use block default title, <none> will remove the title, text will cause block to use specified title.)'),
+      ),
+      'cache' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+        'size' => 'tiny',
+        'description' => t('Binary flag to indicate block cache mode. (-1: Do not cache, 1: Cache per role, 2: Cache per user, 4: Cache per page, 8: Block cache global) See BLOCK_CACHE_* constants in block.module for more detailed information.'),
+      ),
+    ),
+    'primary key' => array('bid'),
+    'unique keys' => array(
+      'tmd' => array('theme', 'module', 'delta'),
+    ),
+    'indexes' => array(
+      'list' => array('theme', 'status', 'region', 'weight', 'module'),
+    ),
+  );
+
+  $schema['blocks_roles'] = array(
+    'description' => t('Sets up access permissions for blocks based on user roles'),
+    'fields' => array(
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'description' => t("The block's origin module, from {blocks}.module."),
+      ),
+      'delta'  => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'description' => t("The block's unique delta within module, from {blocks}.delta."),
+      ),
+      'rid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t("The user's role ID from {users_roles}.rid."),
+      ),
+    ),
+    'primary key' => array(
+      'module',
+      'delta',
+      'rid'
+    ),
+    'indexes' => array(
+      'rid' => array('rid'),
+    ),
+  );
+
+  $schema['boxes'] = array(
+    'description' => t('Stores contents of custom-made blocks.'),
+    'fields' => array(
+      'bid' => array(
+        'type' => 'serial',
+  'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t("The block's {blocks}.bid."),
+      ),
+      'body' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => t('Block contents.'),
+      ),
+      'info' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Block description.'),
+      ),
+      'format' => array(
+        'type' => 'int',
+        'size' => 'small',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("Block body's {filter_formats}.format; for example, 1 = Filtered HTML."),
+      )
+    ),
+    'unique keys' => array('info' => array('info')),
+    'primary key' => array('bid'),
+  );
+
+  $schema['cache_block'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_block']['description'] = t('Cache table for the Block module to store already built blocks, identified by module, delta, and various contexts which may change the block, such as theme, locale, and caching mode defined for the block.');
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,95 @@
+// $Id: block.js,v 1.2 2007/12/16 10:36:53 goba Exp $
+
+/**
+ * Move a block in the blocks table from one region to another via select list.
+ *
+ * This behavior is dependent on the tableDrag behavior, since it uses the
+ * objects initialized in that behavior to update the row.
+ */
+Drupal.behaviors.blockDrag = function(context) {
+  var table = $('table#blocks');
+  var tableDrag = Drupal.tableDrag.blocks; // Get the blocks tableDrag object.
+
+  // Add a handler for when a row is swapped, update empty regions.
+  tableDrag.row.prototype.onSwap = function(swappedRow) {
+    checkEmptyRegions(table, this);
+  };
+
+  // A custom message for the blocks page specifically.
+  Drupal.theme.tableDragChangedWarning = function () {
+    return '<div class="warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("The changes to these blocks will not be saved until the <em>Save blocks</em> button is clicked.") + '</div>';
+  };
+
+  // Add a handler so when a row is dropped, update fields dropped into new regions.
+  tableDrag.onDrop = function() {
+    dragObject = this;
+    if ($(dragObject.rowObject.element).prev('tr').is('.region-message')) {
+      var regionRow = $(dragObject.rowObject.element).prev('tr').get(0);
+      var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+      var regionField = $('select.block-region-select', dragObject.rowObject.element);
+      var weightField = $('select.block-weight', dragObject.rowObject.element);
+      var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
+
+      if (!regionField.is('.block-region-'+ regionName)) {
+        regionField.removeClass('block-region-' + oldRegionName).addClass('block-region-' + regionName);
+        weightField.removeClass('block-weight-' + oldRegionName).addClass('block-weight-' + regionName);
+        regionField.val(regionName);
+      }
+    }
+  };
+
+  // Add the behavior to each region select list.
+  $('select.block-region-select:not(.blockregionselect-processed)', context).each(function() {
+    $(this).change(function(event) {
+      // Make our new row and select field.
+      var row = $(this).parents('tr:first');
+      var select = $(this);
+      tableDrag.rowObject = new tableDrag.row(row);
+
+      // Find the correct region and insert the row as the first in the region.
+      $('tr.region-message', table).each(function() {
+        if ($(this).is('.region-' + select[0].value + '-message')) {
+          // Add the new row and remove the old one.
+          $(this).after(row);
+          // Manually update weights and restripe.
+          tableDrag.updateFields(row.get(0));
+          tableDrag.rowObject.changed = true;
+          if (tableDrag.oldRowElement) {
+            $(tableDrag.oldRowElement).removeClass('drag-previous');
+          }
+          tableDrag.oldRowElement = row.get(0);
+          tableDrag.restripeTable();
+          tableDrag.rowObject.markChanged();
+          tableDrag.oldRowElement = row;
+          $(row).addClass('drag-previous');
+        }
+      });
+
+      // Modify empty regions with added or removed fields.
+      checkEmptyRegions(table, row);
+      // Remove focus from selectbox.
+      select.get(0).blur();
+    });
+    $(this).addClass('blockregionselect-processed');
+  });
+
+  var checkEmptyRegions = function(table, rowObject) {
+    $('tr.region-message', table).each(function() {
+      // If the dragged row is in this region, but above the message row, swap it down one space.
+      if ($(this).prev('tr').get(0) == rowObject.element) {
+        // Prevent a recursion problem when using the keyboard to move rows up.
+        if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
+          rowObject.swap('after', this);
+        }
+      }
+      // This region has become empty
+      if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').size() == 0) {
+        $(this).removeClass('region-populated').addClass('region-empty');
+      }
+      // This region has become populated.
+      else if ($(this).is('.region-empty')) {
+        $(this).removeClass('region-empty').addClass('region-populated');
+      }
+    });
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/block/block.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,537 @@
+<?php
+// $Id: block.module,v 1.299 2008/02/03 19:12:57 goba Exp $
+
+/**
+ * @file
+ * Controls the boxes that are displayed around the main content.
+ */
+
+/**
+ * Denotes that a block is not enabled in any region and should not
+ * be shown.
+ */
+define('BLOCK_REGION_NONE', -1);
+
+/**
+ * Constants defining cache granularity for blocks.
+ *
+ * Modules specify the caching patterns for their blocks using binary
+ * combinations of these constants in their hook_block(op 'list'):
+ *   $block[delta]['cache'] = BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE;
+ * BLOCK_CACHE_PER_ROLE is used as a default when no caching pattern is
+ * specified.
+ *
+ * The block cache is cleared in cache_clear_all(), and uses the same clearing
+ * policy than page cache (node, comment, user, taxonomy added or updated...).
+ * Blocks requiring more fine-grained clearing might consider disabling the
+ * built-in block cache (BLOCK_NO_CACHE) and roll their own.
+ *
+ * Note that user 1 is excluded from block caching.
+ */
+
+/**
+ * The block should not get cached. This setting should be used:
+ * - for simple blocks (notably those that do not perform any db query),
+ * where querying the db cache would be more expensive than directly generating
+ * the content.
+ * - for blocks that change too frequently.
+ */
+define('BLOCK_NO_CACHE', -1);
+
+/**
+ * The block can change depending on the roles the user viewing the page belongs to.
+ * This is the default setting, used when the block does not specify anything.
+ */
+define('BLOCK_CACHE_PER_ROLE', 0x0001);
+
+/**
+ * The block can change depending on the user viewing the page.
+ * This setting can be resource-consuming for sites with large number of users,
+ * and thus should only be used when BLOCK_CACHE_PER_ROLE is not sufficient.
+ */
+define('BLOCK_CACHE_PER_USER', 0x0002);
+
+/**
+ * The block can change depending on the page being viewed.
+ */
+define('BLOCK_CACHE_PER_PAGE', 0x0004);
+
+/**
+ * The block is the same for every user on every page where it is visible.
+ */
+define('BLOCK_CACHE_GLOBAL', 0x0008);
+
+/**
+ * Implementation of hook_help().
+ */
+function block_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#block':
+      $output = '<p>'. t('Blocks are boxes of content rendered into an area, or region, of a web page. The default theme Garland, for example, implements the regions "left sidebar", "right sidebar", "content", "header", and "footer", and a block may appear in any one of these areas. The <a href="@blocks">blocks administration page</a> provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions.', array('@blocks' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('Although blocks are usually generated automatically by modules (like the <em>User login</em> block, for example), administrators can also define custom blocks. Custom blocks have a title, description, and body. The body of the block can be as long as necessary, and can contain content supported by any available <a href="@input-format">input format</a>.', array('@input-format' => url('admin/settings/filters'))) .'</p>';
+      $output .= '<p>'. t('When working with blocks, remember that:') .'</p>';
+      $output .= '<ul><li>'. t('since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis.') .'</li>';
+      $output .= '<li>'. t('disabled blocks, or blocks not in a region, are never shown.') .'</li>';
+      $output .= '<li>'. t('when throttle module is enabled, throttled blocks (blocks with the <em>Throttle</em> checkbox selected) are hidden during high server loads.') .'</li>';
+      $output .= '<li>'. t('blocks can be configured to be visible only on certain pages.') .'</li>';
+      $output .= '<li>'. t('blocks can be configured to be visible only when specific conditions are true.') .'</li>';
+      $output .= '<li>'. t('blocks can be configured to be visible only for certain user roles.') .'</li>';
+      $output .= '<li>'. t('when allowed by an administrator, specific blocks may be enabled or disabled on a per-user basis using the <em>My account</em> page.') .'</li>';
+      $output .= '<li>'. t('some dynamic blocks, such as those generated by modules, will be displayed only on certain pages.') .'</li></ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@block">Block module</a>.', array('@block' => 'http://drupal.org/handbook/modules/block/')) .'</p>';
+      return $output;
+    case 'admin/build/block':
+      $throttle = module_exists('throttle');
+      $output = '<p>'. t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. To change the region or order of a block, grab a drag-and-drop handle under the <em>Block</em> column and drag the block to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page.') .'</p>';
+      if ($throttle) {
+        $output .= '<p>'. t('To reduce CPU usage, database traffic or bandwidth, blocks may be automatically disabled during high server loads by selecting their <em>Throttle</em> checkbox. Adjust throttle thresholds on the <a href="@throttleconfig">throttle configuration page</a>.', array('@throttleconfig' => url('admin/settings/throttle'))) .'</p>';
+      }
+      $output .= '<p>'. t('Click the <em>configure</em> link next to each block to configure its specific title and visibility settings. Use the <a href="@add-block">add block page</a> to create a custom block.', array('@add-block' => url('admin/build/block/add'))) .'</p>';
+      return $output;
+    case 'admin/build/block/add':
+      return '<p>'. t('Use this page to create a new custom block. New blocks are disabled by default, and must be moved to a region on the <a href="@blocks">blocks administration page</a> to be visible.', array('@blocks' => url('admin/build/block'))) .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function block_theme() {
+  return array(
+    'block_admin_display_form' => array(
+      'template' => 'block-admin-display-form',
+      'file' => 'block.admin.inc',
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function block_perm() {
+  return array('administer blocks', 'use PHP for block visibility');
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function block_menu() {
+  $items['admin/build/block'] = array(
+    'title' => 'Blocks',
+    'description' => 'Configure what block content appears in your site\'s sidebars and other regions.',
+    'page callback' => 'block_admin_display',
+    'access arguments' => array('administer blocks'),
+    'file' => 'block.admin.inc',
+  );
+  $items['admin/build/block/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/build/block/list/js'] = array(
+    'title' => 'JavaScript List Form',
+    'page callback' => 'block_admin_display_js',
+    'type' => MENU_CALLBACK,
+    'file' => 'block.admin.inc',
+  );
+  $items['admin/build/block/configure'] = array(
+    'title' => 'Configure block',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('block_admin_configure'),
+    'type' => MENU_CALLBACK,
+    'file' => 'block.admin.inc',
+  );
+  $items['admin/build/block/delete'] = array(
+    'title' => 'Delete block',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('block_box_delete'),
+    'type' => MENU_CALLBACK,
+    'file' => 'block.admin.inc',
+  );
+  $items['admin/build/block/add'] = array(
+    'title' => 'Add block',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('block_add_block_form'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'block.admin.inc',
+  );
+  $default = variable_get('theme_default', 'garland');
+  foreach (list_themes() as $key => $theme) {
+    $items['admin/build/block/list/'. $key] = array(
+      'title' => check_plain($theme->info['name']),
+      'page arguments' => array($key),
+      'type' => $key == $default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
+      'weight' => $key == $default ? -10 : 0,
+      'file' => 'block.admin.inc',
+      'access callback' => '_block_themes_access',
+      'access arguments' => array($theme),
+    );
+  }
+  return $items;
+}
+
+/**
+ * Menu item access callback - only admin or enabled themes can be accessed
+ */
+function _block_themes_access($theme) {
+  return user_access('administer blocks') && ($theme->status || $theme->name == variable_get('admin_theme', '0'));
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generates the administrator-defined blocks for display.
+ */
+function block_block($op = 'list', $delta = 0, $edit = array()) {
+  switch ($op) {
+    case 'list':
+      $blocks = array();
+
+      $result = db_query('SELECT bid, info FROM {boxes} ORDER BY info');
+      while ($block = db_fetch_object($result)) {
+        $blocks[$block->bid]['info'] = $block->info;
+        // Not worth caching.
+        $blocks[$block->bid]['cache'] = BLOCK_NO_CACHE;
+      }
+      return $blocks;
+
+    case 'configure':
+      $box = array('format' => FILTER_FORMAT_DEFAULT);
+      if ($delta) {
+        $box = block_box_get($delta);
+      }
+      if (filter_access($box['format'])) {
+        return block_box_form($box);
+      }
+      break;
+
+    case 'save':
+      block_box_save($edit, $delta);
+      break;
+
+    case 'view':
+      $block = db_fetch_object(db_query('SELECT body, format FROM {boxes} WHERE bid = %d', $delta));
+      $data['content'] = check_markup($block->body, $block->format, FALSE);
+      return $data;
+  }
+}
+
+/**
+ * Update the 'blocks' DB table with the blocks currently exported by modules.
+ *
+ * @return
+ *   Blocks currently exported by modules.
+ */
+function _block_rehash() {
+  global $theme_key;
+
+  init_theme();
+
+  $result = db_query("SELECT * FROM {blocks} WHERE theme = '%s'", $theme_key);
+  $old_blocks = array();
+  while ($old_block = db_fetch_array($result)) {
+    $old_blocks[$old_block['module']][$old_block['delta']] = $old_block;
+  }
+
+  $blocks = array();
+  // Valid region names for the theme.
+  $regions = system_region_list($theme_key);
+
+  foreach (module_list() as $module) {
+    $module_blocks = module_invoke($module, 'block', 'list');
+    if ($module_blocks) {
+      foreach ($module_blocks as $delta => $block) {
+        if (empty($old_blocks[$module][$delta])) {
+          // If it's a new block, add identifiers.
+          $block['module'] = $module;
+          $block['delta']  = $delta;
+          $block['theme']  = $theme_key;
+          if (!isset($block['pages'])) {
+            // {block}.pages is type 'text', so it cannot have a
+            // default value, and not null, so we need to provide
+            // value if the module did not.
+            $block['pages']  = '';
+          }
+          // Add defaults and save it into the database.
+          drupal_write_record('blocks', $block);
+          // Set region to none if not enabled.
+          $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE;
+          // Add to the list of blocks we return.
+          $blocks[] = $block;
+        }
+        else {
+          // If it's an existing block, database settings should overwrite
+          // the code. But aside from 'info' everything that's definable in
+          // code is stored in the database and we do not store 'info', so we
+          // do not need to update the database here.
+          // Add 'info' to this block.
+          $old_blocks[$module][$delta]['info'] = $block['info'];
+          // If the region name does not exist, disable the block and assign it to none.
+          if (!empty($old_blocks[$module][$delta]['region']) && !isset($regions[$old_blocks[$module][$delta]['region']])) {
+            drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]['info'], '%region' => $old_blocks[$module][$delta]['region'])), 'warning');
+            $old_blocks[$module][$delta]['status'] = 0;
+            $old_blocks[$module][$delta]['region'] = BLOCK_REGION_NONE;
+          }
+          else {
+            $old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE;
+          }
+          // Add this block to the list of blocks we return.
+          $blocks[] = $old_blocks[$module][$delta];
+          // Remove this block from the list of blocks to be deleted.
+          unset($old_blocks[$module][$delta]);
+        }
+      }
+    }
+  }
+
+  // Remove blocks that are no longer defined by the code from the database.
+  foreach ($old_blocks as $module => $old_module_blocks) {
+    foreach ($old_module_blocks as $delta => $block) {
+      db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s' AND theme = '%s'", $module, $delta, $theme_key);
+    }
+  }
+  return $blocks;
+}
+
+function block_box_get($bid) {
+  return db_fetch_array(db_query("SELECT bx.*, bl.title FROM {boxes} bx INNER JOIN {blocks} bl ON bx.bid = bl.delta WHERE bl.module = 'block' AND bx.bid = %d", $bid));
+}
+
+/**
+ * Define the custom block form.
+ */
+function block_box_form($edit = array()) {
+  $edit += array(
+    'info' => '',
+    'body' => '',
+  );
+  $form['info'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Block description'),
+    '#default_value' => $edit['info'],
+    '#maxlength' => 64,
+    '#description' => t('A brief description of your block. Used on the <a href="@overview">block overview page</a>.', array('@overview' => url('admin/build/block'))),
+    '#required' => TRUE,
+    '#weight' => -19,
+  );
+  $form['body_field']['#weight'] = -17;
+  $form['body_field']['body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Block body'),
+    '#default_value' => $edit['body'],
+    '#rows' => 15,
+    '#description' => t('The content of the block as shown to the user.'),
+    '#weight' => -17,
+  );
+  if (!isset($edit['format'])) {
+    $edit['format'] = FILTER_FORMAT_DEFAULT;
+  }
+  $form['body_field']['format'] = filter_form($edit['format'], -16);
+
+  return $form;
+}
+
+function block_box_save($edit, $delta) {
+  if (!filter_access($edit['format'])) {
+    $edit['format'] = FILTER_FORMAT_DEFAULT;
+  }
+
+  db_query("UPDATE {boxes} SET body = '%s', info = '%s', format = %d WHERE bid = %d", $edit['body'], $edit['info'], $edit['format'], $delta);
+
+  return TRUE;
+}
+
+/**
+ * Implementation of hook_user().
+ *
+ * Allow users to decide which custom blocks to display when they visit
+ * the site.
+ */
+function block_user($type, $edit, &$account, $category = NULL) {
+  switch ($type) {
+    case 'form':
+      if ($category == 'account') {
+        $rids = array_keys($account->roles);
+        $result = db_query("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom != 0 AND (r.rid IN (". db_placeholders($rids) .") OR r.rid IS NULL) ORDER BY b.weight, b.module", $rids);
+        $form['block'] = array('#type' => 'fieldset', '#title' => t('Block configuration'), '#weight' => 3, '#collapsible' => TRUE, '#tree' => TRUE);
+        while ($block = db_fetch_object($result)) {
+          $data = module_invoke($block->module, 'block', 'list');
+          if ($data[$block->delta]['info']) {
+            $return = TRUE;
+            $form['block'][$block->module][$block->delta] = array('#type' => 'checkbox', '#title' => check_plain($data[$block->delta]['info']), '#default_value' => isset($account->block[$block->module][$block->delta]) ? $account->block[$block->module][$block->delta] : ($block->custom == 1));
+          }
+        }
+
+        if (!empty($return)) {
+          return $form;
+        }
+      }
+
+      break;
+    case 'validate':
+      if (empty($edit['block'])) {
+        $edit['block'] = array();
+      }
+      return $edit;
+  }
+}
+
+/**
+ * Return all blocks in the specified region for the current user.
+ *
+ * @param $region
+ *   The name of a region.
+ *
+ * @return
+ *   An array of block objects, indexed with <i>module</i>_<i>delta</i>.
+ *   If you are displaying your blocks in one or two sidebars, you may check
+ *   whether this array is empty to see how many columns are going to be
+ *   displayed.
+ *
+ * @todo
+ *   Now that the blocks table has a primary key, we should use that as the
+ *   array key instead of <i>module</i>_<i>delta</i>.
+ */
+function block_list($region) {
+  global $user, $theme_key;
+
+  static $blocks = array();
+
+  if (!count($blocks)) {
+    $rids = array_keys($user->roles);
+    $result = db_query(db_rewrite_sql("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = '%s' AND b.status = 1 AND (r.rid IN (". db_placeholders($rids) .") OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module", 'b', 'bid'), array_merge(array($theme_key), $rids));
+    while ($block = db_fetch_object($result)) {
+      if (!isset($blocks[$block->region])) {
+        $blocks[$block->region] = array();
+      }
+      // Use the user's block visibility setting, if necessary
+      if ($block->custom != 0) {
+        if ($user->uid && isset($user->block[$block->module][$block->delta])) {
+          $enabled = $user->block[$block->module][$block->delta];
+        }
+        else {
+          $enabled = ($block->custom == 1);
+        }
+      }
+      else {
+        $enabled = TRUE;
+      }
+
+      // Match path if necessary
+      if ($block->pages) {
+        if ($block->visibility < 2) {
+          $path = drupal_get_path_alias($_GET['q']);
+          // Compare with the internal and path alias (if any).
+          $page_match = drupal_match_path($path, $block->pages);
+          if ($path != $_GET['q']) {
+            $page_match = $page_match || drupal_match_path($_GET['q'], $block->pages);
+          }
+          // When $block->visibility has a value of 0, the block is displayed on
+          // all pages except those listed in $block->pages. When set to 1, it
+          // is displayed only on those pages listed in $block->pages.
+          $page_match = !($block->visibility xor $page_match);
+        }
+        else {
+          $page_match = drupal_eval($block->pages);
+        }
+      }
+      else {
+        $page_match = TRUE;
+      }
+
+      if ($enabled && $page_match) {
+        // Check the current throttle status and see if block should be displayed
+        // based on server load.
+        if (!($block->throttle && (module_invoke('throttle', 'status') > 0))) {
+          // Try fetching the block from cache. Block caching is not compatible with
+          // node_access modules. We also preserve the submission of forms in blocks,
+          // by fetching from cache only if the request method is 'GET'.
+          if (!count(module_implements('node_grants')) && $_SERVER['REQUEST_METHOD'] == 'GET' && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
+            $array = $cache->data;
+          }
+          else {
+            $array = module_invoke($block->module, 'block', 'view', $block->delta);
+            if (isset($cid)) {
+              cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
+            }
+          }
+
+          if (isset($array) && is_array($array)) {
+            foreach ($array as $k => $v) {
+              $block->$k = $v;
+            }
+          }
+        }
+        if (isset($block->content) && $block->content) {
+          // Override default block title if a custom display title is present.
+          if ($block->title) {
+            // Check plain here to allow module generated titles to keep any markup.
+            $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
+          }
+          if (!isset($block->subject)) {
+            $block->subject = '';
+          }
+          $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
+        }
+      }
+    }
+  }
+  // Create an empty array if there were no entries
+  if (!isset($blocks[$region])) {
+    $blocks[$region] = array();
+  }
+  return $blocks[$region];
+}
+
+/**
+ * Assemble the cache_id to use for a given block.
+ *
+ * The cache_id string reflects the viewing context for the current block
+ * instance, obtained by concatenating the relevant context information
+ * (user, page, ...) according to the block's cache settings (BLOCK_CACHE_*
+ * constants). Two block instances can use the same cached content when
+ * they share the same cache_id.
+ *
+ * Theme and language contexts are automatically differenciated.
+ *
+ * @param $block
+ * @return
+ *   The string used as cache_id for the block.
+ */
+function _block_get_cache_id($block) {
+  global $theme, $base_root, $user;
+
+  // User 1 being out of the regular 'roles define permissions' schema,
+  // it brings too many chances of having unwanted output get in the cache
+  // and later be served to other users. We therefore exclude user 1 from
+  // block caching.
+  if (variable_get('block_cache', 0) && $block->cache != BLOCK_NO_CACHE && $user->uid != 1) {
+    $cid_parts = array();
+
+    // Start with common sub-patterns: block identification, theme, language.
+    $cid_parts[] = $block->module;
+    $cid_parts[] = $block->delta;
+    $cid_parts[] = $theme;
+    if (module_exists('locale')) {
+      global $language;
+      $cid_parts[] = $language->language;
+    }
+
+    // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
+    // resource drag for sites with many users, so when a module is being
+    // equivocal, we favor the less expensive 'PER_ROLE' pattern.
+    if ($block->cache & BLOCK_CACHE_PER_ROLE) {
+      $cid_parts[] = 'r.'. implode(',', array_keys($user->roles));
+    }
+    elseif ($block->cache & BLOCK_CACHE_PER_USER) {
+      $cid_parts[] = "u.$user->uid";
+    }
+
+    if ($block->cache & BLOCK_CACHE_PER_PAGE) {
+      $cid_parts[] = $base_root . request_uri();
+    }
+
+    return implode(':', $cid_parts);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blog/blog.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: blog.info,v 1.5 2007/06/08 05:50:53 dries Exp $
+name = Blog
+description = Enables keeping easily and regularly updated user web pages or blogs.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blog/blog.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,193 @@
+<?php
+// $Id: blog.module,v 1.297 2008/01/09 09:51:34 goba Exp $
+
+/**
+ * @file
+ * Enables keeping an easily and regularly updated web page or a blog.
+ */
+
+/**
+ * Implementation of hook_node_info().
+ */
+function blog_node_info() {
+  return array(
+    'blog' => array(
+      'name' => t('Blog entry'),
+      'module' => 'blog',
+      'description' => t('A <em>blog entry</em> is a single post to an online journal, or <em>blog</em>.'),
+    )
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function blog_perm() {
+  return array('create blog entries', 'delete own blog entries', 'delete any blog entry', 'edit own blog entries', 'edit any blog entry');
+}
+
+/**
+ * Implementation of hook_access().
+ */
+function blog_access($op, $node, $account) {
+  switch ($op) {
+    case 'create':
+      // Anonymous users cannot post even if they have the permission. 
+      return user_access('create blog entries', $account) && $account->uid;
+    case 'update':
+      return user_access('edit any blog entry', $account) || (user_access('edit own blog entries', $account) && ($node->uid == $account->uid));
+    case 'delete':
+      return user_access('delete any blog entry', $account) || (user_access('delete own blog entries', $account) && ($node->uid == $account->uid));
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function blog_user($type, &$edit, &$user) {
+  if ($type == 'view' && user_access('create blog entries', $user)) {
+    $user->content['summary']['blog'] =  array(
+      '#type' => 'user_profile_item',
+      '#title' => t('Blog'),
+      '#value' => l(t('View recent blog entries'), "blog/$user->uid", array('title' => t("Read @username's latest blog entries.", array('@username' => $user->name)))),
+      '#attributes' => array('class' => 'blog'),
+    );
+  }
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function blog_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#blog':
+      $output = '<p>'. t('The blog module allows registered users to maintain an online journal, or <em>blog</em>. Blogs are made up of individual <em>blog entries</em>, and the blog entries are most often displayed in descending order by creation time.') .'</p>';
+      $output .= '<p>'. t('There is an (optional) <em>Blogs</em> menu item added to the Navigation menu, which displays all blogs available on your site, and a <em>My blog</em> item displaying the current user\'s blog entries. The <em>Blog entry</em> menu item under <em>Create content</em> allows new blog entries to be created.') .'</p>';
+      $output .= '<p>'. t('Each blog entry is displayed with an automatic link to other blogs created by the same user. By default, blog entries have comments enabled and are automatically promoted to the site front page. The blog module also creates a <em>Recent blog posts</em> block that may be enabled at the <a href="@blocks">blocks administration page</a>.', array('@blocks' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('When using the aggregator module an automatic <em>blog it</em> icon is displayed next to the items in a feed\'s <em>latest items</em> block. Clicking this icon populates a <em>blog entry</em> with a title (the title of the feed item) and body (a link to the source item on its original site and illustrative content suitable for use in a block quote). Blog authors can use this feature to easily comment on items of interest that appear in aggregator feeds from other sites. To use this feature, be sure to <a href="@modules">enable</a> the aggregator module, <a href="@feeds">add and configure</a> a feed from another site, and <a href="@blocks">position</a> the feed\'s <em>latest items</em> block.', array('@modules' => url('admin/build/modules'), '@feeds' => url('admin/content/aggregator'), '@blocks' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@blog">Blog module</a>.', array('@blog' => 'http://drupal.org/handbook/modules/blog/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_form().
+ */
+function blog_form(&$node) {
+  global $nid;
+  $iid = isset($_GET['iid']) ? (int)$_GET['iid'] : 0;
+  $type = node_get_types('type', $node);
+
+
+  if (empty($node->body)) {
+    // If the user clicked a "blog it" link, we load the data from the
+    // database and quote it in the blog.
+    if ($nid && $blog = node_load($nid)) {
+      $node->body = '<em>'. $blog->body .'</em> ['. l($blog->name, "node/$nid") .']';
+    }
+
+    if ($iid && $item = db_fetch_object(db_query('SELECT i.*, f.title as ftitle, f.link as flink FROM {aggregator_item} i, {aggregator_feed} f WHERE i.iid = %d AND i.fid = f.fid', $iid))) {
+      $node->title = $item->title;
+      // Note: $item->description has been validated on aggregation.
+      $node->body = '<a href="'. check_url($item->link) .'">'. check_plain($item->title) .'</a> - <em>'. $item->description .'</em> [<a href="'. check_url($item->flink) .'">'. check_plain($item->ftitle) ."</a>]\n";
+    }
+
+  }
+
+  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => !empty($node->title) ? $node->title : NULL, '#weight' => -5);
+  $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
+  return $form;
+}
+
+/**
+ * Implementation of hook_view().
+ */
+function blog_view($node, $teaser = FALSE, $page = FALSE) {
+  if ($page) {
+    // Breadcrumb navigation
+    drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('Blogs'), 'blog'), l(t("@name's blog", array('@name' => $node->name)), 'blog/'. $node->uid)));
+  }
+  return node_prepare($node, $teaser);
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function blog_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+
+  if ($type == 'node' && $node->type == 'blog') {
+    if (arg(0) != 'blog' || arg(1) != $node->uid) {
+      $links['blog_usernames_blog'] = array(
+        'title' => t("@username's blog", array('@username' => $node->name)),
+        'href' => "blog/$node->uid",
+        'attributes' => array('title' => t("Read @username's latest blog entries.", array('@username' => $node->name)))
+      );
+    }
+  }
+
+  return $links;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function blog_menu() {
+  $items['blog'] = array(
+    'title' => 'Blogs',
+    'page callback' => 'blog_page_last',
+    'access arguments' => array('access content'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'blog.pages.inc',
+  );
+  $items['blog/%user_current'] = array(
+    'title' => 'My blog',
+    'page callback' => 'blog_page_user',
+    'page arguments' => array(1),
+    'access callback' => 'user_access',
+    'access arguments' => array('create blog entries', 1),
+    'file' => 'blog.pages.inc',
+  );
+  $items['blog/%user/feed'] = array(
+    'title' => 'Blogs',
+    'page callback' => 'blog_feed_user',
+    'page arguments' => array(1),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'blog.pages.inc',
+  );
+  $items['blog/feed'] = array(
+    'title' => 'Blogs',
+    'page callback' => 'blog_feed_last',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'blog.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Displays the most recent 10 blog titles.
+ */
+function blog_block($op = 'list', $delta = 0) {
+  global $user;
+  if ($op == 'list') {
+    $block[0]['info'] = t('Recent blog posts');
+    return $block;
+  }
+  else if ($op == 'view') {
+    if (user_access('access content')) {
+      $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.title, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, 10);
+      if ($node_title_list = node_title_list($result)) {
+        $block['content'] = $node_title_list;
+        $block['content'] .= theme('more_link', url('blog'), t('Read the latest blog entries.'));
+        $block['subject'] = t('Recent blog posts');
+        return $block;
+      }
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blog/blog.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,115 @@
+<?php
+// $Id: blog.pages.inc,v 1.6.2.1 2008/02/08 21:15:12 goba Exp $
+
+/**
+ * @file
+ * Page callback file for the blog module.
+ */
+
+/**
+ * Menu callback; displays a Drupal page containing recent blog entries of a given user.
+ */
+function blog_page_user($account) {
+  global $user;
+
+  drupal_set_title($title = t("@name's blog", array('@name' => $account->name)));
+
+  $items = array();
+
+  if (($account->uid == $user->uid) && user_access('create blog entries')) {
+    $items[] = l(t('Post new blog entry.'), "node/add/blog");
+  }
+  else if ($account->uid == $user->uid) {
+    $items[] = t('You are not allowed to post a new blog entry.');
+  }
+
+  $output = theme('item_list', $items);
+
+  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid);
+  $has_posts = FALSE;
+  
+  while ($node = db_fetch_object($result)) {
+    $output .= node_view(node_load($node->nid), 1);
+    $has_posts = TRUE;
+  }
+  
+  if ($has_posts) {
+    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  }
+  else {
+    if ($account->uid == $user->uid) {
+      drupal_set_message(t('You have not created any blog entries.'));
+    }
+    else {
+      drupal_set_message(t('!author has not created any blog entries.', array('!author' => theme('username', $account))));
+    }
+  }
+  drupal_add_feed(url('blog/'. $account->uid .'/feed'), t('RSS - !title', array('!title' => $title)));
+
+  return $output;
+}
+
+/**
+ * Menu callback; displays a Drupal page containing recent blog entries of all users.
+ */
+function blog_page_last() {
+  global $user;
+
+  $output = '';
+  $items = array();
+
+  if (user_access('edit own blog')) {
+    $items[] = l(t('Create new blog entry.'), "node/add/blog");
+  }
+
+  $output = theme('item_list', $items);
+
+  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10));
+  $has_posts = FALSE;
+
+  while ($node = db_fetch_object($result)) {
+    $output .= node_view(node_load($node->nid), 1);
+    $has_posts = TRUE;
+  }
+  
+  if ($has_posts) {
+    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  }
+  else {
+    drupal_set_message(t('No blog entries have been created.'));
+  }
+  drupal_add_feed(url('blog/feed'), t('RSS - blogs'));
+
+  return $output;
+}
+
+/**
+ * Menu callback; displays an RSS feed containing recent blog entries of a given user.
+ */
+function blog_feed_user($account) {
+  $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n  WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.created DESC"), $account->uid, 0, variable_get('feed_default_items', 10));
+  $channel['title'] = $account->name ."'s blog";
+  $channel['link'] = url('blog/'. $account->uid, array('absolute' => TRUE));
+
+  $items = array();
+  while ($row = db_fetch_object($result)) {
+    $items[] = $row->nid;
+  }
+  node_feed($items, $channel);
+}
+
+/**
+ * Menu callback; displays an RSS feed containing recent blog entries of all users.
+ */
+function blog_feed_last() {
+  $result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, variable_get('feed_default_items', 10));
+  $channel['title'] = variable_get('site_name', 'Drupal') .' blogs';
+  $channel['link'] = url('blog', array('absolute' => TRUE));
+
+  $items = array();
+  while ($row = db_fetch_object($result)) {
+    $items[] = $row->nid;
+  }
+
+  node_feed($items, $channel);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blogapi/blogapi.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: blogapi.info,v 1.4 2007/06/08 05:50:53 dries Exp $
+name = Blog API
+description = Allows users to post content using applications that support XML-RPC blog APIs.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blogapi/blogapi.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,20 @@
+<?php
+// $Id: blogapi.install,v 1.1 2008/01/09 09:51:34 goba Exp $
+
+/**
+ * @defgroup updates-5.x-to-6.x Blog API updates from 5.x to 6.x
+ * @{
+ */
+
+/**
+ * Inform users about the new permission.
+ */
+function blogapi_update_6000() {
+  drupal_set_message("Blog API module does not depend on blog module's permissions anymore, but provides its own 'administer content with blog api' permission instead. Until <a href=\"". url('admin/user/permissions', array('fragment' => 'module-blogapi')) .'">this permission is assigned</a> to at least one user role, only the site administrator will be able to use Blog API features.');
+  return array();
+}
+
+/**
+ * @} End of "defgroup updates-5.x-to-6.x"
+ * The next series of updates should start at 7000.
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/blogapi/blogapi.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,728 @@
+<?php
+// $Id: blogapi.module,v 1.115.2.1 2008/02/07 20:11:02 goba Exp $
+
+/**
+ * @file
+ * Enable users to post using applications that support XML-RPC blog APIs.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function blogapi_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#blogapi':
+      $output = '<p>'. t("The Blog API module allows your site's users to access and post to their blogs from external blogging clients. External blogging clients are available for a wide range of desktop operating systems, and generally provide a feature-rich graphical environment for creating and editing posts.") .'</p>';
+      $output .= '<p>'. t('<a href="@ecto-link">Ecto</a>, a blogging client available for both Mac OS X and Microsoft Windows, can be used with Blog API. Blog API also supports <a href="@blogger-api">Blogger API</a>, <a href="@metaweblog-api">MetaWeblog API</a>, and most of the <a href="@movabletype-api">Movable Type API</a>. Blogging clients and other services (e.g. <a href="@flickr">Flickr\'s</a> "post to blog") that support these APIs may also be compatible.', array('@ecto-link' => url('http://infinite-sushi.com/software/ecto/'), '@blogger-api' => url('http://www.blogger.com/developers/api/1_docs/'), '@metaweblog-api' => url('http://www.xmlrpc.com/metaWeblogApi'), '@movabletype-api' => url('http://www.movabletype.org/docs/mtmanual_programmatic.html'), '@flickr' => url('http://www.flickr.com'))) .'</p>';
+      $output .= '<p>'. t('Select the content types available to external clients on the <a href="@blogapi-settings">Blog API settings page</a>. If supported and available, each content type will be displayed as a separate "blog" by the external client.', array('@blogapi-settings' => url('admin/settings/blogapi'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@blogapi">Blog API module</a>.', array('@blogapi' => url('http://drupal.org/handbook/modules/blogapi/'))) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function blogapi_perm() {
+  return array('administer content with blog api');
+}
+
+/**
+ * Implementation of hook_xmlrpc().
+ */
+function blogapi_xmlrpc() {
+  return array(
+    array(
+      'blogger.getUsersBlogs',
+      'blogapi_blogger_get_users_blogs',
+      array('array', 'string', 'string', 'string'),
+      t('Returns a list of blogs to which an author has posting privileges.')),
+    array(
+      'blogger.getUserInfo',
+      'blogapi_blogger_get_user_info',
+      array('struct', 'string', 'string', 'string'),
+      t('Returns information about an author in the system.')),
+    array(
+      'blogger.newPost',
+      'blogapi_blogger_new_post',
+      array('string', 'string', 'string', 'string', 'string', 'string', 'boolean'),
+      t('Creates a new post, and optionally publishes it.')),
+    array(
+      'blogger.editPost',
+      'blogapi_blogger_edit_post',
+      array('boolean', 'string', 'string', 'string', 'string', 'string', 'boolean'),
+      t('Updates the information about an existing post.')),
+    array(
+      'blogger.getPost',
+      'blogapi_blogger_get_post',
+      array('struct', 'string', 'string', 'string', 'string'),
+      t('Returns information about a specific post.')),
+    array(
+      'blogger.deletePost',
+      'blogapi_blogger_delete_post',
+      array('boolean', 'string', 'string', 'string', 'string', 'boolean'),
+      t('Deletes a post.')),
+    array(
+      'blogger.getRecentPosts',
+      'blogapi_blogger_get_recent_posts',
+      array('array', 'string', 'string', 'string', 'string', 'int'),
+      t('Returns a list of the most recent posts in the system.')),
+    array(
+      'metaWeblog.newPost',
+      'blogapi_metaweblog_new_post',
+      array('string', 'string', 'string', 'string', 'struct', 'boolean'),
+      t('Creates a new post, and optionally publishes it.')),
+    array(
+      'metaWeblog.editPost',
+      'blogapi_metaweblog_edit_post',
+      array('boolean', 'string', 'string', 'string', 'struct', 'boolean'),
+      t('Updates information about an existing post.')),
+    array(
+      'metaWeblog.getPost',
+      'blogapi_metaweblog_get_post',
+      array('struct', 'string', 'string', 'string'),
+      t('Returns information about a specific post.')),
+    array(
+      'metaWeblog.newMediaObject',
+      'blogapi_metaweblog_new_media_object',
+      array('string', 'string', 'string', 'string', 'struct'),
+      t('Uploads a file to your webserver.')),
+    array(
+      'metaWeblog.getCategories',
+      'blogapi_metaweblog_get_category_list',
+      array('struct', 'string', 'string', 'string'),
+      t('Returns a list of all categories to which the post is assigned.')),
+    array(
+      'metaWeblog.getRecentPosts',
+      'blogapi_metaweblog_get_recent_posts',
+      array('array', 'string', 'string', 'string', 'int'),
+      t('Returns a list of the most recent posts in the system.')),
+    array(
+      'mt.getRecentPostTitles',
+      'blogapi_mt_get_recent_post_titles',
+      array('array', 'string', 'string', 'string', 'int'),
+      t('Returns a bandwidth-friendly list of the most recent posts in the system.')),
+    array(
+      'mt.getCategoryList',
+      'blogapi_mt_get_category_list',
+      array('array', 'string', 'string', 'string'),
+      t('Returns a list of all categories defined in the blog.')),
+    array(
+      'mt.getPostCategories',
+      'blogapi_mt_get_post_categories',
+      array('array', 'string', 'string', 'string'),
+      t('Returns a list of all categories to which the post is assigned.')),
+    array(
+      'mt.setPostCategories',
+      'blogapi_mt_set_post_categories',
+      array('boolean', 'string', 'string', 'string', 'array'),
+      t('Sets the categories for a post.')),
+    array(
+      'mt.supportedMethods',
+      'xmlrpc_server_list_methods',
+      array('array'),
+      t('Retrieve information about the XML-RPC methods supported by the server.')),
+    array(
+      'mt.supportedTextFilters',
+      'blogapi_mt_supported_text_filters',
+      array('array'),
+      t('Retrieve information about the text formatting plugins supported by the server.')),
+    array(
+      'mt.publishPost',
+      'blogap_mti_publish_post',
+      array('boolean', 'string', 'string', 'string'),
+      t('Publish (rebuild) all of the static files related to an entry from your blog. Equivalent to saving an entry in the system (but without the ping).')));
+}
+
+/**
+ * Blogging API callback. Finds the URL of a user's blog.
+ */
+
+function blogapi_blogger_get_users_blogs($appid, $username, $password) {
+
+  $user = blogapi_validate_user($username, $password);
+  if ($user->uid) {
+    $types = _blogapi_get_node_types();
+    $structs = array();
+    foreach ($types as $type) {
+      $structs[] = array('url' => url('blog/'. $user->uid, array('absolute' => TRUE)), 'blogid' => $type, 'blogName' => $user->name .": ". $type);
+    }
+    return $structs;
+  }
+  else {
+    return blogapi_error($user);
+  }
+}
+
+/**
+ * Blogging API callback. Returns profile information about a user.
+ */
+function blogapi_blogger_get_user_info($appkey, $username, $password) {
+  $user = blogapi_validate_user($username, $password);
+
+  if ($user->uid) {
+    $name = explode(' ', $user->realname ? $user->realname : $user->name, 2);
+    return array(
+      'userid' => $user->uid,
+      'lastname' => $name[1],
+      'firstname' => $name[0],
+      'nickname' => $user->name,
+      'email' => $user->mail,
+      'url' => url('blog/'. $user->uid, array('absolute' => TRUE)));
+  }
+  else {
+    return blogapi_error($user);
+  }
+}
+
+/**
+ * Blogging API callback. Inserts a new blog post as a node.
+ */
+function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $content, $publish) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
+    // Return an error if not configured type.
+    return $error;
+  }
+
+  $edit = array();
+  $edit['type'] = $blogid;
+  // get the node type defaults
+  $node_type_default = variable_get('node_options_'. $edit['type'], array('status', 'promote'));
+  $edit['uid'] = $user->uid;
+  $edit['name'] = $user->name;
+  $edit['promote'] = in_array('promote', $node_type_default);
+  $edit['comment'] = variable_get('comment_'. $edit['type'], 2);
+  $edit['revision'] = in_array('revision', $node_type_default);
+  $edit['format'] = FILTER_FORMAT_DEFAULT;
+  $edit['status'] = $publish;
+
+  // check for bloggerAPI vs. metaWeblogAPI
+  if (is_array($content)) {
+    $edit['title'] = $content['title'];
+    $edit['body'] = $content['description'];
+    _blogapi_mt_extra($edit, $content);
+  }
+  else {
+    $edit['title'] = blogapi_blogger_title($content);
+    $edit['body'] = $content;
+  }
+
+  if (!node_access('create', $edit['type'])) {
+    return blogapi_error(t('You do not have permission to create this type of post.'));
+  }
+
+  if (user_access('administer nodes') && !isset($edit['date'])) {
+    $edit['date'] = format_date(time(), 'custom', 'Y-m-d H:i:s O');
+  }
+
+  node_invoke_nodeapi($edit, 'blogapi new');
+
+  node_validate($edit);
+  if ($errors = form_get_errors()) {
+    return blogapi_error(implode("\n", $errors));
+  }
+
+  $node = node_submit($edit);
+  node_save($node);
+  if ($node->nid) {
+    watchdog('content', '@type: added %title using blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
+    // blogger.newPost returns a string so we cast the nid to a string by putting it in double quotes:
+    return "$node->nid";
+  }
+
+  return blogapi_error(t('Error storing post.'));
+}
+
+/**
+ * Blogging API callback. Modifies the specified blog node.
+ */
+function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $content, $publish) {
+
+  $user = blogapi_validate_user($username, $password);
+
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  $node = node_load($postid);
+  if (!$node) {
+    return blogapi_error(t('n/a'));
+  }
+  // Let the teaser be re-generated.
+  unset($node->teaser);
+
+  if (!node_access('update', $node)) {
+    return blogapi_error(t('You do not have permission to update this post.'));
+  }
+
+  $node->status = $publish;
+
+  // check for bloggerAPI vs. metaWeblogAPI
+  if (is_array($content)) {
+    $node->title = $content['title'];
+    $node->body = $content['description'];
+    _blogapi_mt_extra($node, $content);
+  }
+  else {
+    $node->title = blogapi_blogger_title($content);
+    $node->body = $content;
+  }
+
+  node_invoke_nodeapi($node, 'blogapi edit');
+
+  node_validate($node);
+  if ($errors = form_get_errors()) {
+    return blogapi_error(implode("\n", $errors));
+  }
+
+  if (user_access('administer nodes') && !isset($edit['date'])) {
+    $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
+  }
+  $node = node_submit($node);
+  node_save($node);
+  if ($node->nid) {
+    watchdog('content', '@type: updated %title using Blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
+    return TRUE;
+  }
+
+  return blogapi_error(t('Error storing post.'));
+}
+
+/**
+ * Blogging API callback. Returns a specified blog node.
+ */
+function blogapi_blogger_get_post($appkey, $postid, $username, $password) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  $node = node_load($postid);
+
+  return _blogapi_get_post($node, TRUE);
+}
+
+/**
+ * Blogging API callback. Removes the specified blog node.
+ */
+function blogapi_blogger_delete_post($appkey, $postid, $username, $password, $publish) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  node_delete($postid);
+  return TRUE;
+}
+
+/**
+ * Blogging API callback. Returns the latest few postings in a user's blog. $bodies TRUE
+ * <a href="http://movabletype.org/docs/mtmanual_programmatic.html#item_mt%2EgetRecentPostTitles">
+ * returns a bandwidth-friendly list</a>.
+ */
+function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password, $number_of_posts, $bodies = TRUE) {
+  // Remove unused appkey (from bloggerAPI).
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
+    // Return an error if not configured type.
+    return $error;
+  }
+
+  if ($bodies) {
+    $result = db_query_range("SELECT n.nid, n.title, r.body, r.format, n.comment, n.created, u.name FROM {node} n, {node_revisions} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC",  $blogid, $user->uid, 0, $number_of_posts);
+  }
+  else {
+    $result = db_query_range("SELECT n.nid, n.title, n.created, u.name FROM {node} n, {users} u WHERE n.uid = u.uid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC", $blogid, $user->uid, 0, $number_of_posts);
+  }
+  $blogs = array();
+  while ($blog = db_fetch_object($result)) {
+    $blogs[] = _blogapi_get_post($blog, $bodies);
+  }
+  return $blogs;
+}
+
+function blogapi_metaweblog_new_post($blogid, $username, $password, $content, $publish) {
+  return blogapi_blogger_new_post('0123456789ABCDEF', $blogid, $username, $password, $content, $publish);
+}
+
+function blogapi_metaweblog_edit_post($postid, $username, $password, $content, $publish) {
+  return blogapi_blogger_edit_post('0123456789ABCDEF', $postid, $username, $password, $content, $publish);
+}
+
+function blogapi_metaweblog_get_post($postid, $username, $password) {
+  return blogapi_blogger_get_post('01234567890ABCDEF', $postid, $username, $password);
+}
+
+/**
+ * Blogging API callback. Inserts a file into Drupal.
+ */
+function blogapi_metaweblog_new_media_object($blogid, $username, $password, $file) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  $name = basename($file['name']);
+  $data = $file['bits'];
+
+  if (!$data) {
+    return blogapi_error(t('No file sent.'));
+  }
+
+  if (!$file = file_save_data($data, $name)) {
+    return blogapi_error(t('Error storing file.'));
+  }
+
+  // Return the successful result.
+  return array('url' => file_create_url($file), 'struct');
+}
+/**
+ * Blogging API callback. Returns a list of the taxonomy terms that can be
+ * associated with a blog node.
+ */
+function blogapi_metaweblog_get_category_list($blogid, $username, $password) {
+  if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
+    // Return an error if not configured type.
+    return $error;
+  }
+
+  $vocabularies = module_invoke('taxonomy', 'get_vocabularies', $blogid, 'vid');
+  $categories = array();
+  if ($vocabularies) {
+    foreach ($vocabularies as $vocabulary) {
+      $terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1);
+      foreach ($terms as $term) {
+        $term_name = $term->name;
+        foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
+          $term_name = $parent->name .'/'. $term_name;
+        }
+        $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid);
+      }
+    }
+  }
+  return $categories;
+}
+
+function blogapi_metaweblog_get_recent_posts($blogid, $username, $password, $number_of_posts) {
+  return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, TRUE);
+}
+
+function blogapi_mt_get_recent_post_titles($blogid, $username, $password, $number_of_posts) {
+  return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, FALSE);
+}
+
+function blogapi_mt_get_category_list($blogid, $username, $password) {
+  return blogapi_metaweblog_get_category_list($blogid, $username, $password);
+}
+
+/**
+ * Blogging API callback. Returns a list of the taxonomy terms that are
+ * assigned to a particular node.
+ */
+function blogapi_mt_get_post_categories($postid, $username, $password) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  $node = node_load($postid);
+  $terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid');
+  $categories = array();
+  foreach ($terms as $term) {
+    $term_name = $term->name;
+    foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
+      $term_name = $parent->name .'/'. $term_name;
+    }
+    $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid, 'isPrimary' => TRUE);
+  }
+
+  return $categories;
+}
+
+/**
+ * Blogging API callback. Assigns taxonomy terms to a particular node.
+ */
+function blogapi_mt_set_post_categories($postid, $username, $password, $categories) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+
+  $node = node_load($postid);
+  $node->taxonomy = array();
+  foreach ($categories as $category) {
+    $node->taxonomy[] = $category['categoryId'];
+  }
+  node_save($node);
+  return TRUE;
+}
+
+/**
+ * Blogging API callback. Sends a list of available input formats.
+ */
+function blogapi_mt_supported_text_filters() {
+  // NOTE: we're only using anonymous' formats because the MT spec
+  // does not allow for per-user formats.
+  $formats = filter_formats();
+
+  $filters = array();
+  foreach ($formats as $format) {
+    $filter['key'] = $format->format;
+    $filter['label'] = $format->name;
+    $filters[] = $filter;
+  }
+
+  return $filters;
+}
+
+/**
+ * Blogging API callback. Publishes the given node
+ */
+function blogap_mti_publish_post($postid, $username, $password) {
+  $user = blogapi_validate_user($username, $password);
+  if (!$user->uid) {
+    return blogapi_error($user);
+  }
+  $node = node_load($postid);
+  if (!$node) {
+    return blogapi_error(t('Invalid post.'));
+  }
+
+  $node->status = 1;
+  if (!node_access('update', $node)) {
+    return blogapi_error(t('You do not have permission to update this post.'));
+  }
+
+  node_save($node);
+
+  return TRUE;
+}
+
+/**
+ * Prepare an error message for returning to the XMLRPC caller.
+ */
+function blogapi_error($message) {
+  static $xmlrpcusererr;
+  if (!is_array($message)) {
+    $message = array($message);
+  }
+
+  $message = implode(' ', $message);
+
+  return xmlrpc_error($xmlrpcusererr + 1, strip_tags($message));
+}
+
+/**
+ * Ensure that the given user has permission to edit a blog.
+ */
+function blogapi_validate_user($username, $password) {
+  global $user;
+
+  $user = user_authenticate(array('name' => $username, 'pass' => $password));
+
+  if ($user->uid) {
+    if (user_access('administer content with blog api', $user)) {
+      return $user;
+    }
+    else {
+      return t('You do not have permission to edit this blog.');
+    }
+  }
+  else {
+    return t('Wrong username or password.');
+  }
+}
+
+/**
+ * For the blogger API, extract the node title from the contents field.
+ */
+function blogapi_blogger_title(&$contents) {
+  if (eregi('<title>([^<]*)</title>', $contents, $title)) {
+    $title = strip_tags($title[0]);
+    $contents = ereg_replace('<title>[^<]*</title>', '', $contents);
+  }
+  else {
+    list($title, $contents) = explode("\n", $contents, 2);
+  }
+  return $title;
+}
+
+function blogapi_admin_settings() {
+  $node_types = array_map('check_plain', node_get_types('names'));
+  $defaults = isset($node_types['blog']) ? array('blog' => 1) : array();
+  $form['blogapi_node_types'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Enable for external blogging clients'),
+    '#required' => TRUE,
+    '#default_value' => variable_get('blogapi_node_types', $defaults),
+    '#options' => $node_types,
+    '#description' => t('Select the content types available to external blogging clients via Blog API. If supported, each enabled content type will be displayed as a separate "blog" by the external client.')
+  );
+
+  return system_settings_form($form);
+}
+
+function blogapi_menu() {
+  $items['blogapi/rsd'] = array(
+    'title' => 'RSD',
+    'page callback' => 'blogapi_rsd',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/blogapi'] = array(
+    'title' => 'Blog API',
+    'description' => 'Configure the content types available to external blogging clients.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('blogapi_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+
+  return $items;
+}
+
+function blogapi_init() {
+  if (drupal_is_front_page()) {
+    drupal_add_link(array('rel' => 'EditURI',
+                          'type' => 'application/rsd+xml',
+                          'title' => t('RSD'),
+                          'href' => url('blogapi/rsd', array('absolute' => TRUE))));
+  }
+}
+
+function blogapi_rsd() {
+  global $base_url;
+
+  $xmlrpc = $base_url .'/xmlrpc.php';
+  $base = url('', array('absolute' => TRUE));
+  $blogid = 1; # until we figure out how to handle multiple bloggers
+
+  drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8');
+  print <<<__RSD__
+<?xml version="1.0"?>
+<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
+  <service>
+    <engineName>Drupal</engineName>
+    <engineLink>http://drupal.org/</engineLink>
+    <homePageLink>$base</homePageLink>
+    <apis>
+      <api name="MetaWeblog" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
+      <api name="Blogger" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
+      <api name="MovableType" preferred="true" apiLink="$xmlrpc" blogID="$blogid" />
+    </apis>
+  </service>
+</rsd>
+__RSD__;
+}
+
+/**
+ * Handles extra information sent by clients according to MovableType's spec.
+ */
+function _blogapi_mt_extra(&$node, $struct) {
+  if (is_array($node)) {
+    $was_array = TRUE;
+    $node = (object)$node;
+  }
+
+  // mt_allow_comments
+  if (array_key_exists('mt_allow_comments', $struct)) {
+    switch ($struct['mt_allow_comments']) {
+      case 0:
+        $node->comment = COMMENT_NODE_DISABLED;
+        break;
+      case 1:
+        $node->comment = COMMENT_NODE_READ_WRITE;
+        break;
+      case 2:
+        $node->comment = COMMENT_NODE_READ_ONLY;
+        break;
+    }
+  }
+
+  // merge the 3 body sections (description, mt_excerpt, mt_text_more) into
+  // one body
+  if ($struct['mt_excerpt']) {
+    $node->body = $struct['mt_excerpt'] .'<!--break-->'. $node->body;
+  }
+  if ($struct['mt_text_more']) {
+    $node->body = $node->body .'<!--extended-->'. $struct['mt_text_more'];
+  }
+
+  // mt_convert_breaks
+  if ($struct['mt_convert_breaks']) {
+    $node->format = $struct['mt_convert_breaks'];
+  }
+
+  // dateCreated
+  if ($struct['dateCreated']) {
+    $node->date = format_date(mktime($struct['dateCreated']->hour, $struct['dateCreated']->minute, $struct['dateCreated']->second, $struct['dateCreated']->month, $struct['dateCreated']->day, $struct['dateCreated']->year), 'custom', 'Y-m-d H:i:s O');
+  }
+
+  if ($was_array) {
+    $node = (array)$node;
+  }
+}
+
+function _blogapi_get_post($node, $bodies = TRUE) {
+  $xmlrpcval = array(
+    'userid' => $node->name,
+    'dateCreated' => xmlrpc_date($node->created),
+    'title' => $node->title,
+    'postid' => $node->nid,
+    'link' => url('node/'. $node->nid, array('absolute' => TRUE)),
+    'permaLink' => url('node/'. $node->nid, array('absolute' => TRUE)),
+  );
+  if ($bodies) {
+    if ($node->comment == 1) {
+      $comment = 2;
+    }
+    else if ($node->comment == 2) {
+      $comment = 1;
+    }
+    $xmlrpcval['content'] = "<title>$node->title</title>$node->body";
+    $xmlrpcval['description'] = $node->body;
+    // Add MT specific fields
+    $xmlrpcval['mt_allow_comments'] = (int) $comment;
+    $xmlrpcval['mt_convert_breaks'] = $node->format;
+  }
+
+  return $xmlrpcval;
+}
+
+/**
+ * Validate blog ID, which maps to a content type in Drupal.
+ *
+ * Only content types configured to work with Blog API are supported.
+ *
+ * @return
+ *   TRUE if the content type is supported and the user has permission
+ *   to post, or a blogapi_error() XML construct otherwise.
+ */
+function _blogapi_validate_blogid($blogid) {
+  $types = _blogapi_get_node_types();
+  if (in_array($blogid, $types, TRUE)) {
+    return TRUE;
+  }
+  return blogapi_error(t("Blog API module is not configured to support the %type content type, or you don't have sufficient permissions to post this type of content.", array('%type' => $blogid)));
+}
+
+function _blogapi_get_node_types() {
+  $available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1))));
+  $types = array();
+  foreach (node_get_types() as $type => $name) {
+    if (node_access('create', $type) && in_array($type, $available_types)) {
+      $types[] = $type;
+    }
+  }
+
+  return $types;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book-all-books-block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,22 @@
+<?php
+// $Id: book-all-books-block.tpl.php,v 1.1 2007/11/04 14:29:09 goba Exp $
+
+/**
+ * @file book-all-books-block.tpl.php
+ * Default theme implementation for rendering book outlines within a block.
+ * This template is used only when the block is configured to "show block on
+ * all pages" which presents Multiple independent books on all pages.
+ *
+ * Available variables:
+ * - $book_menus: Array of book outlines rendered as an unordered list. It is
+ *   keyed to the parent book ID which is also the ID of the parent node
+ *   containing an entire outline.
+ *
+ * @see template_preprocess_book_all_books_block()
+ */
+?>
+<?php foreach ($book_menus as $book_id => $menu) : ?>
+<div id="book-block-menu-<?php print $book_id; ?>" class="book-block-menu">
+  <?php print $menu; ?>
+</div>
+<?php endforeach; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book-export-html.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,53 @@
+<?php
+// $Id: book-export-html.tpl.php,v 1.1 2007/11/04 14:29:09 goba Exp $
+
+/**
+ * @file book-export-html.tpl.php
+ * Default theme implementation for printed version of book outline.
+ *
+ * Available variables:
+ * - $title: Top level node title.
+ * - $head: Header tags.
+ * - $language: Language code. e.g. "en" for english.
+ * - $language_rtl: TRUE or FALSE depending on right to left language scripts.
+ * - $base_url: URL to home page.
+ * - $content: Nodes within the current outline rendered through
+ *   book-node-export-html.tpl.php.
+ *
+ * @see template_preprocess_book_export_html()
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language->language; ?>" xml:lang="<?php print $language->language; ?>">
+  <head>
+    <title><?php print $title; ?></title>
+    <?php print $head; ?>
+    <base href="<?php print $base_url; ?>" />
+    <link type="text/css" rel="stylesheet" href="misc/print.css" />
+    <?php if ($language_rtl): ?>
+      <link type="text/css" rel="stylesheet" href="misc/print-rtl.css" />
+    <?php endif; ?>
+  </head>
+  <body>
+    <?php
+    /**
+     * The given node is /embedded to its absolute depth in a top level
+     * section/. For example, a child node with depth 2 in the hierarchy is
+     * contained in (otherwise empty) &lt;div&gt; elements corresponding to
+     * depth 0 and depth 1. This is intended to support WYSIWYG output - e.g.,
+     * level 3 sections always look like level 3 sections, no matter their
+     * depth relative to the node selected to be exported as printer-friendly
+     * HTML.
+     */
+    $div_close = '';
+    ?>
+    <?php for ($i = 1; $i < $depth; $i++) : ?>
+      <div class="section-<?php print $i; ?>">
+      <?php $div_close .= '</div>'; ?>
+    <?php endfor; ?>
+
+    <?php print $contents; ?>
+    <?php print $div_close; ?>
+
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book-navigation.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,52 @@
+<?php
+// $Id: book-navigation.tpl.php,v 1.1 2007/11/04 14:29:09 goba Exp $
+
+/**
+ * @file book-navigation.tpl.php
+ * Default theme implementation to navigate books. Presented under nodes that
+ * are a part of book outlines.
+ *
+ * Available variables:
+ * - $tree: The immediate children of the current node rendered as an
+ *   unordered list.
+ * - $current_depth: Depth of the current node within the book outline.
+ *   Provided for context.
+ * - $prev_url: URL to the previous node.
+ * - $prev_title: Title of the previous node.
+ * - $parent_url: URL to the parent node.
+ * - $parent_title: Title of the parent node. Not printed by default. Provided
+ *   as an option.
+ * - $next_url: URL to the next node.
+ * - $next_title: Title of the next node.
+ * - $has_links: Flags TRUE whenever the previous, parent or next data has a
+ *   value.
+ * - $book_id: The book ID of the current outline being viewed. Same as the
+ *   node ID containing the entire outline. Provided for context.
+ * - $book_url: The book/node URL of the current outline being viewed.
+ *   Provided as an option. Not used by default.
+ * - $book_title: The book/node title of the current outline being viewed.
+ *   Provided as an option. Not used by default.
+ *
+ * @see template_preprocess_book_navigation()
+ */
+?>
+<?php if ($tree || $has_links): ?>
+  <div id="book-navigation-<?php print $book_id; ?>" class="book-navigation">
+    <?php print $tree; ?>
+
+    <?php if ($has_links): ?>
+    <div class="page-links clear-block">
+      <?php if ($prev_url) : ?>
+        <a href="<?php print $prev_url; ?>" class="page-previous" title="<?php print t('Go to previous page'); ?>"><?php print t('‹ ') . $prev_title; ?></a>
+      <?php endif; ?>
+      <?php if ($parent_url) : ?>
+        <a href="<?php print $parent_url; ?>" class="page-up" title="<?php print t('Go to parent page'); ?>"><?php print t('up'); ?></a>
+      <?php endif; ?>
+      <?php if ($next_url) : ?>
+        <a href="<?php print $next_url; ?>" class="page-next" title="<?php print t('Go to next page'); ?>"><?php print $next_title . t(' ›'); ?></a>
+      <?php endif; ?>
+    </div>
+    <?php endif; ?>
+
+  </div>
+<?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book-node-export-html.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,25 @@
+<?php
+// $Id: book-node-export-html.tpl.php,v 1.1 2007/11/04 14:29:09 goba Exp $
+
+/**
+ * @file book-node-export-html.tpl.php
+ * Default theme implementation for rendering a single node in a printer
+ * friendly outline.
+ *
+ * @see book-node-export-html.tpl.php
+ * Where it is collected and printed out.
+ *
+ * Available variables:
+ * - $depth: Depth of the current node inside the outline.
+ * - $title: Node title.
+ * - $content: Node content.
+ * - $children: All the child nodes recursively rendered through this file.
+ *
+ * @see template_preprocess_book_node_export_html()
+ */
+?>
+<div id="node-<?php print $node->nid; ?>" class="section-<?php print $depth; ?>">
+  <h1 class="book-heading"><?php print $title; ?></h1>
+  <?php print $content; ?>
+  <?php print $children; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+/* $Id: book-rtl.css,v 1.1 2007/05/27 17:57:48 goba Exp $ */
+
+.book-navigation .page-previous {
+  float: right;
+}
+.book-navigation .page-up {
+  float: right;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,250 @@
+<?php
+// $Id: book.admin.inc,v 1.8 2008/01/08 10:35:41 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the book module.
+ */
+
+/**
+ * Returns an administrative overview of all books.
+ */
+function book_admin_overview() {
+  $rows = array();
+  foreach (book_get_books() as $book) {
+    $rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/". $book['nid']));
+  }
+  $headers = array(t('Book'), t('Operations'));
+
+  return theme('table', $headers, $rows);
+}
+
+/**
+ * Builds and returns the book settings form.
+ *
+ * @see book_admin_settings_validate()
+ *
+ * @ingroup forms
+ */
+function book_admin_settings() {
+  $types = node_get_types('names');
+  $form['book_allowed_types'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Allowed book outline types'),
+    '#default_value' => variable_get('book_allowed_types', array('book')),
+    '#options' => $types,
+    '#description' => t('Select content types which users with the %add-perm permission will be allowed to add to the book hierarchy. Users with the %outline-perm permission can add all content types.', array('%add-perm' => t('add content to books'),  '%outline-perm' => t('administer book outlines'))),
+    '#required' => TRUE,
+  );
+  $form['book_child_type'] = array(
+    '#type' => 'radios',
+    '#title' => t('Default child page type'),
+    '#default_value' => variable_get('book_child_type', 'book'),
+    '#options' => $types,
+    '#description' => t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', array('%add-child' => t('Add child page'))),
+    '#required' => TRUE,
+  );
+  $form['array_filter'] = array('#type' => 'value', '#value' => TRUE);
+  $form['#validate'][] = 'book_admin_settings_validate';
+  return system_settings_form($form);
+}
+
+/**
+ * Validate the book settings form.
+ *
+ * @see book_admin_settings()
+ */
+function book_admin_settings_validate($form, &$form_state) {
+  $child_type = $form_state['values']['book_child_type'];
+  if (empty($form_state['values']['book_allowed_types'][$child_type])) {
+    form_set_error('book_child_type', t('The content type for the %add-child link must be one of those selected as an allowed book outline type.', array('%add-child' => t('Add child page'))));
+  }
+}
+
+/**
+ * Build the form to administrate the hierarchy of a single book.
+ *
+ * @see book_admin_edit_submit()
+ *
+ * @ingroup forms.
+ */
+function book_admin_edit($form_state, $node) {
+  drupal_set_title(check_plain($node->title));
+  $form = array();
+  $form['#node'] = $node;
+  $form['table'] = _book_admin_table($node);
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save book pages'),
+  );
+  return $form;
+}
+
+/**
+ * Handle submission of the book administrative page form.
+ *
+ * This function takes care to save parent menu items before their children.
+ * Saving menu items in the incorrect order can break the menu tree.
+ *
+ * @see book_admin_edit()
+ * @see menu_overview_form_submit()
+ */
+function book_admin_edit_submit($form, &$form_state) {
+  // Save elements in the same order as defined in post rather than the form.
+  // This ensures parents are updated before their children, preventing orphans.
+  $order = array_flip(array_keys($form['#post']['table']));
+  $form['table'] = array_merge($order, $form['table']);
+
+  foreach (element_children($form['table']) as $key) {
+    if ($form['table'][$key]['#item']) {
+      $row = $form['table'][$key];
+      $values = $form_state['values']['table'][$key];
+
+      // Update menu item if moved.
+      if ($row['plid']['#default_value'] != $values['plid'] || $row['weight']['#default_value'] != $values['weight']) {
+        $row['#item']['plid'] = $values['plid'];
+        $row['#item']['weight'] = $values['weight'];
+        menu_link_save($row['#item']);
+      }
+
+      // Update the title if changed.
+      if ($row['title']['#default_value'] != $values['title']) {
+        $node = node_load($values['nid'], FALSE);
+        $node->title = $values['title'];
+        $node->book['link_title'] = $values['title'];
+        $node->revision = 1;
+        $node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title']));
+        node_save($node);
+        watchdog('content', 'book: updated %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
+      }
+    }
+  }
+
+  drupal_set_message(t('Updated book %title.', array('%title' => $form['#node']->title)));
+}
+
+/**
+ * Build the table portion of the form for the book administration page.
+ *
+ * @see book_admin_edit()
+ */
+function _book_admin_table($node) {
+  $form = array(
+    '#theme' => 'book_admin_table',
+    '#tree' => TRUE,
+  );
+
+  $tree = book_menu_subtree_data($node->book);
+  $tree = array_shift($tree); // Do not include the book item itself.
+  if ($tree['below']) {
+    _book_admin_table_tree($tree['below'], $form);
+  }
+  return $form;
+}
+
+/**
+ * Recursive helper to build the main table in the book administration page form.
+ *
+ * @see book_admin_edit()
+ */
+function _book_admin_table_tree($tree, &$form) {
+  foreach ($tree as $key => $data) {
+    $form[$key] = array(
+      '#item' => $data['link'],
+      'nid' => array('#type' => 'value', '#value' => $data['link']['nid']),
+      'depth' => array('#type' => 'value', '#value' => $data['link']['depth']),
+      'href' => array('#type' => 'value', '#value' => $data['link']['href']),
+      'title' => array(
+        '#type' => 'textfield',
+        '#default_value' => $data['link']['link_title'],
+        '#maxlength' => 255,
+        '#size' => 40,
+      ),
+      'weight' => array(
+        '#type' => 'weight',
+        '#default_value' => $data['link']['weight'],
+        '#delta' => 15,
+      ),
+      'plid' => array(
+        '#type' => 'textfield',
+        '#default_value' => $data['link']['plid'],
+        '#size' => 6,
+      ),
+      'mlid' => array(
+        '#type' => 'hidden',
+        '#default_value' => $data['link']['mlid'],
+      ),
+    );
+    if ($data['below']) {
+      _book_admin_table_tree($data['below'], $form);
+    }
+  }
+
+  return $form;
+}
+
+/**
+ * Theme function for the book administration page form.
+ *
+ * @ingroup themeable
+ * @see book_admin_table()
+ */
+function theme_book_admin_table($form) {
+  drupal_add_tabledrag('book-outline', 'match', 'parent', 'book-plid', 'book-plid', 'book-mlid', TRUE, MENU_MAX_DEPTH - 2);
+  drupal_add_tabledrag('book-outline', 'order', 'sibling', 'book-weight');
+
+  $header = array(t('Title'), t('Weight'), t('Parent'), array('data' => t('Operations'), 'colspan' => '3'));
+
+  $rows = array();
+  $destination = drupal_get_destination();
+  $access = user_access('administer nodes');
+  foreach (element_children($form) as $key) {
+    $nid = $form[$key]['nid']['#value'];
+    $href = $form[$key]['href']['#value'];
+
+    // Add special classes to be used with tabledrag.js.
+    $form[$key]['plid']['#attributes']['class'] = 'book-plid';
+    $form[$key]['mlid']['#attributes']['class'] = 'book-mlid';
+    $form[$key]['weight']['#attributes']['class'] = 'book-weight';
+
+    $data = array(
+      theme('indentation', $form[$key]['depth']['#value'] - 2) . drupal_render($form[$key]['title']),
+      drupal_render($form[$key]['weight']),
+      drupal_render($form[$key]['plid']) . drupal_render($form[$key]['mlid']),
+      l(t('view'), $href),
+      $access ? l(t('edit'), 'node/'. $nid .'/edit', array('query' => $destination)) : '&nbsp',
+      $access ? l(t('delete'), 'node/'. $nid .'/delete', array('query' => $destination) )  : '&nbsp',
+    );
+    $row = array('data' => $data);
+    if (isset($form[$key]['#attributes'])) {
+      $row = array_merge($row, $form[$key]['#attributes']);
+    }
+    $row['class'] = empty($row['class']) ? 'draggable' : $row['class'] .' draggable';
+    $rows[] = $row;
+  }
+
+  return theme('table', $header, $rows, array('id' => 'book-outline'));
+}
+
+/**
+ * Recursive helper to sort each layer of a book tree by weight.
+ */
+function _book_admin_sort_tree(&$tree) {
+  uasort($tree, '_book_admin_compare');
+  foreach ($tree as $key => $subtree) {
+    if (!empty($tree[$key]['below'])) {
+      _book_admin_sort_tree($tree[$key]['below']);
+    }
+  }
+}
+
+/**
+ * Used by uasort() in _book_admin_sort_tree() to compare items in a book tree.
+ */
+function _book_admin_compare($a, $b) {
+  $weight = $a['link']['weight'] - $b['link']['weight'];
+  if ($weight) {
+    return $weight;
+  }
+  return strncmp($a['link']['title'], $b['link']['title']);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,52 @@
+/* $Id: book.css,v 1.6 2007/11/26 16:19:37 dries Exp $ */
+
+.book-navigation .menu {
+  border-top: 1px solid #888;
+  padding: 1em 0 0 3em;
+}
+.book-navigation .page-links {
+  border-top: 1px solid #888;
+  border-bottom: 1px solid #888;
+  text-align: center;
+  padding: 0.5em;
+}
+.book-navigation .page-previous {
+  text-align: left;
+  width: 42%;
+  display: block;
+  float: left; /* LTR */
+}
+.book-navigation .page-up {
+  margin: 0 5%;
+  width: 4%;
+  display: block;
+  float: left; /* LTR */
+}
+.book-navigation .page-next {
+  text-align: right;
+  width: 42%;
+  display: block;
+  float: right;
+}
+#book-outline {
+  min-width: 56em;
+}
+.book-outline-form .form-item {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+#edit-book-bid-wrapper .description {
+  clear: both;
+}
+#book-admin-edit select {
+  margin-right: 24px;
+}
+#book-admin-edit select.progress-disabled {
+  margin-right: 0;
+}
+#book-admin-edit tr.ahah-new-content {
+  background-color: #ffd;
+}
+#book-admin-edit .form-item {
+  float: left;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: book.info,v 1.5 2007/07/30 18:20:21 dries Exp $
+name = Book
+description = Allows users to structure site pages in a hierarchy or outline.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,290 @@
+<?php
+// $Id: book.install,v 1.20 2008/01/10 18:13:42 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function book_install() {
+  // Create tables.
+  drupal_install_schema('book');
+  // Add the node type.
+  _book_install_type_create();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function book_uninstall() {
+  // Delete menu links.
+  db_query("DELETE FROM {menu_links} WHERE module = 'book'");
+  menu_cache_clear_all();
+  // Remove tables.
+  drupal_uninstall_schema('book');
+}
+
+function _book_install_type_create() {
+  // Create an additional node type
+  $book_node_type = array(
+    'type' => 'book',
+    'name' => t('Book page'),
+    'module' => 'node',
+    'description' => t('A <em>book page</em> is a page of content, organized into a collection of related entries collectively known as a <em>book</em>. A <em>book page</em> automatically displays links to adjacent pages, providing a simple navigation system for organizing and reviewing structured content.'),
+    'custom' => TRUE,
+    'modified' => TRUE,
+    'locked' => FALSE,
+  );
+
+  $book_node_type = (object)_node_type_set_defaults($book_node_type);
+  node_type_save($book_node_type);
+  // Default to not promoted.
+  variable_set('node_options_book', array('status'));
+  // Use this default type for adding content to books.
+  variable_set('book_allowed_types', array('book'));
+  variable_set('book_child_type', 'book');
+}
+
+/**
+ * Drupal 5.x to 6.x update.
+ *
+ * This function moves any existing book hierarchy into the new structure used
+ * in the 6.x module.  Rather than storing the hierarchy in the {book} table,
+ * the menu API is used to store the hierarchy in the {menu_links} table and the
+ * {book} table serves to uniquely connect a node to a menu link.
+ *
+ * In order to accomplish this, the current hierarchy is processed using a stack.
+ * The stack insures that each parent is processed before any of its children
+ * in the book hierarchy, and is compatible with batched update processing.
+ *
+ */
+function book_update_6000() {
+  $ret = array();
+
+  // Set up for a multi-part update.
+  if (!isset($_SESSION['book_update_6000'])) {
+
+    $schema['book'] = array(
+      'fields' => array(
+        'mlid'    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'nid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'bid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      ),
+      'primary key' => array('mlid'),
+      'unique keys' => array(
+        'nid' => array('nid'),
+      ),
+      'indexes' => array(
+        'bid' => array('bid'),
+      ),
+    );
+    // Add the node type.
+    _book_install_type_create();
+
+    // Fix role permissions to account for the changed names
+    // Setup the array holding strings to match and the corresponding
+    // strings to replace them with.
+    $replace = array(
+      'outline posts in books' => 'administer book outlines',
+      'create book pages' => 'create book content',
+      'edit book pages' => 'edit any book content',
+      'edit own book pages' => 'edit own book content',
+      'see printer-friendly version' => 'access printer-friendly version',
+    );
+
+    // Loop over all the roles, and do the necessary transformations.
+    $query = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+    while ($role = db_fetch_object($query)) {
+      // Replace all the old permissions with the corresponding new permissions.
+      $fixed_perm = strtr($role->perm, $replace);
+      // If the user could previously create book pages, they should get the new
+      // 'add content to books' permission.
+      if (strpos($role->perm, 'create book pages') !== FALSE) {
+        $fixed_perm .= ', add content to books';
+      }
+      // Only save if the permissions have changed.
+      if ($fixed_perm != $role->perm) {
+        $ret[] = update_sql("UPDATE {permission} SET perm = '$fixed_perm' WHERE rid = $role->rid");
+      }
+    }
+
+    // Determine whether there are any existing nodes in the book hierarchy.
+    if (db_result(db_query("SELECT COUNT(*) FROM {book}"))) {
+      // Temporary table for the old book hierarchy; we'll discard revision info.
+      $schema['book_temp'] = array(
+        'fields' => array(
+          'nid'    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'parent' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')
+        ),
+        'indexes' => array(
+          'parent' => array('parent')
+        ),
+        'primary key' => array('nid'),
+      );
+
+      db_create_table($ret, 'book_temp', $schema['book_temp']);
+
+      // Insert each node in the old table into the temporary table.
+      $ret[] = update_sql("INSERT INTO {book_temp} (nid, parent, weight) SELECT b.nid, b.parent, b.weight FROM {book} b INNER JOIN {node} n on b.vid = n.vid");
+      $ret[] = update_sql("DROP TABLE {book}");
+
+      db_create_table($ret, 'book', $schema['book']);
+
+      $_SESSION['book_update_6000_orphans']['from'] = 0;
+      $_SESSION['book_update_6000'] = array();
+      $result = db_query("SELECT * from {book_temp} WHERE parent = 0");
+
+      // Collect all books - top-level nodes.
+      while ($a = db_fetch_array($result)) {
+        $_SESSION['book_update_6000'][] = $a;
+      }
+      $ret['#finished'] = FALSE;
+      return $ret;
+    }
+    else {
+      // No exising nodes in the hierarchy, so drop the table and re-create it.
+      $ret[] = update_sql("DROP TABLE {book}");
+      db_create_table($ret, 'book', $schema['book']);
+      return $ret;
+    }
+  }
+  elseif ($_SESSION['book_update_6000_orphans']) {
+    // Do the first batched part of the update - collect orphans.
+    $update_count = 400; // Update this many at a time
+
+    $result = db_query_range("SELECT * FROM {book_temp}", $_SESSION['book_update_6000_orphans']['from'], $update_count);
+    $has_rows = FALSE;
+    // Go through the next $update_count book pages and locate the orphans.
+    while ($book = db_fetch_array($result)) {
+      $has_rows = TRUE;
+      // Orphans are defined as nodes whose parent does not exist in the table.
+      if ($book['parent'] && !db_result(db_query("SELECT COUNT(*) FROM {book_temp} WHERE nid = %d", $book['parent']))) {
+        if (empty($_SESSION['book_update_6000_orphans']['book'])) {
+          // The first orphan becomes the parent for all other orphans.
+          $book['parent'] = 0;
+          $_SESSION['book_update_6000_orphans']['book'] = $book;
+          $ret[] = array('success' => TRUE, 'query' => 'Relocated orphan book pages.');
+        }
+        else {
+          // Re-assign the parent value of the book, and add it to the stack.
+          $book['parent'] = $_SESSION['book_update_6000_orphans']['book']['nid'];
+          $_SESSION['book_update_6000'][] = $book;
+        }
+      }
+    }
+    if ($has_rows) {
+      $_SESSION['book_update_6000_orphans']['from'] += $update_count;
+    }
+    else {
+      // Done with this part
+      if (!empty($_SESSION['book_update_6000_orphans']['book'])) {
+        // The orphans' parent is added last, so it will be processed first.
+        $_SESSION['book_update_6000'][] = $_SESSION['book_update_6000_orphans']['book'];
+      }
+      $_SESSION['book_update_6000_orphans'] = FALSE;
+    }
+    $ret['#finished'] = FALSE;
+    return $ret;
+  }
+  else {
+    // Do the next batched part of the update
+    $update_count = 100; // Update this many at a time
+
+    while ($update_count && $_SESSION['book_update_6000']) {
+      // Get the last node off the stack.
+      $book = array_pop($_SESSION['book_update_6000']);
+
+      // Add all of this node's children to the stack
+      $result = db_query("SELECT * FROM {book_temp} WHERE parent = %d", $book['nid']);
+      while ($a = db_fetch_array($result)) {
+        $_SESSION['book_update_6000'][] = $a;
+      }
+
+      if ($book['parent']) {
+        // If its not a top level page, get its parent's mlid.
+        $parent = db_fetch_array(db_query("SELECT b.mlid AS plid, b.bid FROM {book} b WHERE b.nid = %d", $book['parent']));
+        $book = array_merge($book, $parent);
+      }
+      else {
+        // There is not a parent - this is a new book.
+        $book['plid'] = 0;
+        $book['bid'] = $book['nid'];
+      }
+
+      $book += array(
+        'module' => 'book',
+        'link_path' => 'node/'. $book['nid'],
+        'router_path' => 'node/%',
+        'menu_name' => 'book-toc-'. $book['bid'],
+      );
+      $book = array_merge($book, db_fetch_array(db_query("SELECT title AS link_title FROM {node} WHERE nid = %d", $book['nid'])));
+
+      // Items with depth > MENU_MAX_DEPTH cannot be saved.
+      if (menu_link_save($book)) {
+        db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
+      }
+      else {
+        // The depth was greater then MENU_MAX_DEPTH, so attach it to the
+        // closest valid parent.
+        $book['plid'] = db_result(db_query("SELECT plid FROM {menu_links} WHERE mlid = %d", $book['plid']));
+        if (menu_link_save($book)) {
+          db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
+        }
+      }
+      $update_count--;
+    }
+    $ret['#finished'] = FALSE;
+  }
+
+  if (empty($_SESSION['book_update_6000'])) {
+    $ret['#finished'] = TRUE;
+    $ret[] = array('success' => TRUE, 'query' => 'Relocated existing book pages.');
+    $ret[] = update_sql("DROP TABLE {book_temp}");
+    unset($_SESSION['book_update_6000']);
+    unset($_SESSION['book_update_6000_orphans']);
+  }
+
+  return $ret;
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function book_schema() {
+  $schema['book'] = array(
+  'description' => t('Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}'),
+    'fields' => array(
+      'mlid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The book page's {menu_links}.mlid."),
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The book page's {node}.nid."),
+      ),
+      'bid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The book ID is the {book}.nid of the top-level page."),
+      ),
+    ),
+    'primary key' => array('mlid'),
+    'unique keys' => array(
+      'nid' => array('nid'),
+    ),
+    'indexes' => array(
+      'bid' => array('bid'),
+    ),
+  );
+
+  return $schema;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1083 @@
+<?php
+// $Id: book.module,v 1.454.2.2 2008/02/13 11:20:47 goba Exp $
+
+/**
+ * @file
+ * Allows users to structure the pages of a site in a hierarchy or outline.
+ */
+
+/**
+ * Implementation of hook_theme()
+ */
+function book_theme() {
+  return array(
+    'book_navigation' => array(
+      'arguments' => array('book_link' => NULL),
+      'template' => 'book-navigation',
+    ),
+    'book_export_html' => array(
+      'arguments' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
+      'template' => 'book-export-html',
+    ),
+    'book_admin_table' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'book_title_link' => array(
+      'arguments' => array('link' => NULL),
+    ),
+    'book_all_books_block' => array(
+      'arguments' => array('book_menus' => array()),
+      'template' => 'book-all-books-block',
+    ),
+    'book_node_export_html' => array(
+      'arguments' => array('node' => NULL, 'children' => NULL),
+      'template' => 'book-node-export-html',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function book_perm() {
+  return array('add content to books', 'administer book outlines', 'create new books', 'access printer-friendly version');
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function book_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+
+  if ($type == 'node' && isset($node->book)) {
+    if (!$teaser) {
+      $child_type = variable_get('book_child_type', 'book');
+      if ((user_access('add content to books') || user_access('administer book outlines')) && node_access('create', $child_type) && $node->status == 1 && $node->book['depth'] < MENU_MAX_DEPTH) {
+        $links['book_add_child'] = array(
+          'title' => t('Add child page'),
+          'href' => "node/add/". str_replace('_', '-', $child_type),
+          'query' => "parent=". $node->book['mlid'],
+        );
+      }
+      if (user_access('access printer-friendly version')) {
+        $links['book_printer'] = array(
+          'title' => t('Printer-friendly version'),
+          'href' => 'book/export/html/'. $node->nid,
+          'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.'))
+        );
+      }
+    }
+  }
+  return $links;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function book_menu() {
+  $items['admin/content/book'] = array(
+    'title' => 'Books',
+    'description' => "Manage your site's book outlines.",
+    'page callback' => 'book_admin_overview',
+    'access arguments' => array('administer book outlines'),
+    'file' => 'book.admin.inc',
+  );
+  $items['admin/content/book/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/content/book/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('book_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 8,
+    'file' => 'book.admin.inc',
+  );
+  $items['admin/content/book/%node'] = array(
+    'title' => 'Re-order book pages and change titles',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('book_admin_edit', 3),
+    'access callback' => '_book_outline_access',
+    'access arguments' => array(3),
+    'type' => MENU_CALLBACK,
+    'file' => 'book.admin.inc',
+  );
+  $items['book'] = array(
+    'title' => 'Books',
+    'page callback' => 'book_render',
+    'access arguments' => array('access content'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'book.pages.inc',
+  );
+  $items['book/export/%/%'] = array(
+    'page callback' => 'book_export',
+    'page arguments' => array(2, 3),
+    'access arguments' => array('access printer-friendly version'),
+    'type' => MENU_CALLBACK,
+    'file' => 'book.pages.inc',
+  );
+  $items['node/%node/outline'] = array(
+    'title' => 'Outline',
+    'page callback' => 'book_outline',
+    'page arguments' => array(1),
+    'access callback' => '_book_outline_access',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'book.pages.inc',
+  );
+  $items['node/%node/outline/remove'] = array(
+    'title' => 'Remove from outline',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('book_remove_form', 1),
+    'access callback' => '_book_outline_remove_access',
+    'access arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'file' => 'book.pages.inc',
+  );
+  $items['book/js/form'] = array(
+    'page callback' => 'book_form_update',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'book.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * Menu item access callback - determine if the outline tab is accessible.
+ */
+function _book_outline_access($node) {
+  return user_access('administer book outlines') && node_access('view', $node);
+}
+
+/**
+ * Menu item access callback - determine if the user can remove nodes from the outline.
+ */
+function _book_outline_remove_access($node) {
+  return isset($node->book) && ($node->book['bid'] != $node->nid) && _book_outline_access($node);
+}
+
+/**
+ * Implementation of hook_init(). Add's the book module's CSS.
+ */
+function book_init() {
+  drupal_add_css(drupal_get_path('module', 'book') .'/book.css');
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Displays the book table of contents in a block when the current page is a
+ * single-node view of a book node.
+ */
+function book_block($op = 'list', $delta = 0, $edit = array()) {
+  $block = array();
+  switch ($op) {
+    case 'list':
+      $block[0]['info'] = t('Book navigation');
+      $block[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
+      return $block;
+    case 'view':
+      $current_bid = 0;
+      if ($node = menu_get_object()) {
+        $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
+      }
+      if (variable_get('book_block_mode', 'all pages') == 'all pages') {
+        $block['subject'] = t('Book navigation');
+        $book_menus = array();
+        $pseudo_tree = array(0 => array('below' => FALSE));
+        foreach (book_get_books() as $book_id => $book) {
+          if ($book['bid'] == $current_bid) {
+            // If the current page is a node associated with a book, the menu
+            // needs to be retrieved.
+            $book_menus[$book_id] = menu_tree_output(menu_tree_all_data($node->book['menu_name'], $node->book));
+          }
+          else {
+            // Since we know we will only display a link to the top node, there
+            // is no reason to run an additional menu tree query for each book.
+            $book['in_active_trail'] = FALSE;
+            $pseudo_tree[0]['link'] = $book;
+            $book_menus[$book_id] = menu_tree_output($pseudo_tree);
+          }
+        }
+        $block['content'] = theme('book_all_books_block', $book_menus);
+      }
+      elseif ($current_bid) {
+        // Only display this block when the user is browsing a book.
+        $title = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $node->book['bid']));
+        // Only show the block if the user has view access for the top-level node.
+        if ($title) {
+          $tree = menu_tree_all_data($node->book['menu_name'], $node->book);
+          // There should only be one element at the top level.
+          $data = array_shift($tree);
+          $block['subject'] = theme('book_title_link', $data['link']);
+          $block['content'] = ($data['below']) ? menu_tree_output($data['below']) : '';
+        }
+      }
+      return $block;
+    case 'configure':
+      $options = array(
+        'all pages' => t('Show block on all pages'),
+        'book pages' => t('Show block only on book pages'),
+      );
+      $form['book_block_mode'] = array(
+        '#type' => 'radios',
+        '#title' => t('Book navigation block display'),
+        '#options' => $options,
+        '#default_value' => variable_get('book_block_mode', 'all pages'),
+        '#description' => t("If <em>Show block on all pages</em> is selected, the block will contain the automatically generated menus for all of the site's books. If <em>Show block only on book pages</em> is selected, the block will contain only the one menu corresponding to the current page's book. In this case, if the current page is not in a book, no block will be displayed. The <em>Page specific visibility settings</em> or other visibility settings can be used in addition to selectively display this block."),
+        );
+      return $form;
+    case 'save':
+      variable_set('book_block_mode', $edit['book_block_mode']);
+      break;
+  }
+}
+
+/**
+ * Generate the HTML output for a link to a book title when used as a block title.
+ *
+ * @ingroup themeable
+ */
+function theme_book_title_link($link) {
+  $link['options']['attributes']['class'] =  'book-title';
+  return l($link['title'], $link['href'], $link['options']);
+}
+
+/**
+ * Returns an array of all books.
+ *
+ * This list may be used for generating a list of all the books, or for building
+ * the options for a form select.
+ */
+function book_get_books() {
+  static $all_books;
+
+  if (!isset($all_books)) {
+    $all_books = array();
+    $result = db_query("SELECT DISTINCT(bid) FROM {book}");
+    $nids = array();
+    while ($book = db_fetch_array($result)) {
+      $nids[] = $book['bid'];
+    }
+    if ($nids) {
+      $result2 = db_query(db_rewrite_sql("SELECT n.type, n.title, b.*, ml.* FROM {book} b INNER JOIN {node} n on b.nid = n.nid INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE n.nid IN (". implode(',', $nids) .") AND n.status = 1 ORDER BY ml.weight, ml.link_title"));
+      while ($link = db_fetch_array($result2)) {
+        $link['href'] = $link['link_path'];
+        $link['options'] = unserialize($link['options']);
+        $all_books[$link['bid']] = $link;
+      }
+    }
+  }
+  return $all_books;
+}
+
+/**
+ * Implementation of hook_form_alter(). Adds the book fieldset to the node form.
+ *
+ * @see book_pick_book_submit()
+ * @see book_submit()
+ */
+function book_form_alter(&$form, $form_state, $form_id) {
+
+  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
+    // Add elements to the node form
+    $node = $form['#node'];
+
+    $access = user_access('administer book outlines');
+    if (!$access) {
+      if (user_access('add content to books') && ((!empty($node->book['mlid']) && !empty($node->nid)) || book_type_is_allowed($node->type))) {
+        // Already in the book hierarchy or this node type is allowed
+        $access = TRUE;
+      }
+    }
+
+    if ($access) {
+      _book_add_form_elements($form, $node);
+      $form['book']['pick-book'] = array(
+        '#type' => 'submit',
+        '#value' => t('Change book (update list of parents)'),
+         // Submit the node form so the parent select options get updated.
+         // This is typically only used when JS is disabled.  Since the parent options
+         // won't be changed via AJAX, a button is provided in the node form to submit
+         // the form and generate options in the parent select corresponding to the
+         // selected book.  This is similar to what happens during a node preview.
+        '#submit' => array('node_form_submit_build_node'),
+        '#weight' => 20,
+      );
+    }
+  }
+}
+
+/**
+ * Build the parent selection form element for the node form or outline tab
+ *
+ * This function is also called when generating a new set of options during the
+ * AJAX callback, so an array is returned that can be used to replace an existing
+ * form element.
+ */
+function _book_parent_select($book_link) {
+  if (variable_get('menu_override_parent_selector', FALSE)) {
+    return array();
+  }
+  // Offer a message or a drop-down to choose a different parent page.
+  $form = array(
+    '#type' => 'hidden',
+    '#value' => -1,
+    '#prefix' => '<div id="edit-book-plid-wrapper">',
+    '#suffix' => '</div>',
+  );
+
+  if ($book_link['nid'] === $book_link['bid']) {
+    // This is a book - at the top level.
+    if ($book_link['original_bid'] === $book_link['bid']) {
+      $form['#prefix'] .= '<em>'. t('This is the top-level page in this book.') .'</em>';
+    }
+    else {
+      $form['#prefix'] .= '<em>'. t('This will be the top-level page in this book.') .'</em>';
+    }
+  }
+  elseif (!$book_link['bid']) {
+    $form['#prefix'] .= '<em>'. t('No book selected.') .'</em>';
+  }
+  else {
+    $form = array(
+      '#type' => 'select',
+      '#title' => t('Parent item'),
+      '#default_value' => $book_link['plid'],
+      '#description' => t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
+      '#options' => book_toc($book_link['bid'], array($book_link['mlid']), $book_link['parent_depth_limit']),
+      '#attributes' => array('class' => 'book-title-select'),
+    );
+  }
+  return $form;
+}
+
+/**
+ * Build the common elements of the book form for the node and outline forms.
+ */
+function _book_add_form_elements(&$form, $node) {
+  // Need this for AJAX.
+  $form['#cache'] = TRUE;
+  drupal_add_js("if (Drupal.jsEnabled) { $(document).ready(function() { $('#edit-book-pick-book').css('display', 'none'); }); }", 'inline');
+
+  $form['book'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Book outline'),
+    '#weight' => 10,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#tree' => TRUE,
+    '#attributes' => array('class' => 'book-outline-form'),
+  );
+  foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) {
+    $form['book'][$key] = array(
+      '#type' => 'value',
+      '#value' => $node->book[$key],
+    );
+  }
+
+  $form['book']['plid'] = _book_parent_select($node->book);
+
+  $form['book']['weight'] = array(
+    '#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $node->book['weight'],
+    '#delta' => 15,
+    '#weight' => 5,
+    '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
+  );
+  $options = array();
+  $nid = isset($node->nid) ? $node->nid : 'new';
+
+  if (isset($node->nid) && ($nid == $node->book['original_bid']) && ($node->book['parent_depth_limit'] == 0)) {
+    // This is the top level node in a maximum depth book and thus cannot be moved.
+    $options[$node->nid] = $node->title;
+  }
+  else {
+    foreach (book_get_books() as $book) {
+      $options[$book['nid']] = $book['title'];
+    }
+  }
+
+  if (user_access('create new books') && ($nid == 'new' || ($nid != $node->book['original_bid']))) {
+    // The node can become a new book, if it is not one already.
+    $options = array($nid => '<'. t('create a new book') .'>') + $options;
+  }
+  if (!$node->book['mlid']) {
+    // The node is not currently in a the hierarchy.
+    $options = array(0 => '<'. t('none') .'>') + $options;
+  }
+
+  // Add a drop-down to select the destination book.
+  $form['book']['bid'] = array(
+    '#type' => 'select',
+    '#title' => t('Book'),
+    '#default_value' => $node->book['bid'],
+    '#options' => $options,
+    '#access' => (bool)$options,
+    '#description' => t('Your page will be a part of the selected book.'),
+    '#weight' => -5,
+    '#attributes' => array('class' => 'book-title-select'),
+    '#ahah' => array(
+      'path' => 'book/js/form',
+      'wrapper' => 'edit-book-plid-wrapper',
+      'effect' => 'slide',
+    ),
+  );
+}
+
+/**
+ * Common helper function to handles additions and updates to the book outline.
+ *
+ * Performs all additions and updates to the book outline through node addition,
+ * node editing, node deletion, or the outline tab.
+ */
+function _book_update_outline(&$node) {
+  if (empty($node->book['bid'])) {
+    return FALSE;
+  }
+  $new = empty($node->book['mlid']);
+
+  $node->book['link_path'] = 'node/'. $node->nid;
+  $node->book['link_title'] = $node->title;
+  $node->book['parent_mismatch'] = FALSE; // The normal case.
+
+  if ($node->book['bid'] == $node->nid) {
+    $node->book['plid'] = 0;
+    $node->book['menu_name'] = book_menu_name($node->nid);
+  }
+  else {
+    // Check in case the parent is not is this book; the book takes precedence.
+    if (!empty($node->book['plid'])) {
+      $parent = db_fetch_array(db_query("SELECT * FROM {book} WHERE mlid = %d", $node->book['plid']));
+    }
+    if (empty($node->book['plid']) || !$parent || $parent['bid'] != $node->book['bid']) {
+      $node->book['plid'] = db_result(db_query("SELECT mlid FROM {book} WHERE nid = %d", $node->book['bid']));
+      $node->book['parent_mismatch'] = TRUE; // Likely when JS is disabled.
+    }
+  }
+  if (menu_link_save($node->book)) {
+    if ($new) {
+      // Insert new.
+      db_query("INSERT INTO {book} (nid, mlid, bid) VALUES (%d, %d, %d)", $node->nid, $node->book['mlid'], $node->book['bid']);
+    }
+    else {
+      if ($node->book['bid'] != db_result(db_query("SELECT bid FROM {book} WHERE nid = %d", $node->nid))) {
+        // Update the bid for this page and all children.
+        book_update_bid($node->book);
+      }
+    }
+    return TRUE;
+  }
+  // Failed to save the menu link
+  return FALSE;
+}
+
+/**
+ * Update the bid for a page and its children when it is moved to a new book.
+ *
+ * @param $book_link
+ *   A fully loaded menu link that is part of the book hierarchy.
+ */
+function book_update_bid($book_link) {
+
+  for ($i = 1; $i <= MENU_MAX_DEPTH && $book_link["p$i"]; $i++) {
+    $match[] = "p$i = %d";
+    $args[] = $book_link["p$i"];
+  }
+  $result = db_query("SELECT mlid FROM {menu_links} WHERE ". implode(' AND ', $match), $args);
+
+  $mlids = array();
+  while ($a = db_fetch_array($result)) {
+    $mlids[] = $a['mlid'];
+  }
+  if ($mlids) {
+    db_query("UPDATE {book} SET bid = %d WHERE mlid IN (". implode(',', $mlids) .")", $book_link['bid']);
+  }
+}
+
+/**
+ * Get the book menu tree for a page, and return it as a linear array.
+ *
+ * @param $book_link
+ *   A fully loaded menu link that is part of the book hierarchy.
+ * @return
+ *   A linear array of menu links in the order that the links are shown in the
+ *   menu, so the previous and next pages are the elements before and after the
+ *   element corresponding to $node.  The children of $node (if any) will come
+ *   immediately after it in the array.
+ */
+function book_get_flat_menu($book_link) {
+  static $flat = array();
+
+  if (!isset($flat[$book_link['mlid']])) {
+    // Call menu_tree_full_data() to take advantage of the menu system's caching.
+    $tree = menu_tree_all_data($book_link['menu_name'], $book_link);
+    $flat[$book_link['mlid']] = array();
+    _book_flatten_menu($tree, $flat[$book_link['mlid']]);
+  }
+  return $flat[$book_link['mlid']];
+}
+
+/**
+ * Recursive helper function for book_get_flat_menu().
+ */
+function _book_flatten_menu($tree, &$flat) {
+  foreach ($tree as $data) {
+    if (!$data['link']['hidden']) {
+      $flat[$data['link']['mlid']] = $data['link'];
+      if ($data['below']) {
+        _book_flatten_menu($data['below'], $flat);
+      }
+    }
+  }
+}
+
+/**
+ * Fetches the menu link for the previous page of the book.
+ */
+function book_prev($book_link) {
+  // If the parent is zero, we are at the start of a book.
+  if ($book_link['plid'] == 0) {
+    return NULL;
+  }
+  $flat = book_get_flat_menu($book_link);
+  // Assigning the array to $flat resets the array pointer for use with each().
+  $curr = NULL;
+  do {
+    $prev = $curr;
+    list($key, $curr) = each($flat);
+  } while ($key && $key != $book_link['mlid']);
+
+  if ($key == $book_link['mlid']) {
+    // The previous page in the book may be a child of the previous visible link.
+    if ($prev['depth'] == $book_link['depth'] && $prev['has_children']) {
+      // The subtree will have only one link at the top level - get its data.
+      $data = array_shift(book_menu_subtree_data($prev));
+      // The link of interest is the last child - iterate to find the deepest one.
+      while ($data['below']) {
+        $data = end($data['below']);
+      }
+      return $data['link'];
+    }
+    else {
+      return $prev;
+    }
+  }
+}
+
+/**
+ * Fetches the menu link for the next page of the book.
+ */
+function book_next($book_link) {
+  $flat = book_get_flat_menu($book_link);
+  // Assigning the array to $flat resets the array pointer for use with each().
+  do {
+    list($key, $curr) = each($flat);
+  } while ($key && $key != $book_link['mlid']);
+  if ($key == $book_link['mlid']) {
+    return current($flat);
+  }
+}
+
+/**
+ * Format the menu links for the child pages of the current page.
+ */
+function book_children($book_link) {
+  $flat = book_get_flat_menu($book_link);
+
+  $children = array();
+
+  if ($book_link['has_children']) {
+    // Walk through the array until we find the current page.
+    do {
+      $link = array_shift($flat);
+    } while ($link && ($link['mlid'] != $book_link['mlid']));
+    // Continue though the array and collect the links whose parent is this page.
+    while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) {
+      $data['link'] = $link;
+      $data['below'] = '';
+      $children[] = $data;
+    }
+  }
+  return $children ? menu_tree_output($children) : '';
+}
+
+/**
+ * Generate the corresponding menu name from a book ID.
+ */
+function book_menu_name($bid) {
+  return 'book-toc-'. $bid;
+}
+
+/**
+ * Build an active trail to show in the breadcrumb.
+ */
+function book_build_active_trail($book_link) {
+  static $trail;
+
+  if (!isset($trail)) {
+    $trail = array();
+    $trail[] = array('title' => t('Home'), 'href' => '<front>', 'localized_options' => array());
+
+    $tree = menu_tree_all_data($book_link['menu_name'], $book_link);
+    $curr = array_shift($tree);
+
+    while ($curr) {
+      if ($curr['link']['href'] == $book_link['href']) {
+        $trail[] = $curr['link'];
+        $curr = FALSE;
+      }
+      else {
+        if ($curr['below'] && $curr['link']['in_active_trail']) {
+          $trail[] = $curr['link'];
+          $tree = $curr['below'];
+        }
+        $curr = array_shift($tree);
+      }
+    }
+  }
+  return $trail;
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * Appends book navigation to all nodes in the book, and handles book outline
+ * insertions and updates via the node form.
+ */
+function book_nodeapi(&$node, $op, $teaser, $page) {
+  switch ($op) {
+    case 'load':
+      // Note - we cannot use book_link_load() because it will call node_load()
+      $info['book'] = db_fetch_array(db_query('SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid = %d', $node->nid));
+      if ($info['book']) {
+        $info['book']['href'] = $info['book']['link_path'];
+        $info['book']['title'] = $info['book']['link_title'];
+        $info['book']['options'] = unserialize($info['book']['options']);
+        return $info;
+      }
+      break;
+    case 'view':
+    if (!$teaser) {
+        if (!empty($node->book['bid']) && $node->build_mode == NODE_BUILD_NORMAL) {
+
+          $node->content['book_navigation'] = array(
+            '#value' => theme('book_navigation', $node->book),
+            '#weight' => 100,
+          );
+
+          if ($page) {
+            menu_set_active_trail(book_build_active_trail($node->book));
+            menu_set_active_menu_name($node->book['menu_name']);
+          }
+        }
+      }
+      break;
+    case 'presave':
+      // Always save a revision for non-administrators.
+      if (!empty($node->book['bid']) && !user_access('administer nodes')) {
+        $node->revision = 1;
+      }
+      // Make sure a new node gets a new menu link.
+      if (empty($node->nid)) {
+        $node->book['mlid'] = NULL;
+      }
+      break;
+    case 'insert':
+    case 'update':
+      if (!empty($node->book['bid'])) {
+        if ($node->book['bid'] == 'new') {
+          // New nodes that are their own book.
+          $node->book['bid'] = $node->nid;
+        }
+        $node->book['nid'] = $node->nid;
+        $node->book['menu_name'] = book_menu_name($node->book['bid']);
+        _book_update_outline($node);
+      }
+      break;
+    case 'delete':
+      if (!empty($node->book['bid'])) {
+        if ($node->nid == $node->book['bid']) {
+          // Handle deletion of a top-level post.
+          $result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = %d", $node->book['mlid']);
+          while ($child = db_fetch_array($result)) {
+            $child_node = node_load($child['nid']);
+            $child_node->book['bid'] = $child_node->nid;
+            _book_update_outline($child_node);
+          }
+        }
+        menu_link_delete($node->book['mlid']);
+        db_query('DELETE FROM {book} WHERE mlid = %d', $node->book['mlid']);
+      }
+      break;
+    case 'prepare':
+      // Prepare defaults for the add/edit form.
+      if (empty($node->book) && (user_access('add content to books') || user_access('administer book outlines'))) {
+        $node->book = array();
+        if (empty($node->nid) && isset($_GET['parent']) && is_numeric($_GET['parent'])) {
+          // Handle "Add child page" links:
+          $parent = book_link_load($_GET['parent']);
+          if ($parent && $parent['access']) {
+            $node->book['bid'] = $parent['bid'];
+            $node->book['plid'] = $parent['mlid'];
+            $node->book['menu_name'] = $parent['menu_name'];
+          }
+        }
+        // Set defaults.
+        $node->book += _book_link_defaults(!empty($node->nid) ? $node->nid : 'new');
+      }
+      else {
+        if (isset($node->book['bid']) && !isset($node->book['original_bid'])) {
+          $node->book['original_bid'] = $node->book['bid'];
+        }
+      }
+      // Find the depth limit for the parent select.
+      if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) {
+        $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book);
+      }
+      break;
+  }
+}
+
+/**
+ * Find the depth limit for items in the parent select.
+ */
+function _book_parent_depth_limit($book_link) {
+  return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? menu_link_children_relative_depth($book_link) : 0);
+}
+
+/**
+ * Form altering function for the confirm form for a single node deletion.
+ */
+function book_form_node_delete_confirm_alter(&$form, $form_state) {
+
+  $node = node_load($form['nid']['#value']);
+
+  if (isset($node->book) && $node->book['has_children']) {
+    $form['book_warning'] = array(
+      '#value' => '<p>'. t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->title)) .'</p>',
+      '#weight' => -10,
+    );
+  }
+}
+
+/**
+ * Return an array with default values for a book link.
+ */
+function _book_link_defaults($nid) {
+  return array('original_bid' => 0, 'menu_name' => '', 'nid' => $nid, 'bid' => 0, 'router_path' => 'node/%', 'plid' => 0, 'mlid' => 0, 'has_children' => 0, 'weight' => 0, 'module' => 'book', 'options' => array());
+}
+
+/**
+ * Process variables for book-navigation.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $book_link
+ *
+ * @see book-navigation.tpl.php
+ */
+function template_preprocess_book_navigation(&$variables) {
+  $book_link = $variables['book_link'];
+
+  // Provide extra variables for themers. Not needed by default.
+  $variables['book_id'] = $book_link['bid'];
+  $variables['book_title'] = check_plain($book_link['link_title']);
+  $variables['book_url'] = 'node/'. $book_link['bid'];
+  $variables['current_depth'] = $book_link['depth'];
+
+  $variables['tree'] = '';
+  if ($book_link['mlid']) {
+    $variables['tree'] = book_children($book_link);
+
+    if ($prev = book_prev($book_link)) {
+      $prev_href = url($prev['href']);
+      drupal_add_link(array('rel' => 'prev', 'href' => $prev_href));
+      $variables['prev_url'] = $prev_href;
+      $variables['prev_title'] = check_plain($prev['title']);
+    }
+    if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) {
+      $parent_href = url($parent['href']);
+      drupal_add_link(array('rel' => 'up', 'href' => $parent_href));
+      $variables['parent_url'] = $parent_href;
+      $variables['parent_title'] = check_plain($parent['title']);
+    }
+    if ($next = book_next($book_link)) {
+      $next_href = url($next['href']);
+      drupal_add_link(array('rel' => 'next', 'href' => $next_href));
+      $variables['next_url'] = $next_href;
+      $variables['next_title'] = check_plain($next['title']);
+    }
+  }
+
+  $variables['has_links'] = FALSE;
+  // Link variables to filter for values and set state of the flag variable.
+  $links = array('prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title');
+  foreach ($links as $link) {
+    if (isset($variables[$link])) {
+      // Flag when there is a value.
+      $variables['has_links'] = TRUE;
+    }
+    else {
+      // Set empty to prevent notices.
+      $variables[$link] = '';
+    }
+  }
+}
+
+/**
+ * A recursive helper function for book_toc().
+ */
+function _book_toc_recurse($tree, $indent, &$toc, $exclude, $depth_limit) {
+  foreach ($tree as $data) {
+    if ($data['link']['depth'] > $depth_limit) {
+      // Don't iterate through any links on this level.
+      break;
+    }
+    if (!in_array($data['link']['mlid'], $exclude)) {
+      $toc[$data['link']['mlid']] = $indent .' '. truncate_utf8($data['link']['title'], 30, TRUE, TRUE);
+      if ($data['below']) {
+        _book_toc_recurse($data['below'], $indent .'--', $toc, $exclude, $depth_limit);
+      }
+    }
+  }
+}
+
+/**
+ * Returns an array of book pages in table of contents order.
+ *
+ * @param $bid
+ *   The ID of the book whose pages are to be listed.
+ * @param $exclude
+ *   Optional array of mlid values.  Any link whose mlid is in this array
+ *   will be excluded (along with its children).
+ * @param $depth_limit
+ *   Any link deeper than this value will be excluded (along with its children).
+ * @return
+ *   An array of mlid, title pairs for use as options for selecting a book page.
+ */
+function book_toc($bid, $exclude = array(), $depth_limit) {
+
+  $tree = menu_tree_all_data(book_menu_name($bid));
+  $toc = array();
+  _book_toc_recurse($tree, '', $toc, $exclude, $depth_limit);
+
+  return $toc;
+}
+
+/**
+ * Process variables for book-export-html.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $title
+ * - $contents
+ * - $depth
+ *
+ * @see book-export-html.tpl.php
+ */
+function template_preprocess_book_export_html(&$variables) {
+  global $base_url, $language;
+
+  $variables['title'] = check_plain($variables['title']);
+  $variables['base_url'] = $base_url;
+  $variables['language'] = $language;
+  $variables['language_rtl'] = (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) ? TRUE : FALSE;
+  $variables['head'] = drupal_get_html_head();
+}
+
+/**
+ * Traverse the book tree to build printable or exportable output.
+ *
+ * During the traversal, the $visit_func() callback is applied to each
+ * node, and is called recursively for each child of the node (in weight,
+ * title order).
+ *
+ * @param $tree
+ *   A subtree of the book menu hierarchy, rooted at the current page.
+ * @param $visit_func
+ *   A function callback to be called upon visiting a node in the tree.
+ * @return
+ *   The output generated in visiting each node.
+ */
+function book_export_traverse($tree, $visit_func) {
+  $output = '';
+
+  foreach ($tree as $data) {
+    // Note- access checking is already performed when building the tree.
+    if ($node = node_load($data['link']['nid'], FALSE)) {
+      $children = '';
+      if ($data['below']) {
+        $children = book_export_traverse($data['below'], $visit_func);
+      }
+
+      if (function_exists($visit_func)) {
+        $output .= call_user_func($visit_func, $node, $children);
+      }
+      else {
+        // Use the default function.
+        $output .= book_node_export($node, $children);
+      }
+    }
+  }
+  return $output;
+}
+
+/**
+ * Generates printer-friendly HTML for a node.
+ *
+ * @see book_export_traverse()
+ *
+ * @param $node
+ *   The node to generate output for.
+ * @param $children
+ *   All the rendered child nodes within the current node.
+ * @return
+ *   The HTML generated for the given node.
+ */
+function book_node_export($node, $children = '') {
+
+  $node->build_mode = NODE_BUILD_PRINT;
+  $node = node_build_content($node, FALSE, FALSE);
+  $node->body = drupal_render($node->content);
+
+  return theme('book_node_export_html', $node, $children);
+}
+
+/**
+ * Process variables for book-node-export-html.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $node
+ * - $children
+ *
+ * @see book-node-export-html.tpl.php
+ */
+function template_preprocess_book_node_export_html(&$variables) {
+  $variables['depth'] = $variables['node']->book['depth'];
+  $variables['title'] = check_plain($variables['node']->title);
+  $variables['content'] = $variables['node']->body;
+}
+
+/**
+ * Determine if a given node type is in the list of types allowed for books.
+ */
+function book_type_is_allowed($type) {
+  return in_array($type, variable_get('book_allowed_types', array('book')));
+}
+
+/**
+ * Implementation of hook_node_type().
+ *
+ * Update book module's persistent variables if the machine-readable name of a
+ * node type is changed.
+ */
+function book_node_type($op, $type) {
+
+  switch ($op) {
+    case 'update':
+      if (!empty($type->old_type) && $type->old_type != $type->type) {
+        // Update the list of node types that are allowed to be added to books.
+        $allowed_types = variable_get('book_allowed_types', array('book'));
+        $key = array_search($type->old_type, $allowed_types);
+        if ($key !== FALSE) {
+          $allowed_types[$type->type] = $allowed_types[$key] ? $type->type : 0;
+          unset($allowed_types[$key]);
+          variable_set('book_allowed_types', $allowed_types);
+        }
+        // Update the setting for the "Add child page" link.
+        if (variable_get('book_child_type', 'book') == $type->old_type) {
+          variable_set('book_child_type', $type->type);
+        }
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function book_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#book':
+      $output = '<p>'. t('The book module is suited for creating structured, multi-page hypertexts such as site resource guides, manuals, and Frequently Asked Questions (FAQs). It permits a document to have chapters, sections, subsections, etc. Authors with suitable permissions can add pages to a collaborative book, placing them into the existing document by adding them to a table of contents menu.') .'</p>';
+      $output .= '<p>'. t('Pages in the book hierarchy have navigation elements at the bottom of the page for moving through the text. These links lead to the previous and next pages in the book, and to the level above the current page in the book\'s structure. More comprehensive navigation may be provided by enabling the <em>book navigation block</em> on the <a href="@admin-block">blocks administration page</a>.', array('@admin-block' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('Users can select the <em>printer-friendly version</em> link visible at the bottom of a book page to generate a printer-friendly display of the page and all of its subsections. ') .'</p>';
+      $output .= '<p>'. t("Users with the <em>administer book outlines</em> permission can add a post of any content type to a book, by selecting the appropriate book while editing the post or by using the interface available on the post's <em>outline</em> tab.") .'</p>';
+      $output .= '<p>'. t('Administrators can view a list of all books on the <a href="@admin-node-book">book administration page</a>. The <em>Outline</em> page for each book allows section titles to be edited or rearranged.', array('@admin-node-book' => url('admin/content/book'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@book">Book module</a>.', array('@book' => 'http://drupal.org/handbook/modules/book/')) .'</p>';
+      return $output;
+    case 'admin/content/book':
+      return '<p>'. t('The book module offers a means to organize a collection of related posts, collectively known as a book. When viewed, these posts automatically display links to adjacent book pages, providing a simple navigation system for creating and reviewing structured content.') .'</p>';
+    case 'node/%/outline':
+      return '<p>'. t('The outline feature allows you to include posts in the <a href="@book">book hierarchy</a>, as well as move them within the hierarchy or to <a href="@book-admin">reorder an entire book</a>.', array('@book' => url('book'), '@book-admin' => url('admin/content/book'))) .'</p>';
+  }
+}
+
+/**
+ * Like menu_link_load(), but adds additional data from the {book} table.
+ *
+ * Do not call when loading a node, since this function may call node_load().
+ */
+function book_link_load($mlid) {
+  if ($item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
+    _menu_link_translate($item);
+    return $item;
+  }
+  return FALSE;
+}
+
+/**
+ * Get the data representing a subtree of the book hierarchy.
+ *
+ * The root of the subtree will be the link passed as a parameter, so the
+ * returned tree will contain this item and all its descendents in the menu tree.
+ *
+ * @param $item
+ *   A fully loaded menu link.
+ * @return
+ *   An subtree of menu links in an array, in the order they should be rendered.
+ */
+function book_menu_subtree_data($item) {
+  static $tree = array();
+
+  $cid = 'links:'. $item['menu_name'] .':subtree:'. $item['mlid'];
+
+  if (!isset($tree[$cid])) {
+    $cache = cache_get($cid, 'cache_menu');
+    if ($cache && isset($cache->data)) {
+      $data = $cache->data;
+    }
+    else {
+      $match = array("menu_name = '%s'");
+      $args = array($item['menu_name']);
+      $i = 1;
+      while ($i <= MENU_MAX_DEPTH && $item["p$i"]) {
+        $match[] = "p$i = %d";
+        $args[] = $item["p$i"];
+        $i++;
+      }
+      $sql = "
+        SELECT b.*, m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
+        FROM {menu_links} ml INNER JOIN {menu_router} m ON m.path = ml.router_path
+        INNER JOIN {book} b ON ml.mlid = b.mlid
+        WHERE ". implode(' AND ', $match) ."
+        ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
+
+      $data['tree'] = menu_tree_data(db_query($sql, $args), array(), $item['depth']);
+      $data['node_links'] = array();
+      menu_tree_collect_node_links($data['tree'], $data['node_links']);
+      // Cache the data.
+      cache_set($cid, $data, 'cache_menu');
+    }
+    // Check access for the current user to each item in the tree.
+    menu_tree_check_access($data['tree'], $data['node_links']);
+    $tree[$cid] = $data['tree'];
+  }
+
+  return $tree[$cid];
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/book/book.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,263 @@
+<?php
+// $Id: book.pages.inc,v 1.5 2007/12/22 23:24:24 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the book module.
+ */
+
+/**
+ * Menu callback; prints a listing of all books.
+ */
+function book_render() {
+  $book_list = array();
+  foreach (book_get_books() as $book) {
+    $book_list[] = l($book['title'], $book['href'], $book['options']);
+  }
+
+  return theme('item_list', $book_list);
+}
+
+/**
+ * Menu callback; Generates various representation of a book page and its children.
+ *
+ * The function delegates the generation of output to helper functions.
+ * The function name is derived by prepending 'book_export_' to the
+ * given output type. So, e.g., a type of 'html' results in a call to
+ * the function book_export_html().
+ *
+ * @param $type
+ *   A string encoding the type of output requested. The following
+ *   types are currently supported in book module:
+ *
+ *   - html: HTML (printer friendly output)
+ *
+ *   Other types may be supported in contributed modules.
+ * @param $nid
+ *   An integer representing the node id (nid) of the node to export
+ * @return
+ *   A string representing the node and its children in the book hierarchy
+ *   in a format determined by the $type parameter.
+ */
+function book_export($type, $nid) {
+
+  $type = drupal_strtolower($type);
+
+  $export_function = 'book_export_'. $type;
+
+  if (function_exists($export_function)) {
+    print call_user_func($export_function, $nid);
+  }
+  else {
+    drupal_set_message(t('Unknown export format.'));
+    drupal_not_found();
+  }
+}
+
+/**
+ * This function is called by book_export() to generate HTML for export.
+ *
+ * The given node is /embedded to its absolute depth in a top level
+ * section/. For example, a child node with depth 2 in the hierarchy
+ * is contained in (otherwise empty) &lt;div&gt; elements
+ * corresponding to depth 0 and depth 1. This is intended to support
+ * WYSIWYG output - e.g., level 3 sections always look like level 3
+ * sections, no matter their depth relative to the node selected to be
+ * exported as printer-friendly HTML.
+ *
+ * @param $nid
+ *   An integer representing the node id (nid) of the node to export.
+ * @return
+ *   A string containing HTML representing the node and its children in
+ *   the book hierarchy.
+ */
+function book_export_html($nid) {
+  if (user_access('access printer-friendly version')) {
+    $export_data = array();
+    $node = node_load($nid);
+    if (isset($node->book)) {
+      $tree = book_menu_subtree_data($node->book);
+      $contents = book_export_traverse($tree, 'book_node_export');
+    }
+    return theme('book_export_html', $node->title, $contents, $node->book['depth']);
+  }
+  else {
+    drupal_access_denied();
+  }
+}
+
+/**
+ * Menu callback; show the outline form for a single node.
+ */
+function book_outline($node) {
+  drupal_set_title(check_plain($node->title));
+  return drupal_get_form('book_outline_form', $node);
+}
+
+/**
+ * Build the form to handle all book outline operations via the outline tab.
+ *
+ * @see book_outline_form_submit()
+ * @see book_remove_button_submit()
+ *
+ * @ingroup forms
+ */
+function book_outline_form(&$form_state, $node) {
+
+  if (!isset($node->book)) {
+    // The node is not part of any book yet - set default options.
+    $node->book = _book_link_defaults($node->nid);
+  }
+  else {
+    $node->book['original_bid'] = $node->book['bid'];
+  }
+  // Find the depth limit for the parent select.
+  if (!isset($node->book['parent_depth_limit'])) {
+    $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book);
+  }
+  $form['#node'] = $node;
+  $form['#id'] = 'book-outline';
+  _book_add_form_elements($form, $node);
+
+  $form['book']['#collapsible'] = FALSE;
+
+  $form['update'] = array(
+    '#type' => 'submit',
+    '#value' => $node->book['original_bid'] ? t('Update book outline') : t('Add to book outline'),
+    '#weight' => 15,
+  );
+
+  $form['remove'] = array(
+    '#type' => 'submit',
+    '#value' => t('Remove from book outline'),
+    '#access' => $node->nid != $node->book['bid'] && $node->book['bid'],
+    '#weight' => 20,
+    '#submit' => array('book_remove_button_submit'),
+  );
+
+  return $form;
+}
+
+/**
+ * Button submit function to redirect to removal confirm form.
+ *
+ * @see book_outline_form()
+ */
+function book_remove_button_submit($form, &$form_state) {
+  $form_state['redirect'] = 'node/'. $form['#node']->nid .'/outline/remove';
+}
+
+/**
+ * Handles book outline form submissions from the outline tab.
+ *
+ * @see book_outline_form()
+ */
+function book_outline_form_submit($form, &$form_state) {
+  $node = $form['#node'];
+  $form_state['redirect'] = "node/". $node->nid;
+  $book_link = $form_state['values']['book'];
+  if (!$book_link['bid']) {
+    drupal_set_message(t('No changes were made'));
+    return;
+  }
+
+  $book_link['menu_name'] = book_menu_name($book_link['bid']);
+  $node->book = $book_link;
+  if (_book_update_outline($node)) {
+    if ($node->book['parent_mismatch']) {
+      // This will usually only happen when JS is disabled.
+      drupal_set_message(t('The post has been added to the selected book. You may now position it relative to other pages.'));
+      $form_state['redirect'] = "node/". $node->nid ."/outline";
+    }
+    else {
+      drupal_set_message(t('The book outline has been updated.'));
+    }
+  }
+  else {
+    drupal_set_message(t('There was an error adding the post to the book.'), 'error');
+  }
+}
+
+/**
+ * Menu callback; builds a form to confirm removal of a node from the book.
+ *
+ * @see book_remove_form_submit()
+ *
+ * @ingroup forms
+ */
+function book_remove_form(&$form_state, $node) {
+  $form['#node'] = $node;
+  $title = array('%title' => $node->title);
+
+  if ($node->book['has_children']) {
+    $description = t('%title has associated child pages, which will be relocated automatically to maintain their connection to the book. To recreate the hierarchy (as it was before removing this page), %title may be added again using the Outline tab, and each of its former child pages will need to be relocated manually.', $title);
+  }
+  else {
+    $description = t('%title may be added to hierarchy again using the Outline tab.', $title);
+  }
+
+  return confirm_form($form, t('Are you sure you want to remove %title from the book hierarchy?', $title), 'node/'. $node->nid, $description, t('Remove'));
+}
+
+/**
+ * Confirm form submit function to remove a node from the book.
+ *
+ * @see book_remove_form()
+ */
+function book_remove_form_submit($form, &$form_state) {
+  $node = $form['#node'];
+  if ($node->nid != $node->book['bid']) {
+    // Only allowed when this is not a book (top-level page).
+    menu_link_delete($node->book['mlid']);
+    db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+    drupal_set_message(t('The post has been removed from the book.'));
+  }
+  $form_state['redirect'] = 'node/'. $node->nid;
+}
+
+/**
+ * AJAX callback to replace the book parent select options.
+ *
+ * This function is called when the selected book is changed.  It updates the
+ * cached form (either the node form or the book outline form) and returns
+ * rendered output to be used to replace the select containing the possible
+ * parent pages in the newly selected book.
+ *
+ * @param $build_id
+ *   The form's build_id.
+ * @param $bid
+ *   A bid from from among those in the form's book select.
+ * @return
+ *   Prints the replacement HTML in JSON format.
+ */
+function book_form_update() {
+  $cid = 'form_'. $_POST['form_build_id'];
+  $bid = $_POST['book']['bid'];
+  $cache = cache_get($cid, 'cache_form');
+  if ($cache) {
+    $form = $cache->data;
+
+    // Validate the bid.
+    if (isset($form['book']['bid']['#options'][$bid])) {
+      $book_link = $form['#node']->book;
+      $book_link['bid'] = $bid;
+      // Get the new options and update the cache.
+      $form['book']['plid'] = _book_parent_select($book_link);
+      cache_set($cid, $form, 'cache_form', $cache->expire);
+
+      // Build and render the new select element, then return it in JSON format.
+      $form_state = array();
+      $form['#post'] = array();
+      $form = form_builder($form['form_id']['#value'] , $form, $form_state);
+      $output = drupal_render($form['book']['plid']);
+      drupal_json(array('status' => TRUE, 'data' => $output));
+    }
+    else {
+      drupal_json(array('status' => FALSE, 'data' => ''));
+    }
+  }
+  else {
+    drupal_json(array('status' => FALSE, 'data' => ''));
+  }
+  exit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,44 @@
+/* $Id: color-rtl.css,v 1.2 2007/11/27 12:09:25 goba Exp $ */
+
+#placeholder {
+  left: 0;
+  right: auto;
+}
+
+/* Palette */
+.color-form .form-item {
+  padding-left: 0;
+  padding-right: 1em;
+}
+.color-form label {
+  float: right;
+  clear: right;
+}
+.color-form .form-text, .color-form .form-select {
+  float: right;
+}
+.color-form .form-text {
+  margin-right: 0;
+  margin-left: 5px;
+}
+
+#palette .hook {
+  float: right;
+}
+#palette .down, #palette .up, #palette .both {
+  background: url(images/hook-rtl.png) no-repeat 0 0;
+}
+#palette .up {
+  background-position: 0 -27px;
+}
+#palette .both {
+  background-position: 0 -54px;
+}
+
+#palette .lock {
+  float: right;
+  right: -10px;
+}
+html.js #preview {
+  float: right;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,79 @@
+/* $Id: color.css,v 1.4 2007/05/27 17:57:48 goba Exp $ */
+
+/* Farbtastic placement */
+.color-form {
+  max-width: 50em;
+  position: relative;
+}
+#placeholder {
+  position: absolute;
+  top: 0;
+  right: 0; /* LTR */
+}
+
+/* Palette */
+.color-form .form-item {
+  height: 2em;
+  line-height: 2em;
+  padding-left: 1em; /* LTR */
+  margin: 0.5em 0;
+}
+.color-form label {
+  float: left; /* LTR */
+  clear: left; /* LTR */
+  width: 10em;
+}
+.color-form .form-text, .color-form .form-select {
+  float: left; /* LTR */
+}
+.color-form .form-text {
+  text-align: center;
+  margin-right: 5px; /* LTR */
+  cursor: pointer;
+}
+
+#palette .hook {
+  float: left; /* LTR */
+  margin-top: 3px;
+  width: 16px;
+  height: 16px;
+}
+#palette .down, #palette .up, #palette .both {
+  background: url(images/hook.png) no-repeat 100% 0; /* LTR */
+}
+#palette .up {
+  background-position: 100% -27px; /* LTR */
+}
+#palette .both {
+  background-position: 100% -54px; /* LTR */
+}
+
+#palette .lock {
+  float: left; /* LTR */
+  position: relative;
+  top: -1.4em;
+  left: -10px; /* LTR */
+  width: 20px;
+  height: 25px;
+  background: url(images/lock.png) no-repeat 50% 2px;
+  cursor: pointer;
+}
+#palette .unlocked {
+  background-position: 50% -22px;
+}
+#palette .form-item {
+  width: 20em;
+}
+#palette .item-selected {
+  background: #eee;
+}
+
+/* Preview */
+#preview {
+  display: none;
+}
+html.js #preview {
+  display: block;
+  position: relative;
+  float: left; /* LTR */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: color.info,v 1.3 2007/06/08 05:50:54 dries Exp $
+name = Color
+description = Allows the user to change the color scheme of certain themes.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,35 @@
+<?php
+/* $Id: color.install,v 1.2 2006/12/05 05:49:50 dries Exp $ */
+
+function color_requirements($phase) {
+  $requirements = array();
+
+  if ($phase == 'runtime') {
+    // Check GD library
+    if (function_exists('imagegd2')) {
+      $info = gd_info();
+      $requirements['gd'] = array(
+        'value' => $info['GD Version'],
+      );
+
+      // Check PNG support
+      if (function_exists('imagecreatefrompng')) {
+        $requirements['gd']['severity'] = REQUIREMENT_OK;
+      }
+      else {
+        $requirements['gd']['severity'] = REQUIREMENT_ERROR;
+        $requirements['gd']['description'] = t('The GD library for PHP is enabled, but was compiled without PNG support. Please check the <a href="@url">PHP image documentation</a> for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php'));
+      }
+    }
+    else {
+      $requirements['gd'] = array(
+        'value' => t('Not installed'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('The GD library for PHP is missing or outdated. Please check the <a href="@url">PHP image documentation</a> for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php')),
+      );
+    }
+    $requirements['gd']['title'] = t('GD library');
+  }
+
+  return $requirements;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,251 @@
+// $Id: color.js,v 1.6 2007/09/12 18:29:32 goba Exp $
+
+Drupal.behaviors.color = function (context) {
+  // This behavior attaches by ID, so is only valid once on a page.
+  if ($('#color_scheme_form .color-form.color-processed').size()) {
+    return;
+  }
+  var form = $('#color_scheme_form .color-form', context);
+  var inputs = [];
+  var hooks = [];
+  var locks = [];
+  var focused = null;
+
+  // Add Farbtastic
+  $(form).prepend('<div id="placeholder"></div>').addClass('color-processed');
+  var farb = $.farbtastic('#placeholder');
+
+  // Decode reference colors to HSL
+  var reference = Drupal.settings.color.reference;
+  for (i in reference) {
+    reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
+  }
+
+  // Build preview
+  $('#preview:not(.color-processed)')
+    .append('<div id="gradient"></div>')
+    .addClass('color-processed');
+  var gradient = $('#preview #gradient');
+  var h = parseInt(gradient.css('height')) / 10;
+  for (i = 0; i < h; ++i) {
+    gradient.append('<div class="gradient-line"></div>');
+  }
+
+  // Fix preview background in IE6
+  if (navigator.appVersion.match(/MSIE [0-6]\./)) {
+    var e = $('#preview #img')[0];
+    var image = e.currentStyle.backgroundImage;
+    e.style.backgroundImage = 'none';
+    e.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image.substring(5, image.length - 2) + "')";
+  }
+
+  // Set up colorscheme selector
+  $('#edit-scheme', form).change(function () {
+    var colors = this.options[this.selectedIndex].value;
+    if (colors != '') {
+      colors = colors.split(',');
+      for (i in colors) {
+        callback(inputs[i], colors[i], false, true);
+      }
+      preview();
+    }
+  });
+
+  /**
+   * Render the preview.
+   */
+  function preview() {
+    // Solid background
+    $('#preview', form).css('backgroundColor', inputs[0].value);
+
+    // Text preview
+    $('#text', form).css('color', inputs[4].value);
+    $('#text a, #text h2', form).css('color', inputs[1].value);
+
+    // Set up gradient
+    var top = farb.unpack(inputs[2].value);
+    var bottom = farb.unpack(inputs[3].value);
+    if (top && bottom) {
+      var delta = [];
+      for (i in top) {
+        delta[i] = (bottom[i] - top[i]) / h;
+      }
+      var accum = top;
+
+      // Render gradient lines
+      $('#gradient > div', form).each(function () {
+        for (i in accum) {
+          accum[i] += delta[i];
+        }
+        this.style.backgroundColor = farb.pack(accum);
+      });
+    }
+  }
+
+  /**
+   * Shift a given color, using a reference pair (ref in HSL).
+   *
+   * This algorithm ensures relative ordering on the saturation and luminance
+   * axes is preserved, and performs a simple hue shift.
+   *
+   * It is also symmetrical. If: shift_color(c, a, b) == d,
+   *                        then shift_color(d, b, a) == c.
+   */
+  function shift_color(given, ref1, ref2) {
+    // Convert to HSL
+    given = farb.RGBToHSL(farb.unpack(given));
+
+    // Hue: apply delta
+    given[0] += ref2[0] - ref1[0];
+
+    // Saturation: interpolate
+    if (ref1[1] == 0 || ref2[1] == 0) {
+      given[1] = ref2[1];
+    }
+    else {
+      var d = ref1[1] / ref2[1];
+      if (d > 1) {
+        given[1] /= d;
+      }
+      else {
+        given[1] = 1 - (1 - given[1]) * d;
+      }
+    }
+
+    // Luminance: interpolate
+    if (ref1[2] == 0 || ref2[2] == 0) {
+      given[2] = ref2[2];
+    }
+    else {
+      var d = ref1[2] / ref2[2];
+      if (d > 1) {
+        given[2] /= d;
+      }
+      else {
+        given[2] = 1 - (1 - given[2]) * d;
+      }
+    }
+
+    return farb.pack(farb.HSLToRGB(given));
+  }
+
+  /**
+   * Callback for Farbtastic when a new color is chosen.
+   */
+  function callback(input, color, propagate, colorscheme) {
+    // Set background/foreground color
+    $(input).css({
+      backgroundColor: color,
+      'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
+    });
+
+    // Change input value
+    if (input.value && input.value != color) {
+      input.value = color;
+
+      // Update locked values
+      if (propagate) {
+        var i = input.i;
+        for (j = i + 1; ; ++j) {
+          if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) break;
+          var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+          callback(inputs[j], matched, false);
+        }
+        for (j = i - 1; ; --j) {
+          if (!locks[j] || $(locks[j]).is('.unlocked')) break;
+          var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
+          callback(inputs[j], matched, false);
+        }
+
+        // Update preview
+        preview();
+      }
+
+      // Reset colorscheme selector
+      if (!colorscheme) {
+        resetScheme();
+      }
+    }
+
+  }
+
+  /**
+   * Reset the color scheme selector.
+   */
+  function resetScheme() {
+    $('#edit-scheme', form).each(function () {
+      this.selectedIndex = this.options.length - 1;
+    });
+  }
+
+  // Focus the Farbtastic on a particular field.
+  function focus() {
+    var input = this;
+    // Remove old bindings
+    focused && $(focused).unbind('keyup', farb.updateValue)
+        .unbind('keyup', preview).unbind('keyup', resetScheme)
+        .parent().removeClass('item-selected');
+
+    // Add new bindings
+    focused = this;
+    farb.linkTo(function (color) { callback(input, color, true, false); });
+    farb.setColor(this.value);
+    $(focused).keyup(farb.updateValue).keyup(preview).keyup(resetScheme)
+      .parent().addClass('item-selected');
+  }
+
+  // Initialize color fields
+  $('#palette input.form-text', form)
+  .each(function () {
+    // Extract palette field name
+    this.key = this.id.substring(13);
+
+    // Link to color picker temporarily to initialize.
+    farb.linkTo(function () {}).setColor('#000').linkTo(this);
+
+    // Add lock
+    var i = inputs.length;
+    if (inputs.length) {
+      var lock = $('<div class="lock"></div>').toggle(
+        function () {
+          $(this).addClass('unlocked');
+          $(hooks[i - 1]).attr('class',
+            locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook'
+          );
+          $(hooks[i]).attr('class',
+            locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook'
+          );
+        },
+        function () {
+          $(this).removeClass('unlocked');
+          $(hooks[i - 1]).attr('class',
+            locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down'
+          );
+          $(hooks[i]).attr('class',
+            locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up'
+          );
+        }
+      );
+      $(this).after(lock);
+      locks.push(lock);
+    };
+
+    // Add hook
+    var hook = $('<div class="hook"></div>');
+    $(this).after(hook);
+    hooks.push(hook);
+
+    $(this).parent().find('.lock').click();
+    this.i = i;
+    inputs.push(this);
+  })
+  .focus(focus);
+
+  $('#palette label', form);
+
+  // Focus first color
+  focus.call(inputs[0]);
+
+  // Render preview
+  preview();
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/color/color.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,673 @@
+<?php
+// $Id: color.module,v 1.39 2008/01/23 09:43:25 goba Exp $
+
+/**
+ * Implementation of hook_help().
+ */
+function color_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#color':
+      $output = '<p>'. t('The color module allows a site administrator to quickly and easily change the color scheme of certain themes. Although not all themes support color module, both Garland (the default theme) and Minnelli were designed to take advantage of its features. By using color module with a compatible theme, you can easily change the color of links, backgrounds, text, and other theme elements. Color module requires that your <a href="@url">file download method</a> be set to public.', array('@url' => url('admin/settings/file-system'))) .'</p>';
+      $output .= '<p>'. t("It is important to remember that color module saves a modified copy of the theme's specified stylesheets in the files directory. This means that if you make any manual changes to your theme's stylesheet, you must save your color settings again, even if they haven't changed. This causes the color module generated version of the stylesheets in the files directory to be recreated using the new version of the original file.") .'</p>';
+      $output .= '<p>'. t('To change the color settings for a compatible theme, select the "configure" link for the theme on the <a href="@themes">themes administration page</a>.', array('@themes' => url('admin/build/themes'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@color">Color module</a>.', array('@color' => 'http://drupal.org/handbook/modules/color/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function color_theme() {
+  return array(
+    'color_scheme_form' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function color_form_alter(&$form, $form_state, $form_id) {
+  // Insert the color changer into the theme settings page.
+  if ($form_id == 'system_theme_settings' && color_get_info(arg(4)) && function_exists('gd_info')) {
+    if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
+      // Disables the color changer when the private download method is used.
+      // TODO: This should be solved in a different way. See issue #181003.
+      drupal_set_message(t('The color picker only works if the <a href="@url">download method</a> is set to public.', array('@url' => url('admin/settings/file-system'))), 'warning');
+    }
+    else {
+      $form['color'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Color scheme'),
+        '#weight' => -1,
+        '#attributes' => array('id' => 'color_scheme_form'),
+        '#theme' => 'color_scheme_form',
+      );
+      $form['color'] += color_scheme_form($form_state, arg(4));
+      $form['#submit'][] = 'color_scheme_form_submit';
+    }
+  }
+
+  // Use the generated screenshot in the theme list.
+  if ($form_id == 'system_theme_select_form' || $form_id == 'system_themes') {
+    $themes = list_themes();
+    foreach (element_children($form) as $theme) {
+      if ($screenshot = variable_get('color_'. $theme .'_screenshot', NULL)) {
+        if (isset($form[$theme]['screenshot'])) {
+          $form[$theme]['screenshot']['#value'] = theme('image', $screenshot, '', '', array('class' => 'screenshot'), FALSE);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Callback for the theme to alter the resources used.
+ */
+function _color_page_alter(&$vars) {
+  global $language, $theme_key;
+
+  // Override stylesheets.
+  $color_paths = variable_get('color_'. $theme_key .'_stylesheets', array());
+  if (!empty($color_paths)) {
+    // Loop over theme CSS files and try to rebuild CSS array with rewritten
+    // stylesheets. Keep the orginal order intact for CSS cascading.
+    $new_theme_css = array();
+
+    foreach ($vars['css']['all']['theme'] as $old_path => $old_preprocess) {
+      // Add the non-colored stylesheet first as we might not find a
+      // re-colored stylesheet for replacement later.
+      $new_theme_css[$old_path] = $old_preprocess;
+
+      // Loop over the path array with recolored CSS files to find matching
+      // paths which could replace the non-recolored paths.
+      foreach ($color_paths as $color_path) {
+        // Color module currently requires unique file names to be used,
+        // which allows us to compare different file paths.
+        if (basename($old_path) == basename($color_path)) {
+          // Pull out the non-colored and add rewritten stylesheet.
+          unset($new_theme_css[$old_path]);
+          $new_theme_css[$color_path] = $old_preprocess;
+
+          // If the current language is RTL and the CSS file had an RTL variant,
+          // pull out the non-colored and add rewritten RTL stylesheet.
+          if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
+            $rtl_old_path = str_replace('.css', '-rtl.css', $old_path);
+            $rtl_color_path = str_replace('.css', '-rtl.css', $color_path);
+            if (file_exists($rtl_color_path)) {
+              unset($new_theme_css[$rtl_old_path]);
+              $new_theme_css[$rtl_color_path] = $old_preprocess;
+            }
+          }
+          break;
+        }
+      }
+    }
+    $vars['css']['all']['theme'] = $new_theme_css;
+    $vars['styles'] = drupal_get_css($vars['css']);
+  }
+
+  // Override logo.
+  $logo = variable_get('color_'. $theme_key .'_logo', NULL);
+  if ($logo && $vars['logo'] && preg_match('!'. $theme_key .'/logo.png$!', $vars['logo'])) {
+    $vars['logo'] = base_path() . $logo;
+  }
+}
+
+/**
+ * Retrieve the color.module info for a particular theme.
+ */
+function color_get_info($theme) {
+  $path = drupal_get_path('theme', $theme);
+  $file = $path .'/color/color.inc';
+  if ($path && file_exists($file)) {
+    include $file;
+    return $info;
+  }
+}
+
+/**
+ * Helper function to retrieve the color palette for a particular theme.
+ */
+function color_get_palette($theme, $default = false) {
+  // Fetch and expand default palette
+  $fields = array('base', 'link', 'top', 'bottom', 'text');
+  $info = color_get_info($theme);
+  $keys = array_keys($info['schemes']);
+  foreach (explode(',', array_shift($keys)) as $k => $scheme) {
+    $palette[$fields[$k]] = $scheme;
+  }
+
+  // Load variable
+  return $default ? $palette : variable_get('color_'. $theme .'_palette', $palette);
+}
+
+/**
+ * Form callback. Returns the configuration form.
+ */
+function color_scheme_form(&$form_state, $theme) {
+  $base = drupal_get_path('module', 'color');
+  $info = color_get_info($theme);
+
+  // Add Farbtastic color picker
+  drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE);
+  drupal_add_js('misc/farbtastic/farbtastic.js');
+
+  // Add custom CSS/JS
+  drupal_add_css($base .'/color.css', 'module', 'all', FALSE);
+  drupal_add_js($base .'/color.js');
+  drupal_add_js(array('color' => array(
+    'reference' => color_get_palette($theme, true)
+  )), 'setting');
+
+  // See if we're using a predefined scheme
+  $current = implode(',', variable_get('color_'. $theme .'_palette', array()));
+  // Note: we use the original theme when the default scheme is chosen.
+  $current = isset($info['schemes'][$current]) ? $current : ($current == '' ? reset($info['schemes']) : '');
+
+  // Add scheme selector
+  $info['schemes'][''] = t('Custom');
+  $form['scheme'] = array(
+    '#type' => 'select',
+    '#title' => t('Color set'),
+    '#options' => $info['schemes'],
+    '#default_value' => $current,
+  );
+
+  // Add palette fields
+  $palette = color_get_palette($theme);
+  $names = array(
+    'base' => t('Base color'),
+    'link' => t('Link color'),
+    'top' => t('Header top'),
+    'bottom' => t('Header bottom'),
+    'text' => t('Text color')
+  );
+  $form['palette']['#tree'] = true;
+  foreach ($palette as $name => $value) {
+    $form['palette'][$name] = array(
+      '#type' => 'textfield',
+      '#title' => $names[$name],
+      '#default_value' => $value,
+      '#size' => 8,
+    );
+  }
+  $form['theme'] = array('#type' => 'value', '#value' => arg(4));
+  $form['info'] = array('#type' => 'value', '#value' => $info);
+
+  return $form;
+}
+
+/**
+ * Theme color form.
+ *
+ * @ingroup @themeable
+ */
+function theme_color_scheme_form($form) {
+  // Include stylesheet
+  $theme = $form['theme']['#value'];
+  $info = $form['info']['#value'];
+  $path = drupal_get_path('theme', $theme) .'/';
+  drupal_add_css($path . $info['preview_css']);
+  $output = '';
+  // Wrapper
+  $output .= '<div class="color-form clear-block">';
+
+  // Color schemes
+  $output .= drupal_render($form['scheme']);
+
+  // Palette
+  $output .= '<div id="palette" class="clear-block">';
+  foreach (element_children($form['palette']) as $name) {
+    $output .= drupal_render($form['palette'][$name]);
+  }
+  $output .= '</div>';
+
+  // Preview
+  $output .= drupal_render($form);
+  $output .= '<h2>'. t('Preview') .'</h2>';
+  $output .= '<div id="preview"><div id="text"><h2>Lorem ipsum dolor</h2><p>Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud <a href="#">exercitation ullamco</a> laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p></div><div id="img" style="background-image: url('. base_path() . $path . $info['preview_image'] .')"></div></div>';
+
+  // Close wrapper
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Submit handler for color change form.
+ */
+function color_scheme_form_submit($form, &$form_state) {
+  // Get theme coloring info
+  if (!isset($form_state['values']['info'])) {
+    return;
+  }
+  $theme = $form_state['values']['theme'];
+  $info = $form_state['values']['info'];
+
+  // Resolve palette
+  $palette = $form_state['values']['palette'];
+  if ($form_state['values']['scheme'] != '') {
+    $scheme = explode(',', $form_state['values']['scheme']);
+    foreach ($palette as $k => $color) {
+      $palette[$k] = array_shift($scheme);
+    }
+  }
+
+  // Make sure enough memory is available, if PHP's memory limit is compiled in.
+  if (function_exists('memory_get_usage')) {
+    // Fetch source image dimensions.
+    $source = drupal_get_path('theme', $theme) .'/'. $info['base_image'];
+    list($width, $height) = getimagesize($source);
+
+    // We need at least a copy of the source and a target buffer of the same
+    // size (both at 32bpp).
+    $required = $width * $height * 8;
+    $usage = memory_get_usage();
+    $limit = parse_size(ini_get('memory_limit'));
+    if ($usage + $required > $limit) {
+      drupal_set_message(t('There is not enough memory available to PHP to change this theme\'s color scheme. You need at least %size more. Check the <a href="@url">PHP documentation</a> for more information.', array('%size' => format_size($usage + $required - $limit), '@url' => 'http://www.php.net/manual/en/ini.core.php#ini.sect.resource-limits')), 'error');
+      return;
+    }
+  }
+
+  // Delete old files
+  foreach (variable_get('color_'. $theme .'_files', array()) as $file) {
+    @unlink($file);
+  }
+  if (isset($file) && $file = dirname($file)) {
+    @rmdir($file);
+  }
+
+  // Don't render the default colorscheme, use the standard theme instead.
+  if (implode(',', color_get_palette($theme, true)) == implode(',', $palette)
+    || $form_state['values']['op'] == t('Reset to defaults')) {
+    variable_del('color_'. $theme .'_palette');
+    variable_del('color_'. $theme .'_stylesheets');
+    variable_del('color_'. $theme .'_logo');
+    variable_del('color_'. $theme .'_files');
+    variable_del('color_'. $theme .'_screenshot');
+    return;
+  }
+
+  // Prepare target locations for generated files.
+  $id = $theme .'-'. substr(md5(serialize($palette) . microtime()), 0, 8);
+  $paths['color'] = file_directory_path() .'/color';
+  $paths['target'] = $paths['color'] .'/'. $id;
+  foreach ($paths as $path) {
+    file_check_directory($path, FILE_CREATE_DIRECTORY);
+  }
+  $paths['target'] = $paths['target'] .'/';
+  $paths['id'] = $id;
+  $paths['source'] = drupal_get_path('theme', $theme) .'/';
+  $paths['files'] = $paths['map'] = array();
+
+  // Save palette and logo location.
+  variable_set('color_'. $theme .'_palette', $palette);
+  variable_set('color_'. $theme .'_logo', $paths['target'] .'logo.png');
+
+  // Copy over neutral images.
+  foreach ($info['copy'] as $file) {
+    $base = basename($file);
+    $source = $paths['source'] . $file;
+    file_copy($source, $paths['target'] . $base);
+    $paths['map'][$file] = $base;
+    $paths['files'][] = $paths['target'] . $base;
+  }
+
+  // Render new images, if image provided.
+  if ($info['base_image']) {
+    _color_render_images($theme, $info, $paths, $palette);
+  }
+
+  // Rewrite theme stylesheets.
+  $css = array();
+  foreach ($info['css'] as $stylesheet) {
+    // Build a temporary array with LTR and RTL files.
+    $files = array();
+    if (file_exists($paths['source'] . $stylesheet)) {
+      $files[] = $stylesheet;
+
+      $rtl_file = str_replace('.css', '-rtl.css', $stylesheet);
+      if (file_exists($paths['source'] . $rtl_file)) {
+        $files[] = $rtl_file;
+      }
+    }
+
+    foreach ($files as $file) {
+      // Aggregate @imports recursively for each configured top level CSS file
+      // without optimization. Aggregation and optimization will be
+      // handled by drupal_build_css_cache() only.
+      $style = drupal_load_stylesheet($paths['source'] . $file, FALSE);
+
+      // Return the path to where this CSS file originated from, stripping
+      // off the name of the file at the end of the path.
+      $base = base_path() . dirname($paths['source'] . $file) .'/';
+      _drupal_build_css_path(NULL, $base);
+
+      // Prefix all paths within this CSS file, ignoring absolute paths.
+      $style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $style);
+
+      // Rewrite stylesheet with new colors.
+      $style = _color_rewrite_stylesheet($theme, $info, $paths, $palette, $style);
+      $base_file = basename($file);
+      $css[] = $paths['target'] . $base_file;
+      _color_save_stylesheet($paths['target'] . $base_file, $style, $paths);
+    }
+  }
+
+  // Maintain list of files.
+  variable_set('color_'. $theme .'_stylesheets', $css);
+  variable_set('color_'. $theme .'_files', $paths['files']);
+}
+
+/**
+ * Rewrite the stylesheet to match the colors in the palette.
+ */
+function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) {
+  $themes = list_themes();
+
+  // Prepare color conversion table
+  $conversion = $palette;
+  unset($conversion['base']);
+  foreach ($conversion as $k => $v) {
+    $conversion[$k] = drupal_strtolower($v);
+  }
+  $default = color_get_palette($theme, true);
+
+  // Split off the "Don't touch" section of the stylesheet.
+  $split = "Color Module: Don't touch";
+  if (strpos($style, $split) !== FALSE) {
+    list($style, $fixed) = explode($split, $style);
+  }
+
+  // Find all colors in the stylesheet and the chunks in between.
+  $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE);
+  $is_color = false;
+  $output = '';
+  $base = 'base';
+
+  // Iterate over all parts.
+  foreach ($style as $chunk) {
+    if ($is_color) {
+      $chunk = drupal_strtolower($chunk);
+      // Check if this is one of the colors in the default palette.
+      if ($key = array_search($chunk, $default)) {
+        $chunk = $conversion[$key];
+      }
+      // Not a pre-set color. Extrapolate from the base.
+      else {
+        $chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']);
+      }
+    }
+    else {
+      // Determine the most suitable base color for the next color.
+
+      // 'a' declarations. Use link.
+      if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) {
+        $base = 'link';
+      }
+      // 'color:' styles. Use text.
+      else if (preg_match('/(?<!-)color[^{:]*:[^{#]*$/i', $chunk)) {
+        $base = 'text';
+      }
+      // Reset back to base.
+      else {
+        $base = 'base';
+      }
+    }
+    $output .= $chunk;
+    $is_color = !$is_color;
+  }
+  // Append fixed colors segment.
+  if (isset($fixed)) {
+    $output .= $fixed;
+  }
+
+  // Replace paths to images.
+  foreach ($paths['map'] as $before => $after) {
+    $before = base_path() . $paths['source'] . $before;
+    $before = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $before);
+    $output = str_replace($before, $after, $output);
+  }
+
+  return $output;
+}
+
+/**
+ * Save the rewritten stylesheet to disk.
+ */
+function _color_save_stylesheet($file, $style, &$paths) {
+
+  // Write new stylesheet.
+  file_save_data($style, $file, FILE_EXISTS_REPLACE);
+  $paths['files'][] = $file;
+
+  // Set standard file permissions for webserver-generated files.
+  @chmod($file, 0664);
+}
+
+/**
+ * Render images that match a given palette.
+ */
+function _color_render_images($theme, &$info, &$paths, $palette) {
+
+  // Prepare template image.
+  $source = $paths['source'] .'/'. $info['base_image'];
+  $source = imagecreatefrompng($source);
+  $width = imagesx($source);
+  $height = imagesy($source);
+
+  // Prepare target buffer.
+  $target = imagecreatetruecolor($width, $height);
+  imagealphablending($target, true);
+
+  // Fill regions of solid color.
+  foreach ($info['fill'] as $color => $fill) {
+    imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color]));
+  }
+
+  // Render gradient.
+  for ($y = 0; $y < $info['gradient'][3]; ++$y) {
+    $color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1));
+    imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2], $info['gradient'][1] + $y + 1, $color);
+  }
+
+  // Blend over template.
+  imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
+
+  // Clean up template image.
+  imagedestroy($source);
+
+  // Cut out slices.
+  foreach ($info['slices'] as $file => $coord) {
+    list($x, $y, $width, $height) = $coord;
+    $base = basename($file);
+    $image = $paths['target'] . $base;
+
+    // Cut out slice.
+    if ($file == 'screenshot.png') {
+      $slice = imagecreatetruecolor(150, 90);
+      imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height);
+      variable_set('color_'. $theme .'_screenshot', $image);
+    }
+    else {
+      $slice = imagecreatetruecolor($width, $height);
+      imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
+    }
+
+    // Save image.
+    imagepng($slice, $image);
+    imagedestroy($slice);
+    $paths['files'][] = $image;
+
+    // Set standard file permissions for webserver-generated files
+    @chmod(realpath($image), 0664);
+
+    // Build before/after map of image paths.
+    $paths['map'][$file] = $base;
+  }
+
+  // Clean up target buffer.
+  imagedestroy($target);
+}
+
+/**
+ * Shift a given color, using a reference pair and a target blend color.
+ *
+ * Note: this function is significantly different from the JS version, as it
+ * is written to match the blended images perfectly.
+ *
+ * Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta
+ *              then (return == target + (given - target) * delta)
+ *
+ * Loose constraint: Preserve relative positions in saturation and luminance
+ *                   space.
+ */
+function _color_shift($given, $ref1, $ref2, $target) {
+
+  // We assume that ref2 is a blend of ref1 and target and find
+  // delta based on the length of the difference vectors:
+
+  // delta = 1 - |ref2 - ref1| / |white - ref1|
+  $target = _color_unpack($target, true);
+  $ref1 = _color_unpack($ref1, true);
+  $ref2 = _color_unpack($ref2, true);
+  $numerator = 0;
+  $denominator = 0;
+  for ($i = 0; $i < 3; ++$i) {
+    $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
+    $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
+  }
+  $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
+
+  // Calculate the color that ref2 would be if the assumption was true.
+  for ($i = 0; $i < 3; ++$i) {
+    $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
+  }
+
+  // If the assumption is not true, there is a difference between ref2 and ref3.
+  // We measure this in HSL space. Notation: x' = hsl(x).
+  $ref2 = _color_rgb2hsl($ref2);
+  $ref3 = _color_rgb2hsl($ref3);
+  for ($i = 0; $i < 3; ++$i) {
+    $shift[$i] = $ref2[$i] - $ref3[$i];
+  }
+
+  // Take the given color, and blend it towards the target.
+  $given = _color_unpack($given, true);
+  for ($i = 0; $i < 3; ++$i) {
+    $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
+  }
+
+  // Finally, we apply the extra shift in HSL space.
+  // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
+  $result = _color_rgb2hsl($result);
+  for ($i = 0; $i < 3; ++$i) {
+    $result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
+  }
+  $result = _color_hsl2rgb($result);
+
+  // Return hex color.
+  return _color_pack($result, true);
+}
+
+/**
+ * Convert a hex triplet into a GD color.
+ */
+function _color_gd($img, $hex) {
+  $c = array_merge(array($img), _color_unpack($hex));
+  return call_user_func_array('imagecolorallocate', $c);
+}
+
+/**
+ * Blend two hex colors and return the GD color.
+ */
+function _color_blend($img, $hex1, $hex2, $alpha) {
+  $in1 = _color_unpack($hex1);
+  $in2 = _color_unpack($hex2);
+  $out = array($img);
+  for ($i = 0; $i < 3; ++$i) {
+    $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
+  }
+  return call_user_func_array('imagecolorallocate', $out);
+}
+
+/**
+ * Convert a hex color into an RGB triplet.
+ */
+function _color_unpack($hex, $normalize = false) {
+  if (strlen($hex) == 4) {
+    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
+  }
+  $c = hexdec($hex);
+  for ($i = 16; $i >= 0; $i -= 8) {
+    $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
+  }
+  return $out;
+}
+
+/**
+ * Convert an RGB triplet to a hex color.
+ */
+function _color_pack($rgb, $normalize = false) {
+  $out = 0;
+  foreach ($rgb as $k => $v) {
+    $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
+  }
+  return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
+}
+
+/**
+ * Convert a HSL triplet into RGB
+ */
+function _color_hsl2rgb($hsl) {
+  $h = $hsl[0];
+  $s = $hsl[1];
+  $l = $hsl[2];
+  $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
+  $m1 = $l * 2 - $m2;
+  return array(
+    _color_hue2rgb($m1, $m2, $h + 0.33333),
+    _color_hue2rgb($m1, $m2, $h),
+    _color_hue2rgb($m1, $m2, $h - 0.33333),
+  );
+}
+
+/**
+ * Helper function for _color_hsl2rgb().
+ */
+function _color_hue2rgb($m1, $m2, $h) {
+  $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
+  if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
+  if ($h * 2 < 1) return $m2;
+  if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
+  return $m1;
+}
+
+/**
+ * Convert an RGB triplet to HSL.
+ */
+function _color_rgb2hsl($rgb) {
+  $r = $rgb[0];
+  $g = $rgb[1];
+  $b = $rgb[2];
+  $min = min($r, min($g, $b));
+  $max = max($r, max($g, $b));
+  $delta = $max - $min;
+  $l = ($min + $max) / 2;
+  $s = 0;
+  if ($l > 0 && $l < 1) {
+    $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
+  }
+  $h = 0;
+  if ($delta > 0) {
+    if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
+    if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
+    if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
+    $h /= 6;
+  }
+  return array($h, $s, $l);
+}
Binary file modules/color/images/hook-rtl.png has changed
Binary file modules/color/images/hook.png has changed
Binary file modules/color/images/lock.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment-folded.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,21 @@
+<?php
+// $Id: comment-folded.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file comment-folded.tpl.php
+ * Default theme implementation for folded comments.
+ *
+ * Available variables:
+ * - $title: Linked title to full comment.
+ * - $new: New comment marker.
+ * - $author: Comment author. Can be link or plain text.
+ * - $date: Date and time of posting.
+ * - $comment: Full comment object.
+ *
+ * @see template_preprocess_comment_folded()
+ * @see theme_comment_folded()
+ */
+?>
+<div class="comment-folded">
+  <span class="subject"><?php print $title .' '. $new; ?></span><span class="credit"><?php print t('by') .' '. $author; ?></span>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+/* $Id: comment-rtl.css,v 1.2 2007/11/27 12:09:25 goba Exp $ */
+
+.indented {
+  margin-left: 0;
+  margin-right: 25px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment-wrapper.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,36 @@
+<?php
+// $Id: comment-wrapper.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file comment-wrapper.tpl.php
+ * Default theme implementation to wrap comments.
+ *
+ * Available variables:
+ * - $content: All comments for a given page. Also contains sorting controls
+ *   and comment forms if the site is configured for it.
+ *
+ * The following variables are provided for contextual information.
+ * - $node: Node object the comments are attached to.
+ * The constants below the variables show the possible values and should be
+ * used for comparison.
+ * - $display_mode
+ *   - COMMENT_MODE_FLAT_COLLAPSED
+ *   - COMMENT_MODE_FLAT_EXPANDED
+ *   - COMMENT_MODE_THREADED_COLLAPSED
+ *   - COMMENT_MODE_THREADED_EXPANDED
+ * - $display_order
+ *   - COMMENT_ORDER_NEWEST_FIRST
+ *   - COMMENT_ORDER_OLDEST_FIRST
+ * - $comment_controls_state
+ *   - COMMENT_CONTROLS_ABOVE
+ *   - COMMENT_CONTROLS_BELOW
+ *   - COMMENT_CONTROLS_ABOVE_BELOW
+ *   - COMMENT_CONTROLS_HIDDEN
+ *
+ * @see template_preprocess_comment_wrapper()
+ * @see theme_comment_wrapper()
+ */
+?>
+<div id="comments">
+  <?php print $content; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,296 @@
+<?php
+// $Id: comment.admin.inc,v 1.4 2008/01/08 10:35:41 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the comment module.
+ */
+
+/**
+ * Menu callback; present an administrative comment listing.
+ */
+function comment_admin($type = 'new') {
+  $edit = $_POST;
+
+  if (isset($edit['operation']) && ($edit['operation'] == 'delete') && isset($edit['comments']) && $edit['comments']) {
+    return drupal_get_form('comment_multiple_delete_confirm');
+  }
+  else {
+    return drupal_get_form('comment_admin_overview', $type, arg(4));
+  }
+}
+
+/**
+ * Form builder; Builds the comment overview form for the admin.
+ *
+ * @param $type
+ *   Not used.
+ * @param $arg
+ *   Current path's fourth component deciding the form type (Published comments/Approval queue)
+ * @return
+ *   The form structure.
+ * @ingroup forms
+ * @see comment_admin_overview_validate()
+ * @see comment_admin_overview_submit()
+ * @see theme_comment_admin_overview()
+ */
+function comment_admin_overview($type = 'new', $arg) {
+  // build an 'Update options' form
+  $form['options'] = array(
+    '#type' => 'fieldset', '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">', '#suffix' => '</div>'
+  );
+  $options = array();
+  foreach (comment_operations($arg == 'approval' ? 'publish' : 'unpublish') as $key => $value) {
+    $options[$key] = $value[0];
+  }
+  $form['options']['operation'] = array('#type' => 'select', '#options' => $options, '#default_value' => 'publish');
+  $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
+
+  // load the comments that we want to display
+  $status = ($arg == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED;
+  $form['header'] = array('#type' => 'value', '#value' => array(
+    theme('table_select_header_cell'),
+    array('data' => t('Subject'), 'field' => 'subject'),
+    array('data' => t('Author'), 'field' => 'name'),
+    array('data' => t('Posted in'), 'field' => 'node_title'),
+    array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
+    array('data' => t('Operations'))
+  ));
+  $result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid, n.title as node_title FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid INNER JOIN {node} n ON n.nid = c.nid WHERE c.status = %d'. tablesort_sql($form['header']['#value']), 50, 0, NULL, $status);
+
+  // build a table listing the appropriate comments
+  $destination = drupal_get_destination();
+  while ($comment = db_fetch_object($result)) {
+    $comments[$comment->cid] = '';
+    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+    $form['subject'][$comment->cid] = array('#value' => l($comment->subject, 'node/'. $comment->nid, array('title' => truncate_utf8($comment->comment, 128), 'fragment' => 'comment-'. $comment->cid)));
+    $form['username'][$comment->cid] = array('#value' => theme('username', $comment));
+    $form['node_title'][$comment->cid] = array('#value' => l($comment->node_title, 'node/'. $comment->nid));
+    $form['timestamp'][$comment->cid] = array('#value' => format_date($comment->timestamp, 'small'));
+    $form['operations'][$comment->cid] = array('#value' => l(t('edit'), 'comment/edit/'. $comment->cid, array('query' => $destination)));
+  }
+  $form['comments'] = array('#type' => 'checkboxes', '#options' => isset($comments) ? $comments: array());
+  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+  return $form;
+}
+
+/**
+ * Validate comment_admin_overview form submissions.
+ *
+ * We can't execute any 'Update options' if no comments were selected.
+ */
+function comment_admin_overview_validate($form, &$form_state) {
+  $form_state['values']['comments'] = array_diff($form_state['values']['comments'], array(0));
+  if (count($form_state['values']['comments']) == 0) {
+    form_set_error('', t('Please select one or more comments to perform the update on.'));
+    drupal_goto('admin/content/comment');
+  }
+}
+
+/**
+ * Process comment_admin_overview form submissions.
+ *
+ * Execute the chosen 'Update option' on the selected comments, such as
+ * publishing, unpublishing or deleting.
+ */
+function comment_admin_overview_submit($form, &$form_state) {
+  $operations = comment_operations();
+  if ($operations[$form_state['values']['operation']][1]) {
+    // extract the appropriate database query operation
+    $query = $operations[$form_state['values']['operation']][1];
+    foreach ($form_state['values']['comments'] as $cid => $value) {
+      if ($value) {
+        // perform the update action, then refresh node statistics
+        db_query($query, $cid);
+        $comment = _comment_load($cid);
+        _comment_update_node_statistics($comment->nid);
+        // Allow modules to respond to the updating of a comment.
+        comment_invoke_comment($comment, $form_state['values']['operation']);
+        // Add an entry to the watchdog log.
+        watchdog('content', 'Comment: updated %subject.', array('%subject' => $comment->subject), WATCHDOG_NOTICE, l(t('view'), 'node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid)));
+      }
+    }
+    cache_clear_all();
+    drupal_set_message(t('The update has been performed.'));
+    $form_state['redirect'] = 'admin/content/comment';
+  }
+}
+
+/**
+ * Theme the comment admin form.
+ *
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @ingroup themeable
+ */
+function theme_comment_admin_overview($form) {
+  $output = drupal_render($form['options']);
+  if (isset($form['subject']) && is_array($form['subject'])) {
+    foreach (element_children($form['subject']) as $key) {
+      $row = array();
+      $row[] = drupal_render($form['comments'][$key]);
+      $row[] = drupal_render($form['subject'][$key]);
+      $row[] = drupal_render($form['username'][$key]);
+      $row[] = drupal_render($form['node_title'][$key]);
+      $row[] = drupal_render($form['timestamp'][$key]);
+      $row[] = drupal_render($form['operations'][$key]);
+      $rows[] = $row;
+    }
+  }
+  else {
+    $rows[] = array(array('data' => t('No comments available.'), 'colspan' => '6'));
+  }
+
+  $output .= theme('table', $form['header']['#value'], $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * List the selected comments and verify that the admin really wants to delete
+ * them.
+ *
+ * @param $form_state
+ *   An associative array containing the current state of the form.
+ * @return
+ *   TRUE if the comments should be deleted, FALSE otherwise.
+ * @ingroup forms
+ * @see comment_multiple_delete_confirm_submit()
+ */
+function comment_multiple_delete_confirm(&$form_state) {
+  $edit = $form_state['post'];
+
+  $form['comments'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
+  // array_filter() returns only elements with actual values
+  $comment_counter = 0;
+  foreach (array_filter($edit['comments']) as $cid => $value) {
+    $comment = _comment_load($cid);
+    if (is_object($comment) && is_numeric($comment->cid)) {
+      $subject = db_result(db_query('SELECT subject FROM {comments} WHERE cid = %d', $cid));
+      $form['comments'][$cid] = array('#type' => 'hidden', '#value' => $cid, '#prefix' => '<li>', '#suffix' => check_plain($subject) .'</li>');
+      $comment_counter++;
+    }
+  }
+  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+
+  if (!$comment_counter) {
+    drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.'));
+    drupal_goto('admin/content/comment');
+  }
+  else {
+    return confirm_form($form,
+                        t('Are you sure you want to delete these comments and all their children?'),
+                        'admin/content/comment', t('This action cannot be undone.'),
+                        t('Delete comments'), t('Cancel'));
+  }
+}
+
+/**
+ * Process comment_multiple_delete_confirm form submissions.
+ *
+ * Perform the actual comment deletion.
+ */
+function comment_multiple_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    foreach ($form_state['values']['comments'] as $cid => $value) {
+      $comment = _comment_load($cid);
+      _comment_delete_thread($comment);
+      _comment_update_node_statistics($comment->nid);
+    }
+    cache_clear_all();
+    drupal_set_message(t('The comments have been deleted.'));
+  }
+  $form_state['redirect'] = 'admin/content/comment';
+}
+
+/**
+ * Menu callback; delete a comment.
+ *
+ * @param $cid
+ *   The comment do be deleted.
+ */
+function comment_delete($cid = NULL) {
+  $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid));
+  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+
+  $output = '';
+
+  if (is_object($comment) && is_numeric($comment->cid)) {
+    $output = drupal_get_form('comment_confirm_delete', $comment);
+  }
+  else {
+    drupal_set_message(t('The comment no longer exists.'));
+  }
+
+  return $output;
+}
+
+/**
+ * Form builder; Builds the confirmation form for deleting a single comment.
+ *
+ * @ingroup forms
+ * @see comment_confirm_delete_submit()
+ */
+function comment_confirm_delete(&$form_state, $comment) {
+  $form = array();
+  $form['#comment'] = $comment;
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)),
+    'node/'. $comment->nid,
+    t('Any replies to this comment will be lost. This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel'),
+    'comment_confirm_delete');
+}
+
+/**
+ * Process comment_confirm_delete form submissions.
+ */
+function comment_confirm_delete_submit($form, &$form_state) {
+  drupal_set_message(t('The comment and all its replies have been deleted.'));
+
+  $comment = $form['#comment'];
+
+  // Delete comment and its replies.
+  _comment_delete_thread($comment);
+
+  _comment_update_node_statistics($comment->nid);
+
+  // Clear the cache so an anonymous user sees that his comment was deleted.
+  cache_clear_all();
+
+  $form_state['redirect'] = "node/$comment->nid";
+}
+
+/**
+ * Perform the actual deletion of a comment and all its replies.
+ *
+ * @param $comment
+ *   An associative array describing the comment to be deleted.
+ */
+function _comment_delete_thread($comment) {
+  if (!is_object($comment) || !is_numeric($comment->cid)) {
+    watchdog('content', 'Cannot delete non-existent comment.', WATCHDOG_WARNING);
+    return;
+  }
+
+  // Delete the comment:
+  db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
+  watchdog('content', 'Comment: deleted %subject.', array('%subject' => $comment->subject));
+
+  comment_invoke_comment($comment, 'delete');
+
+  // Delete the comment's replies
+  $result = db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE pid = %d', $comment->cid);
+  while ($comment = db_fetch_object($result)) {
+    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+    _comment_delete_thread($comment);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+/* $Id: comment.css,v 1.4 2007/07/24 21:33:53 goba Exp $ */
+
+.indented {
+  margin-left: 25px; /* LTR */
+}
+.comment-unpublished {
+  background-color: #fff4f4;
+}
+.preview .comment {
+  background-color: #ffffea;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: comment.info,v 1.4 2007/06/08 05:50:54 dries Exp $
+name = Comment
+description = Allows users to comment on and discuss published content.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,219 @@
+<?php
+// $Id: comment.install,v 1.19 2008/01/16 21:45:30 goba Exp $
+
+/**
+ * Implementation of hook_enable().
+ */
+function comment_enable() {
+  // Insert records into the node_comment_statistics for nodes that are missing.
+  db_query("INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) SELECT n.nid, n.changed, NULL, n.uid, 0 FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE c.comment_count IS NULL");
+}
+
+/**
+ * Changed node_comment_statistics to use node->changed to avoid future timestamps.
+ */
+function comment_update_1() {
+  // Change any future last comment timestamps to now.
+  db_query('UPDATE {node_comment_statistics} SET last_comment_timestamp = %d WHERE last_comment_timestamp > %d', time(), time());
+
+  // Unstuck node indexing timestamp if needed.
+  if (($last = variable_get('node_cron_last', FALSE)) !== FALSE) {
+    variable_set('node_cron_last', min(time(), $last));
+  }
+  return array();
+}
+
+function comment_update_6001() {
+  $ret[] = update_sql("ALTER TABLE {comments} DROP score");
+  $ret[] = update_sql("ALTER TABLE {comments} DROP users");
+  return $ret;
+}
+
+/**
+ * Changed comment settings from global to per-node -- copy global
+ * settings to all node types.
+ */
+function comment_update_6002() {
+  // Comment module might not be enabled when this is run, but we need the
+  // constants defined by the module for this update.
+  drupal_load('module', 'comment');
+  $settings = array(
+    'comment_default_mode' => COMMENT_MODE_THREADED_EXPANDED,
+    'comment_default_order' => COMMENT_ORDER_NEWEST_FIRST,
+    'comment_default_per_page' => 50,
+    'comment_controls' => COMMENT_CONTROLS_HIDDEN,
+    'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
+    'comment_subject_field' => 1,
+    'comment_preview' => COMMENT_PREVIEW_REQUIRED,
+    'comment_form_location' => COMMENT_FORM_SEPARATE_PAGE,
+  );
+  $types = node_get_types();
+  foreach ($settings as $setting => $default) {
+    $value = variable_get($setting, $default);
+    foreach ($types as $type => $object) {
+      variable_set($setting .'_'. $type, $value);
+    }
+    variable_del($setting);
+  }
+  return array(array('success' => TRUE, 'query' => 'Global comment settings copied to all node types.'));
+}
+
+/**
+ * Add index to parent ID field.
+ */
+function comment_update_6003() {
+  $ret = array();
+  db_add_index($ret, 'comments', 'pid', array('pid'));
+  return $ret;
+}
+
+
+/**
+ * Implementation of hook_schema().
+ */
+function comment_schema() {
+  $schema['comments'] = array(
+    'description' => t('Stores comments and associated data.'),
+    'fields' => array(
+      'cid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique comment ID.'),
+      ),
+      'pid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {comments}.cid to which this comment is a reply. If set to 0, this comment is not a reply to an existing comment.'),
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid to which this comment is a reply.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {users}.uid who authored the comment. If set to 0, this comment was created by an anonymous user.'),
+      ),
+      'subject' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The comment title.'),
+      ),
+      'comment' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('The comment body.'),
+      ),
+      'hostname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("The author's host name."),
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The time that the comment was created, or last edited by its author, as a Unix timestamp.'),
+      ),
+      'status' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The published status of a comment. (0 = Published, 1 = Not Published)'),
+      ),
+      'format' => array(
+        'type' => 'int',
+        'size' => 'small',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {filter_formats}.format of the comment body.'),
+      ),
+      'thread' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'description' => t("The vancode representation of the comment's place in a thread."),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 60,
+        'not null' => FALSE,
+        'description' => t("The comment author's name. Uses {users}.name if the user is logged in, otherwise uses the value typed into the comment form."),
+      ),
+      'mail' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => FALSE,
+        'description' => t("The comment author's e-mail address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on."),
+      ),
+      'homepage' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t("The comment author's home page address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on."),
+      )
+    ),
+    'indexes' => array(
+      'pid'    => array('pid'),
+      'nid'    => array('nid'),
+      'status' => array('status'), // This index is probably unused
+    ),
+    'primary key' => array('cid'),
+  );
+
+  $schema['node_comment_statistics'] = array(
+    'description' => t('Maintains statistics of node and comments posts to show "new" and "updated" flags.'),
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid for which the statistics are compiled.'),
+      ),
+      'last_comment_timestamp' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The Unix timestamp of the last comment that was posted within this node, from {comments}.timestamp.'),
+      ),
+      'last_comment_name' => array(
+        'type' => 'varchar',
+        'length' => 60,
+        'not null' => FALSE,
+        'description' => t('The name of the latest author to post a comment on this node, from {comments}.name.'),
+      ),
+      'last_comment_uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The user ID of the latest author to post a comment on this node, from {comments}.uid.'),
+      ),
+      'comment_count' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The total number of comments on this node.'),
+      ),
+    ),
+    'primary key' => array('nid'),
+    'indexes' => array(
+      'node_comment_timestamp' => array('last_comment_timestamp')
+    ),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,35 @@
+// $Id: comment.js,v 1.5 2007/09/12 18:29:32 goba Exp $
+
+Drupal.behaviors.comment = function (context) {
+  var parts = new Array("name", "homepage", "mail");
+  var cookie = '';
+  for (i=0;i<3;i++) {
+    cookie = Drupal.comment.getCookie('comment_info_' + parts[i]);
+    if (cookie != '') {
+      $("#comment-form input[name=" + parts[i] + "]:not(.comment-processed)", context)
+        .val(cookie)
+        .addClass('comment-processed');
+    }
+  }
+};
+
+Drupal.comment = {};
+
+Drupal.comment.getCookie = function(name) {
+  var search = name + '=';
+  var returnValue = '';
+
+  if (document.cookie.length > 0) {
+    offset = document.cookie.indexOf(search);
+    if (offset != -1) {
+      offset += search.length;
+      var end = document.cookie.indexOf(';', offset);
+      if (end == -1) {
+        end = document.cookie.length;
+      }
+      returnValue = decodeURIComponent(document.cookie.substring(offset, end).replace(/\+/g, '%20'));
+    }
+  }
+
+  return returnValue;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2102 @@
+<?php
+// $Id: comment.module,v 1.617 2008/01/25 16:19:12 goba Exp $
+
+/**
+ * @file
+ * Enables users to comment on published content.
+ *
+ * When enabled, the Drupal comment module creates a discussion
+ * board for each Drupal node. Users can post comments to discuss
+ * a forum topic, weblog post, story, collaborative book page, etc.
+ */
+
+/**
+ * Comment is published.
+ */
+define('COMMENT_PUBLISHED', 0);
+
+/**
+ * Comment is awaiting approval.
+ */
+define('COMMENT_NOT_PUBLISHED', 1);
+
+/**
+ * Comments are displayed in a flat list - collapsed.
+ */
+define('COMMENT_MODE_FLAT_COLLAPSED', 1);
+
+/**
+ * Comments are displayed in a flat list - expanded.
+ */
+define('COMMENT_MODE_FLAT_EXPANDED', 2);
+
+/**
+ * Comments are displayed as a threaded list - collapsed.
+ */
+define('COMMENT_MODE_THREADED_COLLAPSED', 3);
+
+/**
+ * Comments are displayed as a threaded list - expanded.
+ */
+define('COMMENT_MODE_THREADED_EXPANDED', 4);
+
+/**
+ * Comments are ordered by date - newest first.
+ */
+define('COMMENT_ORDER_NEWEST_FIRST', 1);
+
+/**
+ * Comments are ordered by date - oldest first.
+ */
+define('COMMENT_ORDER_OLDEST_FIRST', 2);
+
+/**
+ * Comment controls should be shown above the comment list.
+ */
+define('COMMENT_CONTROLS_ABOVE', 0);
+
+/**
+ * Comment controls should be shown below the comment list.
+ */
+define('COMMENT_CONTROLS_BELOW', 1);
+
+/**
+ * Comment controls should be shown both above and below the comment list.
+ */
+define('COMMENT_CONTROLS_ABOVE_BELOW', 2);
+
+/**
+ * Comment controls are hidden.
+ */
+define('COMMENT_CONTROLS_HIDDEN', 3);
+
+/**
+ * Anonymous posters may not enter their contact information.
+ */
+define('COMMENT_ANONYMOUS_MAYNOT_CONTACT', 0);
+
+/**
+ * Anonymous posters may leave their contact information.
+ */
+define('COMMENT_ANONYMOUS_MAY_CONTACT', 1);
+
+/**
+ * Anonymous posters must leave their contact information.
+ */
+define('COMMENT_ANONYMOUS_MUST_CONTACT', 2);
+
+/**
+ * Comment form should be displayed on a separate page.
+ */
+define('COMMENT_FORM_SEPARATE_PAGE', 0);
+
+/**
+ * Comment form should be shown below post or list of comments.
+ */
+define('COMMENT_FORM_BELOW', 1);
+
+/**
+ * Comments for this node are disabled.
+ */
+define('COMMENT_NODE_DISABLED', 0);
+
+/**
+ * Comments for this node are locked.
+ */
+define('COMMENT_NODE_READ_ONLY', 1);
+
+/**
+ * Comments are enabled on this node.
+ */
+define('COMMENT_NODE_READ_WRITE', 2);
+
+/**
+ * Comment preview is optional.
+ */
+define('COMMENT_PREVIEW_OPTIONAL', 0);
+
+/**
+ * Comment preview is required.
+ */
+define('COMMENT_PREVIEW_REQUIRED', 1);
+
+/**
+ * Implementation of hook_help().
+ */
+function comment_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#comment':
+      $output = '<p>'. t('The comment module allows visitors to comment on your posts, creating ad hoc discussion boards. Any <a href="@content-type">content type</a> may have its <em>Default comment setting</em> set to <em>Read/Write</em> to allow comments, or <em>Disabled</em>, to prevent comments. Comment display settings and other controls may also be customized for each content type (some display settings are customizable by individual users).', array('@content-type' => url('admin/content/types'))) .'</p>';
+      $output .= '<p>'. t('Comment permissions are assigned to user roles, and are used to determine whether anonymous users (or other roles) are allowed to comment on posts. If anonymous users are allowed to comment, their individual contact information may be retained in cookies stored on their local computer for use in later comment submissions. When a comment has no replies, it may be (optionally) edited by its author. The comment module uses the same input formats and HTML tags available when creating other forms of content.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@comment">Comment module</a>.', array('@comment' => 'http://drupal.org/handbook/modules/comment/')) .'</p>';
+      return $output;
+    case 'admin/content/comment':
+      return '<p>'. t("Below is a list of the latest comments posted to your site. Click on a subject to see the comment, the author's name to edit the author's user information, 'edit' to modify the text, and 'delete' to remove their submission.") .'</p>';
+    case 'admin/content/comment/approval':
+      return '<p>'. t("Below is a list of the comments posted to your site that need approval. To approve a comment, click on 'edit' and then change its 'moderation status' to Approved. Click on a subject to see the comment, the author's name to edit the author's user information, 'edit' to modify the text, and 'delete' to remove their submission.") .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function comment_theme() {
+  return array(
+    'comment_block' => array(
+      'arguments' => array(),
+    ),
+    'comment_admin_overview' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'comment_preview' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
+    ),
+    'comment_view' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
+    ),
+    'comment_controls' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'comment' => array(
+      'template' => 'comment',
+      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
+    ),
+    'comment_folded' => array(
+      'template' => 'comment-folded',
+      'arguments' => array('comment' => NULL),
+    ),
+    'comment_flat_collapsed' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL),
+    ),
+    'comment_flat_expanded' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL),
+    ),
+    'comment_thread_collapsed' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL),
+    ),
+    'comment_thread_expanded' => array(
+      'arguments' => array('comment' => NULL, 'node' => NULL),
+    ),
+    'comment_post_forbidden' => array(
+      'arguments' => array('nid' => NULL),
+    ),
+    'comment_wrapper' => array(
+      'template' => 'comment-wrapper',
+      'arguments' => array('content' => NULL, 'node' => NULL),
+    ),
+    'comment_submitted' => array(
+      'arguments' => array('comment' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function comment_menu() {
+  $items['admin/content/comment'] = array(
+    'title' => 'Comments',
+    'description' => 'List and edit site comments and the comment moderation queue.',
+    'page callback' => 'comment_admin',
+    'access arguments' => array('administer comments'),
+    'file' => 'comment.admin.inc',
+  );
+
+  // Tabs:
+  $items['admin/content/comment/new'] = array(
+    'title' => 'Published comments',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/content/comment/approval'] = array(
+    'title' => 'Approval queue',
+    'page arguments' => array('approval'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'comment.admin.inc',
+  );
+
+  $items['comment/delete'] = array(
+    'title' => 'Delete comment',
+    'page callback' => 'comment_delete',
+    'access arguments' => array('administer comments'),
+    'type' => MENU_CALLBACK,
+    'file' => 'comment.admin.inc',
+  );
+
+  $items['comment/edit'] = array(
+    'title' => 'Edit comment',
+    'page callback' => 'comment_edit',
+    'access arguments' => array('post comments'),
+    'type' => MENU_CALLBACK,
+    'file' => 'comment.pages.inc',
+  );
+  $items['comment/reply/%node'] = array(
+    'title' => 'Reply to comment',
+    'page callback' => 'comment_reply',
+    'page arguments' => array(2),
+    'access callback' => 'node_access',
+    'access arguments' => array('view', 2),
+    'type' => MENU_CALLBACK,
+    'file' => 'comment.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_node_type().
+ */
+function comment_node_type($op, $info) {
+  $settings = array(
+    'comment',
+    'comment_default_mode',
+    'comment_default_order',
+    'comment_default_per_page',
+    'comment_controls',
+    'comment_anonymous',
+    'comment_subject_field',
+    'comment_preview',
+    'comment_form_location',
+  );
+  switch ($op) {
+    case 'delete':
+      foreach ($settings as $setting) {
+        variable_del($setting .'_'. $info->type);
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function comment_perm() {
+  return array('access comments', 'post comments', 'administer comments', 'post comments without approval');
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generates a block with the most recent comments.
+ */
+function comment_block($op = 'list', $delta = 0) {
+  if ($op == 'list') {
+    $blocks[0]['info'] = t('Recent comments');
+    return $blocks;
+  }
+  else if ($op == 'view' && user_access('access comments')) {
+    $block['subject'] = t('Recent comments');
+    $block['content'] = theme('comment_block');
+    return $block;
+  }
+}
+
+/**
+ * Find a number of recent comments. This is done in two steps.
+ *   1. Find the n (specified by $number) nodes that have the most recent
+ *      comments.  This is done by querying node_comment_statistics which has
+ *      an index on last_comment_timestamp, and is thus a fast query.
+ *   2. Loading the information from the comments table based on the nids found
+ *      in step 1.
+ *
+ * @param $number
+ *   (optional) The maximum number of comments to find.
+ * @return
+ *   An array of comment objects each containing a nid,
+ *   subject, cid, and timestamp, or an empty array if there are no recent
+ *   comments visible to the current user.
+ */
+function comment_get_recent($number = 10) {
+  // Select the $number nodes (visible to the current user) with the most
+  // recent comments. This is efficient due to the index on
+  // last_comment_timestamp.
+  $result = db_query_range(db_rewrite_sql("SELECT nc.nid FROM {node_comment_statistics} nc WHERE nc.comment_count > 0 ORDER BY nc.last_comment_timestamp DESC", 'nc'), 0, $number);
+
+  $nids = array();
+  while ($row = db_fetch_object($result)) {
+    $nids[] = $row->nid;
+  }
+
+  $comments = array();
+  if (!empty($nids)) {
+    // From among the comments on the nodes selected in the first query,
+    // find the $number most recent comments.
+    $result = db_query_range('SELECT c.nid, c.subject, c.cid, c.timestamp FROM {comments} c INNER JOIN {node} n ON n.nid = c.nid WHERE c.nid IN ('. implode(',', $nids) .') AND n.status = 1 AND c.status = %d ORDER BY c.cid DESC', COMMENT_PUBLISHED, 0, $number);
+    while ($comment = db_fetch_object($result)) {
+      $comments[] = $comment;
+    }
+  }
+
+  return $comments;
+}
+
+/**
+ * Calculate page number for first new comment.
+ *
+ * @param $num_comments
+ *   Number of comments.
+ * @param $new_replies
+ *   Number of new replies.
+ * @param $node
+ *   The first new comment node.
+ * @return
+ *   "page=X" if the page number is greater than zero; empty string otherwise.
+ */
+function comment_new_page_count($num_comments, $new_replies, $node) {
+  $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
+  $mode = _comment_get_display_setting('mode', $node);
+  $order = _comment_get_display_setting('sort', $node);
+  $pagenum = NULL;
+  $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
+  if ($num_comments <= $comments_per_page || ($flat && $order == COMMENT_ORDER_NEWEST_FIRST)) {
+    // Only one page of comments or flat forum and newest first.
+    // First new comment will always be on first page.
+    $pageno = 0;
+  }
+  else {
+    if ($flat) {
+      // Flat comments and oldest first.
+      $count = $num_comments - $new_replies;
+    }
+    else {
+      // Threaded comments. See the documentation for comment_render().
+      if ($order == COMMENT_ORDER_NEWEST_FIRST) {
+        // Newest first: find the last thread with new comment
+        $result = db_query('(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1', $node->nid, $new_replies);
+        $thread = db_result($result);
+        $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = 0 AND thread > '". $thread ."'", $node->nid);
+      }
+      else {
+        // Oldest first: find the first thread with new comment
+        $result = db_query('(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1', $node->nid, $new_replies);
+        $thread = substr(db_result($result), 0, -1);
+        $result_count = db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < '". $thread ."'", $node->nid);
+      }
+      $count = db_result($result_count);
+    }
+    $pageno =  $count / $comments_per_page;
+  }
+  if ($pageno >= 1) {
+    $pagenum = "page=". intval($pageno);
+  }
+  return $pagenum;
+}
+
+/**
+ * Returns a formatted list of recent comments to be displayed in the comment block.
+ *
+ * @return
+ *   The comment list HTML.
+ * @ingroup themeable
+ */
+function theme_comment_block() {
+  $items = array();
+  foreach (comment_get_recent() as $comment) {
+    $items[] = l($comment->subject, 'node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid)) .'<br />'. t('@time ago', array('@time' => format_interval(time() - $comment->timestamp)));
+  }
+  if ($items) {
+    return theme('item_list', $items);
+  }
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function comment_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+
+  if ($type == 'node' && $node->comment) {
+
+    if ($teaser) {
+      // Main page: display the number of comments that have been posted.
+
+      if (user_access('access comments')) {
+        $all = comment_num_all($node->nid);
+
+        if ($all) {
+          $links['comment_comments'] = array(
+            'title' => format_plural($all, '1 comment', '@count comments'),
+            'href' => "node/$node->nid",
+            'attributes' => array('title' => t('Jump to the first comment of this posting.')),
+            'fragment' => 'comments'
+          );
+
+          $new = comment_num_new($node->nid);
+
+          if ($new) {
+            $links['comment_new_comments'] = array(
+              'title' => format_plural($new, '1 new comment', '@count new comments'),
+              'href' => "node/$node->nid",
+              'query' => comment_new_page_count($all, $new, $node),
+              'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
+              'fragment' => 'new'
+            );
+          }
+        }
+        else {
+          if ($node->comment == COMMENT_NODE_READ_WRITE) {
+            if (user_access('post comments')) {
+              $links['comment_add'] = array(
+                'title' => t('Add new comment'),
+                'href' => "comment/reply/$node->nid",
+                'attributes' => array('title' => t('Add a new comment to this page.')),
+                'fragment' => 'comment-form'
+              );
+            }
+            else {
+              $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
+            }
+          }
+        }
+      }
+    }
+    else {
+      // Node page: add a "post comment" link if the user is allowed to
+      // post comments, if this node is not read-only, and if the comment form isn't already shown
+
+      if ($node->comment == COMMENT_NODE_READ_WRITE) {
+        if (user_access('post comments')) {
+          if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
+            $links['comment_add'] = array(
+              'title' => t('Add new comment'),
+              'href' => "comment/reply/$node->nid",
+              'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
+              'fragment' => 'comment-form'
+            );
+          }
+        }
+        else {
+          $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
+        }
+      }
+    }
+  }
+
+  if ($type == 'comment') {
+    $links = comment_links($node, $teaser);
+  }
+  if (isset($links['comment_forbidden'])) {
+    $links['comment_forbidden']['html'] = TRUE;
+  }
+
+  return $links;
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function comment_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
+    $form['comment'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Comment settings'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['comment']['comment'] = array(
+      '#type' => 'radios',
+      '#title' => t('Default comment setting'),
+      '#default_value' => variable_get('comment_'. $form['#node_type']->type, COMMENT_NODE_READ_WRITE),
+      '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
+      '#description' => t('Users with the <em>administer comments</em> permission will be able to override this setting.'),
+    );
+    $form['comment']['comment_default_mode'] = array(
+      '#type' => 'radios',
+      '#title' => t('Default display mode'),
+      '#default_value' => variable_get('comment_default_mode_'. $form['#node_type']->type, COMMENT_MODE_THREADED_EXPANDED),
+      '#options' => _comment_get_modes(),
+      '#description' => t('The default view for comments. Expanded views display the body of the comment. Threaded views keep replies together.'),
+    );
+    $form['comment']['comment_default_order'] = array(
+      '#type' => 'radios',
+      '#title' => t('Default display order'),
+      '#default_value' => variable_get('comment_default_order_'. $form['#node_type']->type, COMMENT_ORDER_NEWEST_FIRST),
+      '#options' => _comment_get_orders(),
+      '#description' => t('The default sorting for new users and anonymous users while viewing comments. These users may change their view using the comment control panel. For registered users, this change is remembered as a persistent user preference.'),
+    );
+    $form['comment']['comment_default_per_page'] = array(
+      '#type' => 'select',
+      '#title' => t('Default comments per page'),
+      '#default_value' => variable_get('comment_default_per_page_'. $form['#node_type']->type, 50),
+      '#options' => _comment_per_page(),
+      '#description' => t('Default number of comments for each page: more comments are distributed in several pages.'),
+    );
+    $form['comment']['comment_controls'] = array(
+      '#type' => 'radios',
+      '#title' => t('Comment controls'),
+      '#default_value' => variable_get('comment_controls_'. $form['#node_type']->type, COMMENT_CONTROLS_HIDDEN),
+      '#options' => array(
+        t('Display above the comments'),
+        t('Display below the comments'),
+        t('Display above and below the comments'),
+        t('Do not display')),
+      '#description' => t('Position of the comment controls box. The comment controls let the user change the default display mode and display order of comments.'),
+    );
+    $form['comment']['comment_anonymous'] = array(
+      '#type' => 'radios',
+      '#title' => t('Anonymous commenting'),
+      '#default_value' => variable_get('comment_anonymous_'. $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
+      '#options' => array(
+        COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'),
+        COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'),
+        COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information')),
+      '#description' => t('This option is enabled when anonymous users have permission to post comments on the <a href="@url">permissions page</a>.', array('@url' => url('admin/user/permissions', array('fragment' => 'module-comment')))),
+    );
+    if (!user_access('post comments', drupal_anonymous_user())) {
+      $form['comment']['comment_anonymous']['#disabled'] = TRUE;
+    }
+    $form['comment']['comment_subject_field'] = array(
+      '#type' => 'radios',
+      '#title' => t('Comment subject field'),
+      '#default_value' => variable_get('comment_subject_field_'. $form['#node_type']->type, 1),
+      '#options' => array(t('Disabled'), t('Enabled')),
+      '#description' => t('Can users provide a unique subject for their comments?'),
+    );
+    $form['comment']['comment_preview'] = array(
+      '#type' => 'radios',
+      '#title' => t('Preview comment'),
+      '#default_value' => variable_get('comment_preview_'. $form['#node_type']->type, COMMENT_PREVIEW_REQUIRED),
+      '#options' => array(t('Optional'), t('Required')),
+      '#description' => t("Forces a user to look at their comment by clicking on a 'Preview' button before they can actually add the comment"),
+    );
+    $form['comment']['comment_form_location'] = array(
+      '#type' => 'radios',
+      '#title' => t('Location of comment submission form'),
+      '#default_value' => variable_get('comment_form_location_'. $form['#node_type']->type, COMMENT_FORM_SEPARATE_PAGE),
+      '#options' => array(t('Display on separate page'), t('Display below post or comments')),
+    );
+  }
+  elseif (isset($form['type']) && isset($form['#node'])) {
+    if ($form['type']['#value'] .'_node_form' == $form_id) {
+      $node = $form['#node'];
+      $form['comment_settings'] = array(
+        '#type' => 'fieldset',
+        '#access' => user_access('administer comments'),
+        '#title' => t('Comment settings'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#weight' => 30,
+      );
+      $form['comment_settings']['comment'] = array(
+        '#type' => 'radios',
+        '#parents' => array('comment'),
+        '#default_value' => $node->comment,
+        '#options' => array(t('Disabled'), t('Read only'), t('Read/Write')),
+      );
+    }
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function comment_nodeapi(&$node, $op, $arg = 0) {
+  switch ($op) {
+    case 'load':
+      return db_fetch_array(db_query("SELECT last_comment_timestamp, last_comment_name, comment_count FROM {node_comment_statistics} WHERE nid = %d", $node->nid));
+      break;
+
+    case 'prepare':
+      if (!isset($node->comment)) {
+        $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
+      }
+      break;
+
+    case 'insert':
+      db_query('INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) VALUES (%d, %d, NULL, %d, 0)', $node->nid, $node->changed, $node->uid);
+      break;
+
+    case 'delete':
+      db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
+      db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
+      break;
+
+    case 'update index':
+      $text = '';
+      $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
+      while ($comment = db_fetch_object($comments)) {
+        $text .= '<h2>'. check_plain($comment->subject) .'</h2>'. check_markup($comment->comment, $comment->format, FALSE);
+      }
+      return $text;
+
+    case 'search result':
+      $comments = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $node->nid));
+      return format_plural($comments, '1 comment', '@count comments');
+
+    case 'rss item':
+      if ($node->comment != COMMENT_NODE_DISABLED) {
+        return array(array('key' => 'comments', 'value' => url('node/'. $node->nid, array('fragment' => 'comments', 'absolute' => TRUE))));
+      }
+      else {
+        return array();
+      }
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function comment_user($type, $edit, &$user, $category = NULL) {
+  if ($type == 'delete') {
+    db_query('UPDATE {comments} SET uid = 0 WHERE uid = %d', $user->uid);
+    db_query('UPDATE {node_comment_statistics} SET last_comment_uid = 0 WHERE last_comment_uid = %d', $user->uid);
+  }
+}
+
+/**
+ * This is *not* a hook_access() implementation. This function is called
+ * to determine whether the current user has access to a particular comment.
+ *
+ * Authenticated users can edit their comments as long they have not been
+ * replied to. This prevents people from changing or revising their
+ * statements based on the replies to their posts.
+ *
+ * @param $op
+ *   The operation that is to be performed on the comment. Only 'edit' is recognized now.
+ * @param $comment
+ *   The comment object.
+ * @return
+ *   TRUE if the current user has acces to the comment, FALSE otherwise.
+ */
+function comment_access($op, $comment) {
+  global $user;
+
+  if ($op == 'edit') {
+    return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments');
+  }
+}
+
+/**
+ * A simple helper function.
+ *
+ * @return
+ *   The 0th and the 1st path components joined by a slash.
+ */
+function comment_node_url() {
+  return arg(0) .'/'. arg(1);
+}
+
+/**
+ * Accepts a submission of new or changed comment content.
+ *
+ * @param $edit
+ *   A comment array.
+ *
+ * @return
+ *   If the comment is successfully saved the comment ID is returned. If the comment
+ *   is not saved, FALSE is returned.
+ */
+function comment_save($edit) {
+  global $user;
+  if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) {
+    if (!form_get_errors()) {
+      $edit += array(
+        'mail' => '',
+        'homepage' => '',
+        'name' => '',
+        'status' => user_access('post comments without approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED,
+      );
+      if ($edit['cid']) {
+        // Update the comment in the database.
+        db_query("UPDATE {comments} SET status = %d, timestamp = %d, subject = '%s', comment = '%s', format = %d, uid = %d, name = '%s', mail = '%s', homepage = '%s' WHERE cid = %d", $edit['status'], $edit['timestamp'], $edit['subject'], $edit['comment'], $edit['format'], $edit['uid'], $edit['name'], $edit['mail'], $edit['homepage'], $edit['cid']);
+
+        // Allow modules to respond to the updating of a comment.
+        comment_invoke_comment($edit, 'update');
+
+        // Add an entry to the watchdog log.
+        watchdog('content', 'Comment: updated %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
+      }
+      else {
+        // Add the comment to database.
+        // Here we are building the thread field. See the documentation for
+        // comment_render().
+        if ($edit['pid'] == 0) {
+          // This is a comment with no parent comment (depth 0): we start
+          // by retrieving the maximum thread level.
+          $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $edit['nid']));
+
+          // Strip the "/" from the end of the thread.
+          $max = rtrim($max, '/');
+
+          // Finally, build the thread field for this new comment.
+          $thread = int2vancode(vancode2int($max) + 1) .'/';
+        }
+        else {
+          // This is comment with a parent comment: we increase
+          // the part of the thread value at the proper depth.
+
+          // Get the parent comment:
+          $parent = _comment_load($edit['pid']);
+
+          // Strip the "/" from the end of the parent thread.
+          $parent->thread = (string) rtrim((string) $parent->thread, '/');
+
+          // Get the max value in _this_ thread.
+          $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $edit['nid']));
+
+          if ($max == '') {
+            // First child of this parent.
+            $thread = $parent->thread .'.'. int2vancode(0) .'/';
+          }
+          else {
+            // Strip the "/" at the end of the thread.
+            $max = rtrim($max, '/');
+
+            // We need to get the value at the correct depth.
+            $parts = explode('.', $max);
+            $parent_depth = count(explode('.', $parent->thread));
+            $last = $parts[$parent_depth];
+
+            // Finally, build the thread field for this new comment.
+            $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/';
+          }
+        }
+
+        $edit['timestamp'] = time();
+
+        if ($edit['uid'] === $user->uid) { // '===' because we want to modify anonymous users too
+          $edit['name'] = $user->name;
+        }
+
+        db_query("INSERT INTO {comments} (nid, pid, uid, subject, comment, format, hostname, timestamp, status, thread, name, mail, homepage) VALUES (%d, %d, %d, '%s', '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", $edit['nid'], $edit['pid'], $edit['uid'], $edit['subject'], $edit['comment'], $edit['format'], ip_address(), $edit['timestamp'], $edit['status'], $thread, $edit['name'], $edit['mail'], $edit['homepage']);
+        $edit['cid'] = db_last_insert_id('comments', 'cid');
+
+        // Tell the other modules a new comment has been submitted.
+        comment_invoke_comment($edit, 'insert');
+
+        // Add an entry to the watchdog log.
+        watchdog('content', 'Comment: added %subject.', array('%subject' => $edit['subject']), WATCHDOG_NOTICE, l(t('view'), 'node/'. $edit['nid'], array('fragment' => 'comment-'. $edit['cid'])));
+      }
+      _comment_update_node_statistics($edit['nid']);
+
+      // Clear the cache so an anonymous user can see his comment being added.
+      cache_clear_all();
+
+      // Explain the approval queue if necessary, and then
+      // redirect the user to the node he's commenting on.
+      if ($edit['status'] == COMMENT_NOT_PUBLISHED) {
+        drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
+      }
+      else {
+        comment_invoke_comment($edit, 'publish');
+      }
+      return $edit['cid'];
+    }
+    else {
+      return FALSE;
+    }
+  }
+  else {
+    watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject']), WATCHDOG_WARNING);
+    drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $edit['subject'])), 'error');
+    return FALSE;
+  }
+}
+
+/**
+ * Build command links for a comment (e.g.\ edit, reply, delete) with respect to the current user's access permissions.
+ *
+ * @param $comment
+ *   The comment to which the links will be related.
+ * @param $return
+ *   Not used.
+ * @return
+ *   An associative array containing the links.
+ */
+function comment_links($comment, $return = 1) {
+  global $user;
+
+  $links = array();
+
+  // If we are viewing just this comment, we link back to the node.
+  if ($return) {
+    $links['comment_parent'] = array(
+      'title' => t('parent'),
+      'href' => comment_node_url(),
+      'fragment' => "comment-$comment->cid"
+    );
+  }
+
+  if (node_comment_mode($comment->nid) == COMMENT_NODE_READ_WRITE) {
+    if (user_access('administer comments') && user_access('post comments')) {
+      $links['comment_delete'] = array(
+        'title' => t('delete'),
+        'href' => "comment/delete/$comment->cid"
+      );
+      $links['comment_edit'] = array(
+        'title' => t('edit'),
+        'href' => "comment/edit/$comment->cid"
+      );
+      $links['comment_reply'] = array(
+        'title' => t('reply'),
+        'href' => "comment/reply/$comment->nid/$comment->cid"
+      );
+    }
+    else if (user_access('post comments')) {
+      if (comment_access('edit', $comment)) {
+        $links['comment_edit'] = array(
+          'title' => t('edit'),
+          'href' => "comment/edit/$comment->cid"
+        );
+      }
+      $links['comment_reply'] = array(
+        'title' => t('reply'),
+        'href' => "comment/reply/$comment->nid/$comment->cid"
+      );
+    }
+    else {
+      $node = node_load($comment->nid);
+      $links['comment_forbidden']['title'] = theme('comment_post_forbidden', $node);
+    }
+  }
+
+  return $links;
+}
+
+/**
+ * Renders comment(s).
+ *
+ * @param $node
+ *   The node which comment(s) needs rendering.
+ * @param $cid
+ *   Optional, if given, only one comment is rendered.
+ *
+ * To display threaded comments in the correct order we keep a 'thread' field
+ * and order by that value. This field keeps this data in
+ * a way which is easy to update and convenient to use.
+ *
+ * A "thread" value starts at "1". If we add a child (A) to this comment,
+ * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
+ * brother of (A) will get "1.2". Next brother of the parent of (A) will get
+ * "2" and so on.
+ *
+ * First of all note that the thread field stores the depth of the comment:
+ * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
+ *
+ * Now to get the ordering right, consider this example:
+ *
+ * 1
+ * 1.1
+ * 1.1.1
+ * 1.2
+ * 2
+ *
+ * If we "ORDER BY thread ASC" we get the above result, and this is the
+ * natural order sorted by time. However, if we "ORDER BY thread DESC"
+ * we get:
+ *
+ * 2
+ * 1.2
+ * 1.1.1
+ * 1.1
+ * 1
+ *
+ * Clearly, this is not a natural way to see a thread, and users will get
+ * confused. The natural order to show a thread by time desc would be:
+ *
+ * 2
+ * 1
+ * 1.2
+ * 1.1
+ * 1.1.1
+ *
+ * which is what we already did before the standard pager patch. To achieve
+ * this we simply add a "/" at the end of each "thread" value. This way out
+ * thread fields will look like depicted below:
+ *
+ * 1/
+ * 1.1/
+ * 1.1.1/
+ * 1.2/
+ * 2/
+ *
+ * we add "/" since this char is, in ASCII, higher than every number, so if
+ * now we "ORDER BY thread DESC" we get the correct order. However this would
+ * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
+ * to consider the trailing "/" so we use a substring only.
+ */
+function comment_render($node, $cid = 0) {
+  global $user;
+
+  $output = '';
+
+  if (user_access('access comments')) {
+    // Pre-process variables.
+    $nid = $node->nid;
+    if (empty($nid)) {
+      $nid = 0;
+    }
+
+    $mode = _comment_get_display_setting('mode', $node);
+    $order = _comment_get_display_setting('sort', $node);
+    $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
+
+    if ($cid && is_numeric($cid)) {
+      // Single comment view.
+      $query = 'SELECT c.cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.signature, u.picture, u.data, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d';
+      $query_args = array($cid);
+      if (!user_access('administer comments')) {
+        $query .= ' AND c.status = %d';
+        $query_args[] = COMMENT_PUBLISHED;
+      }
+
+      $query = db_rewrite_sql($query, 'c', 'cid');
+      $result = db_query($query, $query_args);
+
+      if ($comment = db_fetch_object($result)) {
+        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+        $links = module_invoke_all('link', 'comment', $comment, 1);
+        drupal_alter('link', $links, $node);
+
+        $output .= theme('comment_view', $comment, $node, $links);
+      }
+    }
+    else {
+      // Multiple comment view
+      $query_count = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d';
+      $query = 'SELECT c.cid as cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp, c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.signature, u.picture, u.data, c.thread, c.status FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d';
+
+      $query_args = array($nid);
+      if (!user_access('administer comments')) {
+        $query .= ' AND c.status = %d';
+        $query_count .= ' AND status = %d';
+        $query_args[] = COMMENT_PUBLISHED;
+      }
+
+      if ($order == COMMENT_ORDER_NEWEST_FIRST) {
+        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
+          $query .= ' ORDER BY c.cid DESC';
+        }
+        else {
+          $query .= ' ORDER BY c.thread DESC';
+        }
+      }
+      else if ($order == COMMENT_ORDER_OLDEST_FIRST) {
+        if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
+          $query .= ' ORDER BY c.cid';
+        }
+        else {
+          // See comment above. Analysis reveals that this doesn't cost too
+          // much. It scales much much better than having the whole comment
+          // structure.
+          $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))';
+        }
+      }
+      $query = db_rewrite_sql($query, 'c', 'cid');
+      $query_count = db_rewrite_sql($query_count, 'c', 'cid');
+
+      // Start a form, for use with comment control.
+      $result = pager_query($query, $comments_per_page, 0, $query_count, $query_args);
+
+      $divs = 0;
+      $num_rows = FALSE;
+      $comments = '';
+      drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css');
+      while ($comment = db_fetch_object($result)) {
+        $comment = drupal_unpack($comment);
+        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+        $comment->depth = count(explode('.', $comment->thread)) - 1;
+
+        if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) {
+          if ($comment->depth > $divs) {
+            $divs++;
+            $comments .= '<div class="indented">';
+          }
+          else {
+            while ($comment->depth < $divs) {
+              $divs--;
+              $comments .= '</div>';
+            }
+          }
+        }
+
+        if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
+          $comments .= theme('comment_flat_collapsed', $comment, $node);
+        }
+        else if ($mode == COMMENT_MODE_FLAT_EXPANDED) {
+          $comments .= theme('comment_flat_expanded', $comment, $node);
+        }
+        else if ($mode == COMMENT_MODE_THREADED_COLLAPSED) {
+          $comments .= theme('comment_thread_collapsed', $comment, $node);
+        }
+        else if ($mode == COMMENT_MODE_THREADED_EXPANDED) {
+          $comments .= theme('comment_thread_expanded', $comment, $node);
+        }
+
+        $num_rows = TRUE;
+      }
+      while ($divs-- > 0) {
+        $comments .= '</div>';
+      }
+
+      $comment_controls = variable_get('comment_controls_'. $node->type, COMMENT_CONTROLS_HIDDEN);
+      if ($num_rows && ($comment_controls == COMMENT_CONTROLS_ABOVE || $comment_controls == COMMENT_CONTROLS_ABOVE_BELOW)) {
+        $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page);
+      }
+
+      $output .= $comments;
+      $output .= theme('pager', NULL, $comments_per_page, 0);
+
+      if ($num_rows && ($comment_controls == COMMENT_CONTROLS_BELOW || $comment_controls == COMMENT_CONTROLS_ABOVE_BELOW)) {
+        $output .= drupal_get_form('comment_controls', $mode, $order, $comments_per_page);
+      }
+    }
+
+    // If enabled, show new comment form if it's not already being displayed.
+    $reply = arg(0) == 'comment' && arg(1) == 'reply';
+    if (user_access('post comments') && node_comment_mode($nid) == COMMENT_NODE_READ_WRITE && (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW) && !$reply) {
+      $output .= comment_form_box(array('nid' => $nid), t('Post new comment'));
+    }
+
+    $output = theme('comment_wrapper', $output, $node);
+  }
+
+  return $output;
+}
+
+/**
+ * Comment operations. We offer different update operations depending on
+ * which comment administration page we're on.
+ *
+ * @param $action
+ *   The comment administration page.
+ * @return
+ *   An associative array containing the offered operations.
+ */
+function comment_operations($action = NULL) {
+  if ($action == 'publish') {
+    $operations = array(
+      'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'),
+      'delete' => array(t('Delete the selected comments'), '')
+    );
+  }
+  else if ($action == 'unpublish') {
+    $operations = array(
+      'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'),
+      'delete' => array(t('Delete the selected comments'), '')
+    );
+  }
+  else {
+    $operations = array(
+      'publish' => array(t('Publish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_PUBLISHED .' WHERE cid = %d'),
+      'unpublish' => array(t('Unpublish the selected comments'), 'UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d'),
+      'delete' => array(t('Delete the selected comments'), '')
+    );
+  }
+  return $operations;
+}
+
+/**
+ * Misc functions: helpers, privates, history
+ */
+
+/**
+ * Load the entire comment by cid.
+ *
+ * @param $cid
+ *   The identifying comment id.
+ * @return
+ *   The comment object.
+ */
+function _comment_load($cid) {
+  return db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $cid));
+}
+
+/**
+ * Get comment count for a node.
+ *
+ * @param $nid
+ *   The node id.
+ * @return
+ *   The comment count.
+ */
+function comment_num_all($nid) {
+  static $cache;
+
+  if (!isset($cache[$nid])) {
+    $cache[$nid] = db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $nid));
+  }
+  return $cache[$nid];
+}
+
+/**
+ * Get replies count for a comment.
+ *
+ * @param $pid
+ *   The comment id.
+ * @return
+ *   The replies count.
+ */
+function comment_num_replies($pid) {
+  static $cache;
+
+  if (!isset($cache[$pid])) {
+    $cache[$pid] = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE pid = %d AND status = %d', $pid, COMMENT_PUBLISHED));
+  }
+
+  return $cache[$pid];
+}
+
+/**
+ * Get number of new comments for current user and specified node.
+ *
+ * @param $nid
+ *   node-id to count comments for
+ * @param $timestamp
+ *   time to count from (defaults to time of last user access
+ *   to node)
+ */
+function comment_num_new($nid, $timestamp = 0) {
+  global $user;
+
+  if ($user->uid) {
+    // Retrieve the timestamp at which the current user last viewed the
+    // specified node.
+    if (!$timestamp) {
+      $timestamp = node_last_viewed($nid);
+    }
+    $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT);
+
+    // Use the timestamp to retrieve the number of new comments.
+    $result = db_result(db_query('SELECT COUNT(c.cid) FROM {node} n INNER JOIN {comments} c ON n.nid = c.nid WHERE n.nid = %d AND timestamp > %d AND c.status = %d', $nid, $timestamp, COMMENT_PUBLISHED));
+
+    return $result;
+  }
+  else {
+    return 0;
+  }
+
+}
+
+/**
+ * Validate comment data.
+ *
+ * @param $edit
+ *   An associative array containig the comment data.
+ * @return
+ *   The original $edit.
+ */
+function comment_validate($edit) {
+  global $user;
+
+  // Invoke other validation handlers
+  comment_invoke_comment($edit, 'validate');
+
+  if (isset($edit['date'])) {
+    // As of PHP 5.1.0, strtotime returns FALSE upon failure instead of -1.
+    if (strtotime($edit['date']) <= 0) {
+      form_set_error('date', t('You have to specify a valid date.'));
+    }
+  }
+  if (isset($edit['author']) && !$account = user_load(array('name' => $edit['author']))) {
+    form_set_error('author', t('You have to specify a valid author.'));
+  }
+
+  // Check validity of name, mail and homepage (if given)
+  if (!$user->uid || isset($edit['is_anonymous'])) {
+    $node = node_load($edit['nid']);
+    if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) > COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
+      if ($edit['name']) {
+        $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = '%s'", $edit['name']));
+
+        if ($taken != 0) {
+          form_set_error('name', t('The name you used belongs to a registered user.'));
+        }
+
+      }
+      else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
+        form_set_error('name', t('You have to leave your name.'));
+      }
+
+      if ($edit['mail']) {
+        if (!valid_email_address($edit['mail'])) {
+          form_set_error('mail', t('The e-mail address you specified is not valid.'));
+        }
+      }
+      else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
+        form_set_error('mail', t('You have to leave an e-mail address.'));
+      }
+
+      if ($edit['homepage']) {
+        if (!valid_url($edit['homepage'], TRUE)) {
+          form_set_error('homepage', t('The URL of your homepage is not valid. Remember that it must be fully qualified, i.e. of the form <code>http://example.com/directory</code>.'));
+        }
+      }
+    }
+  }
+
+  return $edit;
+}
+
+/**
+ * Generate the basic commenting form, for appending to a node or display on a separate page.
+ *
+ * @param $title
+ *   Not used.
+ * @ingroup forms
+ * @see comment_form_validate()
+ * @see comment_form_submit()
+ */
+function comment_form(&$form_state, $edit, $title = NULL) {
+  global $user;
+
+  $op = isset($_POST['op']) ? $_POST['op'] : '';
+  $node = node_load($edit['nid']);
+
+  if (!$user->uid && variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
+    drupal_add_js(drupal_get_path('module', 'comment') .'/comment.js');
+  }
+  $edit += array('name' => '', 'mail' => '', 'homepage' => '');
+  if ($user->uid) {
+    if (!empty($edit['cid']) && user_access('administer comments')) {
+      if (!empty($edit['author'])) {
+        $author = $edit['author'];
+      }
+      elseif (!empty($edit['name'])) {
+        $author = $edit['name'];
+      }
+      else {
+        $author = $edit['registered_name'];
+      }
+
+      if (!empty($edit['status'])) {
+        $status = $edit['status'];
+      }
+      else {
+        $status = 0;
+      }
+
+      if (!empty($edit['date'])) {
+        $date = $edit['date'];
+      }
+      else {
+        $date = format_date($edit['timestamp'], 'custom', 'Y-m-d H:i O');
+      }
+
+      $form['admin'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Administration'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#weight' => -2,
+      );
+
+      if ($edit['registered_name'] != '') {
+        // The comment is by a registered user
+        $form['admin']['author'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Authored by'),
+          '#size' => 30,
+          '#maxlength' => 60,
+          '#autocomplete_path' => 'user/autocomplete',
+          '#default_value' => $author,
+          '#weight' => -1,
+        );
+      }
+      else {
+        // The comment is by an anonymous user
+        $form['is_anonymous'] = array(
+          '#type' => 'value',
+          '#value' => TRUE,
+        );
+        $form['admin']['name'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Authored by'),
+          '#size' => 30,
+          '#maxlength' => 60,
+          '#default_value' => $author,
+          '#weight' => -1,
+        );
+        $form['admin']['mail'] = array(
+          '#type' => 'textfield',
+          '#title' => t('E-mail'),
+          '#maxlength' => 64,
+          '#size' => 30,
+          '#default_value' => $edit['mail'],
+          '#description' => t('The content of this field is kept private and will not be shown publicly.'),
+        );
+
+        $form['admin']['homepage'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Homepage'),
+          '#maxlength' => 255,
+          '#size' => 30,
+          '#default_value' => $edit['homepage'],
+        );
+      }
+
+      $form['admin']['date'] = array('#type' => 'textfield', '#parents' => array('date'), '#title' => t('Authored on'), '#size' => 20, '#maxlength' => 25, '#default_value' => $date, '#weight' => -1);
+
+      $form['admin']['status'] = array('#type' => 'radios', '#parents' => array('status'), '#title' => t('Status'), '#default_value' =>  $status, '#options' => array(t('Published'), t('Not published')), '#weight' => -1);
+
+    }
+    else {
+      $form['_author'] = array('#type' => 'item', '#title' => t('Your name'), '#value' => theme('username', $user)
+      );
+      $form['author'] = array('#type' => 'value', '#value' => $user->name);
+    }
+  }
+  else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MAY_CONTACT) {
+    $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous'))
+    );
+
+    $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.')
+    );
+
+    $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']);
+  }
+  else if (variable_get('comment_anonymous_'. $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) == COMMENT_ANONYMOUS_MUST_CONTACT) {
+    $form['name'] = array('#type' => 'textfield', '#title' => t('Your name'), '#maxlength' => 60, '#size' => 30, '#default_value' => $edit['name'] ? $edit['name'] : variable_get('anonymous', t('Anonymous')), '#required' => TRUE);
+
+    $form['mail'] = array('#type' => 'textfield', '#title' => t('E-mail'), '#maxlength' => 64, '#size' => 30, '#default_value' => $edit['mail'], '#description' => t('The content of this field is kept private and will not be shown publicly.'), '#required' => TRUE);
+
+    $form['homepage'] = array('#type' => 'textfield', '#title' => t('Homepage'), '#maxlength' => 255, '#size' => 30, '#default_value' => $edit['homepage']);
+  }
+
+  if (variable_get('comment_subject_field_'. $node->type, 1) == 1) {
+    $form['subject'] = array('#type' => 'textfield', '#title' => t('Subject'), '#maxlength' => 64, '#default_value' => !empty($edit['subject']) ? $edit['subject'] : '');
+  }
+
+  if (!empty($edit['comment'])) {
+    $default = $edit['comment'];
+  }
+  else {
+    $default = '';
+  }
+
+  $form['comment_filter']['comment'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Comment'),
+    '#rows' => 15,
+    '#default_value' => $default,
+    '#required' => TRUE,
+  );
+  if (!isset($edit['format'])) {
+    $edit['format'] = FILTER_FORMAT_DEFAULT;
+  }
+  $form['comment_filter']['format'] = filter_form($edit['format']);
+
+  $form['cid'] = array('#type' => 'value', '#value' => !empty($edit['cid']) ? $edit['cid'] : NULL);
+  $form['pid'] = array('#type' => 'value', '#value' => !empty($edit['pid']) ? $edit['pid'] : NULL);
+  $form['nid'] = array('#type' => 'value', '#value' => $edit['nid']);
+  $form['uid'] = array('#type' => 'value', '#value' => !empty($edit['uid']) ? $edit['uid'] : NULL);
+
+  // Only show save button if preview is optional or if we are in preview mode.
+  // We show the save button in preview mode even if there are form errors so that
+  // optional form elements (e.g., captcha) can be updated in preview mode.
+  if (!form_get_errors() && ((variable_get('comment_preview_'. $node->type, COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) || ($op == t('Preview')) || ($op == t('Save')))) {
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 19);
+  }
+
+  $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 20);
+  $form['#token'] = 'comment'. $edit['nid'] . (isset($edit['pid']) ? $edit['pid'] : '');
+
+  if ($op == t('Preview')) {
+    $form['#after_build'] = array('comment_form_add_preview');
+  }
+
+  if (empty($edit['cid']) && empty($edit['pid'])) {
+    $form['#action'] = url('comment/reply/'. $edit['nid']);
+  }
+
+  return $form;
+}
+
+/**
+ * Theme the comment form box.
+ *
+ * @param $edit
+ *   The form structure.
+ * @param $title
+ *   The form title.
+ */
+function comment_form_box($edit, $title = NULL) {
+  return theme('box', $title, drupal_get_form('comment_form', $edit, $title));
+}
+
+/**
+ * Form builder; Generate and validate a comment preview form.
+ *
+ * @ingroup forms
+ */
+function comment_form_add_preview($form, &$form_state) {
+  global $user;
+  $edit = $form_state['values'];
+  drupal_set_title(t('Preview comment'));
+
+  $output = '';
+  $node = node_load($edit['nid']);
+
+  // Invoke full validation for the form, to protect against cross site
+  // request forgeries (CSRF) and setting arbitrary values for fields such as
+  // the input format. Preview the comment only when form validation does not
+  // set any errors.
+  drupal_validate_form($form['form_id']['#value'], $form, $form_state);
+  if (!form_get_errors()) {
+    _comment_form_submit($edit);
+    $comment = (object)$edit;
+
+    // Attach the user and time information.
+    if (!empty($edit['author'])) {
+      $account = user_load(array('name' => $edit['author']));
+    }
+    elseif ($user->uid && !isset($edit['is_anonymous'])) {
+      $account = $user;
+    }
+    if (!empty($account)) {
+      $comment->uid = $account->uid;
+      $comment->name = check_plain($account->name);
+    }
+    elseif (empty($comment->name)) {
+      $comment->name = variable_get('anonymous', t('Anonymous'));
+    }
+    $comment->timestamp = !empty($edit['timestamp']) ? $edit['timestamp'] : time();
+    $output .= theme('comment_view', $comment, $node);
+  }
+  $form['comment_preview'] = array(
+    '#value' => $output,
+    '#weight' => -100,
+    '#prefix' => '<div class="preview">',
+    '#suffix' => '</div>',
+  );
+
+  $output = '';
+
+  if ($edit['pid']) {
+    $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $edit['pid'], COMMENT_PUBLISHED));
+    $comment = drupal_unpack($comment);
+    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+    $output .= theme('comment_view', $comment, $node);
+  }
+  else {
+    $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
+    $form['#suffix'] = $suffix . node_view($node);
+    $edit['pid'] = 0;
+  }
+
+  $form['comment_preview_below'] = array('#value' => $output, '#weight' => 100);
+
+  return $form;
+}
+
+/**
+ * Validate comment form submissions.
+ */
+function comment_form_validate($form, &$form_state) {
+  global $user;
+  if ($user->uid === 0) {
+    foreach (array('name', 'homepage', 'mail') as $field) {
+      // Set cookie for 365 days.
+      if (isset($form_state['values'][$field])) {
+        setcookie('comment_info_'. $field, $form_state['values'][$field], time() + 31536000, '/');
+      }
+    }
+  }
+  comment_validate($form_state['values']);
+}
+
+/**
+ * Prepare a comment for submission.
+ *
+ * @param $comment_values
+ *   An associative array containing the comment data.
+ */
+function _comment_form_submit(&$comment_values) {
+  $comment_values += array('subject' => '');
+  if (!isset($comment_values['date'])) {
+    $comment_values['date'] = 'now';
+  }
+  $comment_values['timestamp'] = strtotime($comment_values['date']);
+  if (isset($comment_values['author'])) {
+    $account = user_load(array('name' => $comment_values['author']));
+    $comment_values['uid'] = $account->uid;
+    $comment_values['name'] = $comment_values['author'];
+  }
+  // Validate the comment's subject. If not specified, extract
+  // one from the comment's body.
+  if (trim($comment_values['subject']) == '') {
+    // The body may be in any format, so we:
+    // 1) Filter it into HTML
+    // 2) Strip out all HTML tags
+    // 3) Convert entities back to plain-text.
+    // Note: format is checked by check_markup().
+    $comment_values['subject'] = trim(truncate_utf8(decode_entities(strip_tags(check_markup($comment_values['comment'], $comment_values['format']))), 29, TRUE));
+    // Edge cases where the comment body is populated only by HTML tags will
+    // require a default subject.
+    if ($comment_values['subject'] == '') {
+      $comment_values['subject'] = t('(No subject)');
+    }
+  }
+}
+
+/**
+ * Process comment form submissions; prepare the comment, store it, and set a redirection target.
+ */
+function comment_form_submit($form, &$form_state) {
+  _comment_form_submit($form_state['values']);
+  if ($cid = comment_save($form_state['values'])) {
+    $node = node_load($form_state['values']['nid']);
+    $page = comment_new_page_count($node->comment_count, 1, $node);
+    $form_state['redirect'] = array('node/'. $node->nid, $page, "comment-$cid");
+    return;
+  }
+}
+
+/**
+ * Theme a single comment block.
+ *
+ * @param $comment
+ *   The comment object.
+ * @param $node
+ *   The comment node.
+ * @param $links
+ *   An associative array containing control links.
+ * @param $visible
+ *   Switches between folded/unfolded view.
+ * @ingroup themeable
+ */
+function theme_comment_view($comment, $node, $links = array(), $visible = TRUE) {
+  static $first_new = TRUE;
+
+  $output = '';
+  $comment->new = node_mark($comment->nid, $comment->timestamp);
+  if ($first_new && $comment->new != MARK_READ) {
+    // Assign the anchor only for the first new comment. This avoids duplicate
+    // id attributes on a page.
+    $first_new = FALSE;
+    $output .= "<a id=\"new\"></a>\n";
+  }
+
+  $output .= "<a id=\"comment-$comment->cid\"></a>\n";
+
+  // Switch to folded/unfolded view of the comment
+  if ($visible) {
+    $comment->comment = check_markup($comment->comment, $comment->format, FALSE);
+
+    // Comment API hook
+    comment_invoke_comment($comment, 'view');
+
+    $output .= theme('comment', $comment, $node, $links);
+  }
+  else {
+    $output .= theme('comment_folded', $comment);
+  }
+
+  return $output;
+}
+
+
+/**
+ * Build a comment control form.
+ *
+ * @param $mode
+ *   Comment display mode.
+ * @param $order
+ *   Comment order mode.
+ * @param $comments_per_page
+ *   Comments per page.
+ * @ingroup forms
+ */
+function comment_controls($mode = COMMENT_MODE_THREADED_EXPANDED, $order = COMMENT_ORDER_NEWEST_FIRST, $comments_per_page = 50) {
+  $form['mode'] = array('#type' => 'select',
+    '#default_value' => $mode,
+    '#options' => _comment_get_modes(),
+    '#weight' => 1,
+  );
+  $form['order'] = array(
+    '#type' => 'select',
+    '#default_value' => $order,
+    '#options' => _comment_get_orders(),
+    '#weight' => 2,
+  );
+  foreach (_comment_per_page() as $i) {
+    $options[$i] = t('!a comments per page', array('!a' => $i));
+  }
+  $form['comments_per_page'] = array('#type' => 'select',
+    '#default_value' => $comments_per_page,
+    '#options' => $options,
+    '#weight' => 3,
+  );
+
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Save settings'),
+    '#weight' => 20,
+  );
+
+  return $form;
+}
+
+/**
+ * Theme comment controls box where the user can change the default display mode and display order of comments.
+ *
+ * @param $form
+ *   The form structure.
+ * @ingroup themeable
+ */
+function theme_comment_controls($form) {
+  $output = '<div class="container-inline">';
+  $output .=  drupal_render($form);
+  $output .= '</div>';
+  $output .= '<div class="description">'. t('Select your preferred way to display the comments and click "Save settings" to activate your changes.') .'</div>';
+  return theme('box', t('Comment viewing options'), $output);
+}
+
+/**
+ * Process comment_controls form submissions.
+ */
+function comment_controls_submit($form, &$form_state) {
+  global $user;
+
+  $mode = $form_state['values']['mode'];
+  $order = $form_state['values']['order'];
+  $comments_per_page = $form_state['values']['comments_per_page'];
+
+  if ($user->uid) {
+    $account = user_save($user, array('mode' => $mode, 'sort' => $order, 'comments_per_page' => $comments_per_page));
+    // Terminate if an error occured during user_save().
+    if (!$account) {
+      drupal_set_message(t("Error saving user account."), 'error');
+      return;
+    }
+    $user = $account;
+  }
+  else {
+    $_SESSION['comment_mode'] = $mode;
+    $_SESSION['comment_sort'] = $order;
+    $_SESSION['comment_comments_per_page'] = $comments_per_page;
+  }
+}
+
+/**
+ * Process variables for comment.tpl.php.
+ *
+ * @see comment.tpl.php
+ * @see theme_comment()
+ */
+function template_preprocess_comment(&$variables) {
+  $comment = $variables['comment'];
+  $node = $variables['node'];
+  $variables['author']    = theme('username', $comment);
+  $variables['content']   = $comment->comment;
+  $variables['date']      = format_date($comment->timestamp);
+  $variables['links']     = isset($variables['links']) ? theme('links', $variables['links']) : '';
+  $variables['new']       = $comment->new ? t('new') : '';
+  $variables['picture']   = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', $comment) : '';
+  $variables['signature'] = $comment->signature;
+  $variables['submitted'] = theme('comment_submitted', $comment);
+  $variables['title']     = l($comment->subject, $_GET['q'], array('fragment' => "comment-$comment->cid"));
+  $variables['template_files'][] = 'comment-'. $node->type;
+  // set status to a string representation of comment->status.
+  if (isset($comment->preview)) {
+    $variables['status']  = 'comment-preview';
+  }
+  else {
+    $variables['status']  = ($comment->status == COMMENT_NOT_PUBLISHED) ? 'comment-unpublished' : 'comment-published';
+  }
+}
+
+/**
+ * Process variables for comment-folded.tpl.php.
+ *
+ * @see comment-folded.tpl.php
+ * @see theme_comment_folded()
+ */
+function template_preprocess_comment_folded(&$variables) {
+  $comment = $variables['comment'];
+  $variables['author'] = theme('username', $comment);
+  $variables['date']   = format_date($comment->timestamp);
+  $variables['new']    = $comment->new ? t('new') : '';
+  $variables['title']  = l($comment->subject, comment_node_url() .'/'. $comment->cid, array('fragment' => "comment-$comment->cid"));
+}
+
+/**
+ * Theme comment flat collapsed view.
+ *
+ * @param $comment
+ *   The comment to be themed.
+ * @param $node
+ *   The comment node.
+ * @ingroup themeable
+ */
+function theme_comment_flat_collapsed($comment, $node) {
+  return theme('comment_view', $comment, $node, '', 0);
+}
+
+/**
+ * Theme comment flat expanded view.
+ *
+ * @param $comment
+ *   The comment to be themed.
+ * @param $node
+ *   The comment node.
+ * @ingroup themeable
+ */
+function theme_comment_flat_expanded($comment, $node) {
+  return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
+}
+
+/**
+ * Theme comment thread collapsed view.
+ *
+ * @param $comment
+ *   The comment to be themed.
+ * @param $node
+ *   The comment node.
+ * @ingroup themeable
+ */
+function theme_comment_thread_collapsed($comment, $node) {
+  return theme('comment_view', $comment, $node, '', 0);
+}
+
+/**
+ * Theme comment thread expanded view.
+ *
+ * @param $comment
+ *   The comment to be themed.
+ * @param $node
+ *   The comment node.
+ * @ingroup themeable
+ */
+function theme_comment_thread_expanded($comment, $node) {
+  return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
+}
+
+/**
+ * Theme a "you can't post comments" notice.
+ *
+ * @param $node
+ *   The comment node.
+ * @ingroup themeable
+ */
+function theme_comment_post_forbidden($node) {
+  global $user;
+  static $authenticated_post_comments;
+  
+  if (!$user->uid) {
+    if (!isset($authenticated_post_comments)) {
+      // We only output any link if we are certain, that users get permission
+      // to post comments by logging in. We also locally cache this information.
+      $authenticated_post_comments = array_key_exists(DRUPAL_AUTHENTICATED_RID, user_roles(TRUE, 'post comments') + user_roles(TRUE, 'post comments without approval'));
+    }
+    
+    if ($authenticated_post_comments) {
+      // We cannot use drupal_get_destination() because these links
+      // sometimes appear on /node and taxonomy listing pages.
+      if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
+        $destination = 'destination='. drupal_urlencode("comment/reply/$node->nid#comment-form");
+      }
+      else {
+        $destination = 'destination='. drupal_urlencode("node/$node->nid#comment-form");
+      }
+
+      if (variable_get('user_register', 1)) {
+        // Users can register themselves.
+        return t('<a href="@login">Login</a> or <a href="@register">register</a> to post comments', array('@login' => url('user/login', array('query' => $destination)), '@register' => url('user/register', array('query' => $destination))));
+      }
+      else {
+        // Only admins can add new users, no public registration.
+        return t('<a href="@login">Login</a> to post comments', array('@login' => url('user/login', array('query' => $destination))));
+      }
+    }
+  }
+}
+
+/**
+ * Process variables for comment-wrapper.tpl.php.
+ *
+ * @see comment-wrapper.tpl.php
+ * @see theme_comment_wrapper()
+ */
+function template_preprocess_comment_wrapper(&$variables) {
+  // Provide contextual information.
+  $variables['display_mode']  = _comment_get_display_setting('mode', $variables['node']);
+  $variables['display_order'] = _comment_get_display_setting('sort', $variables['node']);
+  $variables['comment_controls_state'] = variable_get('comment_controls_'. $variables['node']->type, COMMENT_CONTROLS_HIDDEN);
+  $variables['template_files'][] = 'comment-wrapper-'. $variables['node']->type;
+}
+
+/**
+ * Theme a "Submitted by ..." notice.
+ *
+ * @param $comment
+ *   The comment.
+ * @ingroup themeable
+ */
+function theme_comment_submitted($comment) {
+  return t('Submitted by !username on @datetime.',
+    array(
+      '!username' => theme('username', $comment),
+      '@datetime' => format_date($comment->timestamp)
+    ));
+}
+
+/**
+ * Return an array of viewing modes for comment listings.
+ *
+ * We can't use a global variable array because the locale system
+ * is not initialized yet when the comment module is loaded.
+ */
+function _comment_get_modes() {
+  return array(
+    COMMENT_MODE_FLAT_COLLAPSED => t('Flat list - collapsed'),
+    COMMENT_MODE_FLAT_EXPANDED => t('Flat list - expanded'),
+    COMMENT_MODE_THREADED_COLLAPSED => t('Threaded list - collapsed'),
+    COMMENT_MODE_THREADED_EXPANDED => t('Threaded list - expanded')
+  );
+}
+
+/**
+ * Return an array of viewing orders for comment listings.
+ *
+ * We can't use a global variable array because the locale system
+ * is not initialized yet when the comment module is loaded.
+ */
+function _comment_get_orders() {
+  return array(
+    COMMENT_ORDER_NEWEST_FIRST => t('Date - newest first'),
+    COMMENT_ORDER_OLDEST_FIRST => t('Date - oldest first')
+  );
+}
+
+/**
+ * Return an array of "comments per page" settings from which the user
+ * can choose.
+ */
+function _comment_per_page() {
+  return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300));
+}
+
+/**
+ * Return a current comment display setting
+ *
+ * @param $setting
+ *   can be one of these: 'mode', 'sort', 'comments_per_page'
+ * @param $node
+ *   The comment node in question.
+ */
+function _comment_get_display_setting($setting, $node) {
+  global $user;
+
+  if (isset($_GET[$setting])) {
+    $value = $_GET[$setting];
+  }
+  else {
+    // get the setting's site default
+    switch ($setting) {
+      case 'mode':
+        $default = variable_get('comment_default_mode_'. $node->type, COMMENT_MODE_THREADED_EXPANDED);
+        break;
+      case 'sort':
+        $default = variable_get('comment_default_order_'. $node->type, COMMENT_ORDER_NEWEST_FIRST);
+        break;
+      case 'comments_per_page':
+        $default = variable_get('comment_default_per_page_'. $node->type, 50);
+    }
+    if (variable_get('comment_controls_'. $node->type, COMMENT_CONTROLS_HIDDEN) == COMMENT_CONTROLS_HIDDEN) {
+      // if comment controls are disabled use site default
+      $value = $default;
+    }
+    else {
+      // otherwise use the user's setting if set
+      if (isset($user->$setting) && $user->$setting) {
+        $value = $user->$setting;
+      }
+      else if (isset($_SESSION['comment_'. $setting]) && $_SESSION['comment_'. $setting]) {
+        $value = $_SESSION['comment_'. $setting];
+      }
+      else {
+        $value = $default;
+      }
+    }
+  }
+  return $value;
+}
+
+/**
+ * Updates the comment statistics for a given node. This should be called any
+ * time a comment is added, deleted, or updated.
+ *
+ * The following fields are contained in the node_comment_statistics table.
+ * - last_comment_timestamp: the timestamp of the last comment for this node or the node create stamp if no comments exist for the node.
+ * - last_comment_name: the name of the anonymous poster for the last comment
+ * - last_comment_uid: the uid of the poster for the last comment for this node or the node authors uid if no comments exists for the node.
+ * - comment_count: the total number of approved/published comments on this node.
+ */
+function _comment_update_node_statistics($nid) {
+  $count = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE nid = %d AND status = %d', $nid, COMMENT_PUBLISHED));
+
+  // comments exist
+  if ($count > 0) {
+    $last_reply = db_fetch_object(db_query_range('SELECT cid, name, timestamp, uid FROM {comments} WHERE nid = %d AND status = %d ORDER BY cid DESC', $nid, COMMENT_PUBLISHED, 0, 1));
+    db_query("UPDATE {node_comment_statistics} SET comment_count = %d, last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d WHERE nid = %d", $count, $last_reply->timestamp, $last_reply->uid ? '' : $last_reply->name, $last_reply->uid, $nid);
+  }
+
+  // no comments
+  else {
+    $node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid));
+    db_query("UPDATE {node_comment_statistics} SET comment_count = 0, last_comment_timestamp = %d, last_comment_name = '', last_comment_uid = %d WHERE nid = %d", $node->created, $node->uid, $nid);
+  }
+}
+
+/**
+ * Invoke a hook_comment() operation in all modules.
+ *
+ * @param &$comment
+ *   A comment object.
+ * @param $op
+ *   A string containing the name of the comment operation.
+ * @return
+ *   The returned value of the invoked hooks.
+ */
+function comment_invoke_comment(&$comment, $op) {
+  $return = array();
+  foreach (module_implements('comment') as $name) {
+    $function = $name .'_comment';
+    $result = $function($comment, $op);
+    if (isset($result) && is_array($result)) {
+      $return = array_merge($return, $result);
+    }
+    else if (isset($result)) {
+      $return[] = $result;
+    }
+  }
+  return $return;
+}
+
+/**
+ * Generate vancode.
+ *
+ * Consists of a leading character indicating length, followed by N digits
+ * with a numerical value in base 36. Vancodes can be sorted as strings
+ * without messing up numerical order.
+ *
+ * It goes:
+ * 00, 01, 02, ..., 0y, 0z,
+ * 110, 111, ... , 1zy, 1zz,
+ * 2100, 2101, ..., 2zzy, 2zzz,
+ * 31000, 31001, ...
+ */
+function int2vancode($i = 0) {
+  $num = base_convert((int)$i, 10, 36);
+  $length = strlen($num);
+  return chr($length + ord('0') - 1) . $num;
+}
+
+/**
+ * Decode vancode back to an integer.
+ */
+function vancode2int($c = '00') {
+  return base_convert(substr($c, 1), 36, 10);
+}
+
+/**
+ * Implementation of hook_hook_info().
+ */
+function comment_hook_info() {
+  return array(
+    'comment' => array(
+      'comment' => array(
+        'insert' => array(
+          'runs when' => t('After saving a new comment'),
+        ),
+        'update' => array(
+          'runs when' => t('After saving an updated comment'),
+        ),
+        'delete' => array(
+          'runs when' => t('After deleting a comment')
+        ),
+        'view' => array(
+          'runs when' => t('When a comment is being viewed by an authenticated user')
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_action_info().
+ */
+function comment_action_info() {
+  return array(
+    'comment_unpublish_action' => array(
+      'description' => t('Unpublish comment'),
+      'type' => 'comment',
+      'configurable' => FALSE,
+      'hooks' => array(
+        'comment' => array('insert', 'update'),
+      )
+    ),
+    'comment_unpublish_by_keyword_action' => array(
+      'description' => t('Unpublish comment containing keyword(s)'),
+      'type' => 'comment',
+      'configurable' => TRUE,
+      'hooks' => array(
+        'comment' => array('insert', 'update'),
+      )
+    )
+  );
+}
+
+/**
+ * Drupal action to unpublish a comment.
+ *
+ * @param $context
+ *   Keyed array. Must contain the id of the comment if $comment is not passed.
+ * @param $comment
+ *   An optional comment object.
+ */
+function comment_unpublish_action($comment, $context = array()) {
+  if (isset($comment->cid)) {
+    $cid = $comment->cid;
+    $subject = $comment->subject;
+  }
+  else {
+    $cid = $context['cid'];
+    $subject = db_result(db_query("SELECT subject FROM {comments} WHERE cid = %d", $cid));
+  }
+  db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_NOT_PUBLISHED, $cid);
+  watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
+}
+
+/**
+ * Form builder; Prepare a form for blacklisted keywords.
+ *
+ * @ingroup forms
+ */
+function comment_unpublish_by_keyword_action_form($context) {
+  $form['keywords'] = array(
+    '#title' => t('Keywords'),
+    '#type' => 'textarea',
+    '#description' => t('The comment will be unpublished if it contains any of the character sequences above. Use a comma-separated list of character sequences. Example: funny, bungee jumping, "Company, Inc.". Character sequences are case-sensitive.'),
+    '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
+  );
+  return $form;
+}
+
+/**
+ * Process comment_unpublish_by_keyword_action_form form submissions.
+ */
+function comment_unpublish_by_keyword_action_submit($form, $form_state) {
+  return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
+}
+
+/**
+ * Implementation of a configurable Drupal action.
+ * Unpublish a comment if it contains a certain string.
+ *
+ * @param $context
+ *   An array providing more information about the context of the call to this action.
+ *   Unused here since this action currently only supports the insert and update ops of
+ *   the comment hook, both of which provide a complete $comment object.
+ * @param $comment
+ *   A comment object.
+ */
+function comment_unpublish_by_keyword_action($comment, $context) {
+  foreach ($context['keywords'] as $keyword) {
+    if (strstr($comment->comment, $keyword) || strstr($comment->subject, $keyword)) {
+      db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_NOT_PUBLISHED, $comment->cid);
+      watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
+      break;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,116 @@
+<?php
+// $Id: comment.pages.inc,v 1.2.2.1 2008/02/07 18:53:38 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the comment module.
+ */
+
+/**
+ * Form builder; generate a comment editing form.
+ *
+ * @param $cid
+ *   ID of the comment to be edited.
+ * @ingroup forms
+ */
+function comment_edit($cid) {
+  global $user;
+
+  $comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d', $cid));
+  $comment = drupal_unpack($comment);
+  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+  if (comment_access('edit', $comment)) {
+    return comment_form_box((array)$comment);
+  }
+  else {
+    drupal_access_denied();
+  }
+}
+
+/**
+ * This function is responsible for generating a comment reply form.
+ * There are several cases that have to be handled, including:
+ *   - replies to comments
+ *   - replies to nodes
+ *   - attempts to reply to nodes that can no longer accept comments
+ *   - respecting access permissions ('access comments', 'post comments', etc.)
+ *
+ * The node or comment that is being replied to must appear above the comment
+ * form to provide the user context while authoring the comment.
+ *
+ * @param $node
+ *   Every comment belongs to a node. This is that node.
+ *
+ * @param $pid
+ *   Some comments are replies to other comments. In those cases, $pid is the parent
+ *   comment's cid.
+ *
+ * @return
+ *   The rendered parent node or comment plus the new comment form.
+ */
+function comment_reply($node, $pid = NULL) {
+  // Set the breadcrumb trail.
+  drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, 'node/'. $node->nid)));
+  $op = isset($_POST['op']) ? $_POST['op'] : '';
+
+  $output = '';
+
+  if (user_access('access comments')) {
+    // The user is previewing a comment prior to submitting it.
+    if ($op == t('Preview')) {
+      if (user_access('post comments')) {
+        $output .= comment_form_box(array('pid' => $pid, 'nid' => $node->nid), NULL);
+      }
+      else {
+        drupal_set_message(t('You are not authorized to post comments.'), 'error');
+        drupal_goto("node/$node->nid");
+      }
+    }
+    else {
+      // $pid indicates that this is a reply to a comment.
+      if ($pid) {
+        // load the comment whose cid = $pid
+        if ($comment = db_fetch_object(db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.picture, u.data FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = %d AND c.status = %d', $pid, COMMENT_PUBLISHED))) {
+          // If that comment exists, make sure that the current comment and the parent comment both
+          // belong to the same parent node.
+          if ($comment->nid != $node->nid) {
+            // Attempting to reply to a comment not belonging to the current nid.
+            drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
+            drupal_goto("node/$node->nid");
+          }
+          // Display the parent comment
+          $comment = drupal_unpack($comment);
+          $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+          $output .= theme('comment_view', $comment, $node);
+        }
+        else {
+          drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
+          drupal_goto("node/$node->nid");
+        }
+      }
+      // This is the case where the comment is in response to a node. Display the node.
+      else if (user_access('access content')) {
+        $output .= node_view($node);
+      }
+
+      // Should we show the reply box?
+      if (node_comment_mode($node->nid) != COMMENT_NODE_READ_WRITE) {
+        drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error');
+        drupal_goto("node/$node->nid");
+      }
+      else if (user_access('post comments')) {
+        $output .= comment_form_box(array('pid' => $pid, 'nid' => $node->nid), t('Reply'));
+      }
+      else {
+        drupal_set_message(t('You are not authorized to post comments.'), 'error');
+        drupal_goto("node/$node->nid");
+      }
+    }
+  }
+  else {
+    drupal_set_message(t('You are not authorized to view comments.'), 'error');
+    drupal_goto("node/$node->nid");
+  }
+
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/comment/comment.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,52 @@
+<?php
+// $Id: comment.tpl.php,v 1.4 2008/01/04 19:24:23 goba Exp $
+
+/**
+ * @file comment.tpl.php
+ * Default theme implementation for comments.
+ *
+ * Available variables:
+ * - $author: Comment author. Can be link or plain text.
+ * - $content: Body of the post.
+ * - $date: Date and time of posting.
+ * - $links: Various operational links.
+ * - $new: New comment marker.
+ * - $picture: Authors picture.
+ * - $signature: Authors signature.
+ * - $status: Comment status. Possible values are:
+ *   comment-unpublished, comment-published or comment-review.
+ * - $submitted: By line with date and time.
+ * - $title: Linked title.
+ *
+ * These two variables are provided for context.
+ * - $comment: Full comment object.
+ * - $node: Node object the comments are attached to.
+ *
+ * @see template_preprocess_comment()
+ * @see theme_comment()
+ */
+?>
+<div class="comment<?php print ($comment->new) ? ' comment-new' : ''; print ' '. $status ?> clear-block">
+  <?php print $picture ?>
+
+  <?php if ($comment->new): ?>
+    <span class="new"><?php print $new ?></span>
+  <?php endif; ?>
+
+  <h3><?php print $title ?></h3>
+
+  <div class="submitted">
+    <?php print $submitted ?>
+  </div>
+
+  <div class="content">
+    <?php print $content ?>
+    <?php if ($signature): ?>
+    <div class="user-signature clear-block">
+      <?php print $signature ?>
+    </div>
+    <?php endif; ?>
+  </div>
+
+  <?php print $links ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/contact/contact.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,173 @@
+<?php
+// $Id: contact.admin.inc,v 1.3 2007/11/09 07:55:13 dries Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the contact module.
+ */
+
+/**
+ * Categories/list tab.
+ */
+function contact_admin_categories() {
+  $result = db_query('SELECT cid, category, recipients, selected FROM {contact} ORDER BY weight, category');
+  $rows = array();
+  while ($category = db_fetch_object($result)) {
+    $rows[] = array($category->category, $category->recipients, ($category->selected ? t('Yes') : t('No')), l(t('edit'), 'admin/build/contact/edit/'. $category->cid), l(t('delete'), 'admin/build/contact/delete/'. $category->cid));
+  }
+  $header = array(t('Category'), t('Recipients'), t('Selected'), array('data' => t('Operations'), 'colspan' => 2));
+
+  return theme('table', $header, $rows);
+}
+
+/**
+ * Category edit page.
+ */
+function contact_admin_edit($form_state = array(), $op, $contact = NULL) {
+
+  if (empty($contact) || $op == 'add') {
+    $contact = array(
+      'category' => '',
+      'recipients' => '',
+      'reply' => '',
+      'weight' => 0,
+      'selected' => 0,
+      'cid' => NULL,
+    );
+  }
+  $form['contact_op'] = array('#type' => 'value', '#value' => $op);
+  $form['category'] = array('#type' => 'textfield',
+    '#title' => t('Category'),
+    '#maxlength' => 255,
+    '#default_value' => $contact['category'],
+    '#description' => t("Example: 'website feedback' or 'product information'."),
+    '#required' => TRUE,
+  );
+  $form['recipients'] = array('#type' => 'textarea',
+    '#title' => t('Recipients'),
+    '#default_value' => $contact['recipients'],
+    '#description' => t("Example: 'webmaster@example.com' or 'sales@example.com,support@example.com'. To specify multiple recipients, separate each e-mail address with a comma."),
+    '#required' => TRUE,
+  );
+  $form['reply'] = array('#type' => 'textarea',
+    '#title' => t('Auto-reply'),
+    '#default_value' => $contact['reply'],
+    '#description' => t('Optional auto-reply. Leave empty if you do not want to send the user an auto-reply message.'),
+  );
+  $form['weight'] = array('#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $contact['weight'],
+    '#description' => t('When listing categories, those with lighter (smaller) weights get listed before categories with heavier (larger) weights. Categories with equal weights are sorted alphabetically.'),
+  );
+  $form['selected'] = array('#type' => 'select',
+    '#title' => t('Selected'),
+    '#options' => array('0' => t('No'), '1' => t('Yes')),
+    '#default_value' => $contact['selected'],
+    '#description' => t('Set this to <em>Yes</em> if you would like this category to be selected by default.'),
+  );
+  $form['cid'] = array('#type' => 'value',
+    '#value' => $contact['cid'],
+  );
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate the contact category edit page form submission.
+ */
+function contact_admin_edit_validate($form, &$form_state) {
+  if (empty($form_state['values']['category'])) {
+    form_set_error('category', t('You must enter a category.'));
+  }
+  if (empty($form_state['values']['recipients'])) {
+    form_set_error('recipients', t('You must enter one or more recipients.'));
+  }
+  else {
+    $recipients = explode(',', $form_state['values']['recipients']);
+    foreach ($recipients as $recipient) {
+      if (!valid_email_address(trim($recipient))) {
+        form_set_error('recipients', t('%recipient is an invalid e-mail address.', array('%recipient' => $recipient)));
+      }
+    }
+  }
+}
+
+/**
+ * Process the contact category edit page form submission.
+ */
+function contact_admin_edit_submit($form, &$form_state) {
+  if ($form_state['values']['selected']) {
+    // Unselect all other contact categories.
+    db_query('UPDATE {contact} SET selected = 0');
+  }
+  $recipients = explode(',', $form_state['values']['recipients']);
+  foreach ($recipients as $key => $recipient) {
+    // E-mail address validation has already been done in _validate.
+    $recipients[$key] = trim($recipient);
+  }
+  $form_state['values']['recipients'] = implode(',', $recipients);
+  if (empty($form_state['values']['cid']) || $form_state['values']['contact_op'] == 'add') {
+    drupal_write_record('contact', $form_state['values']);
+    drupal_set_message(t('Category %category has been added.', array('%category' => $form_state['values']['category'])));
+    watchdog('mail', 'Contact form: category %category added.', array('%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/build/contact'));
+
+  }
+  else {
+    drupal_write_record('contact', $form_state['values'], 'cid');
+    drupal_set_message(t('Category %category has been updated.', array('%category' => $form_state['values']['category'])));
+    watchdog('mail', 'Contact form: category %category updated.', array('%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/build/contact'));
+  }
+
+  $form_state['redirect'] = 'admin/build/contact';
+  return;
+}
+
+/**
+ * Category delete page.
+ */
+function contact_admin_delete(&$form_state, $contact) {
+
+  $form['contact'] = array(
+    '#type' => 'value',
+    '#value' => $contact,
+  );
+
+  return confirm_form($form, t('Are you sure you want to delete %category?', array('%category' => $contact['category'])), 'admin/build/contact', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
+}
+
+/**
+ * Process category delete form submission.
+ */
+function contact_admin_delete_submit($form, &$form_state) {
+  $contact = $form_state['values']['contact'];
+  db_query("DELETE FROM {contact} WHERE cid = %d", $contact['cid']);
+  drupal_set_message(t('Category %category has been deleted.', array('%category' => $contact['category'])));
+  watchdog('mail', 'Contact form: category %category deleted.', array('%category' => $contact['category']), WATCHDOG_NOTICE);
+
+  $form_state['redirect'] = 'admin/build/contact';
+  return;
+}
+
+function contact_admin_settings() {
+  $form['contact_form_information'] = array('#type' => 'textarea',
+    '#title' => t('Additional information'),
+    '#default_value' => variable_get('contact_form_information', t('You can leave a message using the contact form below.')),
+    '#description' => t('Information to show on the <a href="@form">contact page</a>. Can be anything from submission guidelines to your postal address or telephone number.', array('@form' => url('contact'))),
+  );
+  $form['contact_hourly_threshold'] = array('#type' => 'select',
+    '#title' => t('Hourly threshold'),
+    '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50)),
+    '#default_value' => variable_get('contact_hourly_threshold', 3),
+    '#description' => t('The maximum number of contact form submissions a user can perform per hour.'),
+  );
+  $form['contact_default_status'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable personal contact form by default'),
+    '#default_value' => variable_get('contact_default_status', 1),
+    '#description' => t('Default status of the personal contact form for new users.'),
+  );
+  return system_settings_form($form);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/contact/contact.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: contact.info,v 1.4 2007/06/08 05:50:54 dries Exp $
+name = Contact
+description = Enables the use of both personal and site-wide contact forms.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/contact/contact.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,81 @@
+<?php
+// $Id: contact.install,v 1.10 2007/12/18 12:59:21 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function contact_install() {
+  // Create tables.
+  drupal_install_schema('contact');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function contact_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('contact');
+
+  variable_del('contact_default_status');
+  variable_del('contact_form_information');
+  variable_del('contact_hourly_threshold');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function contact_schema() {
+  $schema['contact'] = array(
+    'description' => t('Contact form category settings.'),
+    'fields' => array(
+      'cid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique category ID.'),
+      ),
+      'category' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Category name.'),
+      ),
+      'recipients' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Comma-separated list of recipient e-mail addresses.'),
+      ),
+      'reply' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Text of the auto-reply message.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t("The category's weight."),
+      ),
+      'selected' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Flag to indicate whether or not category is selected by default. (1 = Yes, 0 = No)'),
+      ),
+    ),
+    'primary key' => array('cid'),
+    'unique keys' => array(
+      'category' => array('category'),
+    ),
+    'indexes' => array(
+      'list' => array('weight', 'category'),
+    ),
+  );
+
+  return $schema;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/contact/contact.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,189 @@
+<?php
+// $Id: contact.module,v 1.103 2008/01/16 12:46:52 goba Exp $
+
+/**
+ * @file
+ * Enables the use of personal and site-wide contact forms.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function contact_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#contact':
+      $output = '<p>'. t('The contact module facilitates communication via e-mail, by allowing your site\'s visitors to contact one another (personal contact forms), and by providing a simple way to direct messages to a set of administrator-defined recipients (the <a href="@contact">contact page</a>). With either form, users specify a subject, write their message, and (optionally) have a copy of their message sent to their own e-mail address.', array('@contact' => url('contact'))) .'</p>';
+      $output .= '<p>'. t("Personal contact forms allow users to be contacted via e-mail, while keeping recipient e-mail addresses private. Users may enable or disable their personal contact forms by editing their <em>My account</em> page. If enabled, a <em>Contact</em> tab leading to their personal contact form is available on their user profile. Site administrators have access to all personal contact forms (even if they have been disabled). The <em>Contact</em> tab is only visible when viewing another user's profile (users do not see their own <em>Contact</em> tab).") .'</p>';
+      $output .= '<p>'. t('The <a href="@contact">contact page</a> provides a simple form for visitors to leave comments, feedback, or other requests. Messages are routed by selecting a category from a list of administrator-defined options; each category has its own set of e-mail recipients. Common categories for a business site include, for example, "Website feedback" (messages are forwarded to web site administrators) and "Product information" (messages are forwarded to members of the sales department). The actual e-mail addresses defined within a category are not displayed. Only users in roles with the <em>access site-wide contact form</em> permission may access the <a href="@contact">contact page</a>.', array('@contact' => url('contact'))) .'</p>';
+      $output .= '<p>'. t('A link to your site\'s <a href="@contact">contact page</a> from the main <em>Navigation</em> menu is created, but is disabled by default. Create a similar link on another menu by adding a menu item pointing to the path "contact"', array('@contact' => url('contact'))) .'</p>';
+      $output .= '<p>'. t('Customize the <a href="@contact">contact page</a> with additional information (like physical location, mailing address, and telephone number) using the <a href="@contact-settings">contact form settings page</a>. The <a href="@contact-settings">settings page</a> also provides configuration options for the maximum number of contact form submissions a user may perform per hour, and the default status of users\' personal contact forms.', array('@contact-settings' => url('admin/build/contact/settings'), '@contact' => url('contact'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@contact">Contact module</a>.', array('@contact' => url('http://drupal.org/handbook/modules/contact/', array('absolute' => TRUE)))) .'</p>';
+      return $output;
+    case 'admin/build/contact':
+      $output = '<p>'. t('This page lets you set up <a href="@form">your site-wide contact form</a>. To do so, add one or more categories. You can associate different recipients with each category to route e-mails to different people. For example, you can route website feedback to the webmaster and direct product information requests to the sales department. On the <a href="@settings">settings page</a>, you can customize the information shown above the contact form. This can be useful to provide additional contact information such as your postal address and telephone number.', array('@settings' => url('admin/build/contact/settings'), '@form' => url('contact'))) .'</p>';
+      if (!module_exists('menu')) {
+        $menu_note = t('The menu item can be customized and configured only once the menu module has been <a href="@modules-page">enabled</a>.', array('@modules-page' => url('admin/settings/modules')));
+      }
+      else {
+        $menu_note = '';
+      }
+      $output .= '<p>'. t('The contact module also adds a <a href="@menu-settings">menu item</a> (disabled by default) to the navigation block.', array('@menu-settings' => url('admin/build/menu'))) .' '. $menu_note .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_perm
+ */
+function contact_perm() {
+  return array('access site-wide contact form', 'administer site-wide contact form');
+}
+/**
+ * Implementation of hook_menu().
+ */
+function contact_menu() {
+  $items['admin/build/contact'] = array(
+    'title' => 'Contact form',
+    'description' => 'Create a system contact form and set up categories for the form to use.',
+    'page callback' => 'contact_admin_categories',
+    'access arguments' => array('administer site-wide contact form'),
+    'file' => 'contact.admin.inc',
+  );
+  $items['admin/build/contact/list'] = array(
+    'title' => 'List',
+    'page callback' => 'contact_admin_categories',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'file' => 'contact.admin.inc',
+  );
+  $items['admin/build/contact/add'] = array(
+    'title' => 'Add category',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('contact_admin_edit', 3),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+    'file' => 'contact.admin.inc',
+  );
+  $items['admin/build/contact/edit/%contact'] = array(
+    'title' => 'Edit contact category',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('contact_admin_edit', 3, 4),
+    'type' => MENU_CALLBACK,
+    'file' => 'contact.admin.inc',
+  );
+  $items['admin/build/contact/delete/%contact'] = array(
+    'title' => 'Delete contact',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('contact_admin_delete', 4),
+    'type' => MENU_CALLBACK,
+    'file' => 'contact.admin.inc',
+  );
+  $items['admin/build/contact/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('contact_admin_settings'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'contact.admin.inc',
+  );
+  $items['contact'] = array(
+    'title' => 'Contact',
+    'page callback' => 'contact_site_page',
+    'access arguments' => array('access site-wide contact form'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'contact.pages.inc',
+  );
+  $items['user/%user/contact'] = array(
+    'title' => 'Contact',
+    'page callback' => 'contact_user_page',
+    'page arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => '_contact_user_tab_access',
+    'access arguments' => array(1),
+    'weight' => 2,
+    'file' => 'contact.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * Determine if a user can access to the contact tab.
+ */
+function _contact_user_tab_access($account) {
+  global $user;
+  if (!isset($account->contact)) {
+    $account->contact = FALSE;
+  }
+  return
+    $account && $user->uid &&
+    (
+      ($user->uid != $account->uid && $account->contact) ||
+      user_access('administer users')
+    );
+}
+
+/**
+ * Load the data for a single contact category.
+ */
+function contact_load($cid) {
+  $contact = db_fetch_array(db_query("SELECT * FROM {contact} WHERE cid = %d", $cid));
+  return empty($contact) ? FALSE : $contact;
+}
+
+/**
+ * Implementation of hook_user().
+ *
+ * Allows the user the option of enabling/disabling his personal contact form.
+ */
+function contact_user($type, &$edit, &$user, $category = NULL) {
+  if ($type == 'form' && $category == 'account') {
+    $form['contact'] = array('#type' => 'fieldset',
+      '#title' => t('Contact settings'),
+      '#weight' => 5,
+      '#collapsible' => TRUE,
+    );
+    $form['contact']['contact'] = array('#type' => 'checkbox',
+      '#title' => t('Personal contact form'),
+      '#default_value' => !empty($edit['contact']) ? $edit['contact'] : FALSE,
+      '#description' => t('Allow other users to contact you by e-mail via <a href="@url">your personal contact form</a>. Note that while your e-mail address is not made public to other members of the community, privileged users such as site administrators are able to contact you even if you choose not to enable this feature.', array('@url' => url("user/$user->uid/contact"))),
+    );
+    return $form;
+  }
+  elseif ($type == 'validate') {
+    return array('contact' => isset($edit['contact']) ? $edit['contact'] : FALSE);
+  }
+  elseif ($type == 'insert') {
+    $edit['contact'] = variable_get('contact_default_status', 1);
+  }
+}
+
+/**
+ * Implementation of hook_mail().
+ */
+function contact_mail($key, &$message, $params) {
+  $language = $message['language'];
+  switch ($key) {
+    case 'page_mail':
+    case 'page_copy':
+      $contact = $params['contact'];
+      $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language);
+      $message['body'][] = t("!name sent a message using the contact form at !form.", array('!name' => $params['name'], '!form' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language))), $language->language);
+      $message['body'][] = $params['message'];
+      break;
+    case 'page_autoreply':
+      $contact = $params['contact'];
+      $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language);
+      $message['body'][] = $contact['reply'];
+      break;
+    case 'user_mail':
+    case 'user_copy':
+      $user = $params['user'];
+      $account = $params['account'];
+      $message['subject'] .= '['. variable_get('site_name', 'Drupal') .'] '. $params['subject'];
+      $message['body'][] = "$account->name,";
+      $message['body'][] = t("!name (!name-url) has sent you a message via your contact form (!form-url) at !site.", array('!name' => $user->name, '!name-url' => url("user/$user->uid", array('absolute' => TRUE, 'language' => $language)), '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), '!site' => variable_get('site_name', 'Drupal')), $language->language);
+      $message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE, 'language' => $language))), $language->language);
+      $message['body'][] = t('Message:', NULL, $language->language);
+      $message['body'][] = $params['message'];
+      break;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/contact/contact.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,236 @@
+<?php
+// $Id: contact.pages.inc,v 1.6.2.1 2008/02/12 14:42:50 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the contact module.
+ */
+
+
+/**
+ * Site-wide contact page.
+ */
+function contact_site_page() {
+  global $user;
+
+  if (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3))) {
+    $output = t("You cannot send more than %number messages per hour. Please try again later.", array('%number' => variable_get('contact_hourly_threshold', 3)));
+  }
+  else {
+    $output = drupal_get_form('contact_mail_page');
+  }
+
+  return $output;
+}
+
+function contact_mail_page() {
+  global $user;
+
+  $form = $categories = array();
+
+  $result = db_query('SELECT cid, category, selected FROM {contact} ORDER BY weight, category');
+  while ($category = db_fetch_object($result)) {
+    $categories[$category->cid] = $category->category;
+    if ($category->selected) {
+      $default_category = $category->cid;
+    }
+  }
+
+  if (count($categories) > 0) {
+    $form['#token'] = $user->uid ? $user->name . $user->mail : '';
+    $form['contact_information'] = array('#value' => filter_xss_admin(variable_get('contact_form_information', t('You can leave a message using the contact form below.'))));
+    $form['name'] = array('#type' => 'textfield',
+      '#title' => t('Your name'),
+      '#maxlength' => 255,
+      '#default_value' => $user->uid ? $user->name : '',
+      '#required' => TRUE,
+    );
+    $form['mail'] = array('#type' => 'textfield',
+      '#title' => t('Your e-mail address'),
+      '#maxlength' => 255,
+      '#default_value' => $user->uid ? $user->mail : '',
+      '#required' => TRUE,
+    );
+    $form['subject'] = array('#type' => 'textfield',
+      '#title' => t('Subject'),
+      '#maxlength' => 255,
+      '#required' => TRUE,
+    );
+    if (count($categories) > 1) {
+      // If there is more than one category available and no default category has been selected,
+      // prepend a default placeholder value.
+      if (!isset($default_category)) {
+        $default_category = t('- Please choose -');
+        $categories = array($default_category) + $categories;
+      }
+      $form['cid'] = array('#type' => 'select',
+        '#title' => t('Category'),
+        '#default_value' => $default_category,
+        '#options' => $categories,
+        '#required' => TRUE,
+      );
+    }
+    else {
+      // If there is only one category, store its cid.
+      $category_keys = array_keys($categories);
+      $form['cid'] = array('#type' => 'value',
+        '#value' => array_shift($category_keys),
+      );
+    }
+    $form['message'] = array('#type' => 'textarea',
+      '#title' => t('Message'),
+      '#required' => TRUE,
+    );
+    // We do not allow anonymous users to send themselves a copy
+    // because it can be abused to spam people.
+    if ($user->uid) {
+      $form['copy'] = array('#type' => 'checkbox',
+        '#title' => t('Send yourself a copy.'),
+      );
+    }
+    else {
+      $form['copy'] = array('#type' => 'value', '#value' => FALSE);
+    }
+    $form['submit'] = array('#type' => 'submit',
+      '#value' => t('Send e-mail'),
+    );
+  }
+  else {
+    drupal_set_message(t('The contact form has not been configured. <a href="@add">Add one or more categories</a> to the form.', array('@add' => url('admin/build/contact/add'))), 'error');
+  }
+  return $form;
+}
+
+/**
+ * Validate the site-wide contact page form submission.
+ */
+function contact_mail_page_validate($form, &$form_state) {
+  if (!$form_state['values']['cid']) {
+    form_set_error('cid', t('You must select a valid category.'));
+  }
+  if (!valid_email_address($form_state['values']['mail'])) {
+    form_set_error('mail', t('You must enter a valid e-mail address.'));
+  }
+}
+
+/**
+ * Process the site-wide contact page form submission.
+ */
+function contact_mail_page_submit($form, &$form_state) {
+  global $language;
+
+  $values = $form_state['values'];
+
+  // E-mail address of the sender: as the form field is a text field,
+  // all instances of \r and \n have been automatically stripped from it.
+  $from = $values['mail'];
+
+  // Load category properties and save form values for email composition.
+  $contact = contact_load($values['cid']);
+  $values['contact'] = $contact;
+
+  // Send the e-mail to the recipients using the site default language.
+  drupal_mail('contact', 'page_mail', $contact['recipients'], language_default(), $values, $from);
+
+  // If the user requests it, send a copy using the current language.
+  if ($values['copy']) {
+    drupal_mail('contact', 'page_copy', $from, $language, $values, $from);
+  }
+
+  // Send an auto-reply if necessary using the current language.
+  if ($contact['reply']) {
+    drupal_mail('contact', 'page_autoreply', $from, $language, $values, $contact['recipients']);
+  }
+
+  flood_register_event('contact');
+  watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $values['name'] ." [$from]", '%category' => $contact['category']));
+  drupal_set_message(t('Your message has been sent.'));
+
+  // Jump to home page rather than back to contact page to avoid
+  // contradictory messages if flood control has been activated.
+  $form_state['redirect'] = '';
+}
+
+/**
+ * Personal contact page.
+ */
+function contact_user_page($account) {
+  global $user;
+
+  if (!valid_email_address($user->mail)) {
+    $output = t('You need to provide a valid e-mail address to contact other users. Please update your <a href="@url">user information</a> and try again.', array('@url' => url("user/$user->uid/edit")));
+  }
+  else if (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3))) {
+    $output = t('You cannot contact more than %number users per hour. Please try again later.', array('%number' => variable_get('contact_hourly_threshold', 3)));
+  }
+  else {
+    drupal_set_title(check_plain($account->name));
+    $output = drupal_get_form('contact_mail_user', $account);
+  }
+
+  return $output;
+}
+
+function contact_mail_user(&$form_state, $recipient) {
+  global $user;
+  $form['#token'] = $user->name . $user->mail;
+  $form['recipient'] = array('#type' => 'value', '#value' => $recipient);
+  $form['from'] = array('#type' => 'item',
+    '#title' => t('From'),
+    '#value' => check_plain($user->name) .' &lt;'. check_plain($user->mail) .'&gt;',
+  );
+  $form['to'] = array('#type' => 'item',
+    '#title' => t('To'),
+    '#value' => check_plain($recipient->name),
+  );
+  $form['subject'] = array('#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#maxlength' => 50,
+    '#required' => TRUE,
+  );
+  $form['message'] = array('#type' => 'textarea',
+    '#title' => t('Message'),
+    '#rows' => 15,
+    '#required' => TRUE,
+  );
+  $form['copy'] = array('#type' => 'checkbox',
+    '#title' => t('Send yourself a copy.'),
+  );
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Send e-mail'),
+  );
+  return $form;
+}
+
+/**
+ * Process the personal contact page form submission.
+ */
+function contact_mail_user_submit($form, &$form_state) {
+  global $user, $language;
+
+  $account = $form_state['values']['recipient'];
+
+  // Send from the current user to the requested user.
+  $to = $account->mail;
+  $from = $user->mail;
+
+  // Save both users and all form values for email composition.
+  $values = $form_state['values'];
+  $values['account'] = $account;
+  $values['user'] = $user;
+
+  // Send the e-mail in the requested user language.
+  drupal_mail('contact', 'user_mail', $to, user_preferred_language($account), $values, $from);
+
+  // Send a copy if requested, using current page language.
+  if ($form_state['values']['copy']) {
+    drupal_mail('contact', 'user_copy', $from, $language, $values, $from);
+  }
+
+  flood_register_event('contact');
+  watchdog('mail', '%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
+  drupal_set_message(t('The message has been sent.'));
+
+  // Back to the requested users profile page.
+  $form_state['redirect'] = "user/$account->uid";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+/* $Id: dblog-rtl.css,v 1.2 2007/11/27 12:09:26 goba Exp $ */
+
+#dblog-filter-form .form-item {
+  float: right;
+  padding-right: 0;
+  padding-left: .8em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,320 @@
+<?php
+// $Id: dblog.admin.inc,v 1.6 2008/01/08 10:35:41 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbacks for the dblog module.
+ */
+
+/**
+ * dblog module settings form.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function dblog_admin_settings() {
+  $form['dblog_row_limit'] = array(
+    '#type' => 'select',
+    '#title' => t('Discard log entries above the following row limit'),
+    '#default_value' => variable_get('dblog_row_limit', 1000),
+    '#options' => drupal_map_assoc(array(100, 1000, 10000, 100000, 1000000)),
+    '#description' => t('The maximum number of rows to keep in the database log. Older entries will be automatically discarded. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status')))
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback; displays a listing of log messages.
+ */
+function dblog_overview() {
+  $filter = dblog_build_filter_query();
+  $rows = array();
+  $icons = array(
+    WATCHDOG_NOTICE  => '',
+    WATCHDOG_WARNING => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')),
+    WATCHDOG_ERROR   => theme('image', 'misc/watchdog-error.png', t('error'), t('error')),
+  );
+  $classes = array(
+    WATCHDOG_NOTICE => 'dblog-notice',
+    WATCHDOG_WARNING => 'dblog-warning',
+    WATCHDOG_ERROR => 'dblog-error',
+  );
+
+  $output = drupal_get_form('dblog_filter_form');
+
+  $header = array(
+    ' ',
+    array('data' => t('Type'), 'field' => 'w.type'),
+    array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc'),
+    t('Message'),
+    array('data' => t('User'), 'field' => 'u.name'),
+    array('data' => t('Operations')),
+  );
+
+  $sql = "SELECT w.wid, w.uid, w.severity, w.type, w.timestamp, w.message, w.variables, w.link, u.name FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid";
+  $tablesort = tablesort_sql($header);
+  if (!empty($filter['where'])) {
+    $result = pager_query($sql ." WHERE ". $filter['where'] . $tablesort, 50, 0, NULL, $filter['args']);
+  }
+  else {
+    $result = pager_query($sql . $tablesort, 50);
+  }
+
+  while ($dblog = db_fetch_object($result)) {
+    $rows[] = array('data' =>
+      array(
+        // Cells
+        $icons[$dblog->severity],
+        t($dblog->type),
+        format_date($dblog->timestamp, 'small'),
+        l(truncate_utf8(_dblog_format_message($dblog), 56, TRUE, TRUE), 'admin/reports/event/'. $dblog->wid, array('html' => TRUE)),
+        theme('username', $dblog),
+        $dblog->link,
+      ),
+      // Attributes for tr
+      'class' => "dblog-". preg_replace('/[^a-z]/i', '-', $dblog->type) .' '. $classes[$dblog->severity]
+    );
+  }
+
+  if (!$rows) {
+    $rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 6));
+  }
+
+  $output .= theme('table', $header, $rows, array('id' => 'admin-dblog'));
+  $output .= theme('pager', NULL, 50, 0);
+
+  return $output;
+}
+
+/**
+ * Menu callback; generic function to display a page of the most frequent
+ * dblog events of a specified type.
+ */
+function dblog_top($type) {
+
+  $header = array(
+    array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'),
+    array('data' => t('Message'), 'field' => 'message')
+  );
+
+  $result = pager_query("SELECT COUNT(wid) AS count, message, variables FROM {watchdog} WHERE type = '%s' GROUP BY message, variables ". tablesort_sql($header), 30, 0, "SELECT COUNT(DISTINCT(message)) FROM {watchdog} WHERE type = '%s'", $type);
+
+  $rows = array();
+  while ($dblog = db_fetch_object($result)) {
+    $rows[] = array($dblog->count, truncate_utf8(_dblog_format_message($dblog), 56, TRUE, TRUE));
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 2));
+  }
+
+  $output  = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 30, 0);
+
+  return $output;
+}
+
+/**
+ * Menu callback; displays details about a log message.
+ */
+function dblog_event($id) {
+  $severity = watchdog_severity_levels();
+  $output = '';
+  $result = db_query('SELECT w.*, u.name, u.uid FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid WHERE w.wid = %d', $id);
+  if ($dblog = db_fetch_object($result)) {
+    $rows = array(
+      array(
+        array('data' => t('Type'), 'header' => TRUE),
+        t($dblog->type),
+      ),
+      array(
+        array('data' => t('Date'), 'header' => TRUE),
+        format_date($dblog->timestamp, 'large'),
+      ),
+      array(
+        array('data' => t('User'), 'header' => TRUE),
+        theme('username', $dblog),
+      ),
+      array(
+        array('data' => t('Location'), 'header' => TRUE),
+        l($dblog->location, $dblog->location),
+      ),
+      array(
+        array('data' => t('Referrer'), 'header' => TRUE),
+        l($dblog->referer, $dblog->referer),
+      ),
+      array(
+        array('data' => t('Message'), 'header' => TRUE),
+        _dblog_format_message($dblog),
+      ),
+      array(
+        array('data' => t('Severity'), 'header' => TRUE),
+        $severity[$dblog->severity],
+      ),
+      array(
+        array('data' => t('Hostname'), 'header' => TRUE),
+        check_plain($dblog->hostname),
+      ),
+      array(
+        array('data' => t('Operations'), 'header' => TRUE),
+        $dblog->link,
+      ),
+    );
+    $attributes = array('class' => 'dblog-event');
+    $output = theme('table', array(), $rows, $attributes);
+  }
+  return $output;
+}
+
+/**
+ * Build query for dblog administration filters based on session.
+ */
+function dblog_build_filter_query() {
+  if (empty($_SESSION['dblog_overview_filter'])) {
+    return;
+  }
+
+  $filters = dblog_filters();
+
+  // Build query
+  $where = $args = array();
+  foreach ($_SESSION['dblog_overview_filter'] as $key => $filter) {
+    $filter_where = array();
+    foreach ($filter as $value) {
+      $filter_where[] = $filters[$key]['where'];
+      $args[] = $value;
+    }
+    if (!empty($filter_where)) {
+      $where[] = '('. implode(' OR ', $filter_where) .')';
+    }
+  }
+  $where = !empty($where) ? implode(' AND ', $where) : '';
+
+  return array(
+    'where' => $where,
+    'args' => $args,
+  );
+}
+
+
+/**
+ * List dblog administration filters that can be applied.
+ */
+function dblog_filters() {
+  $filters = array();
+
+  foreach (_dblog_get_message_types() as $type) {
+    $types[$type] = $type;
+  }
+
+  if (!empty($types)) {
+    $filters['type'] = array(
+      'title' => t('Type'),
+      'where' => "w.type = '%s'",
+      'options' => $types,
+    );
+  }
+
+  $filters['severity'] = array(
+    'title' => t('Severity'),
+    'where' => 'w.severity = %d',
+    'options' => watchdog_severity_levels(),
+  );
+
+  return $filters;
+}
+
+/**
+ * Formats a log message for display.
+ *
+ * @param $dblog
+ *   An object with at least the message and variables properties
+ */
+function _dblog_format_message($dblog) {
+  // Legacy messages and user specified text
+  if ($dblog->variables === 'N;') {
+    return $dblog->message;
+  }
+  // Message to translate with injected variables
+  else {
+    return t($dblog->message, unserialize($dblog->variables));
+  }
+}
+
+
+/**
+ * Return form for dblog administration filters.
+ *
+ * @ingroup forms
+ * @see dblog_filter_form_submit()
+ * @see dblog_filter_form_validate()
+ */
+function dblog_filter_form() {
+  $session = &$_SESSION['dblog_overview_filter'];
+  $session = is_array($session) ? $session : array();
+  $filters = dblog_filters();
+
+  $form['filters'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Filter log messages'),
+    '#theme' => 'dblog_filters',
+    '#collapsible' => TRUE,
+    '#collapsed' => empty($session),
+  );
+  foreach ($filters as $key => $filter) {
+    $form['filters']['status'][$key] = array(
+      '#title' => $filter['title'],
+      '#type' => 'select',
+      '#multiple' => TRUE,
+      '#size' => 8,
+      '#options' => $filter['options'],
+    );
+    if (!empty($session[$key])) {
+      $form['filters']['status'][$key]['#default_value'] = $session[$key];
+    }
+  }
+
+  $form['filters']['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Filter'),
+  );
+  if (!empty($session)) {
+    $form['filters']['buttons']['reset'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset')
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Validate result from dblog administration filter form.
+ */
+function dblog_filter_form_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) {
+    form_set_error('type', t('You must select something to filter by.'));
+  }
+}
+
+/**
+ * Process result from dblog administration filter form.
+ */
+function dblog_filter_form_submit($form, &$form_state) {
+  $op = $form_state['values']['op'];
+  $filters = dblog_filters();
+  switch ($op) {
+    case t('Filter'):
+      foreach ($filters as $name => $filter) {
+        if (isset($form_state['values'][$name])) {
+          $_SESSION['dblog_overview_filter'][$name] = $form_state['values'][$name];
+        }
+      }
+      break;
+    case t('Reset'):
+      $_SESSION['dblog_overview_filter'] = array();
+      break;
+  }
+  return 'admin/reports/dblog';
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,40 @@
+/* $Id: dblog.css,v 1.4 2007/09/01 05:27:04 dries Exp $ */
+
+#dblog-filter-form .form-item {
+  float: left; /* LTR */
+  padding-right: .8em; /* LTR */
+  margin: 0.1em;
+  /**
+   * In Opera 9, DOM elements with the property of "overflow: auto"
+   * will partially hide its contents with unnecessary scrollbars when
+   * its immediate child is floated without an explicit width set.
+   */
+  width: 15em;
+}
+#dblog-filter-form .form-item select.form-select {
+  width: 100%;
+}
+tr.dblog-user {
+  background: #ffd;
+}
+tr.dblog-user .active {
+  background: #eed;
+}
+tr.dblog-content {
+  background: #ddf;
+}
+tr.dblog-content .active {
+  background: #cce;
+}
+tr.dblog-page-not-found, tr.dblog-access-denied {
+  background: #dfd;
+}
+tr.dblog-page-not-found .active, tr.dblog-access-denied .active {
+  background: #cec;
+}
+tr.dblog-error {
+  background: #ffc9c9;
+}
+tr.dblog-error .active {
+  background: #eeb9b9;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: dblog.info,v 1.2 2007/06/08 05:50:54 dries Exp $
+name = Database logging
+description = Logs and records system events to the database.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,104 @@
+<?php
+// $Id: dblog.install,v 1.6 2007/11/04 14:33:06 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function dblog_install() {
+  // Create tables.
+  drupal_install_schema('dblog');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function dblog_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('dblog');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function dblog_schema() {
+  $schema['watchdog'] = array(
+    'description' => t('Table that contains logs of all system events.'),
+    'fields' => array(
+      'wid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique watchdog event ID.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {users}.uid of the user who triggered the event.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 16,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Type of log message, for example "user" or "page not found."'),
+      ),
+      'message' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Text of log message to be passed into the t() function.'),
+      ),
+      'variables' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('Serialized array of variables that match the message string and that is passed into the t() function.'),
+      ),
+      'severity' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The severity level of the event; ranges from 0 (Emergency) to 7 (Debug)'),
+      ),
+      'link' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Link to view the result of the event.'),
+      ),
+      'location'  => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => t('URL of the origin of the event.'),
+      ),
+      'referer' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('URL of referring page.'),
+      ),
+      'hostname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Hostname of the user who triggered the event.'),
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Unix timestamp of when event occurred.'),
+      ),
+    ),
+    'primary key' => array('wid'),
+    'indexes' => array('type' => array('type')),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/dblog/dblog.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,159 @@
+<?php
+// $Id: dblog.module,v 1.21 2008/01/08 10:35:41 goba Exp $
+
+/**
+ * @file
+ * System monitoring and logging for administrators.
+ *
+ * The dblog module monitors your site and keeps a list of
+ * recorded events containing usage and performance data, errors,
+ * warnings, and similar operational information.
+ *
+ * @see watchdog()
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function dblog_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#dblog':
+      $output = '<p>'. t('The dblog module monitors your system, capturing system events in a log to be reviewed by an authorized individual at a later time. This is useful for site administrators who want a quick overview of activities on their site. The logs also record the sequence of events, so it can be useful for debugging site errors.') .'</p>';
+      $output .= '<p>'. t('The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. Administrators should check the dblog report on a regular basis to ensure their site is working properly.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@dblog">Dblog module</a>.', array('@dblog' => 'http://drupal.org/handbook/modules/dblog/')) .'</p>';
+      return $output;
+    case 'admin/reports/dblog':
+      return '<p>'. t('The dblog module monitors your website, capturing system events in a log to be reviewed by an authorized individual at a later time. The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. It is vital to check the dblog report on a regular basis as it is often the only way to tell what is going on.') .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function dblog_theme() {
+  return array(
+    'dblog_filters' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function dblog_menu() {
+  $items['admin/settings/logging/dblog'] = array(
+    'title' => 'Database logging',
+    'description' => 'Settings for logging to the Drupal database logs. This is the most common method for small to medium sites on shared hosting. The logs are viewable from the admin pages.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('dblog_admin_settings'),
+    'file' => 'dblog.admin.inc',
+  );
+
+  $items['admin/reports/dblog'] = array(
+    'title' => 'Recent log entries',
+    'description' => 'View events that have recently been logged.',
+    'page callback' => 'dblog_overview',
+    'weight' => -1,
+    'file' => 'dblog.admin.inc',
+  );
+  $items['admin/reports/page-not-found'] = array(
+    'title' => "Top 'page not found' errors",
+    'description' => "View 'page not found' errors (404s).",
+    'page callback' => 'dblog_top',
+    'page arguments' => array('page not found'),
+    'file' => 'dblog.admin.inc',
+  );
+  $items['admin/reports/access-denied'] = array(
+    'title' => "Top 'access denied' errors",
+    'description' => "View 'access denied' errors (403s).",
+    'page callback' => 'dblog_top',
+    'page arguments' => array('access denied'),
+    'file' => 'dblog.admin.inc',
+  );
+  $items['admin/reports/event/%'] = array(
+    'title' => 'Details',
+    'page callback' => 'dblog_event',
+    'page arguments' => array(3),
+    'type' => MENU_CALLBACK,
+    'file' => 'dblog.admin.inc',
+  );
+  return $items;
+}
+
+function dblog_init() {
+  if (arg(0) == 'admin' && arg(1) == 'reports') {
+    // Add the CSS for this module
+    drupal_add_css(drupal_get_path('module', 'dblog') .'/dblog.css', 'module', 'all', FALSE);
+  }
+}
+
+
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Remove expired log messages and flood control events.
+ */
+function dblog_cron() {
+  // Cleanup the watchdog table
+  $max = db_result(db_query('SELECT MAX(wid) FROM {watchdog}'));
+  db_query('DELETE FROM {watchdog} WHERE wid < %d', $max - variable_get('dblog_row_limit', 1000));
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function dblog_user($op, &$edit, &$user) {
+  if ($op == 'delete') {
+    db_query('UPDATE {watchdog} SET uid = 0 WHERE uid = %d', $user->uid);
+  }
+}
+
+function _dblog_get_message_types() {
+  $types = array();
+
+  $result = db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type');
+  while ($object = db_fetch_object($result)) {
+    $types[] = $object->type;
+  }
+
+  return $types;
+}
+
+function dblog_watchdog($log = array()) {
+  $current_db = db_set_active();
+  db_query("INSERT INTO {watchdog}
+    (uid, type, message, variables, severity, link, location, referer, hostname, timestamp)
+    VALUES
+    (%d, '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', %d)",
+    $log['user']->uid,
+    $log['type'],
+    $log['message'],
+    serialize($log['variables']),
+    $log['severity'],
+    $log['link'],
+    $log['request_uri'],
+    $log['referer'],
+    $log['ip'],
+    $log['timestamp']);
+
+  if ($current_db) {
+    db_set_active($current_db);
+  }
+}
+
+/**
+ * Theme dblog administration filter selector.
+ *
+ * @ingroup themeable
+ */
+function theme_dblog_filters($form) {
+  $output = '';
+  foreach (element_children($form['status']) as $key) {
+    $output .= drupal_render($form['status'][$key]);
+  }
+  $output .= '<div id="dblog-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
+  return $output;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/filter/filter.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,394 @@
+<?php
+// $Id: filter.admin.inc,v 1.8.2.1 2008/02/12 14:25:34 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the filter module.
+ */
+
+/**
+ * Menu callback; Displays a list of all input formats and which
+ * one is the default.
+ *
+ * @ingroup forms
+ * @see filter_admin_overview_submit()
+ */
+function filter_admin_overview() {
+
+  // Overview of all formats.
+  $formats = filter_formats();
+  $error = FALSE;
+
+  foreach ($formats as $id => $format) {
+    $roles = array();
+    foreach (user_roles() as $rid => $name) {
+      // Prepare a roles array with roles that may access the filter.
+      if (strstr($format->roles, ",$rid,")) {
+        $roles[] = $name;
+      }
+    }
+    $default = ($id == variable_get('filter_default_format', 1));
+    $options[$id] = '';
+    $form[$format->name]['id'] = array('#value' => $id);
+    $form[$format->name]['roles'] = array('#value' => $default ? t('All roles may use default format') : ($roles ? implode(', ', $roles) : t('No roles may use this format')));
+    $form[$format->name]['configure'] = array('#value' => l(t('configure'), 'admin/settings/filters/'. $id));
+    $form[$format->name]['delete'] = array('#value' => $default ? '' : l(t('delete'), 'admin/settings/filters/delete/'. $id));
+  }
+  $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1));
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Set default format'));
+  return $form;
+}
+
+function filter_admin_overview_submit($form, &$form_state) {
+  // Process form submission to set the default format.
+  if (is_numeric($form_state['values']['default'])) {
+    drupal_set_message(t('Default format updated.'));
+    variable_set('filter_default_format', $form_state['values']['default']);
+  }
+}
+
+/**
+ * Theme the admin overview form.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_admin_overview($form) {
+  $rows = array();
+  foreach ($form as $name => $element) {
+    if (isset($element['roles']) && is_array($element['roles'])) {
+      $rows[] = array(
+        drupal_render($form['default'][$element['id']['#value']]),
+        check_plain($name),
+        drupal_render($element['roles']),
+        drupal_render($element['configure']),
+        drupal_render($element['delete'])
+      );
+      unset($form[$name]);
+    }
+  }
+  $header = array(t('Default'), t('Name'), t('Roles'), array('data' => t('Operations'), 'colspan' => 2));
+  $output = theme('table', $header, $rows);
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Menu callback; Display a filter format form.
+ */
+function filter_admin_format_page($format = NULL) {
+  if (!isset($format->name)) {
+    drupal_set_title(t("Add input format"));
+    $format = (object)array('name' => '', 'roles' => '', 'format' => '');
+  }
+  return drupal_get_form('filter_admin_format_form', $format);
+}
+
+/**
+ * Generate a filter format form.
+ *
+ * @ingroup forms
+ * @see filter_admin_format_form_validate()
+ * @see filter_admin_format_form_submit()
+ */
+function filter_admin_format_form(&$form_state, $format) {
+  $default = ($format->format == variable_get('filter_default_format', 1));
+  if ($default) {
+    $help = t('All roles for the default format must be enabled and cannot be changed.');
+    $form['default_format'] = array('#type' => 'hidden', '#value' => 1);
+  }
+
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Name'),
+    '#default_value' => $format->name,
+    '#description' => t('Specify a unique name for this filter format.'),
+    '#required' => TRUE,
+  );
+
+  // Add a row of checkboxes for form group.
+  $form['roles'] = array('#type' => 'fieldset',
+    '#title' => t('Roles'),
+    '#description' => $default ? $help : t('Choose which roles may use this filter format. Note that roles with the "administer filters" permission can always use all the filter formats.'),
+    '#tree' => TRUE,
+  );
+
+  foreach (user_roles() as $rid => $name) {
+    $checked = strstr($format->roles, ",$rid,");
+    $form['roles'][$rid] = array('#type' => 'checkbox',
+      '#title' => $name,
+      '#default_value' => ($default || $checked),
+    );
+    if ($default) {
+      $form['roles'][$rid]['#disabled'] = TRUE;
+    }
+  }
+  // Table with filters
+  $all = filter_list_all();
+  $enabled = filter_list_format($format->format);
+
+  $form['filters'] = array('#type' => 'fieldset',
+    '#title' => t('Filters'),
+    '#description' => t('Choose the filters that will be used in this filter format.'),
+    '#tree' => TRUE,
+  );
+  foreach ($all as $id => $filter) {
+    $form['filters'][$id] = array('#type' => 'checkbox',
+      '#title' => $filter->name,
+      '#default_value' => isset($enabled[$id]),
+      '#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta),
+    );
+  }
+  if (!empty($format->format)) {
+    $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+
+    // Composition tips (guidelines)
+    $tips = _filter_tips($format->format, FALSE);
+    $extra = '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
+    $tiplist = theme('filter_tips', $tips, FALSE, $extra);
+    if (!$tiplist) {
+      $tiplist = '<p>'. t('No guidelines available.') .'</p>';
+    }
+    $group = '<p>'. t('These are the guidelines that users will see for posting in this input format. They are automatically generated from the filter settings.') .'</p>';
+    $group .= $tiplist;
+    $form['tips'] = array('#value' => '<h2>'. t('Formatting guidelines') .'</h2>'. $group);
+  }
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+  return $form;
+}
+
+/**
+ * Validate filter format form submissions.
+ */
+function filter_admin_format_form_validate($form, &$form_state) {
+  if (!isset($form_state['values']['format'])) {
+    $name = trim($form_state['values']['name']);
+    $result = db_fetch_object(db_query("SELECT format FROM {filter_formats} WHERE name='%s'", $name));
+    if ($result) {
+      form_set_error('name', t('Filter format names need to be unique. A format named %name already exists.', array('%name' => $name)));
+    }
+  }
+}
+
+/**
+ * Process filter format form submissions.
+ */
+function filter_admin_format_form_submit($form, &$form_state) {
+  $format = isset($form_state['values']['format']) ? $form_state['values']['format'] : NULL;
+  $current = filter_list_format($format);
+  $name = trim($form_state['values']['name']);
+  $cache = TRUE;
+
+  // Add a new filter format.
+  if (!$format) {
+    $new = TRUE;
+    db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name);
+    $format = db_result(db_query("SELECT MAX(format) AS format FROM {filter_formats}"));
+    drupal_set_message(t('Added input format %format.', array('%format' => $name)));
+  }
+  else {
+    drupal_set_message(t('The input format settings have been updated.'));
+  }
+
+  db_query("DELETE FROM {filters} WHERE format = %d", $format);
+  foreach ($form_state['values']['filters'] as $id => $checked) {
+    if ($checked) {
+      list($module, $delta) = explode('/', $id);
+      // Add new filters to the bottom.
+      $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10;
+      db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format, $module, $delta, $weight);
+
+      // Check if there are any 'no cache' filters.
+      $cache &= !module_invoke($module, 'filter', 'no cache', $delta);
+    }
+  }
+
+  // We store the roles as a string for ease of use.
+  // We should always set all roles to TRUE when saving a default role.
+  // We use leading and trailing comma's to allow easy substring matching.
+  $roles = array();
+  if (isset($form_state['values']['roles'])) {
+    foreach ($form_state['values']['roles'] as $id => $checked) {
+      if ($checked) {
+        $roles[] = $id;
+      }
+    }
+  }
+  if (!empty($form_state['values']['default_format'])) {
+    $roles = ','. implode(',', array_keys(user_roles())) .',';
+  }
+  else {
+    $roles = ','. implode(',', $roles) .',';
+  }
+
+  db_query("UPDATE {filter_formats} SET cache = %d, name='%s', roles = '%s' WHERE format = %d", $cache, $name, $roles, $format);
+
+  cache_clear_all($format .':', 'cache_filter', TRUE);
+
+  // If a new filter was added, return to the main list of filters. Otherwise, stay on edit filter page to show new changes.
+  $return = 'admin/settings/filters';
+  if (!empty($new)) {
+    $return .= '/'. $format;
+  }
+  $form_state['redirect'] = $return;
+  return;
+}
+
+/**
+ * Menu callback; confirm deletion of a format.
+ *
+ * @ingroup forms
+ * @see filter_admin_delete_submit()
+ */
+function filter_admin_delete() {
+  $format = arg(4);
+  $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format));
+
+  if ($format) {
+    if ($format->format != variable_get('filter_default_format', 1)) {
+      $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+      $form['name'] = array('#type' => 'hidden', '#value' => $format->name);
+
+      return confirm_form($form, t('Are you sure you want to delete the input format %format?', array('%format' => $format->name)), 'admin/settings/filters', t('If you have any content left in this input format, it will be switched to the default input format. This action cannot be undone.'), t('Delete'), t('Cancel'));
+    }
+    else {
+      drupal_set_message(t('The default format cannot be deleted.'));
+      drupal_goto('admin/settings/filters');
+    }
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+/**
+ * Process filter delete form submission.
+ */
+function filter_admin_delete_submit($form, &$form_state) {
+  db_query("DELETE FROM {filter_formats} WHERE format = %d", $form_state['values']['format']);
+  db_query("DELETE FROM {filters} WHERE format = %d", $form_state['values']['format']);
+
+  $default = variable_get('filter_default_format', 1);
+  // Replace existing instances of the deleted format with the default format.
+  db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $form_state['values']['format']);
+  db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $form_state['values']['format']);
+  db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $form_state['values']['format']);
+
+  cache_clear_all($form_state['values']['format'] .':', 'cache_filter', TRUE);
+  drupal_set_message(t('Deleted input format %format.', array('%format' => $form_state['values']['name'])));
+
+  $form_state['redirect'] = 'admin/settings/filters';
+  return;
+}
+
+
+/**
+ * Menu callback; display settings defined by a format's filters.
+ */
+function filter_admin_configure_page($format) {
+  drupal_set_title(t("Configure %format", array('%format' => $format->name)));
+  return drupal_get_form('filter_admin_configure', $format);
+}
+
+/**
+ * Build a form to change the settings for a format's filters.
+ *
+ * @ingroup forms
+ */
+function filter_admin_configure(&$form_state, $format) {
+  $list = filter_list_format($format->format);
+  $form = array();
+  foreach ($list as $filter) {
+    $form_module = module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format->format);
+    if (isset($form_module) && is_array($form_module)) {
+      $form = array_merge($form, $form_module);
+    }
+  }
+
+  if (!empty($form)) {
+    $form = system_settings_form($form);
+  }
+  else {
+    $form['error'] = array('#value' => t('No settings are available.'));
+  }
+  $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+  $form['#submit'][] = 'filter_admin_configure_submit';
+  return $form;
+}
+
+/**
+ * Clear the filter's cache when configuration settings are saved.
+ */
+function filter_admin_configure_submit($form, &$form_state) {
+  cache_clear_all($form_state['values']['format'] .':', 'cache_filter', TRUE);
+}
+
+/**
+ * Menu callback; display form for ordering filters for a format.
+ */
+function filter_admin_order_page($format) {
+  drupal_set_title(t("Rearrange %format", array('%format' => $format->name)));
+  return drupal_get_form('filter_admin_order', $format);
+}
+
+/**
+ * Build the form for ordering filters for a format.
+ *
+ * @ingroup forms
+ * @see theme_filter_admin_order()
+ * @see filter_admin_order_submit()
+ */
+function filter_admin_order(&$form_state, $format = NULL) {
+  // Get list (with forced refresh).
+  $filters = filter_list_format($format->format);
+
+  $form['weights'] = array('#tree' => TRUE);
+  foreach ($filters as $id => $filter) {
+    $form['names'][$id] = array('#value' => $filter->name);
+    $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
+  }
+  $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+  return $form;
+}
+
+/**
+ * Theme filter order configuration form.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_admin_order($form) {
+  $header = array(t('Name'), t('Weight'));
+  $rows = array();
+  foreach (element_children($form['names']) as $id) {
+    // Don't take form control structures.
+    if (is_array($form['names'][$id])) {
+      $form['weights'][$id]['#attributes']['class'] = 'filter-order-weight';
+      $rows[] = array(
+        'data' => array(drupal_render($form['names'][$id]), drupal_render($form['weights'][$id])),
+        'class' => 'draggable',
+      );
+    }
+  }
+
+  $output = theme('table', $header, $rows, array('id' => 'filter-order'));
+  $output .= drupal_render($form);
+
+  drupal_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, FALSE);
+
+  return $output;
+}
+
+/**
+ * Process filter order configuration form submission.
+ */
+function filter_admin_order_submit($form, &$form_state) {
+  foreach ($form_state['values']['weights'] as $id => $weight) {
+    list($module, $delta) = explode('/', $id);
+    db_query("UPDATE {filters} SET weight = %d WHERE format = %d AND module = '%s' AND delta = %d", $weight, $form_state['values']['format'], $module, $delta);
+  }
+  drupal_set_message(t('The filter ordering has been saved.'));
+
+  cache_clear_all($form_state['values']['format'] .':', 'cache_filter', TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/filter/filter.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: filter.info,v 1.4 2007/06/08 05:50:54 dries Exp $
+name = Filter
+description = Handles the filtering of content in preparation for display.
+package = Core - required
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/filter/filter.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,91 @@
+<?php
+// $Id: filter.install,v 1.5 2007/12/18 12:59:21 dries Exp $
+
+/**
+ * Implementation of hook_schema().
+ */
+function filter_schema() {
+  $schema['filters'] = array(
+    'description' => t('Table that maps filters (HTML corrector) to input formats (Filtered HTML).'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Auto-incrementing filter ID.'),
+      ),
+      'format' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Foreign key: The {filter_formats}.format to which this filter is assigned.'),
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The origin module of the filter.'),
+      ),
+      'delta' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('ID to identify which filter within module is being referenced.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Weight of filter within format.'),
+      )
+    ),
+    'primary key' => array('fid'),
+    'unique keys' => array(
+      'fmd' => array('format', 'module', 'delta'),
+    ),
+    'indexes' => array(
+      'list' => array('format', 'weight', 'module', 'delta'),
+    ),
+  );
+  $schema['filter_formats'] = array(
+    'description' => t('Stores input formats: custom groupings of filters, such as Filtered HTML.'),
+    'fields' => array(
+      'format' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique ID for format.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Name of the input format (Filtered HTML).'),
+      ),
+      'roles' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('A comma-separated string of roles; references {role}.rid.'), // This is bad since you can't use joins, nor index.
+      ),
+      'cache' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Flag to indicate whether format is cachable. (1 = cachable, 0 = not cachable)'),
+      ),
+    ),
+    'primary key' => array('format'),
+    'unique keys' => array('name' => array('name')),
+  );
+
+  $schema['cache_filter'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_filter']['description'] = t('Cache table for the Filter module to store already filtered pieces of text, identified by input format and md5 hash of the text.');
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/filter/filter.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1207 @@
+<?php
+// $Id: filter.module,v 1.204 2008/01/21 15:08:24 goba Exp $
+
+/**
+ * @file
+ * Framework for handling filtering of content.
+ */
+
+// This is a special format ID which means "use the default format". This value
+// can be passed to the filter APIs as a format ID: this is equivalent to not
+// passing an explicit format at all.
+define('FILTER_FORMAT_DEFAULT', 0);
+
+define('FILTER_HTML_STRIP', 1);
+define('FILTER_HTML_ESCAPE', 2);
+
+/**
+ * Implementation of hook_help().
+ */
+function filter_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#filter':
+      $output = '<p>'. t("The filter module allows administrators to configure text input formats for use on your site. An input format defines the HTML tags, codes, and other input allowed in both content and comments, and is a key feature in guarding against potentially damaging input from malicious users. Two input formats included by default are <em>Filtered HTML</em> (which allows only an administrator-approved subset of HTML tags) and <em>Full HTML</em> (which allows the full set of HTML tags). Additional input formats may be created by an administrator.") .'</p>';
+      $output .= '<p>'. t('Each input format uses filters to manipulate text, and most input formats apply several different filters to text in a specific order. Each filter is designed for a specific purpose, and generally either adds, removes or transforms elements within user-entered text before it is displayed. A filter does not change the actual content of a post, but instead, modifies it temporarily before it is displayed. A filter may remove unapproved HTML tags, for instance, while another automatically adds HTML to make links referenced in text clickable.') .'</p>';
+      $output .= '<p>'. t('Users can choose between the available input formats when creating or editing content. Administrators can configure which input formats are available to which user roles, as well as choose a default input format.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@filter">Filter module</a>.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) .'</p>';
+      return $output;
+    case 'admin/settings/filters':
+      $output = '<p>'. t('<em>Input formats</em> define a way of processing user-supplied text in Drupal. Each input format uses filters to manipulate text, and most input formats apply several different filters to text, in a specific order. Each filter is designed to accomplish a specific purpose, and generally either removes elements from or adds elements to text before it is displayed. Users can choose between the available input formats when submitting content.') .'</p>';
+      $output .= '<p>'. t('Use the list below to configure which input formats are available to which roles, as well as choose a default input format (used for imported content, for example). The default format is always available to users. All input formats are available to users in a role with the "administer filters" permission.') .'</p>';
+      return $output;
+    case 'admin/settings/filters/%':
+      return '<p>'. t('Every <em>filter</em> performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this input format. If you notice some filters are causing conflicts in the output, you can <a href="@rearrange">rearrange them</a>.', array('@rearrange' => url('admin/settings/filters/'. $arg[3] .'/order'))) .'</p>';
+    case 'admin/settings/filters/%/configure':
+      return '<p>'. t('If you cannot find the settings for a certain filter, make sure you have enabled it on the <a href="@url">view tab</a> first.', array('@url' => url('admin/settings/filters/'. $arg[3]))) .'</p>';
+    case 'admin/settings/filters/%/order':
+      $output = '<p>'. t('Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted to a clickable link. When this happens, rearrange the order of the filters.') .'</p>';
+      $output .= '<p>'. t("Filters are executed from top-to-bottom. To change the order of the filters, modify the values in the <em>Weight</em> column or grab a drag-and-drop handle under the <em>Name</em> column and drag filters to new locations in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save configuration</em> button at the bottom of the page.") .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function filter_theme() {
+  return array(
+    'filter_admin_overview' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'filter.admin.inc',
+    ),
+    'filter_admin_order' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'filter.admin.inc',
+    ),
+    'filter_tips' => array(
+      'arguments' => array('tips' => NULL, 'long' => FALSE, 'extra' => ''),
+      'file' => 'filter.pages.inc',
+    ),
+    'filter_tips_more_info' => array(
+      'arguments' => array(),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function filter_menu() {
+  $items['admin/settings/filters'] = array(
+    'title' => 'Input formats',
+    'description' => 'Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filter_admin_overview'),
+    'access arguments' => array('administer filters'),
+    'file' => 'filter.admin.inc',
+  );
+  $items['admin/settings/filters/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/settings/filters/add'] = array(
+    'title' => 'Add input format',
+    'page callback' => 'filter_admin_format_page',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+    'file' => 'filter.admin.inc',
+  );
+  $items['admin/settings/filters/delete'] = array(
+    'title' => 'Delete input format',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('filter_admin_delete'),
+    'type' => MENU_CALLBACK,
+    'file' => 'filter.admin.inc',
+  );
+  $items['filter/tips'] = array(
+    'title' => 'Compose tips',
+    'page callback' => 'filter_tips_long',
+    'access callback' => TRUE,
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'filter.pages.inc',
+  );
+  $items['admin/settings/filters/%filter_format'] = array(
+    'type' => MENU_CALLBACK,
+    'title callback' => 'filter_admin_format_title',
+    'title arguments' => array(3),
+    'page callback' => 'filter_admin_format_page',
+    'page arguments' => array(3),
+    'access arguments' => array('administer filters'),
+    'file' => 'filter.admin.inc',
+  );
+
+  $items['admin/settings/filters/%filter_format/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => 0,
+    'file' => 'filter.admin.inc',
+  );
+  $items['admin/settings/filters/%filter_format/configure'] = array(
+    'title' => 'Configure',
+    'page callback' => 'filter_admin_configure_page',
+    'page arguments' => array(3),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+    'file' => 'filter.admin.inc',
+  );
+  $items['admin/settings/filters/%filter_format/order'] = array(
+    'title' => 'Rearrange',
+    'page callback' => 'filter_admin_order_page',
+    'page arguments' => array(3),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'filter.admin.inc',
+  );
+  return $items;
+}
+
+function filter_format_load($arg) {
+  return filter_formats($arg);
+}
+
+/**
+ * Display a filter format form title.
+ */
+function filter_admin_format_title($format) {
+  return $format->name;
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function filter_perm() {
+  return array('administer filters');
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Expire outdated filter cache entries
+ */
+function filter_cron() {
+  cache_clear_all(NULL, 'cache_filter');
+}
+
+/**
+ * Implementation of hook_filter_tips().
+ */
+function filter_filter_tips($delta, $format, $long = FALSE) {
+  global $base_url;
+  switch ($delta) {
+    case 0:
+      if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) {
+        if ($allowed_html = variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>')) {
+          switch ($long) {
+            case 0:
+              return t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
+            case 1:
+              $output = '<p>'. t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)) .'</p>';
+              if (!variable_get("filter_html_help_$format", 1)) {
+                return $output;
+              }
+
+              $output .= t('
+<p>This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.</p>
+<p>For more information see W3C\'s <a href="http://www.w3.org/TR/html/">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.</p>');
+              $tips = array(
+                'a' => array( t('Anchors are used to make links to other pages.'), '<a href="'. $base_url .'">'. variable_get('site_name', 'Drupal') .'</a>'),
+                'br' => array( t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')),
+                'p' => array( t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>'. t('Paragraph one.') .'</p> <p>'. t('Paragraph two.') .'</p>'),
+                'strong' => array( t('Strong'), '<strong>'. t('Strong') .'</strong>'),
+                'em' => array( t('Emphasized'), '<em>'. t('Emphasized') .'</em>'),
+                'cite' => array( t('Cited'), '<cite>'. t('Cited') .'</cite>'),
+                'code' => array( t('Coded text used to show programming source code'), '<code>'. t('Coded') .'</code>'),
+                'b' => array( t('Bolded'), '<b>'. t('Bolded') .'</b>'),
+                'u' => array( t('Underlined'), '<u>'. t('Underlined') .'</u>'),
+                'i' => array( t('Italicized'), '<i>'. t('Italicized') .'</i>'),
+                'sup' => array( t('Superscripted'), t('<sup>Super</sup>scripted')),
+                'sub' => array( t('Subscripted'), t('<sub>Sub</sub>scripted')),
+                'pre' => array( t('Preformatted'), '<pre>'. t('Preformatted') .'</pre>'),
+                'abbr' => array( t('Abbreviation'), t('<abbr title="Abbreviation">Abbrev.</abbr>')),
+                'acronym' => array( t('Acronym'), t('<acronym title="Three-Letter Acronym">TLA</acronym>')),
+                'blockquote' => array( t('Block quoted'), '<blockquote>'. t('Block quoted') .'</blockquote>'),
+                'q' => array( t('Quoted inline'), '<q>'. t('Quoted inline') .'</q>'),
+                // Assumes and describes tr, td, th.
+                'table' => array( t('Table'), '<table> <tr><th>'. t('Table header') .'</th></tr> <tr><td>'. t('Table cell') .'</td></tr> </table>'),
+                'tr' => NULL, 'td' => NULL, 'th' => NULL,
+                'del' => array( t('Deleted'), '<del>'. t('Deleted') .'</del>'),
+                'ins' => array( t('Inserted'), '<ins>'. t('Inserted') .'</ins>'),
+                 // Assumes and describes li.
+                'ol' => array( t('Ordered list - use the &lt;li&gt; to begin each list item'), '<ol> <li>'. t('First item') .'</li> <li>'. t('Second item') .'</li> </ol>'),
+                'ul' => array( t('Unordered list - use the &lt;li&gt; to begin each list item'), '<ul> <li>'. t('First item') .'</li> <li>'. t('Second item') .'</li> </ul>'),
+                'li' => NULL,
+                // Assumes and describes dt and dd.
+                'dl' => array( t('Definition lists are similar to other HTML lists. &lt;dl&gt; begins the definition list, &lt;dt&gt; begins the definition term and &lt;dd&gt; begins the definition description.'), '<dl> <dt>'. t('First term') .'</dt> <dd>'. t('First definition') .'</dd> <dt>'. t('Second term') .'</dt> <dd>'. t('Second definition') .'</dd> </dl>'),
+                'dt' => NULL, 'dd' => NULL,
+                'h1' => array( t('Header'), '<h1>'. t('Title') .'</h1>'),
+                'h2' => array( t('Header'), '<h2>'. t('Subtitle') .'</h2>'),
+                'h3' => array( t('Header'), '<h3>'. t('Subtitle three') .'</h3>'),
+                'h4' => array( t('Header'), '<h4>'. t('Subtitle four') .'</h4>'),
+                'h5' => array( t('Header'), '<h5>'. t('Subtitle five') .'</h5>'),
+                'h6' => array( t('Header'), '<h6>'. t('Subtitle six') .'</h6>')
+              );
+              $header = array(t('Tag Description'), t('You Type'), t('You Get'));
+              preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out);
+              foreach ($out[1] as $tag) {
+                if (array_key_exists($tag, $tips)) {
+                  if ($tips[$tag]) {
+                    $rows[] = array(
+                      array('data' => $tips[$tag][0], 'class' => 'description'),
+                      array('data' => '<code>'. check_plain($tips[$tag][1]) .'</code>', 'class' => 'type'),
+                      array('data' => $tips[$tag][1], 'class' => 'get')
+                    );
+                  }
+                }
+                else {
+                  $rows[] = array(
+                    array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => 'description', 'colspan' => 3),
+                  );
+                }
+              }
+              $output .= theme('table', $header, $rows);
+
+              $output .= t('
+<p>Most unusual characters can be directly entered without any problems.</p>
+<p>If you do encounter problems, try using HTML character entities. A common example looks like &amp;amp; for an ampersand &amp; character. For a full list of entities see HTML\'s <a href="http://www.w3.org/TR/html4/sgml/entities.html">entities</a> page. Some of the available characters include:</p>');
+              $entities = array(
+                array( t('Ampersand'), '&amp;'),
+                array( t('Greater than'), '&gt;'),
+                array( t('Less than'), '&lt;'),
+                array( t('Quotation mark'), '&quot;'),
+              );
+              $header = array(t('Character Description'), t('You Type'), t('You Get'));
+              unset($rows);
+              foreach ($entities as $entity) {
+                $rows[] = array(
+                  array('data' => $entity[0], 'class' => 'description'),
+                  array('data' => '<code>'. check_plain($entity[1]) .'</code>', 'class' => 'type'),
+                  array('data' => $entity[1], 'class' => 'get')
+                );
+              }
+              $output .= theme('table', $header, $rows);
+              return $output;
+          }
+        }
+        else {
+          return t('No HTML tags allowed');
+        }
+      }
+      break;
+
+    case 1:
+      switch ($long) {
+        case 0:
+          return t('Lines and paragraphs break automatically.');
+        case 1:
+          return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
+      }
+      break;
+    case 2:
+      return t('Web page addresses and e-mail addresses turn into links automatically.');
+  }
+}
+
+/**
+ * Retrieve a list of input formats.
+ */
+function filter_formats($index = NULL) {
+  global $user;
+  static $formats;
+
+  // Administrators can always use all input formats.
+  $all = user_access('administer filters');
+
+  if (!isset($formats)) {
+    $formats = array();
+
+    $query = 'SELECT * FROM {filter_formats}';
+
+    // Build query for selecting the format(s) based on the user's roles.
+    $args = array();
+    if (!$all) {
+      $where = array();
+      foreach ($user->roles as $rid => $role) {
+        $where[] = "roles LIKE '%%,%d,%%'";
+        $args[] = $rid;
+      }
+      $query .= ' WHERE '. implode(' OR ', $where) .' OR format = %d';
+      $args[] = variable_get('filter_default_format', 1);
+    }
+
+    $result = db_query($query, $args);
+    while ($format = db_fetch_object($result)) {
+      $formats[$format->format] = $format;
+    }
+  }
+  if (isset($index)) {
+    return isset($formats[$index]) ? $formats[$index] : FALSE;
+  }
+  return $formats;
+}
+
+/**
+ * Build a list of all filters.
+ */
+function filter_list_all() {
+  $filters = array();
+
+  foreach (module_list() as $module) {
+    $list = module_invoke($module, 'filter', 'list');
+    if (isset($list) && is_array($list)) {
+      foreach ($list as $delta => $name) {
+        $filters[$module .'/'. $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name);
+      }
+    }
+  }
+
+  uasort($filters, '_filter_list_cmp');
+
+  return $filters;
+}
+
+/**
+ * Helper function for sorting the filter list by filter name.
+ */
+function _filter_list_cmp($a, $b) {
+  return strcmp($a->name, $b->name);
+}
+
+/**
+ * Resolve a format id, including the default format.
+ */
+function filter_resolve_format($format) {
+  return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format;
+}
+/**
+ * Check if text in a certain input format is allowed to be cached.
+ */
+function filter_format_allowcache($format) {
+  static $cache = array();
+  $format = filter_resolve_format($format);
+  if (!isset($cache[$format])) {
+    $cache[$format] = db_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format));
+  }
+  return $cache[$format];
+}
+
+/**
+ * Retrieve a list of filters for a certain format.
+ */
+function filter_list_format($format) {
+  static $filters = array();
+
+  if (!isset($filters[$format])) {
+    $filters[$format] = array();
+    $result = db_query("SELECT * FROM {filters} WHERE format = %d ORDER BY weight, module, delta", $format);
+    while ($filter = db_fetch_object($result)) {
+      $list = module_invoke($filter->module, 'filter', 'list');
+      if (isset($list) && is_array($list) && isset($list[$filter->delta])) {
+        $filter->name = $list[$filter->delta];
+        $filters[$format][$filter->module .'/'. $filter->delta] = $filter;
+      }
+    }
+  }
+
+  return $filters[$format];
+}
+
+/**
+ * @name Filtering functions
+ * @{
+ * Modules which need to have content filtered can use these functions to
+ * interact with the filter system.
+ *
+ * For more info, see the hook_filter() documentation.
+ *
+ * Note: because filters can inject JavaScript or execute PHP code, security is
+ * vital here. When a user supplies a $format, you should validate it with
+ * filter_access($format) before accepting/using it. This is normally done in
+ * the validation stage of the node system. You should for example never make a
+ * preview of content in a disallowed format.
+ */
+
+/**
+ * Run all the enabled filters on a piece of text.
+ *
+ * @param $text
+ *    The text to be filtered.
+ * @param $format
+ *    The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for
+ *    the default format.
+ * @param $check
+ *    Whether to check the $format with filter_access() first. Defaults to TRUE.
+ *    Note that this will check the permissions of the current user, so you
+ *    should specify $check = FALSE when viewing other people's content. When
+ *    showing content that is not (yet) stored in the database (eg. upon preview),
+ *    set to TRUE so the user's permissions are checked.
+ */
+function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) {
+  // When $check = TRUE, do an access check on $format.
+  if (isset($text) && (!$check || filter_access($format))) {
+    $format = filter_resolve_format($format);
+
+    // Check for a cached version of this piece of text.
+    $cache_id = $format .':'. md5($text);
+    if ($cached = cache_get($cache_id, 'cache_filter')) {
+      return $cached->data;
+    }
+
+    // See if caching is allowed for this format.
+    $cache = filter_format_allowcache($format);
+
+    // Convert all Windows and Mac newlines to a single newline,
+    // so filters only need to deal with one possibility.
+    $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+    // Get a complete list of filters, ordered properly.
+    $filters = filter_list_format($format);
+
+    // Give filters the chance to escape HTML-like data such as code or formulas.
+    foreach ($filters as $filter) {
+      $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text, $cache_id);
+    }
+
+    // Perform filtering.
+    foreach ($filters as $filter) {
+      $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text, $cache_id);
+    }
+
+    // Store in cache with a minimum expiration time of 1 day.
+    if ($cache) {
+      cache_set($cache_id, $text, 'cache_filter', time() + (60 * 60 * 24));
+    }
+  }
+  else {
+    $text = t('n/a');
+  }
+
+  return $text;
+}
+
+/**
+ * Generate a selector for choosing a format in a form.
+ *
+ * @ingroup forms
+ * @see filter_form_validate()
+ * @param $value
+ *   The ID of the format that is currently selected.
+ * @param $weight
+ *   The weight of the input format.
+ * @param $parents
+ *   Required when defining multiple input formats on a single node or having a different parent than 'format'.
+ * @return
+ *   HTML for the form element.
+ */
+function filter_form($value = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) {
+  $value = filter_resolve_format($value);
+  $formats = filter_formats();
+
+  $extra = theme('filter_tips_more_info');
+
+  if (count($formats) > 1) {
+    $form = array(
+      '#type' => 'fieldset',
+      '#title' => t('Input format'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#weight' => $weight,
+      '#element_validate' => array('filter_form_validate'),
+    );
+    // Multiple formats available: display radio buttons with tips.
+    foreach ($formats as $format) {
+      // Generate the parents as the autogenerator does, so we will have a
+      // unique id for each radio button.
+      $parents_for_id = array_merge($parents, array($format->format));
+      $form[$format->format] = array(
+        '#type' => 'radio',
+        '#title' => $format->name,
+        '#default_value' => $value,
+        '#return_value' => $format->format,
+        '#parents' => $parents,
+        '#description' => theme('filter_tips', _filter_tips($format->format, FALSE)),
+        '#id' => form_clean_id('edit-'. implode('-', $parents_for_id)),
+      );
+    }
+  }
+  else {
+    // Only one format available: use a hidden form item and only show tips.
+    $format = array_shift($formats);
+    $form[$format->format] = array('#type' => 'value', '#value' => $format->format, '#parents' => $parents);
+    $tips = _filter_tips(variable_get('filter_default_format', 1), FALSE);
+    $form['format']['guidelines'] = array(
+      '#title' => t('Formatting guidelines'),
+      '#value' => theme('filter_tips', $tips, FALSE, $extra),
+    );
+  }
+  $form[] = array('#value' => $extra);
+  return $form;
+}
+
+function filter_form_validate($form) {
+  foreach (element_children($form) as $key) {
+    if ($form[$key]['#value'] == $form[$key]['#return_value']) {
+      return;
+    }
+  }
+  form_error($form, t('An illegal choice has been detected. Please contact the site administrator.'));
+  watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $form[$key]['#value'], '%name' => empty($form['#title']) ? $form['#parents'][0] : $form['#title']), WATCHDOG_ERROR);
+}
+
+/**
+ * Returns TRUE if the user is allowed to access this format.
+ */
+function filter_access($format) {
+  $format = filter_resolve_format($format);
+  if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) {
+    return TRUE;
+  }
+  else {
+    $formats = filter_formats();
+    return isset($formats[$format]);
+  }
+}
+
+/**
+ * @} End of "Filtering functions".
+ */
+
+
+/**
+ * Helper function for fetching filter tips.
+ */
+function _filter_tips($format, $long = FALSE) {
+  if ($format == -1) {
+    $formats = filter_formats();
+  }
+  else {
+    $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format)));
+  }
+
+  $tips = array();
+
+  foreach ($formats as $format) {
+    $filters = filter_list_format($format->format);
+
+    $tips[$format->name] = array();
+    foreach ($filters as $id => $filter) {
+      if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) {
+        $tips[$format->name][] = array('tip' => $tip, 'id' => $id);
+      }
+    }
+  }
+
+  return $tips;
+}
+
+
+/**
+ * Format a link to the more extensive filter tips.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_tips_more_info() {
+  return '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
+}
+
+/**
+ * @name Standard filters
+ * @{
+ * Filters implemented by the filter.module.
+ */
+
+/**
+ * Implementation of hook_filter(). Contains a basic set of essential filters.
+ * - HTML filter:
+ *     Validates user-supplied HTML, transforming it as necessary.
+ * - Line break converter:
+ *     Converts newlines into paragraph and break tags.
+ * - URL and e-mail address filter:
+ *     Converts newlines into paragraph and break tags.
+ */
+function filter_filter($op, $delta = 0, $format = -1, $text = '') {
+  switch ($op) {
+    case 'list':
+      return array(0 => t('HTML filter'), 1 => t('Line break converter'), 2 => t('URL filter'), 3 => t('HTML corrector'));
+
+    case 'description':
+      switch ($delta) {
+        case 0:
+          return t('Allows you to restrict whether users can post HTML and which tags to filter out. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.');
+        case 1:
+          return t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt; tags).');
+        case 2:
+          return t('Turns web and e-mail addresses into clickable links.');
+        case 3:
+          return t('Corrects faulty and chopped off HTML in postings.');
+        default:
+          return;
+      }
+
+    case 'process':
+      switch ($delta) {
+        case 0:
+          return _filter_html($text, $format);
+        case 1:
+          return _filter_autop($text);
+        case 2:
+          return _filter_url($text, $format);
+        case 3:
+          return _filter_htmlcorrector($text);
+        default:
+          return $text;
+      }
+
+    case 'settings':
+      switch ($delta) {
+        case 0:
+          return _filter_html_settings($format);
+        case 2:
+          return _filter_url_settings($format);
+        default:
+          return;
+      }
+
+    default:
+      return $text;
+  }
+}
+
+/**
+ * Settings for the HTML filter.
+ */
+function _filter_html_settings($format) {
+  $form['filter_html'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('HTML filter'),
+    '#collapsible' => TRUE,
+  );
+  $form['filter_html']["filter_html_$format"] = array(
+    '#type' => 'radios',
+    '#title' => t('Filter HTML tags'),
+    '#default_value' => variable_get("filter_html_$format", FILTER_HTML_STRIP),
+    '#options' => array(FILTER_HTML_STRIP => t('Strip disallowed tags'), FILTER_HTML_ESCAPE => t('Escape all tags')),
+    '#description' => t('How to deal with HTML tags in user-contributed content. If set to "Strip disallowed tags", dangerous tags are removed (see below). If set to "Escape tags", all HTML is escaped and presented as it was typed.'),
+  );
+  $form['filter_html']["allowed_html_$format"] = array(
+    '#type' => 'textfield',
+    '#title' => t('Allowed HTML tags'),
+    '#default_value' => variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'),
+    '#size' => 64,
+    '#maxlength' => 255,
+    '#description' => t('If "Strip disallowed tags" is selected, optionally specify tags which should not be stripped. JavaScript event attributes are always stripped.'),
+  );
+  $form['filter_html']["filter_html_help_$format"] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Display HTML help'),
+    '#default_value' => variable_get("filter_html_help_$format", 1),
+    '#description' => t('If enabled, Drupal will display some basic HTML help in the long filter tips.'),
+  );
+  $form['filter_html']["filter_html_nofollow_$format"] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Spam link deterrent'),
+    '#default_value' => variable_get("filter_html_nofollow_$format", FALSE),
+    '#description' => t('If enabled, Drupal will add rel="nofollow" to all links, as a measure to reduce the effectiveness of spam links. Note: this will also prevent valid links from being followed by search engines, therefore it is likely most effective when enabled for anonymous users.'),
+  );
+  return $form;
+}
+
+/**
+ * HTML filter. Provides filtering of input into accepted HTML.
+ */
+function _filter_html($text, $format) {
+  if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_STRIP) {
+    $allowed_tags = preg_split('/\s+|<|>/', variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'), -1, PREG_SPLIT_NO_EMPTY);
+    $text = filter_xss($text, $allowed_tags);
+  }
+
+  if (variable_get("filter_html_$format", FILTER_HTML_STRIP) == FILTER_HTML_ESCAPE) {
+    // Escape HTML
+    $text = check_plain($text);
+  }
+
+  if (variable_get("filter_html_nofollow_$format", FALSE)) {
+    $text = preg_replace('/<a([^>]+)>/i', '<a\\1 rel="nofollow">', $text);
+  }
+
+  return trim($text);
+}
+
+/**
+ * Settings for URL filter.
+ */
+function _filter_url_settings($format) {
+  $form['filter_urlfilter'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('URL filter'),
+    '#collapsible' => TRUE,
+  );
+  $form['filter_urlfilter']['filter_url_length_'. $format] = array(
+    '#type' => 'textfield',
+    '#title' => t('Maximum link text length'),
+    '#default_value' => variable_get('filter_url_length_'. $format, 72),
+    '#maxlength' => 4,
+    '#description' => t('URLs longer than this number of characters will be truncated to prevent long strings that break formatting. The link itself will be retained; just the text portion of the link will be truncated.'),
+  );
+  return $form;
+}
+
+/**
+ * URL filter. Automatically converts text web addresses (URLs, e-mail addresses,
+ * ftp links, etc.) into hyperlinks.
+ */
+function _filter_url($text, $format) {
+  // Pass length to regexp callback
+  _filter_url_trim(NULL, variable_get('filter_url_length_'. $format, 72));
+
+  $text = ' '. $text .' ';
+
+  // Match absolute URLs.
+  $text = preg_replace_callback("`(<p>|<li>|<br\s*/?>|[ \n\r\t\(])((http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-]))([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '_filter_url_parse_full_links', $text);
+
+  // Match e-mail addresses.
+  $text = preg_replace("`(<p>|<li>|<br\s*/?>|[ \n\r\t\(])([A-Za-z0-9._-]+@[A-Za-z0-9._+-]+\.[A-Za-z]{2,4})([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '\1<a href="mailto:\2">\2</a>\3', $text);
+
+  // Match www domains/addresses.
+  $text = preg_replace_callback("`(<p>|<li>|[ \n\r\t\(])(www\.[a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+~#\&=/;-])([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", '_filter_url_parse_partial_links', $text);
+  $text = substr($text, 1, -1);
+
+  return $text;
+}
+
+/**
+ * Scan input and make sure that all HTML tags are properly closed and nested.
+ */
+function _filter_htmlcorrector($text) {
+  // Prepare tag lists.
+  static $no_nesting, $single_use;
+  if (!isset($no_nesting)) {
+    // Tags which cannot be nested but are typically left unclosed.
+    $no_nesting = drupal_map_assoc(array('li', 'p'));
+
+    // Single use tags in HTML4
+    $single_use = drupal_map_assoc(array('base', 'meta', 'link', 'hr', 'br', 'param', 'img', 'area', 'input', 'col', 'frame'));
+  }
+
+  // Properly entify angles.
+  $text = preg_replace('!<([^a-zA-Z/])!', '&lt;\1', $text);
+
+  // Split tags from text.
+  $split = preg_split('/<([^>]+?)>/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+  // Note: PHP ensures the array consists of alternating delimiters and literals
+  // and begins and ends with a literal (inserting $null as required).
+
+  $tag = false; // Odd/even counter. Tag or no tag.
+  $stack = array();
+  $output = '';
+  foreach ($split as $value) {
+    // Process HTML tags.
+    if ($tag) {
+      list($tagname) = explode(' ', strtolower($value), 2);
+      // Closing tag
+      if ($tagname{0} == '/') {
+        $tagname = substr($tagname, 1);
+        // Discard XHTML closing tags for single use tags.
+        if (!isset($single_use[$tagname])) {
+          // See if we possibly have a matching opening tag on the stack.
+          if (in_array($tagname, $stack)) {
+            // Close other tags lingering first.
+            do {
+              $output .= '</'. $stack[0] .'>';
+            } while (array_shift($stack) != $tagname);
+          }
+          // Otherwise, discard it.
+        }
+      }
+      // Opening tag
+      else {
+        // See if we have an identical 'no nesting' tag already open and close it if found.
+        if (count($stack) && ($stack[0] == $tagname) && isset($no_nesting[$stack[0]])) {
+          $output .= '</'. array_shift($stack) .'>';
+        }
+        // Push non-single-use tags onto the stack
+        if (!isset($single_use[$tagname])) {
+          array_unshift($stack, $tagname);
+        }
+        // Add trailing slash to single-use tags as per X(HT)ML.
+        else {
+          $value = rtrim($value, ' /') .' /';
+        }
+        $output .= '<'. $value .'>';
+      }
+    }
+    else {
+      // Passthrough all text.
+      $output .= $value;
+    }
+    $tag = !$tag;
+  }
+  // Close remaining tags.
+  while (count($stack) > 0) {
+    $output .= '</'. array_shift($stack) .'>';
+  }
+  return $output;
+}
+
+/**
+ * Make links out of absolute URLs.
+ */
+function _filter_url_parse_full_links($match) {
+  $match[2] = decode_entities($match[2]);
+  $caption = check_plain(_filter_url_trim($match[2]));
+  $match[2] = check_url($match[2]);
+  return $match[1] .'<a href="'. $match[2] .'" title="'. $match[2] .'">'. $caption .'</a>'. $match[5];
+}
+
+/**
+ * Make links out of domain names starting with "www."
+ */
+function _filter_url_parse_partial_links($match) {
+  $match[2] = decode_entities($match[2]);
+  $caption = check_plain(_filter_url_trim($match[2]));
+  $match[2] = check_plain($match[2]);
+  return $match[1] .'<a href="http://'. $match[2] .'" title="'. $match[2] .'">'. $caption .'</a>'. $match[3];
+}
+
+/**
+ * Shortens long URLs to http://www.example.com/long/url...
+ */
+function _filter_url_trim($text, $length = NULL) {
+  static $_length;
+  if ($length !== NULL) {
+    $_length = $length;
+  }
+
+  // Use +3 for '...' string length.
+  if (strlen($text) > $_length + 3) {
+    $text = substr($text, 0, $_length) .'...';
+  }
+
+  return $text;
+}
+
+/**
+ * Convert line breaks into <p> and <br> in an intelligent fashion.
+ * Based on: http://photomatt.net/scripts/autop
+ */
+function _filter_autop($text) {
+  // All block level tags
+  $block = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|p|h[1-6]|hr)';
+
+  // Split at <pre>, <script>, <style> and </pre>, </script>, </style> tags.
+  // We don't apply any processing to the contents of these tags to avoid messing
+  // up code. We look for matched pairs and allow basic nesting. For example:
+  // "processed <pre> ignored <script> ignored </script> ignored </pre> processed"
+  $chunks = preg_split('@(</?(?:pre|script|style|object)[^>]*>)@i', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+  // Note: PHP ensures the array consists of alternating delimiters and literals
+  // and begins and ends with a literal (inserting NULL as required).
+  $ignore = FALSE;
+  $ignoretag = '';
+  $output = '';
+  foreach ($chunks as $i => $chunk) {
+    if ($i % 2) {
+      // Opening or closing tag?
+      $open = ($chunk[1] != '/');
+      list($tag) = split('[ >]', substr($chunk, 2 - $open), 2);
+      if (!$ignore) {
+        if ($open) {
+          $ignore = TRUE;
+          $ignoretag = $tag;
+        }
+      }
+      // Only allow a matching tag to close it.
+      else if (!$open && $ignoretag == $tag) {
+        $ignore = FALSE;
+        $ignoretag = '';
+      }
+    }
+    else if (!$ignore) {
+      $chunk = preg_replace('|\n*$|', '', $chunk) ."\n\n"; // just to make things a little easier, pad the end
+      $chunk = preg_replace('|<br />\s*<br />|', "\n\n", $chunk);
+      $chunk = preg_replace('!(<'. $block .'[^>]*>)!', "\n$1", $chunk); // Space things out a little
+      $chunk = preg_replace('!(</'. $block .'>)!', "$1\n\n", $chunk); // Space things out a little
+      $chunk = preg_replace("/\n\n+/", "\n\n", $chunk); // take care of duplicates
+      $chunk = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $chunk); // make paragraphs, including one at the end
+      $chunk = preg_replace('|<p>\s*</p>\n|', '', $chunk); // under certain strange conditions it could create a P of entirely whitespace
+      $chunk = preg_replace("|<p>(<li.+?)</p>|", "$1", $chunk); // problem with nested lists
+      $chunk = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $chunk);
+      $chunk = str_replace('</blockquote></p>', '</p></blockquote>', $chunk);
+      $chunk = preg_replace('!<p>\s*(</?'. $block .'[^>]*>)!', "$1", $chunk);
+      $chunk = preg_replace('!(</?'. $block .'[^>]*>)\s*</p>!', "$1", $chunk);
+      $chunk = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $chunk); // make line breaks
+      $chunk = preg_replace('!(</?'. $block .'[^>]*>)\s*<br />!', "$1", $chunk);
+      $chunk = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $chunk);
+      $chunk = preg_replace('/&([^#])(?![A-Za-z0-9]{1,8};)/', '&amp;$1', $chunk);
+    }
+    $output .= $chunk;
+  }
+  return $output;
+}
+
+/**
+ * Very permissive XSS/HTML filter for admin-only use.
+ *
+ * Use only for fields where it is impractical to use the
+ * whole filter system, but where some (mainly inline) mark-up
+ * is desired (so check_plain() is not acceptable).
+ *
+ * Allows all tags that can be used inside an HTML body, save
+ * for scripts and styles.
+ */
+function filter_xss_admin($string) {
+  return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'object', 'ol', 'p', 'param', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'));
+}
+
+/**
+ * Filters XSS. Based on kses by Ulf Harnhammar, see
+ * http://sourceforge.net/projects/kses
+ *
+ * For examples of various XSS attacks, see:
+ * http://ha.ckers.org/xss.html
+ *
+ * This code does four things:
+ * - Removes characters and constructs that can trick browsers
+ * - Makes sure all HTML entities are well-formed
+ * - Makes sure all HTML tags and attributes are well-formed
+ * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:)
+ *
+ * @param $string
+ *   The string with raw HTML in it. It will be stripped of everything that can cause
+ *   an XSS attack.
+ * @param $allowed_tags
+ *   An array of allowed tags.
+ * @param $format
+ *   The format to use.
+ */
+function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
+  // Only operate on valid UTF-8 strings. This is necessary to prevent cross
+  // site scripting issues on Internet Explorer 6.
+  if (!drupal_validate_utf8($string)) {
+    return '';
+  }
+  // Store the input format
+  _filter_xss_split($allowed_tags, TRUE);
+  // Remove NUL characters (ignored by some browsers)
+  $string = str_replace(chr(0), '', $string);
+  // Remove Netscape 4 JS entities
+  $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
+
+  // Defuse all HTML entities
+  $string = str_replace('&', '&amp;', $string);
+  // Change back only well-formed entities in our whitelist
+  // Named entities
+  $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
+  // Decimal numeric entities
+  $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
+  // Hexadecimal numeric entities
+  $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
+
+  return preg_replace_callback('%
+    (
+    <(?=[^a-zA-Z!/])  # a lone <
+    |                 # or
+    <[^>]*.(>|$)      # a string that starts with a <, up until the > or the end of the string
+    |                 # or
+    >                 # just a >
+    )%x', '_filter_xss_split', $string);
+}
+
+/**
+ * Processes an HTML tag.
+ *
+ * @param @m
+ *   An array with various meaning depending on the value of $store.
+ *   If $store is TRUE then the array contains the allowed tags.
+ *   If $store is FALSE then the array has one element, the HTML tag to process.
+ * @param $store
+ *   Whether to store $m.
+ * @return
+ *   If the element isn't allowed, an empty string. Otherwise, the cleaned up
+ *   version of the HTML element.
+ */
+function _filter_xss_split($m, $store = FALSE) {
+  static $allowed_html;
+
+  if ($store) {
+    $allowed_html = array_flip($m);
+    return;
+  }
+
+  $string = $m[1];
+
+  if (substr($string, 0, 1) != '<') {
+    // We matched a lone ">" character
+    return '&gt;';
+  }
+  else if (strlen($string) == 1) {
+    // We matched a lone "<" character
+    return '&lt;';
+  }
+
+  if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) {
+    // Seriously malformed
+    return '';
+  }
+
+  $slash = trim($matches[1]);
+  $elem = &$matches[2];
+  $attrlist = &$matches[3];
+
+  if (!isset($allowed_html[strtolower($elem)])) {
+    // Disallowed HTML element
+    return '';
+  }
+
+  if ($slash != '') {
+    return "</$elem>";
+  }
+
+  // Is there a closing XHTML slash at the end of the attributes?
+  // In PHP 5.1.0+ we could count the changes, currently we need a separate match
+  $xhtml_slash = preg_match('%\s?/\s*$%', $attrlist) ? ' /' : '';
+  $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist);
+
+  // Clean up attributes
+  $attr2 = implode(' ', _filter_xss_attributes($attrlist));
+  $attr2 = preg_replace('/[<>]/', '', $attr2);
+  $attr2 = strlen($attr2) ? ' '. $attr2 : '';
+
+  return "<$elem$attr2$xhtml_slash>";
+}
+
+/**
+ * Processes a string of HTML attributes.
+ *
+ * @return
+ *   Cleaned up version of the HTML attributes.
+ */
+function _filter_xss_attributes($attr) {
+  $attrarr = array();
+  $mode = 0;
+  $attrname = '';
+
+  while (strlen($attr) != 0) {
+    // Was the last operation successful?
+    $working = 0;
+
+    switch ($mode) {
+      case 0:
+        // Attribute name, href for instance
+        if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
+          $attrname = strtolower($match[1]);
+          $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
+          $working = $mode = 1;
+          $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
+        }
+
+        break;
+
+      case 1:
+        // Equals sign or valueless ("selected")
+        if (preg_match('/^\s*=\s*/', $attr)) {
+          $working = 1; $mode = 2;
+          $attr = preg_replace('/^\s*=\s*/', '', $attr);
+          break;
+        }
+
+        if (preg_match('/^\s+/', $attr)) {
+          $working = 1; $mode = 0;
+          if (!$skip) {
+            $attrarr[] = $attrname;
+          }
+          $attr = preg_replace('/^\s+/', '', $attr);
+        }
+
+        break;
+
+      case 2:
+        // Attribute value, a URL after href= for instance
+        if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
+          $thisval = filter_xss_bad_protocol($match[1]);
+
+          if (!$skip) {
+            $attrarr[] = "$attrname=\"$thisval\"";
+          }
+          $working = 1;
+          $mode = 0;
+          $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
+          break;
+        }
+
+        if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
+          $thisval = filter_xss_bad_protocol($match[1]);
+
+          if (!$skip) {
+            $attrarr[] = "$attrname='$thisval'";;
+          }
+          $working = 1; $mode = 0;
+          $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
+          break;
+        }
+
+        if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
+          $thisval = filter_xss_bad_protocol($match[1]);
+
+          if (!$skip) {
+            $attrarr[] = "$attrname=\"$thisval\"";
+          }
+          $working = 1; $mode = 0;
+          $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
+        }
+
+        break;
+    }
+
+    if ($working == 0) {
+      // not well formed, remove and try again
+      $attr = preg_replace('/
+        ^
+        (
+        "[^"]*("|$)     # - a string that starts with a double quote, up until the next double quote or the end of the string
+        |               # or
+        \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
+        |               # or
+        \S              # - a non-whitespace character
+        )*              # any number of the above three
+        \s*             # any number of whitespaces
+        /x', '', $attr);
+      $mode = 0;
+    }
+  }
+
+  // the attribute list ends with a valueless attribute like "selected"
+  if ($mode == 1) {
+    $attrarr[] = $attrname;
+  }
+  return $attrarr;
+}
+
+/**
+ * Processes an HTML attribute value and ensures it does not contain an URL
+ * with a disallowed protocol (e.g. javascript:)
+ *
+ * @param $string
+ *   The string with the attribute value.
+ * @param $decode
+ *   Whether to decode entities in the $string. Set to FALSE if the $string
+ *   is in plain text, TRUE otherwise. Defaults to TRUE.
+ * @return
+ *   Cleaned up and HTML-escaped version of $string.
+ */
+function filter_xss_bad_protocol($string, $decode = TRUE) {
+  static $allowed_protocols;
+  if (!isset($allowed_protocols)) {
+    $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal')));
+  }
+
+  // Get the plain text representation of the attribute value (i.e. its meaning).
+  if ($decode) {
+    $string = decode_entities($string);
+  }
+
+  // Iteratively remove any invalid protocol found.
+
+  do {
+    $before = $string;
+    $colonpos = strpos($string, ':');
+    if ($colonpos > 0) {
+      // We found a colon, possibly a protocol. Verify.
+      $protocol = substr($string, 0, $colonpos);
+      // If a colon is preceded by a slash, question mark or hash, it cannot
+      // possibly be part of the URL scheme. This must be a relative URL,
+      // which inherits the (safe) protocol of the base document.
+      if (preg_match('![/?#]!', $protocol)) {
+        break;
+      }
+      // Per RFC2616, section 3.2.3 (URI Comparison) scheme comparison must be case-insensitive
+      // Check if this is a disallowed protocol.
+      if (!isset($allowed_protocols[strtolower($protocol)])) {
+        $string = substr($string, $colonpos + 1);
+      }
+    }
+  } while ($before != $string);
+  return check_plain($string);
+}
+
+/**
+ * @} End of "Standard filters".
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/filter/filter.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,66 @@
+<?php
+// $Id: filter.pages.inc,v 1.2 2007/11/10 17:41:18 dries Exp $
+
+/**
+ * @file
+ * User page callbacks for the filter module.
+ */
+
+
+/**
+ * Menu callback; show a page with long filter tips.
+ */
+function filter_tips_long() {
+  $format = arg(2);
+  if ($format) {
+    $output = theme('filter_tips', _filter_tips($format, TRUE), TRUE);
+  }
+  else {
+    $output = theme('filter_tips', _filter_tips(-1, TRUE), TRUE);
+  }
+  return $output;
+}
+
+
+/**
+ * Format a set of filter tips.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_tips($tips, $long = FALSE, $extra = '') {
+  $output = '';
+
+  $multiple = count($tips) > 1;
+  if ($multiple) {
+    $output = t('input formats') .':';
+  }
+
+  if (count($tips)) {
+    if ($multiple) {
+      $output .= '<ul>';
+    }
+    foreach ($tips as $name => $tiplist) {
+      if ($multiple) {
+        $output .= '<li>';
+        $output .= '<strong>'. $name .'</strong>:<br />';
+      }
+
+      if (count($tiplist) > 0) {
+        $output .= '<ul class="tips">';
+        foreach ($tiplist as $tip) {
+          $output .= '<li'. ($long ? ' id="filter-'. str_replace("/", "-", $tip['id']) .'">' : '>') . $tip['tip'] .'</li>';
+        }
+        $output .= '</ul>';
+      }
+
+      if ($multiple) {
+        $output .= '</li>';
+      }
+    }
+    if ($multiple) {
+      $output .= '</ul>';
+    }
+  }
+
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-icon.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,25 @@
+<?php
+// $Id: forum-icon.tpl.php,v 1.3 2007/12/20 09:35:09 goba Exp $
+
+/**
+ * @file forum-icon.tpl.php
+ * Display an appropriate icon for a forum post.
+ *
+ * Available variables:
+ * - $new_posts: Indicates whether or not the topic contains new posts.
+ * - $icon: The icon to display. May be one of 'hot', 'hot-new', 'new',
+ *   'default', 'closed', or 'sticky'.
+ *
+ * @see template_preprocess_forum_icon()
+ * @see theme_forum_icon()
+ */
+?>
+<?php if ($new_posts): ?>
+  <a name="new">
+<?php endif; ?>
+
+<?php print theme('image', "misc/forum-$icon.png") ?>
+
+<?php if ($new_posts): ?>
+  </a>
+<?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-list.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,72 @@
+<?php
+// $Id: forum-list.tpl.php,v 1.4 2007/08/30 18:58:12 goba Exp $
+
+/**
+ * @file forum-list.tpl.php
+ * Default theme implementation to display a list of forums and containers.
+ *
+ * Available variables:
+ * - $forums: An array of forums and containers to display. It is keyed to the
+ *   numeric id's of all child forums and containers.
+ * - $forum_id: Forum id for the current forum. Parent to all items within
+ *   the $forums array.
+ *
+ * Each $forum in $forums contains:
+ * - $forum->is_container: Is TRUE if the forum can contain other forums. Is
+ *   FALSE if the forum can contain only topics.
+ * - $forum->depth: How deep the forum is in the current hierarchy.
+ * - $forum->zebra: 'even' or 'odd' string used for row class.
+ * - $forum->name: The name of the forum.
+ * - $forum->link: The URL to link to this forum.
+ * - $forum->description: The description of this forum.
+ * - $forum->new_topics: True if the forum contains unread posts.
+ * - $forum->new_url: A URL to the forum's unread posts.
+ * - $forum->new_text: Text for the above URL which tells how many new posts.
+ * - $forum->old_topics: A count of posts that have already been read.
+ * - $forum->num_posts: The total number of posts in the forum.
+ * - $forum->last_reply: Text representing the last time a forum was posted or
+ *   commented in.
+ *
+ * @see template_preprocess_forum_list()
+ * @see theme_forum_list()
+ */
+?>
+<table id="forum-<?php print $forum_id; ?>">
+  <thead>
+    <tr>
+      <th><?php print t('Forum'); ?></th>
+      <th><?php print t('Topics');?></th>
+      <th><?php print t('Posts'); ?></th>
+      <th><?php print t('Last post'); ?></th>
+    </tr>
+  </thead>
+  <tbody>
+  <?php foreach ($forums as $child_id => $forum): ?>
+    <tr id="forum-list-<?php print $child_id; ?>" class="<?php print $forum->zebra; ?>">
+      <td <?php print $forum->is_container ? 'colspan="4" class="container"' : 'class="forum"'; ?>>
+        <?php /* Enclose the contents of this cell with X divs, where X is the
+               * depth this forum resides at. This will allow us to use CSS
+               * left-margin for indenting.
+               */ ?>
+        <?php print str_repeat('<div class="indent">', $forum->depth); ?>
+          <div class="name"><a href="<?php print $forum->link; ?>"><?php print $forum->name; ?></a></div>
+          <?php if ($forum->description): ?>
+            <div class="description"><?php print $forum->description; ?></div>
+          <?php endif; ?>
+        <?php print str_repeat('</div>', $forum->depth); ?>
+      </td>
+      <?php if (!$forum->is_container): ?>
+        <td class="topics">
+          <?php print $forum->num_topics ?>
+          <?php if ($forum->new_topics): ?>
+            <br />
+            <a href="<?php print $forum->new_url; ?>"><?php print $forum->new_text; ?></a>
+          <?php endif; ?>
+        </td>
+        <td class="posts"><?php print $forum->num_posts ?></td>
+        <td class="last-reply"><?php print $forum->last_reply ?></td>
+      <?php endif; ?>
+    </tr>
+  <?php endforeach; ?>
+  </tbody>
+</table>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,18 @@
+/* $Id: forum-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */
+
+#forum tr td.forum {
+  padding-left: 0.5em;
+  padding-right: 25px;
+  background-position: 98% 2px;
+}
+.forum-topic-navigation {
+  padding: 1em 3em 0 0;
+}
+.forum-topic-navigation .topic-previous {
+  text-align: left;
+  float: right;
+}
+.forum-topic-navigation .topic-next {
+  text-align: right;
+  float: left;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-submitted.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,28 @@
+<?php
+// $Id: forum-submitted.tpl.php,v 1.3 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file forum-submitted.tpl.php
+ * Default theme implementation to format a simple string indicated when and
+ * by whom a topic was submitted.
+ *
+ * Available variables:
+ *
+ * - $author: The author of the post.
+ * - $time: How long ago the post was created.
+ * - $topic: An object with the raw data of the post. Unsafe, be sure
+ *   to clean this data before printing.
+ *
+ * @see template_preprocess_forum_submitted()
+ * @see theme_forum_submitted()
+ */
+?>
+<?php if ($time): ?>
+  <?php print t(
+  '@time ago<br />by !author', array(
+    '@time' => $time,
+    '!author' => $author,
+    )); ?>
+<?php else: ?>
+  <?php print t('n/a'); ?>
+<?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-topic-list.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,62 @@
+<?php
+// $Id: forum-topic-list.tpl.php,v 1.4 2007/08/30 18:58:12 goba Exp $
+
+/**
+ * @file forum-topic-list.tpl.php
+ * Theme implementation to display a list of forum topics.
+ *
+ * Available variables:
+ * - $header: The table header. This is pre-generated with click-sorting
+ *   information. If you need to change this, @see template_preprocess_forum_topic_list().
+ * - $pager: The pager to display beneath the table.
+ * - $topics: An array of topics to be displayed.
+ * - $topic_id: Numeric id for the current forum topic.
+ *
+ * Each $topic in $topics contains:
+ * - $topic->icon: The icon to display.
+ * - $topic->moved: A flag to indicate whether the topic has been moved to
+ *   another forum.
+ * - $topic->title: The title of the topic. Safe to output.
+ * - $topic->message: If the topic has been moved, this contains an
+ *   explanation and a link.
+ * - $topic->zebra: 'even' or 'odd' string used for row class.
+ * - $topic->num_comments: The number of replies on this topic.
+ * - $topic->new_replies: A flag to indicate whether there are unread comments.
+ * - $topic->new_url: If there are unread replies, this is a link to them.
+ * - $topic->new_text: Text containing the translated, properly pluralized count.
+ * - $topic->created: An outputtable string represented when the topic was posted.
+ * - $topic->last_reply: An outputtable string representing when the topic was
+ *   last replied to.
+ * - $topic->timestamp: The raw timestamp this topic was posted.
+ *
+ * @see template_preprocess_forum_topic_list()
+ * @see theme_forum_topic_list()
+ */
+?>
+<table id="forum-topic-<?php print $topic_id; ?>">
+  <thead>
+    <tr><?php print $header; ?></tr>
+  </thead>
+  <tbody>
+  <?php foreach ($topics as $topic): ?>
+    <tr class="<?php print $topic->zebra;?>">
+      <td class="icon"><?php print $topic->icon; ?></td>
+      <td class="title"><?php print $topic->title; ?></td>
+    <?php if ($topic->moved): ?>
+      <td colspan="3"><?php print $topic->message; ?></td>
+    <?php else: ?>
+      <td class="replies">
+        <?php print $topic->num_comments; ?>
+        <?php if ($topic->new_replies): ?>
+          <br />
+          <a href="<?php print $topic->new_url; ?>"><?php print $topic->new_text; ?></a>
+        <?php endif; ?>
+      </td>
+      <td class="created"><?php print $topic->created; ?>
+      <td class="last-reply"><?php print $topic->last_reply; ?>
+    <?php endif; ?>
+    </tr>
+  <?php endforeach; ?>
+  </tbody>
+</table>
+<?php print $pager; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum-topic-navigation.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,35 @@
+<?php
+// $Id: forum-topic-navigation.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file forum-topic-navigation.tpl.php
+ * Default theme implementation to display the topic navigation string at the
+ * bottom of all forum topics.
+ *
+ * Available variables:
+ *
+ * - $prev: The node ID of the previous post.
+ * - $prev_url: The URL of the previous post.
+ * - $prev_title: The title of the previous post.
+ *
+ * - $next: The node ID of the next post.
+ * - $next_url: The URL of the next post.
+ * - $next_title: The title of the next post.
+ *
+ * - $node: The raw node currently being viewed. Contains unsafe data
+ *   and any data in this must be cleaned before presenting.
+ *
+ * @see template_preprocess_forum_topic_navigation()
+ * @see theme_forum_topic_navigation()
+ */
+?>
+<?php if ($prev || $next): ?>
+  <div class="forum-topic-navigation clear-block">
+    <?php if ($prev): ?>
+      <a href="<?php print $prev_url; ?>" class="topic-previous" title="<?php print t('Go to previous forum topic') ?>">‹ <?php print $prev_title ?></a>
+    <?php endif; ?>
+    <?php if ($next): ?>
+      <a href="<?php print $next_url; ?>" class="topic-next" title="<?php print t('Go to next forum topic') ?>"><?php print $next_title ?> ›</a>
+    <?php endif; ?>
+  </div>
+<?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,293 @@
+<?php
+// $Id: forum.admin.inc,v 1.8 2008/01/30 10:14:42 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbacks for the forum module.
+ */
+
+function forum_form_main($type, $edit = array()) {
+  if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || !empty($_POST['confirm'])) {
+    return drupal_get_form('forum_confirm_delete', $edit['tid']);
+  }
+  switch ($type) {
+    case 'forum':
+      return drupal_get_form('forum_form_forum', $edit);
+      break;
+    case 'container':
+      return drupal_get_form('forum_form_container', $edit);
+      break;
+  }
+}
+
+/**
+ * Returns a form for adding a forum to the forum vocabulary
+ *
+ * @param $edit Associative array containing a forum term to be added or edited.
+ * @ingroup forms
+ * @see forum_form_submit()
+ */
+function forum_form_forum(&$form_state, $edit = array()) {
+  $edit += array(
+    'name' => '',
+    'description' => '',
+    'tid' => NULL,
+    'weight' => 0,
+  );
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Forum name'),
+    '#default_value' => $edit['name'],
+    '#maxlength' => 255,
+    '#description' => t('Short but meaningful name for this collection of threaded discussions.'),
+    '#required' => TRUE,
+  );
+  $form['description'] = array('#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $edit['description'],
+    '#description' => t('Description and guidelines for discussions within this forum.'),
+  );
+  $form['parent']['#tree'] = TRUE;
+  $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'forum');
+  $form['weight'] = array('#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $edit['weight'],
+    '#description' => t('Forums are displayed in ascending order by weight (forums with equal weights are displayed alphabetically).'),
+  );
+
+  $form['vid'] = array('#type' => 'hidden', '#value' => variable_get('forum_nav_vocabulary', ''));
+  $form['submit' ] = array('#type' => 'submit', '#value' => t('Save'));
+  if ($edit['tid']) {
+    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['tid'] = array('#type' => 'hidden', '#value' => $edit['tid']);
+  }
+  $form['#submit'][] = 'forum_form_submit';
+  $form['#theme'] = 'forum_form';
+
+  return $form;
+}
+
+/**
+ * Process forum form and container form submissions.
+ */
+function forum_form_submit($form, &$form_state) {
+  if ($form['form_id']['#value'] == 'forum_form_container') {
+    $container = TRUE;
+    $type = t('forum container');
+  }
+  else {
+    $container = FALSE;
+    $type = t('forum');
+  }
+
+  $status = taxonomy_save_term($form_state['values']);
+  switch ($status) {
+    case SAVED_NEW:
+      if ($container) {
+        $containers = variable_get('forum_containers', array());
+        $containers[] = $form_state['values']['tid'];
+        variable_set('forum_containers', $containers);
+      }
+      drupal_set_message(t('Created new @type %term.', array('%term' => $form_state['values']['name'], '@type' => $type)));
+      break;
+    case SAVED_UPDATED:
+      drupal_set_message(t('The @type %term has been updated.', array('%term' => $form_state['values']['name'], '@type' => $type)));
+      break;
+  }
+  $form_state['redirect'] = 'admin/content/forum';
+  return;
+}
+
+/**
+ * Returns a form for adding a container to the forum vocabulary
+ *
+ * @param $edit Associative array containing a container term to be added or edited.
+ * @ingroup forms
+ * @see forum_form_submit()
+ */
+function forum_form_container(&$form_state, $edit = array()) {
+  $edit += array(
+    'name' => '',
+    'description' => '',
+    'tid' => NULL,
+    'weight' => 0,
+  );
+  // Handle a delete operation.
+  $form['name'] = array(
+    '#title' => t('Container name'),
+    '#type' => 'textfield',
+    '#default_value' => $edit['name'],
+    '#maxlength' => 255,
+    '#description' => t('Short but meaningful name for this collection of related forums.'),
+    '#required' => TRUE
+  );
+
+  $form['description'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $edit['description'],
+    '#description' => t('Description and guidelines for forums within this container.')
+  );
+  $form['parent']['#tree'] = TRUE;
+  $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'container');
+  $form['weight'] = array(
+    '#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $edit['weight'],
+    '#description' => t('Containers are displayed in ascending order by weight (containers with equal weights are displayed alphabetically).')
+  );
+
+  $form['vid'] = array(
+    '#type' => 'hidden',
+    '#value' => variable_get('forum_nav_vocabulary', ''),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save')
+  );
+  if ($edit['tid']) {
+    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']);
+  }
+  $form['#submit'][] = 'forum_form_submit';
+  $form['#theme'] = 'forum_form';
+
+  return $form;
+}
+
+/**
+ * Returns a confirmation page for deleting a forum taxonomy term.
+ *
+ * @param $tid ID of the term to be deleted
+ */
+function forum_confirm_delete(&$form_state, $tid) {
+  $term = taxonomy_get_term($tid);
+
+  $form['tid'] = array('#type' => 'value', '#value' => $tid);
+  $form['name'] = array('#type' => 'value', '#value' => $term->name);
+
+  return confirm_form($form, t('Are you sure you want to delete the forum %name?', array('%name' => $term->name)), 'admin/content/forum', t('Deleting a forum or container will also delete its sub-forums, if any. To delete posts in this forum, visit <a href="@content">content administration</a> first. This action cannot be undone.', array('@content' => url('admin/content/node'))), t('Delete'), t('Cancel'));
+}
+
+/**
+ * Implementation of forms api _submit call. Deletes a forum after confirmation.
+ */
+function forum_confirm_delete_submit($form, &$form_state) {
+  taxonomy_del_term($form_state['values']['tid']);
+  drupal_set_message(t('The forum %term and all sub-forums and associated posts have been deleted.', array('%term' => $form_state['values']['name'])));
+  watchdog('content', 'forum: deleted %term and all its sub-forums and associated posts.', array('%term' => $form_state['values']['name']));
+
+  $form_state['redirect'] = 'admin/content/forum';
+  return;
+}
+
+/**
+ * Form builder for the forum settings page.
+ *
+ * @see system_settings_form()
+ */
+function forum_admin_settings() {
+  $form = array();
+  $number = drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100, 150, 200, 250, 300, 350, 400, 500));
+  $form['forum_hot_topic'] = array('#type' => 'select',
+    '#title' => t('Hot topic threshold'),
+    '#default_value' => variable_get('forum_hot_topic', 15),
+    '#options' => $number,
+    '#description' => t('The number of posts a topic must have to be considered "hot".'),
+  );
+  $number = drupal_map_assoc(array(10, 25, 50, 75, 100));
+  $form['forum_per_page'] = array('#type' => 'select',
+    '#title' => t('Topics per page'),
+    '#default_value' => variable_get('forum_per_page', 25),
+    '#options' => $number,
+    '#description' => t('Default number of forum topics displayed per page.'),
+  );
+  $forder = array(1 => t('Date - newest first'), 2 => t('Date - oldest first'), 3 => t('Posts - most active first'), 4 => t('Posts - least active first'));
+  $form['forum_order'] = array('#type' => 'radios',
+    '#title' => t('Default order'),
+    '#default_value' => variable_get('forum_order', '1'),
+    '#options' => $forder,
+    '#description' => t('Default display order for topics.'),
+  );
+  return system_settings_form($form);
+}
+
+/**
+ * Returns an overview list of existing forums and containers
+ */
+function forum_overview(&$form_state) {
+  module_load_include('inc', 'taxonomy', 'taxonomy.admin');
+
+  $vid = variable_get('forum_nav_vocabulary', '');
+  $vocabulary = taxonomy_vocabulary_load($vid);
+  $form = taxonomy_overview_terms($form_state, $vocabulary);
+  drupal_set_title('Forums');
+
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['#term'])) {
+      $term = $form[$key]['#term'];
+      $form[$key]['view']['#value'] = l($term['name'], 'forum/'. $term['tid']);
+      if (in_array($form[$key]['#term']['tid'], variable_get('forum_containers', array()))) {
+        $form[$key]['edit']['#value'] = l(t('edit container'), 'admin/content/forum/edit/container/'. $term['tid']);
+      }
+      else {
+        $form[$key]['edit']['#value'] = l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term['tid']);
+      }
+    }
+  }
+
+  // Remove the alphabetical reset.
+  unset($form['reset_alphabetical']);
+
+  // The form needs to have submit and validate handlers set explicitly.
+  $form['#theme'] = 'taxonomy_overview_terms';
+  $form['#submit'] = array('taxonomy_overview_terms_submit'); // Use the existing taxonomy overview submit handler.
+  $form['#validate'] = array('taxonomy_overview_terms_validate');
+  $form['#empty_text'] = '<em>'. t('There are no existing containers or forums. Containers and forums may be added using the <a href="@container">add container</a> and <a href="@forum">add forum</a> pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) .'</em>';
+  return $form;
+}
+
+/**
+ * Returns a select box for available parent terms
+ *
+ * @param $tid ID of the term which is being added or edited
+ * @param $title Title to display the select box with
+ * @param $child_type Whether the child is forum or container
+ */
+function _forum_parent_select($tid, $title, $child_type) {
+
+  $parents = taxonomy_get_parents($tid);
+  if ($parents) {
+    $parent = array_shift($parents);
+    $parent = $parent->tid;
+  }
+  else {
+    $parent = 0;
+  }
+
+  $vid = variable_get('forum_nav_vocabulary', '');
+  $children = taxonomy_get_tree($vid, $tid);
+
+  // A term can't be the child of itself, nor of its children.
+  foreach ($children as $child) {
+    $exclude[] = $child->tid;
+  }
+  $exclude[] = $tid;
+
+  $tree = taxonomy_get_tree($vid);
+  $options[0] = '<'. t('root') .'>';
+  if ($tree) {
+    foreach ($tree as $term) {
+      if (!in_array($term->tid, $exclude)) {
+        $options[$term->tid] = str_repeat(' -- ', $term->depth) . $term->name;
+      }
+    }
+  }
+  if ($child_type == 'container') {
+    $description = t('Containers are usually placed at the top (root) level, but may also be placed inside another container or forum.');
+  }
+  else if ($child_type == 'forum') {
+    $description = t('Forums may be placed at the top (root) level, or inside another container or forum.');
+  }
+
+  return array('#type' => 'select', '#title' => $title, '#default_value' => $parent, '#options' => $options, '#description' => $description, '#required' => TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,42 @@
+/* $Id: forum.css,v 1.5 2007/07/22 07:01:07 dries Exp $ */
+
+#forum .description {
+  font-size: 0.9em;
+  margin: 0.5em;
+}
+#forum td.created, #forum td.posts, #forum td.topics, #forum td.last-reply, #forum td.replies, #forum td.pager {
+  white-space: nowrap;
+}
+#forum td.posts, #forum td.topics, #forum td.replies, #forum td.pager {
+  text-align: center;
+}
+#forum tr td.forum {
+  padding-left: 25px; /* LTR */
+  background-position: 2px 2px; /* LTR */
+  background-image: url(../../misc/forum-default.png);
+  background-repeat: no-repeat;
+}
+#forum tr.new-topics td.forum {
+  background-image: url(../../misc/forum-new.png);
+}
+#forum div.indent {
+  margin-left: 20px;
+}
+
+.forum-topic-navigation {
+  padding: 1em 0 0 3em; /* LTR */
+  border-top: 1px solid #888;
+  border-bottom: 1px solid #888;
+  text-align: center;
+  padding: 0.5em;
+}
+.forum-topic-navigation .topic-previous {
+  text-align: right; /* LTR */
+  float: left; /* LTR */
+  width: 46%;
+}
+.forum-topic-navigation .topic-next {
+  text-align: left; /* LTR */
+  float: right; /* LTR */
+  width: 46%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+; $Id: forum.info,v 1.6 2007/06/08 05:50:54 dries Exp $
+name = Forum
+description = Enables threaded discussions about general topics.
+dependencies[] = taxonomy
+dependencies[] = comment
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,131 @@
+<?php
+// $Id: forum.install,v 1.16 2007/12/31 16:58:34 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function forum_install() {
+  // Create tables.
+  drupal_install_schema('forum');
+  // Set the weight of the forum.module to 1 so it is loaded after the taxonomy.module.
+  db_query("UPDATE {system} SET weight = 1 WHERE name = 'forum'");
+}
+
+function forum_enable() {
+  if ($vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0))) {
+    // Existing install. Add back forum node type, if the forums
+    // vocabulary still exists. Keep all other node types intact there.
+    $vocabulary = (array) $vocabulary;
+    $vocabulary['nodes']['forum'] = 1;
+    taxonomy_save_vocabulary($vocabulary);
+  }
+  else {
+    // Create the forum vocabulary if it does not exist. Assign the vocabulary
+    // a low weight so it will appear first in forum topic create and edit
+    // forms.
+    $vocabulary = array(
+      'name' => t('Forums'),
+      'multiple' => 0,
+      'required' => 0,
+      'hierarchy' => 1,
+      'relations' => 0,
+      'module' => 'forum',
+      'weight' => -10,
+      'nodes' => array('forum' => 1),
+    );
+    taxonomy_save_vocabulary($vocabulary);
+
+    variable_set('forum_nav_vocabulary', $vocabulary['vid']);
+  }
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function forum_uninstall() {
+  // Load the dependent Taxonomy module, in case it has been disabled.
+  drupal_load('module', 'taxonomy');
+
+  // Delete the vocabulary.
+  $vid = variable_get('forum_nav_vocabulary', '');
+  taxonomy_del_vocabulary($vid);
+
+  db_query("DELETE FROM {node} WHERE type = 'forum'");
+  db_query('DROP TABLE {forum}');
+  variable_del('forum_containers');
+  variable_del('forum_nav_vocabulary');
+  variable_del('forum_hot_topic');
+  variable_del('forum_per_page');
+  variable_del('forum_order');
+  variable_del('forum_block_num_0');
+  variable_del('forum_block_num_1');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function forum_schema() {
+  $schema['forum'] = array(
+    'description' => t('Stores the relationship of nodes to forum terms.'),
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid of the node.'),
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {node}.vid of the node.'),
+      ),
+      'tid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {term_data}.tid of the forum term assigned to the node.'),
+      ),
+    ),
+    'indexes' => array(
+      'nid' => array('nid'),
+      'tid' => array('tid')
+    ),
+    'primary key' => array('vid'),
+  );
+
+  return $schema;
+}
+
+/**
+ * Create the forum vocabulary if does not exist. Assign the
+ * vocabulary a low weight so it will appear first in forum topic
+ * create and edit forms.  Do not just call forum_enable() because in
+ * future versions it might do something different.
+ */
+function forum_update_6000() {
+  $ret = array();
+
+  $vid = variable_get('forum_nav_vocabulary', 0);
+  $vocabularies = taxonomy_get_vocabularies();
+  if (!isset($vocabularies[$vid])) {
+    $vocabulary = array(
+      'name' => t('Forums'),
+      'multiple' => 0,
+      'required' => 0,
+      'hierarchy' => 1,
+      'relations' => 0,
+      'module' => 'forum',
+      'weight' => -10,
+      'nodes' => array('forum' => 1),
+    );
+    taxonomy_save_vocabulary($vocabulary);
+
+    variable_set('forum_nav_vocabulary', $vocabulary['vid']);
+  }
+
+  return $ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,953 @@
+<?php
+// $Id: forum.module,v 1.448.2.2 2008/02/13 14:06:36 goba Exp $
+
+/**
+ * @file
+ * Enable threaded discussions about general topics.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function forum_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#forum':
+      $output = '<p>'. t('The forum module lets you create threaded discussion forums with functionality similar to other message board systems. Forums are useful because they allow community members to discuss topics with one another while ensuring those conversations are archived for later reference. The <a href="@create-topic">forum topic</a> menu item (under <em>Create content</em> on the Navigation menu) creates the initial post of a new threaded discussion, or thread.', array('@create-topic' => url('node/add/forum'))) .'</p>';
+      $output .= '<p>'. t('A threaded discussion occurs as people leave comments on a forum topic (or on other comments within that topic). A forum topic is contained within a forum, which may hold many similar or related forum topics. Forums are (optionally) nested within a container, which may hold many similar or related forums. Both containers and forums may be nested within other containers and forums, and provide structure for your message board. By carefully planning this structure, you make it easier for users to find and comment on a specific forum topic.') .'</p>';
+      $output .= '<p>'. t('When administering a forum, note that:') .'</p>';
+      $output .= '<ul><li>'. t('a forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic.') .'</li>';
+      $output .= '<li>'. t('when moving a forum topic between forums, the <em>Leave shadow copy</em> option creates a link in the original forum pointing to the new location.') .'</li>';
+      $output .= '<li>'. t('selecting <em>Read only</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments) on the thread.') .'</li>';
+      $output .= '<li>'. t('selecting <em>Disabled</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') .'</li></ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) .'</p>';
+      return $output;
+    case 'admin/content/forum':
+      return '<p>'. t('This page displays a list of existing forums and containers. Containers (optionally) hold forums, and forums hold forum topics (a forum topic is the initial post to a threaded discussion). To provide structure, both containers and forums may be placed inside other containers and forums. To rearrange forums and containers, grab a drag-and-drop handle under the <em>Name</em> column and drag the forum or container to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save</em> button at the bottom of the page.') .'</p>';
+    case 'admin/content/forum/add/container':
+      return '<p>'. t('By grouping related or similar forums, containers help organize forums. For example, a container named "Food" may hold two forums named "Fruit" and "Vegetables", respectively.') .'</p>';
+    case 'admin/content/forum/add/forum':
+      return '<p>'. t('A forum holds related or similar forum topics (a forum topic is the initial post to a threaded discussion). For example, a forum named "Fruit" may contain forum topics titled "Apples" and "Bananas", respectively.') .'</p>';
+    case 'admin/content/forum/settings':
+      return '<p>'. t('These settings allow you to adjust the display of your forum topics. The content types available for use within a forum may be selected by editing the <em>Content types</em> on the <a href="@forum-vocabulary">forum vocabulary page</a>.', array('@forum-vocabulary' => url('admin/content/taxonomy/edit/vocabulary/'. variable_get('forum_nav_vocabulary', '')))) .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function forum_theme() {
+  return array(
+    'forums' => array(
+      'template' => 'forums',
+      'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+    ),
+    'forum_list' => array(
+      'template' => 'forum-list',
+      'arguments' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
+    ),
+    'forum_topic_list' => array(
+      'template' => 'forum-topic-list',
+      'arguments' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+    ),
+    'forum_icon' => array(
+      'template' => 'forum-icon',
+      'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
+    ),
+    'forum_topic_navigation' => array(
+      'template' => 'forum-topic-navigation',
+      'arguments' => array('node' => NULL),
+    ),
+    'forum_submitted' => array(
+      'template' => 'forum-submitted',
+      'arguments' => array('topic' => NULL),
+    ),
+  );
+}
+
+/**
+ * Fetch a forum term.
+ *
+ * @param $tid
+ *   The ID of the term which should be loaded.
+ *
+ * @return
+ *   An associative array containing the term data or FALSE if the term cannot be loaded, or is not part of the forum vocabulary.
+ */
+function forum_term_load($tid) {
+  $result = db_query(db_rewrite_sql('SELECT t.tid, t.vid, t.name, t.description, t.weight FROM {term_data} t WHERE t.tid = %d AND t.vid = %d', 't', 'tid'), $tid, variable_get('forum_nav_vocabulary', ''));
+  return db_fetch_array($result);
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function forum_menu() {
+  $items['forum'] = array(
+    'title' => 'Forums',
+    'page callback' => 'forum_page',
+    'access arguments' => array('access content'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'forum.pages.inc',
+  );
+  $items['admin/content/forum'] = array(
+    'title' => 'Forums',
+    'description' => 'Control forums and their hierarchy and change forum settings.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('forum_overview'),
+    'access arguments' => array('administer forums'),
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/content/forum/add/container'] = array(
+    'title' => 'Add container',
+    'page callback' => 'forum_form_main',
+    'page arguments' => array('container'),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/forum',
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/add/forum'] = array(
+    'title' => 'Add forum',
+    'page callback' => 'forum_form_main',
+    'page arguments' => array('forum'),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/forum',
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('forum_admin_settings'),
+    'weight' => 5,
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/forum',
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/edit/%forum_term'] = array(
+    'page callback' => 'forum_form_main',
+    'type' => MENU_CALLBACK,
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/edit/container/%forum_term'] = array(
+    'title' => 'Edit container',
+    'page callback' => 'forum_form_main',
+    'page arguments' => array('container', 5),
+    'type' => MENU_CALLBACK,
+    'file' => 'forum.admin.inc',
+  );
+  $items['admin/content/forum/edit/forum/%forum_term'] = array(
+    'title' => 'Edit forum',
+    'page callback' => 'forum_form_main',
+    'page arguments' => array('forum', 5),
+    'type' => MENU_CALLBACK,
+    'file' => 'forum.admin.inc',
+  );
+  return $items;
+}
+
+
+/**
+ * Implementation of hook_init().
+ */
+function forum_init() {
+  drupal_add_css(drupal_get_path('module', 'forum') .'/forum.css');
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function forum_nodeapi(&$node, $op, $teaser, $page) {
+  // We are going to return if $node->type is not one of the node
+  // types assigned to the forum vocabulary.  If forum_nav_vocabulary
+  // is undefined or the vocabulary does not exist, it clearly cannot
+  // be assigned to $node->type, so return to avoid E_ALL warnings.
+  $vid = variable_get('forum_nav_vocabulary', '');
+  $vocabulary = taxonomy_vocabulary_load($vid);
+  if (empty($vocabulary)) {
+    return;
+  }
+
+  // Operate only on node types assigned for the forum vocabulary.
+  if (!in_array($node->type, $vocabulary->nodes)) {
+    return;
+  }
+
+  switch ($op) {
+    case 'view':
+      if ($page && taxonomy_node_get_terms_by_vocabulary($node, $vid) && $tree = taxonomy_get_tree($vid)) {
+        // Get the forum terms from the (cached) tree
+        foreach ($tree as $term) {
+          $forum_terms[] = $term->tid;
+        }
+        foreach ($node->taxonomy as $term_id => $term) {
+          if (in_array($term_id, $forum_terms)) {
+            $node->tid = $term_id;
+          }
+        }
+        // Breadcrumb navigation
+        $breadcrumb[] = l(t('Home'), NULL);
+        $breadcrumb[] = l($vocabulary->name, 'forum');
+        if ($parents = taxonomy_get_parents_all($node->tid)) {
+          $parents = array_reverse($parents);
+          foreach ($parents as $p) {
+            $breadcrumb[] = l($p->name, 'forum/'. $p->tid);
+          }
+        }
+        drupal_set_breadcrumb($breadcrumb);
+
+        if (!$teaser) {
+          $node->content['forum_navigation'] = array(
+            '#value' => theme('forum_topic_navigation', $node),
+            '#weight' => 100,
+          );
+        }
+      }
+      break;
+
+    case 'prepare':
+      if (empty($node->nid)) {
+        // New topic
+        $node->taxonomy[arg(3)]->vid = $vid;
+        $node->taxonomy[arg(3)]->tid = arg(3);
+      }
+      break;
+
+    // Check in particular that only a "leaf" term in the associated taxonomy
+    // vocabulary is selected, not a "container" term.
+    case 'validate':
+      if ($node->taxonomy) {
+        // Extract the node's proper topic ID.
+        $vocabulary = $vid;
+        $containers = variable_get('forum_containers', array());
+        foreach ($node->taxonomy as $term) {
+          if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) {
+            if (in_array($term, $containers)) {
+              $term = taxonomy_get_term($term);
+              form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
+            }
+          }
+        }
+      }
+      break;
+
+    // Assign forum taxonomy when adding a topic from within a forum.
+    case 'presave':
+      // Make sure all fields are set properly:
+      $node->icon = !empty($node->icon) ? $node->icon : '';
+
+      if ($node->taxonomy && $tree = taxonomy_get_tree($vid)) {
+        // Get the forum terms from the (cached) tree if we have a taxonomy.
+        foreach ($tree as $term) {
+          $forum_terms[] = $term->tid;
+        }
+        foreach ($node->taxonomy as $term_id) {
+          if (in_array($term_id, $forum_terms)) {
+            $node->tid = $term_id;
+          }
+        }
+        $old_tid = db_result(db_query_range("SELECT t.tid FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.nid = %d ORDER BY t.vid DESC", $node->nid, 0, 1));
+        if ($old_tid && isset($node->tid) && ($node->tid != $old_tid) && !empty($node->shadow)) {
+          // A shadow copy needs to be created. Retain new term and add old term.
+          $node->taxonomy[] = $old_tid;
+        }
+      }
+      break;
+
+    case 'update':
+      if (empty($node->revision) && db_result(db_query('SELECT tid FROM {forum} WHERE nid=%d', $node->nid))) {
+        if (!empty($node->tid)) {
+          db_query('UPDATE {forum} SET tid = %d WHERE vid = %d', $node->tid, $node->vid);
+        }
+        // The node is removed from the forum.
+        else {
+          db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
+        }
+        break;
+      }
+      // Deliberate no break -- for new revisions and for previously unassigned terms we need an insert.
+
+    case 'insert':
+      if (!empty($node->tid)) {
+        db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $node->tid, $node->vid, $node->nid);
+      }
+      break;
+
+    case 'delete':
+      db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
+      break;
+
+    case 'load':
+      return db_fetch_array(db_query('SELECT tid AS forum_tid FROM {forum} WHERE vid = %d', $node->vid));
+  }
+
+  return;
+}
+
+/**
+ * Implementation of hook_node_info().
+ */
+function forum_node_info() {
+  return array(
+    'forum' => array(
+      'name' => t('Forum topic'),
+      'module' => 'forum',
+      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
+      'title_label' => t('Subject'),
+    )
+  );
+}
+
+/**
+ * Implementation of hook_access().
+ */
+function forum_access($op, $node, $account) {
+  switch ($op) {
+    case 'create':
+      return user_access('create forum topics', $account);
+    case 'update':
+      return user_access('edit any forum topic', $account) || (user_access('edit own forum topics', $account) && ($account->uid == $node->uid));
+    case 'delete':
+      return user_access('delete any forum topic', $account) || (user_access('delete own forum topics', $account) && ($account->uid == $node->uid));
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function forum_perm() {
+  return array('create forum topics', 'delete own forum topics', 'delete any forum topic', 'edit own forum topics', 'edit any forum topic', 'administer forums');
+}
+
+/**
+ * Implementation of hook_taxonomy().
+ */
+function forum_taxonomy($op, $type, $term = NULL) {
+  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', '')) {
+    switch ($type) {
+      case 'term':
+        $results = db_query('SELECT tn.nid FROM {term_node} tn WHERE tn.tid = %d', $term['tid']);
+        while ($node = db_fetch_object($results)) {
+          // node_delete will also remove any association with non-forum vocabularies.
+          node_delete($node->nid);
+        }
+
+        // For containers, remove the tid from the forum_containers variable.
+        $containers = variable_get('forum_containers', array());
+        $key = array_search($term['tid'], $containers);
+        if ($key !== FALSE) {
+          unset($containers[$key]);
+        }
+        variable_set('forum_containers', $containers);
+        break;
+      case 'vocabulary':
+        variable_del('forum_nav_vocabulary');
+    }
+  }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function forum_form_alter(&$form, $form_state, $form_id) {
+  $vid = variable_get('forum_nav_vocabulary', '');
+  if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
+    // Hide critical options from forum vocabulary.
+    if ($form_id == 'taxonomy_form_vocabulary') {
+      $form['help_forum_vocab'] = array(
+        '#value' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
+        '#weight' => -1,
+      );
+      $form['content_types']['nodes']['#required'] = TRUE;
+      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
+      $form['settings']['required'] = array('#type' => 'value', '#value' => FALSE);
+      $form['settings']['relations'] = array('#type' => 'value', '#value' => FALSE);
+      $form['settings']['tags'] = array('#type' => 'value', '#value' => FALSE);
+      $form['settings']['multiple'] = array('#type' => 'value', '#value' => FALSE);
+      unset($form['delete']);
+    }
+    // Hide multiple parents select from forum terms.
+    elseif ($form_id == 'taxonomy_form_term') {
+      $form['advanced']['parent']['#access'] = FALSE;
+    }    
+  }
+  if ($form_id == 'forum_node_form') {
+    // Make the vocabulary required for 'real' forum-nodes.
+    $vid = variable_get('forum_nav_vocabulary', '');
+    $form['taxonomy'][$vid]['#required'] = TRUE;
+    $form['taxonomy'][$vid]['#options'][''] = t('- Please choose -');
+  }
+}
+
+/**
+ * Implementation of hook_load().
+ */
+function forum_load($node) {
+  $forum = db_fetch_object(db_query('SELECT * FROM {term_node} WHERE vid = %d', $node->vid));
+
+  return $forum;
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generates a block containing the currently active forum topics and the
+ * most recently added forum topics.
+ */
+function forum_block($op = 'list', $delta = 0, $edit = array()) {
+  switch ($op) {
+    case 'list':
+      $blocks[0]['info'] = t('Active forum topics');
+      $blocks[1]['info'] = t('New forum topics');
+      return $blocks;
+
+    case 'configure':
+      $form['forum_block_num_'. $delta] = array('#type' => 'select', '#title' => t('Number of topics'), '#default_value' => variable_get('forum_block_num_'. $delta, '5'), '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
+      return $form;
+
+    case 'save':
+      variable_set('forum_block_num_'. $delta, $edit['forum_block_num_'. $delta]);
+      break;
+
+    case 'view':
+      if (user_access('access content')) {
+        switch ($delta) {
+          case 0:
+            $title = t('Active forum topics');
+            $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {term_node} tn ON tn.vid = n.vid INNER JOIN {term_data} td ON td.tid = tn.tid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 AND td.vid = %d ORDER BY l.last_comment_timestamp DESC");
+            $result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_0', '5'));
+            $content = node_title_list($result);
+            break;
+
+          case 1:
+            $title = t('New forum topics');
+            $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count FROM {node} n INNER JOIN {term_node} tn ON tn.vid = n.vid INNER JOIN {term_data} td ON td.tid = tn.tid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 AND td.vid = %d ORDER BY n.nid DESC");
+            $result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_1', '5'));
+            $content = node_title_list($result);
+            break;
+        }
+
+        if (!empty($content)) {
+          $block['subject'] = $title;
+          $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
+          return $block;
+        }
+      }
+  }
+}
+
+/**
+ * Implementation of hook_form().
+ */
+function forum_form(&$node, $form_state) {
+  $type = node_get_types('type', $node);
+  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
+
+  if (!empty($node->nid)) {
+    $vid = variable_get('forum_nav_vocabulary', '');
+    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
+    // if editing, give option to leave shadows
+    $shadow = (count($forum_terms) > 1);
+    $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'));
+  }
+
+  $form['body_field'] = node_body_field($node, $type->body_label, 1);
+
+  $form['#submit'][] = 'forum_submit';
+  // Assign the forum topic submit handler.
+
+  return $form;
+}
+
+function forum_link_alter(&$links, $node) {
+  foreach ($links as $module => $link) {
+    if (strstr($module, 'taxonomy_term')) {
+      // Link back to the forum and not the taxonomy term page. We'll only
+      // do this if the taxonomy term in question belongs to forums.
+      $tid = str_replace('taxonomy/term/', '', $link['href']);
+      $vid = variable_get('forum_nav_vocabulary', '');
+      $term = taxonomy_get_term($tid);
+      if ($term->vid == $vid) {
+        $links[$module]['href'] = str_replace('taxonomy/term', 'forum', $link['href']);
+      }
+    }
+  }
+}
+
+/**
+ * Returns a list of all forums for a given taxonomy id
+ *
+ * Forum objects contain the following fields
+ * -num_topics Number of topics in the forum
+ * -num_posts Total number of posts in all topics
+ * -last_post Most recent post for the forum
+ *
+ * @param $tid
+ *   Taxonomy ID of the vocabulary that holds the forum list.
+ * @return
+ *   Array of object containing the forum information.
+ */
+function forum_get_forums($tid = 0) {
+
+  $forums = array();
+  $vid = variable_get('forum_nav_vocabulary', '');
+  $_forums = taxonomy_get_tree($vid, $tid);
+
+  if (count($_forums)) {
+
+    $counts = array();
+
+    $sql = "SELECT r.tid, COUNT(n.nid) AS topic_count, SUM(l.comment_count) AS comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {term_node} r ON n.vid = r.vid WHERE n.status = 1 GROUP BY r.tid";
+    $sql = db_rewrite_sql($sql);
+    $_counts = db_query($sql);
+    while ($count = db_fetch_object($_counts)) {
+      $counts[$count->tid] = $count;
+    }
+  }
+
+  foreach ($_forums as $forum) {
+    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
+      $forum->container = 1;
+    }
+
+    if (!empty($counts[$forum->tid])) {
+      $forum->num_topics = $counts[$forum->tid]->topic_count;
+      $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count;
+    }
+    else {
+      $forum->num_topics = 0;
+      $forum->num_posts = 0;
+    }
+
+    // This query does not use full ANSI syntax since MySQL 3.x does not support
+    // table1 INNER JOIN table2 INNER JOIN table3 ON table2_criteria ON table3_criteria
+    // used to join node_comment_statistics to users.
+    $sql = "SELECT ncs.last_comment_timestamp, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name, ncs.last_comment_uid FROM {node} n INNER JOIN {users} u1 ON n.uid = u1.uid INNER JOIN {term_node} tn ON n.vid = tn.vid INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {users} u2 ON ncs.last_comment_uid=u2.uid WHERE n.status = 1 AND tn.tid = %d ORDER BY ncs.last_comment_timestamp DESC";
+    $sql = db_rewrite_sql($sql);
+    $topic = db_fetch_object(db_query_range($sql, $forum->tid, 0, 1));
+
+    $last_post = new stdClass();
+    if (!empty($topic->last_comment_timestamp)) {
+      $last_post->timestamp = $topic->last_comment_timestamp;
+      $last_post->name = $topic->last_comment_name;
+      $last_post->uid = $topic->last_comment_uid;
+    }
+    $forum->last_post = $last_post;
+
+    $forums[$forum->tid] = $forum;
+  }
+
+  return $forums;
+}
+
+/**
+ * Calculate the number of nodes the user has not yet read and are newer
+ * than NODE_NEW_LIMIT.
+ */
+function _forum_topics_unread($term, $uid) {
+  $sql = "SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid AND tn.tid = %d LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d WHERE n.status = 1 AND n.created > %d AND h.nid IS NULL";
+  $sql = db_rewrite_sql($sql);
+  return db_result(db_query($sql, $term, $uid, NODE_NEW_LIMIT));
+}
+
+function forum_get_topics($tid, $sortby, $forum_per_page) {
+  global $user, $forum_topic_list_header;
+
+  $forum_topic_list_header = array(
+    array('data' => '&nbsp;', 'field' => NULL),
+    array('data' => t('Topic'), 'field' => 'n.title'),
+    array('data' => t('Replies'), 'field' => 'l.comment_count'),
+    array('data' => t('Created'), 'field' => 'n.created'),
+    array('data' => t('Last reply'), 'field' => 'l.last_comment_timestamp'),
+  );
+
+  $order = _forum_get_topic_order($sortby);
+  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
+    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
+      $forum_topic_list_header[$i]['sort'] = $order['sort'];
+    }
+  }
+
+  $term = taxonomy_get_term($tid);
+
+  $sql = db_rewrite_sql("SELECT n.nid, r.tid, n.title, n.type, n.sticky, u.name, u.uid, n.created AS timestamp, n.comment AS comment_mode, l.last_comment_timestamp, IF(l.last_comment_uid != 0, cu.name, l.last_comment_name) AS last_comment_name, l.last_comment_uid, l.comment_count AS num_comments, f.tid AS forum_tid FROM {node_comment_statistics} l INNER JOIN {node} n ON n.nid = l.nid INNER JOIN {users} cu ON l.last_comment_uid = cu.uid INNER JOIN {term_node} r ON n.vid = r.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {forum} f ON n.vid = f.vid WHERE n.status = 1 AND r.tid = %d");
+  $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,');
+  $sql .= ', n.created DESC';  // Always add a secondary sort order so that the news forum topics are on top.
+
+  $sql_count = db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {term_node} r ON n.vid = r.vid AND r.tid = %d WHERE n.status = 1");
+
+  $result = pager_query($sql, $forum_per_page, 0, $sql_count, $tid);
+  $topics = array();
+  while ($topic = db_fetch_object($result)) {
+    if ($user->uid) {
+      // folder is new if topic is new or there are new comments since last visit
+      if ($topic->tid != $tid) {
+        $topic->new = 0;
+      }
+      else {
+        $history = _forum_user_last_visit($topic->nid);
+        $topic->new_replies = comment_num_new($topic->nid, $history);
+        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
+      }
+    }
+    else {
+      // Do not track "new replies" status for topics if the user is anonymous.
+      $topic->new_replies = 0;
+      $topic->new = 0;
+    }
+
+    if ($topic->num_comments > 0) {
+      $last_reply = new stdClass();
+      $last_reply->timestamp = $topic->last_comment_timestamp;
+      $last_reply->name = $topic->last_comment_name;
+      $last_reply->uid = $topic->last_comment_uid;
+      $topic->last_reply = $last_reply;
+    }
+    $topics[] = $topic;
+  }
+
+  return $topics;
+}
+
+/**
+ * Finds the first unread node for a given forum.
+ */
+function _forum_new($tid) {
+  global $user;
+
+  $sql = "SELECT n.nid FROM {node} n LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND h.nid IS NULL AND n.created > %d ORDER BY created";
+  $sql = db_rewrite_sql($sql);
+  $nid = db_result(db_query_range($sql, $user->uid, $tid, NODE_NEW_LIMIT, 0, 1));
+
+  return $nid ? $nid : 0;
+}
+
+/**
+ * Process variables for forums.tpl.php
+ *
+ * The $variables array contains the following arguments:
+ * - $forums
+ * - $topics
+ * - $parents
+ * - $tid
+ * - $sortby
+ * - $forum_per_page
+ *
+ * @see forums.tpl.php
+ */
+function template_preprocess_forums(&$variables) {
+  global $user;
+
+  $vid = variable_get('forum_nav_vocabulary', '');
+  $vocabulary = taxonomy_vocabulary_load($vid);
+  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
+
+  // Breadcrumb navigation:
+  $breadcrumb[] = l(t('Home'), NULL);
+  if ($variables['tid']) {
+    $breadcrumb[] = l($vocabulary->name, 'forum');
+  }
+  if ($variables['parents']) {
+    $variables['parents'] = array_reverse($variables['parents']);
+    foreach ($variables['parents'] as $p) {
+      if ($p->tid == $variables['tid']) {
+        $title = $p->name;
+      }
+      else {
+        $breadcrumb[] = l($p->name, 'forum/'. $p->tid);
+      }
+    }
+  }
+  drupal_set_breadcrumb($breadcrumb);
+  drupal_set_title(check_plain($title));
+
+  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
+    // Format the "post new content" links listing.
+    $forum_types = array();
+
+    // Loop through all node types for forum vocabulary.
+    foreach ($vocabulary->nodes as $type) {
+      // Check if the current user has the 'create' permission for this node type.
+      if (node_access('create', $type)) {
+        // Fetch the "General" name of the content type;
+        // Push the link with title and url to the array.
+        $forum_types[$type] = array('title' => t('Post new @node_type', array('@node_type' => node_get_types('name', $type))), 'href' => 'node/add/'. str_replace('_', '-', $type) .'/'. $variables['tid']);
+      }
+    }
+
+    if (empty($forum_types)) {
+      // The user is logged-in; but denied access to create any new forum content type.
+      if ($user->uid) {
+        $forum_types['disallowed'] = array('title' => t('You are not allowed to post new content in forum.'));
+      }
+      // The user is not logged-in; and denied access to create any new forum content type.
+      else {
+        $forum_types['login'] = array('title' => t('<a href="@login">Login</a> to post new content in forum.', array('@login' => url('user/login', array('query' => drupal_get_destination())))), 'html' => TRUE);
+      }
+    }
+    $variables['links'] = $forum_types;
+
+    if (!empty($variables['forums'])) {
+      $variables['forums'] = theme('forum_list', $variables['forums'], $variables['parents'], $variables['tid']);
+    }
+    else {
+      $variables['forums'] = '';
+    }
+
+    if ($variables['tid'] && !in_array($variables['tid'], variable_get('forum_containers', array()))) {
+      $variables['topics'] = theme('forum_topic_list', $variables['tid'], $variables['topics'], $variables['sortby'], $variables['forum_per_page']);
+      drupal_add_feed(url('taxonomy/term/'. $variables['tid'] .'/0/feed'), 'RSS - '. $title);
+    }
+    else {
+      $variables['topics'] = '';
+    }
+
+    // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
+    // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
+    if ($variables['forums'] && !$variables['topics']) {
+      $variables['template_files'][] = 'forums-containers';
+      $variables['template_files'][] = 'forums-'. $variables['tid'];
+      $variables['template_files'][] = 'forums-containers-'. $variables['tid'];
+    }
+    elseif (!$variables['forums'] && $variables['topics']) {
+      $variables['template_files'][] = 'forums-topics';
+      $variables['template_files'][] = 'forums-'. $variables['tid'];
+      $variables['template_files'][] = 'forums-topics-'. $variables['tid'];
+    }
+    else {
+      $variables['template_files'][] = 'forums-'. $variables['tid'];
+    }
+
+  }
+  else {
+    drupal_set_title(t('No forums defined'));
+    $variables['links'] = array();
+    $variables['forums'] = '';
+    $variables['topics'] = '';
+  }
+}
+
+/**
+ * Process variables to format a forum listing.
+ *
+ * $variables contains the following information:
+ * - $forums
+ * - $parents
+ * - $tid
+ *
+ * @see forum-list.tpl.php
+ * @see theme_forum_list()
+ */
+function template_preprocess_forum_list(&$variables) {
+  global $user;
+  $row = 0;
+  // Sanitize each forum so that the template can safely print the data.
+  foreach ($variables['forums'] as $id => $forum) {
+    $variables['forums'][$id]->description = !empty($forum->description) ? filter_xss_admin($forum->description) : '';
+    $variables['forums'][$id]->link = url("forum/$forum->tid");
+    $variables['forums'][$id]->name = check_plain($forum->name);
+    $variables['forums'][$id]->is_container = !empty($forum->container);
+    $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
+    $row++;
+
+    $variables['forums'][$id]->new_text = '';
+    $variables['forums'][$id]->new_url = '';
+    $variables['forums'][$id]->new_topics = 0;
+    $variables['forums'][$id]->old_topics = $forum->num_topics;
+    if ($user->uid) {
+      $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
+      if ($variables['forums'][$id]->new_topics) {
+        $variables['forums'][$id]->new_text = format_plural($variables['forums'][$id]->new_topics, '1 new', '@count new');
+        $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
+      }
+      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
+    }
+    $variables['forums'][$id]->last_reply = theme('forum_submitted', $forum->last_post);
+  }
+  // Give meaning to $tid for themers. $tid actually stands for term id.
+  $variables['forum_id'] = $variables['tid'];
+  unset($variables['tid']);
+}
+
+/**
+ * Preprocess variables to format the topic listing.
+ *
+ * $variables contains the following data:
+ * - $tid
+ * - $topics
+ * - $sortby
+ * - $forum_per_page
+ *
+ * @see forum-topic-list.tpl.php
+ * @see theme_forum_topic_list()
+ */
+function template_preprocess_forum_topic_list(&$variables) {
+  global $forum_topic_list_header;
+
+  // Create the tablesorting header.
+  $ts = tablesort_init($forum_topic_list_header);
+  $header = '';
+  foreach ($forum_topic_list_header as $cell) {
+    $cell = tablesort_header($cell, $forum_topic_list_header, $ts);
+    $header .= _theme_table_cell($cell, TRUE);
+  }
+  $variables['header'] = $header;
+
+  if (!empty($variables['topics'])) {
+    $row = 0;
+    foreach ($variables['topics'] as $id => $topic) {
+      $variables['topics'][$id]->icon = theme('forum_icon', $topic->new, $topic->num_comments, $topic->comment_mode, $topic->sticky);
+      $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
+      $row++;
+
+      // We keep the actual tid in forum table, if it's different from the
+      // current tid then it means the topic appears in two forums, one of
+      // them is a shadow copy.
+      if ($topic->forum_tid != $variables['tid']) {
+        $variables['topics'][$id]->moved = TRUE;
+        $variables['topics'][$id]->title = check_plain($topic->title);
+        $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
+      }
+      else {
+        $variables['topics'][$id]->moved = FALSE;
+        $variables['topics'][$id]->title = l($topic->title, "node/$topic->nid");
+        $variables['topics'][$id]->message = '';
+      }
+      $variables['topics'][$id]->created = theme('forum_submitted', $topic);
+      $variables['topics'][$id]->last_reply = theme('forum_submitted', isset($topic->last_reply) ? $topic->last_reply : NULL);
+
+      $variables['topics'][$id]->new_text = '';
+      $variables['topics'][$id]->new_url = '';
+      if ($topic->new_replies) {
+        $variables['topics'][$id]->new_text = format_plural($topic->new_replies, '1 new', '@count new');
+        $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->num_comments, $topic->new_replies, $topic), 'fragment' => 'new'));
+      }
+
+    }
+  }
+  else {
+    // Make this safe for the template
+    $variables['topics'] = array();
+  }
+  // Give meaning to $tid for themers. $tid actually stands for term id.
+  $variables['topic_id'] = $variables['tid'];
+  unset($variables['tid']);
+
+  $variables['pager'] = theme('pager', NULL, $variables['forum_per_page'], 0);
+}
+
+/**
+ * Process variables to format the icon for each individual topic.
+ *
+ * $variables contains the following data:
+ * - $new_posts
+ * - $num_posts = 0
+ * - $comment_mode = 0
+ * - $sticky = 0
+ *
+ * @see forum-icon.tpl.php
+ * @see theme_forum_icon()
+ */
+function template_preprocess_forum_icon(&$variables) {
+  $variables['hot_threshold'] = variable_get('forum_hot_topic', 15);
+  if ($variables['num_posts'] > $variables['hot_threshold']) {
+    $variables['icon'] = $variables['new_posts'] ? 'hot-new' : 'hot';
+  }
+  else {
+    $variables['icon'] = $variables['new_posts'] ? 'new' : 'default';
+  }
+
+  if ($variables['comment_mode'] == COMMENT_NODE_READ_ONLY || $variables['comment_mode'] == COMMENT_NODE_DISABLED) {
+    $variables['icon'] = 'closed';
+  }
+
+  if ($variables['sticky'] == 1) {
+    $variables['icon'] = 'sticky';
+  }
+}
+
+/**
+ * Preprocess variables to format the next/previous forum topic navigation links.
+ *
+ * $variables contains $node.
+ *
+ * @see forum-topic-navigation.tpl.php
+ * @see theme_forum_topic_navigation()
+ */
+function template_preprocess_forum_topic_navigation(&$variables) {
+  $output = '';
+
+  // get previous and next topic
+  $sql = "SELECT n.nid, n.title, n.sticky, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 ORDER BY n.sticky DESC, ". _forum_get_topic_order_sql(variable_get('forum_order', 1));
+  $result = db_query(db_rewrite_sql($sql), isset($variables['node']->tid) ? $variables['node']->tid : 0);
+
+  $stop = $variables['prev'] = $variables['next'] = 0;
+
+  while ($topic = db_fetch_object($result)) {
+    if ($stop == 1) {
+      $variables['next'] = $topic->nid;
+      $variables['next_title'] = check_plain($topic->title);
+      $variables['next_url'] = url("node/$topic->nid");
+      break;
+    }
+    if ($topic->nid == $variables['node']->nid) {
+      $stop = 1;
+    }
+    else {
+      $variables['prev'] = $topic->nid;
+      $variables['prev_title'] = check_plain($topic->title);
+      $variables['prev_url'] = url("node/$topic->nid");
+    }
+  }
+}
+
+/**
+ * Process variables to format submission info for display in the forum list and topic list.
+ *
+ * $variables will contain: $topic
+ *
+ * @see forum-submitted.tpl.php
+ * @see theme_forum_submitted()
+ */
+function template_preprocess_forum_submitted(&$variables) {
+  $variables['author'] = isset($variables['topic']->uid) ? theme('username', $variables['topic']) : '';
+  $variables['time'] = isset($variables['topic']->timestamp) ? format_interval(time() - $variables['topic']->timestamp) : '';
+}
+
+function _forum_user_last_visit($nid) {
+  global $user;
+  static $history = array();
+
+  if (empty($history)) {
+    $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = %d', $user->uid);
+    while ($t = db_fetch_object($result)) {
+      $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
+    }
+  }
+  return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
+}
+
+function _forum_get_topic_order($sortby) {
+  switch ($sortby) {
+    case 1:
+      return array('field' => 'l.last_comment_timestamp', 'sort' => 'desc');
+      break;
+    case 2:
+      return array('field' => 'l.last_comment_timestamp', 'sort' => 'asc');
+      break;
+    case 3:
+      return array('field' => 'l.comment_count', 'sort' => 'desc');
+      break;
+    case 4:
+      return array('field' => 'l.comment_count', 'sort' => 'asc');
+      break;
+  }
+}
+
+function _forum_get_topic_order_sql($sortby) {
+  $order = _forum_get_topic_order($sortby);
+  return $order['field'] .' '. strtoupper($order['sort']);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forum.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,24 @@
+<?php
+// $Id: forum.pages.inc,v 1.2 2007/07/26 06:48:03 dries Exp $
+
+/**
+ * @file
+ * User page callbacks for the forum module.
+ */
+
+/**
+ * Menu callback; prints a forum listing.
+ */
+function forum_page($tid = 0) {
+  $topics = '';
+  $forum_per_page = variable_get('forum_per_page', 25);
+  $sortby = variable_get('forum_order', 1);
+
+  $forums = forum_get_forums($tid);
+  $parents = taxonomy_get_parents_all($tid);
+  if ($tid && !in_array($tid, variable_get('forum_containers', array()))) {
+    $topics = forum_get_topics($tid, $sortby, $forum_per_page);
+  }
+
+  return theme('forums', $forums, $topics, $parents, $tid, $sortby, $forum_per_page);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/forum/forums.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,27 @@
+<?php
+// $Id: forums.tpl.php,v 1.4 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file forums.tpl.php
+ * Default theme implementation to display a forum which may contain forum
+ * containers as well as forum topics.
+ *
+ * Variables available:
+ * - $links: An array of links that allow a user to post new forum topics.
+ *   It may also contain a string telling a user they must log in in order
+ *   to post.
+ * - $forums: The forums to display (as processed by forum-list.tpl.php)
+ * - $topics: The topics to display (as processed by forum-topic-list.tpl.php)
+ * - $forums_defined: A flag to indicate that the forums are configured.
+ *
+ * @see template_preprocess_forums()
+ * @see theme_forums()
+ */
+?>
+<?php if ($forums_defined): ?>
+<div id="forum">
+  <?php print theme('links', $links); ?>
+  <?php print $forums; ?>
+  <?php print $topics; ?>
+</div>
+<?php endif; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/help/help-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+/* $Id: help-rtl.css,v 1.2 2007/11/27 12:09:26 goba Exp $ */
+
+.help-items {
+  float: right;
+  padding-right: 0;
+  padding-left: 3%;
+}
+.help-items-last {
+  padding-right: 0;
+  padding-left: 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/help/help.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,76 @@
+<?php
+// $Id: help.admin.inc,v 1.5 2007/11/25 11:11:17 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the help module.
+ */
+
+/**
+ * Menu callback; prints a page listing a glossary of Drupal terminology.
+ */
+function help_main() {
+  // Add CSS
+  drupal_add_css(drupal_get_path('module', 'help') .'/help.css', 'module', 'all', FALSE);
+  $output = '<h2>'. t('Help topics') .'</h2><p>'. t('Help is available on the following items:') .'</p>'. help_links_as_list();
+  return $output;
+}
+
+/**
+ * Menu callback; prints a page listing general help for a module.
+ */
+function help_page($name) {
+  $output = '';
+  if (module_hook($name, 'help')) {
+    $module = drupal_parse_info_file(drupal_get_path('module', $name) .'/'. $name .'.info');
+    drupal_set_title($module['name']);
+
+    $temp = module_invoke($name, 'help', "admin/help#$name", drupal_help_arg());
+    if (empty($temp)) {
+      $output .= t("No help is available for module %module.", array('%module' => $module['name']));
+    }
+    else {
+      $output .= $temp;
+    }
+
+    // Only print list of administration pages if the module in question has
+    // any such pages associated to it.
+    $admin_tasks = system_get_module_admin_tasks($name);
+    if (!empty($admin_tasks)) {
+      ksort($admin_tasks);
+      $output .= theme('item_list', $admin_tasks, t('@module administration pages', array('@module' => $module['name'])));
+    }
+
+  }
+  return $output;
+}
+
+function help_links_as_list() {
+  $empty_arg = drupal_help_arg();
+  $module_info = module_rebuild_cache();
+
+  $modules = array();
+  foreach (module_implements('help', TRUE) as $module) {
+    if (module_invoke($module, 'help', "admin/help#$module", $empty_arg)) {
+      $modules[$module] = $module_info[$module]->info['name'];
+    }
+  }
+  asort($modules);
+
+  // Output pretty four-column list
+  $count = count($modules);
+  $break = ceil($count / 4);
+  $output = '<div class="clear-block"><div class="help-items"><ul>';
+  $i = 0;
+  foreach ($modules as $module => $name) {
+    $output .= '<li>'. l($name, 'admin/help/'. $module) .'</li>';
+    if (($i + 1) % $break == 0 && ($i + 1) != $count) {
+      $output .= '</ul></div><div class="help-items'. ($i + 1 == $break * 3 ? ' help-items-last' : '') .'"><ul>';
+    }
+    $i++;
+  }
+  $output .= '</ul></div></div>';
+
+  return $output;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/help/help.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,10 @@
+/* $Id: help.css,v 1.2 2007/05/27 17:57:48 goba Exp $ */
+
+.help-items {
+  float: left; /* LTR */
+  width: 22%;
+  padding-right: 3%; /* LTR */
+}
+.help-items-last {
+  padding-right: 0; /* LTR */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/help/help.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: help.info,v 1.4 2007/06/08 05:50:54 dries Exp $
+name = Help
+description = Manages the display of online help.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/help/help.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,47 @@
+<?php
+// $Id: help.module,v 1.78 2007/12/14 18:08:46 goba Exp $
+
+/**
+ * @file
+ * Manages displaying online help.
+ */
+
+/**
+ * Implementation of hook_menu().
+ */
+function help_menu() {
+  $items['admin/help'] = array(
+    'title' => 'Help',
+    'page callback' => 'help_main',
+    'access arguments' => array('access administration pages'),
+    'weight' => 9,
+    'file' => 'help.admin.inc',
+  );
+
+  foreach (module_implements('help', TRUE) as $module) {
+    $items['admin/help/'. $module] = array(
+      'title' => $module,
+      'page callback' => 'help_page',
+      'page arguments' => array(2),
+      'type' => MENU_CALLBACK,
+      'file' => 'help.admin.inc',
+    );
+  }
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function help_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help':
+      $output = '<p>'. t('This guide provides context sensitive help on the use and configuration of <a href="@drupal">Drupal</a> and its modules, and is a supplement to the more extensive online <a href="@handbook">Drupal handbook</a>. The online handbook may contain more up-to-date information, is annotated with helpful user-contributed comments, and serves as the definitive reference point for all Drupal documentation.', array('@drupal' => 'http://drupal.org', '@handbook' => 'http://drupal.org/handbook')) .'</p>';
+      return $output;
+    case 'admin/help#help':
+      $output = '<p>'. t('The help module provides context sensitive help on the use and configuration of <a href="@drupal">Drupal</a> and its modules, and is a supplement to the more extensive online <a href="@handbook">Drupal handbook</a>. The online handbook may contain more up-to-date information, is annotated with helpful user-contributed comments, and serves as the definitive reference point for all Drupal documentation.', array('@drupal' => 'http://drupal.org', '@handbook' => 'http://drupal.org/handbook')) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@help">Help module</a>.', array('@help' => 'http://drupal.org/handbook/modules/help/')) .'</p>';
+      return $output;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/locale/locale.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+/* $Id: locale.css,v 1.1 2006/08/14 07:14:49 drumm Exp $ */
+
+.locale-untranslated {
+  font-style: normal;
+  text-decoration: line-through;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/locale/locale.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: locale.info,v 1.6 2007/11/05 08:43:48 goba Exp $
+name = Locale
+description = Adds language handling functionality and enables the translation of the user interface to languages other than English.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/locale/locale.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,398 @@
+<?php
+// $Id: locale.install,v 1.27 2008/01/10 14:35:24 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function locale_install() {
+  // locales_source.source and locales_target.target are not used as binary
+  // fields; non-MySQL database servers need to ensure the field type is text
+  // and that LIKE produces a case-sensitive comparison.
+
+  // Create tables.
+  drupal_install_schema('locale');
+
+  db_query("INSERT INTO {languages} (language, name, native, direction, enabled, weight, javascript) VALUES ('en', 'English', 'English', '0', '1', '0', '')");
+}
+
+/**
+ * @defgroup updates-5.x-to-6.x Locale updates from 5.x to 6.x
+ * @{
+ */
+
+/**
+ * {locales_meta} table became {languages}.
+ */
+function locale_update_6000() {
+  $ret = array();
+
+  $schema['languages'] = array(
+    'fields' => array(
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'native' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'direction' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'enabled' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'plurals' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'formula' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'domain' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'prefix' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'javascript' => array( //Adds a column to store the filename of the JavaScript translation file.
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('language'),
+    'indexes' => array(
+      'list' => array('weight', 'name'),
+    ),
+  );
+
+  db_create_table($ret, 'languages', $schema['languages']);
+
+  // Save the languages
+  $ret[] = update_sql("INSERT INTO {languages} (language, name, native, direction, enabled, plurals, formula, domain, prefix, weight) SELECT locale, name, name, 0, enabled, plurals, formula, '', locale, 0 FROM {locales_meta}");
+
+  // Save the language count in the variable table
+  $count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1'));
+  variable_set('language_count', $count);
+
+  // Save the default language in the variable table
+  $default = db_fetch_object(db_query('SELECT * FROM {locales_meta} WHERE isdefault = 1'));
+  variable_set('language_default', (object) array('language' => $default->locale, 'name' => $default->name, 'native' => '', 'direction' => 0, 'enabled' => 1, 'plurals' => $default->plurals, 'formula' => $default->formula, 'domain' => '', 'prefix' => $default->locale, 'weight' => 0));
+
+  $ret[] = update_sql("DROP TABLE {locales_meta}");
+  return $ret;
+}
+
+/**
+ * Change locale column to language. The language column is added by
+ * update_fix_d6_requirements() in update.php to avoid a large number
+ * of error messages from update.php.  All we need to do here is copy
+ * locale to language and then drop locale.
+ */
+function locale_update_6001() {
+  $ret = array();
+  $ret[] = update_sql('UPDATE {locales_target} SET language = locale');
+  db_drop_field($ret, 'locales_target', 'locale');
+  return $ret;
+}
+
+/**
+ * Remove empty translations, we don't need these anymore.
+ */
+function locale_update_6002() {
+  $ret = array();
+  $ret[] = update_sql("DELETE FROM {locales_target} WHERE translation = ''");
+  return $ret;
+}
+
+/**
+ * Prune strings with no translations (will be automatically re-registered if still in use)
+ */
+function locale_update_6003() {
+  $ret = array();
+  $ret[] = update_sql("DELETE FROM {locales_source} WHERE lid NOT IN (SELECT lid FROM {locales_target})");
+  return $ret;
+}
+
+/**
+ * Fix remaining inconsistent indexes.
+ */
+function locale_update_6004() {
+  $ret = array();
+  db_add_index($ret, 'locales_target', 'language', array('language'));
+
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      db_drop_index($ret, 'locales_source', 'source');
+      db_add_index($ret, 'locales_source', 'source', array(array('source', 30)));
+      break;
+  }
+
+  return $ret;
+}
+
+/**
+ * Change language setting variable of content types.
+ * 
+ * Use language_content_type_<content_type> instead of language_<content_type>
+ * so content types such as 'default', 'count' or 'negotiation' will not
+ * interfere with language variables.
+ */
+function locale_update_6005() {
+  foreach (node_get_types() as $type => $content_type) {
+    // Default to NULL, so we can skip dealing with non-existent settings.
+    $setting = variable_get('language_'. $type, NULL);
+    if ($type == 'default' && is_numeric($setting)) {
+      // language_default was overwritten with the content type setting,
+      // so reset the default language and save the content type setting.
+      variable_set('language_content_type_default', $setting);
+      variable_del('language_default');
+      drupal_set_message('The default language setting has been reset to its default value. Check the '. l('language configuration page', 'admin/settings/language') .' to configure it correctly.');
+    }
+    elseif ($type == 'negotiation') {
+      // language_content_type_negotiation is an integer either if it is
+      // the negotiation setting or the content type setting.
+      // The language_negotiation setting is not reset, but
+      // the user is alerted that this setting possibly was overwritten
+      variable_set('language_content_type_negotiation', $setting);
+      drupal_set_message('The language negotiation setting was possibly overwritten by a content type of the same name. Check the '. l('language configuration page', 'admin/settings/language/configure') .' and the '. l('<em>'. $content_type->name ."</em> content type's multilingual support settings", 'admin/content/types/negotiation', array('html' => TRUE)) .' to configure them correctly.');
+    }
+    elseif (!is_null($setting)) {
+      // Change the language setting variable for any other content type.
+      // Do not worry about language_count, it will be updated below.
+      variable_set('language_content_type_'. $type, $setting);
+      variable_del('language_'. $type);
+    }
+  }
+  // Update language count variable that might be overwritten.
+  $count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1'));
+  variable_set('language_count', $count);
+  return array();
+}
+
+/**
+ * @} End of "defgroup updates-5.x-to-6.x"
+ */
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function locale_uninstall() {
+  // Delete all JavaScript translation files
+  $files = db_query('SELECT javascript FROM {languages}');
+  while ($file = db_fetch_object($files)) {
+    if (!empty($file)) {
+      file_delete(file_create_path($file->javascript));
+    }
+  }
+
+  // Remove tables.
+  drupal_uninstall_schema('locale');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function locale_schema() {
+  $schema['languages'] = array(
+    'description' => t('List of all available languages in the system.'),
+    'fields' => array(
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("Language code, e.g. 'de' or 'en-US'."),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Language name in English.'),
+      ),
+      'native' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Native language name.'),
+      ),
+      'direction' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Direction of language (Left-to-Right = 0, Right-to-Left = 1).'),
+      ),
+      'enabled' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Enabled flag (1 = Enabled, 0 = Disabled).'),
+      ),
+      'plurals' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Number of plural indexes in this language.'),
+      ),
+      'formula' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Plural formula in PHP code to evaluate to get plural indexes.'),
+      ),
+      'domain' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Domain to use for this language.'),
+      ),
+      'prefix' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Path prefix to use for this language.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Weight, used in lists of languages.'),
+      ),
+      'javascript' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Location of JavaScript translation file.'),
+      ),
+    ),
+    'primary key' => array('language'),
+    'indexes' => array(
+      'list' => array('weight', 'name'),
+    ),
+  );
+
+  $schema['locales_source'] = array(
+    'description' => t('List of English source strings.'),
+    'fields' => array(
+      'lid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Unique identifier of this string.'),
+      ),
+      'location' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Drupal path in case of online discovered translations or file path in case of imported strings.'),
+      ),
+      'textgroup' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => 'default',
+        'description' => t('A module defined group of translations, see hook_locale().'),
+      ),
+      'source' => array(
+        'type' => 'text',
+        'mysql_type' => 'blob',
+        'not null' => TRUE,
+        'description' => t('The original string in English.'),
+      ),
+      'version' => array(
+        'type' => 'varchar',
+        'length' => 20,
+        'not null' => TRUE,
+        'default' => 'none',
+        'description' => t('Version of Drupal, where the string was last used (for locales optimization).'),
+      ),
+    ),
+    'primary key' => array('lid'),
+    'indexes' => array(
+      'source' => array(array('source', 30)),
+    ),
+  );
+
+  $schema['locales_target'] = array(
+    'description' => t('Stores translated versions of strings.'),
+    'fields' => array(
+      'lid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Source string ID. References {locales_source}.lid.'),
+      ),
+      'translation' => array(
+        'type' => 'text',
+        'mysql_type' => 'blob',
+        'not null' => TRUE,
+        'description' => t('Translation string value in this language.'),
+      ),
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Language code. References {languages}.language.'),
+      ),
+      'plid' => array(
+        'type' => 'int',
+        'not null' => TRUE, // This should be NULL for no referenced string, not zero.
+        'default' => 0,
+        'description' => t('Parent lid (lid of the previous string in the plural chain) in case of plural strings. References {locales_source}.lid.'),
+      ),
+      'plural' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Plural index number in case of plural strings.'),
+      ),
+    ),
+    'primary key' => array('language', 'lid', 'plural'),
+    'indexes' => array(
+      'lid'      => array('lid'),
+      'plid'     => array('plid'),
+      'plural'   => array('plural'),
+    ),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/locale/locale.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,588 @@
+<?php
+// $Id: locale.module,v 1.212 2008/01/28 17:16:34 goba Exp $
+
+/**
+ * @file
+ *   Add language handling functionality and enables the translation of the
+ *   user interface to languages other than English.
+ *
+ *   When enabled, multiple languages can be set up. The site interface
+ *   can be displayed in different languages, as well as nodes can have languages
+ *   assigned. The setup of languages and translations is completely web based.
+ *   Gettext portable object files are supported.
+ */
+
+/**
+ * Language written left to right. Possible value of $language->direction.
+ */
+define('LANGUAGE_LTR', 0);
+
+/**
+ * Language written right to left. Possible value of $language->direction.
+ */
+define('LANGUAGE_RTL', 1);
+
+
+// ---------------------------------------------------------------------------------
+// Hook implementations
+
+/**
+ * Implementation of hook_help().
+ */
+function locale_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#locale':
+      $output = '<p>'. t('The locale module allows your Drupal site to be presented in languages other than the default English, a defining feature of multi-lingual websites. The locale module works by examining text as it is about to be displayed: when a translation of the text is available in the language to be displayed, the translation is displayed rather than the original text. When a translation is unavailable, the original text is displayed, and then stored for later review by a translator.') .'</p>';
+      $output .= '<p>'. t('Beyond translation of the Drupal interface, the locale module provides a feature set tailored to the needs of a multi-lingual site. Language negotiation allows your site to automatically change language based on the domain or path used for each request. Users may (optionally) select their preferred language on their <em>My account</em> page, and your site can be configured to honor a web browser\'s preferred language settings. Your site content can be created in (and translated to) any enabled language, and each post may have a language-appropriate alias for each of its translations. The locale module works in concert with the <a href="@content-help">content translation module</a> to manage translated content.', array('@content-help' => url('admin/help/translation'))) .'</p>';
+      $output .= '<p>'. t('Translations may be provided by:') .'</p>';
+      $output .= '<ul><li>'. t("translating the original text via the locale module's integrated web interface, or") .'</li>';
+      $output .= '<li>'. t('importing files from a set of existing translations, known as a translation package. A translation package enables the display of a specific version of Drupal in a specific language, and contain files in the Gettext Portable Object (<em>.po</em>) format. Although not all languages are available for every version of Drupal, translation packages for many languages are available for download from the <a href="@translations">Drupal translation page</a>.', array('@translations' => 'http://drupal.org/project/translations')) .'</li></ul>';
+      $output .= '<p>'. t('If an existing translation package does not meet your needs, the Gettext Portable Object (<em>.po</em>) files within a package may be modified, or new <em>.po</em> files may be created, using a desktop Gettext editor. The locale module\'s <a href="@import">import</a> feature allows the translated strings from a new or modified <em>.po</em> file to be added to your site. The locale module\'s <a href="@export">export</a> feature generates files from your site\'s translated strings, that can either be shared with others or edited offline by a Gettext translation editor.', array('@import' => url('admin/build/translate/import'), '@export' => url('admin/build/translate/export'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@locale">Locale module</a>.', array('@locale' => 'http://drupal.org/handbook/modules/locale/')) .'</p>';
+      return $output;
+    case 'admin/settings/language':
+      $output = '<p>'. t("This page provides an overview of your site's enabled languages. If multiple languages are available and enabled, the text on your site interface may be translated, registered users may select their preferred language on the <em>My account</em> page, and site authors may indicate a specific language when creating posts. The site's default language is used for anonymous visitors and for users who have not selected a preferred language.") .'</p>';
+      $output .= '<p>'. t('For each language available on the site, use the <em>edit</em> link to configure language details, including name, an optional language-specific path or domain, and whether the language is natively presented either left-to-right or right-to-left. These languages also appear in the <em>Language</em> selection when creating a post of a content type with multilingual support.') .'</p>';
+      $output .= '<p>'. t('Use the <a href="@add-language">add language page</a> to enable additional languages (and automatically import files from a translation package, if available), the <a href="@search">translate interface page</a> to locate strings for manual translation, or the <a href="@import">import page</a> to add translations from individual <em>.po</em> files. A number of contributed translation packages containing <em>.po</em> files are available on the <a href="@translations">Drupal.org translations page</a>.', array('@add-language' => url('admin/settings/language/add'), '@search' => url('admin/build/translate/search'), '@import' => url('admin/build/translate/import'), '@translations' => 'http://drupal.org/project/translations')) .'</p>';
+      return $output;
+    case 'admin/settings/language/add':
+      return '<p>'. t('Add all languages to be supported by your site. If your desired language is not available in the <em>Language name</em> drop-down, click <em>Custom language</em> and provide a language code and other details manually. When providing a language code manually, be sure to enter a standardized language code, since this code may be used by browsers to determine an appropriate display language.') .'</p>';
+    case 'admin/settings/language/configure':
+      $output = '<p>'. t("Language negotiation settings determine the site's presentation language. Available options include:") .'</p>';
+      $output .= '<ul><li>'. t('<strong>None.</strong> The default language is used for site presentation, though users may (optionally) select a preferred language on the <em>My Account</em> page. (User language preferences will be used for site e-mails, if available.)') .'</li>';
+      $output .= '<li>'. t('<strong>Path prefix only.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the default language is used. <em>Example: "example.com/de/contact" sets presentation language to German based on the use of "de" within the path.</em>') .'</li>';
+      $output .= '<li>'. t("<strong>Path prefix with language fallback.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the display language is determined by the user's language preferences from the <em>My Account</em> page, or by the browser's language settings. If a presentation language cannot be determined, the default language is used.") .'</t>';
+      $output .= '<li>'. t('<strong>Domain name only.</strong> The presentation language is determined by examining the domain used to access the site, and comparing it to the language domain (if any) specified for each language. If a match is not identified, the default language is used. <em>Example: "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.</em>') .'</li></ul>';
+      $output .= '<p>'. t('The path prefix or domain name for a language may be set by editing the <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>.', array('@languages' => url('admin/settings/language'))) .'</p>';
+      return $output;
+    case 'admin/build/translate':
+      $output = '<p>'. t('This page provides an overview of available translatable strings. Drupal displays translatable strings in text groups; modules may define additional text groups containing other translatable strings. Because text groups provide a method of grouping related strings, they are often used to focus translation efforts on specific areas of the Drupal interface.') .'</p>';
+      $output .= '<p>'. t('Review the <a href="@languages">languages page</a> for more information on adding support for additional languages.', array('@languages' => url('admin/settings/language'))) .'</p>';
+      return $output;
+    case 'admin/build/translate/import':
+      $output = '<p>'. t('This page imports the translated strings contained in an individual Gettext Portable Object (<em>.po</em>) file. Normally distributed as part of a translation package (each translation package may contain several <em>.po</em> files), a <em>.po</em> file may need to be imported after off-line editing in a Gettext translation editor. Importing an individual <em>.po</em> file may be a lengthy process.') .'</p>';
+      $output .= '<p>'. t('Note that the <em>.po</em> files within a translation package are imported automatically (if available) when new modules or themes are enabled, or as new languages are added. Since this page only allows the import of one <em>.po</em> file at a time, it may be simpler to download and extract a translation package into your Drupal installation directory and <a href="@language-add">add the language</a> (which automatically imports all <em>.po</em> files within the package). Translation packages are available for download on the <a href="@translations">Drupal translation page</a>.', array('@language-add' => url('admin/settings/language/add'), '@translations' => 'http://drupal.org/project/translations')) .'</p>';
+      return $output;
+    case 'admin/build/translate/export':
+      return '<p>'. t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (<em>.po</em>) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (<em>.pot</em>) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') .'</p>';
+    case 'admin/build/translate/search':
+      return '<p>'. t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: For translation tasks involving many strings, it may be more convenient to <a href="@export">export</a> strings for off-line editing in a desktop Gettext translation editor.) Searches may be limited to strings found within a specific text group or in a specific language.', array('@export' => url('admin/build/translate/export'))) .'</p>';
+    case 'admin/build/block/configure':
+      if ($arg[4] == 'locale' && $arg[5] == 0) {
+        return '<p>'. t('This block is only shown if <a href="@languages">at least two languages are enabled</a> and <a href="@configuration">language negotiation</a> is set to something other than <em>None</em>.', array('@languages' => url('admin/settings/language'), '@configuration' => url('admin/settings/language/configure'))) .'</p>';
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ *
+ * Locale module only provides administrative menu items, so all
+ * menu items are invoked through locale_inc_callback().
+ */
+function locale_menu() {
+  // Manage languages
+  $items['admin/settings/language'] = array(
+    'title' => 'Languages',
+    'description' => 'Configure languages for content and the user interface.',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_languages_overview_form'),
+    'access arguments' => array('administer languages'),
+  );
+  $items['admin/settings/language/overview'] = array(
+    'title' => 'List',
+    'weight' => 0,
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/settings/language/add'] = array(
+    'title' => 'Add language',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('locale_languages_add_screen'), // two forms concatenated
+    'weight' => 5,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/settings/language/configure'] = array(
+    'title' => 'Configure',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_languages_configure_form'),
+    'weight' => 10,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/settings/language/edit/%'] = array(
+    'title' => 'Edit language',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_languages_edit_form', 4),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/language/delete/%'] = array(
+    'title' => 'Confirm',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_languages_delete_form', 4),
+    'type' => MENU_CALLBACK,
+  );
+
+  // Translation functionality
+  $items['admin/build/translate'] = array(
+    'title' => 'Translate interface',
+    'description' => 'Translate the built in interface and optionally other text.',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('locale_translate_overview_screen'), // not a form, just a table
+    'access arguments' => array('translate interface'),
+  );
+  $items['admin/build/translate/overview'] = array(
+    'title' => 'Overview',
+    'weight' => 0,
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/build/translate/search'] = array(
+    'title' => 'Search',
+    'weight' => 10,
+    'type' => MENU_LOCAL_TASK,
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('locale_translate_seek_screen'), // search results and form concatenated
+  );
+  $items['admin/build/translate/import'] = array(
+    'title' => 'Import',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_translate_import_form'),
+    'weight' => 20,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/build/translate/export'] = array(
+    'title' => 'Export',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('locale_translate_export_screen'), // possibly multiple forms concatenated
+    'weight' => 30,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/build/translate/edit/%'] = array(
+    'title' => 'Edit string',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('drupal_get_form', 'locale_translate_edit_form', 4),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/build/translate/delete/%'] = array(
+    'title' => 'Delete string',
+    'page callback' => 'locale_inc_callback',
+    'page arguments' => array('locale_translate_delete', 4),  // directly deletes, no confirmation
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Wrapper function to be able to set callbacks in locale.inc
+ */
+function locale_inc_callback() {
+  $args = func_get_args();
+  $function = array_shift($args);
+  include_once './includes/locale.inc';
+  return call_user_func_array($function, $args);
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function locale_perm() {
+  return array('administer languages', 'translate interface');
+}
+
+/**
+ * Implementation of hook_locale().
+ */
+function locale_locale($op = 'groups') {
+  switch ($op) {
+    case 'groups':
+      return array('default' => t('Built-in interface'));
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function locale_user($type, $edit, &$user, $category = NULL) {
+  global $language;
+
+  // If we have more then one language and either creating a user on the
+  // admin interface or edit the user, show the language selector.
+  if (variable_get('language_count', 1) > 1 && ($type == 'register' && user_access('administer users') || $type == 'form' && $category == 'account' )) {
+    $languages = language_list('enabled');
+    $languages = $languages[1];
+
+    // If the user is being created, we set the user language to the page language.
+    $user_preferred_language = $user ? user_preferred_language($user) : $language;
+
+    $names = array();
+    foreach ($languages as $langcode => $item) {
+      $name = t($item->name);
+      $names[$langcode] = $name . ($item->native != $name ? ' ('. $item->native .')' : '');
+    }
+    $form['locale'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Language settings'),
+      '#weight' => 1,
+    );
+
+    // Get language negotiation settings.  
+    $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
+    $form['locale']['language'] = array(
+      '#type' => (count($names) <= 5 ? 'radios' : 'select'),
+      '#title' => t('Language'),
+      '#default_value' => $user_preferred_language->language,
+      '#options' => $names,
+      '#description' => ($mode == LANGUAGE_NEGOTIATION_PATH) ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
+    );
+    return $form;
+  }
+}
+
+/**
+ * Implementation of hook_form_alter(). Adds language fields to forms.
+ */
+function locale_form_alter(&$form, $form_state, $form_id) {
+  switch ($form_id) {
+
+    // Language field for paths
+    case 'path_admin_form':
+      $form['language'] = array(
+        '#type' => 'select',
+        '#title' => t('Language'),
+        '#options' => array('' => t('All languages')) + locale_language_list('name'),
+        '#default_value' => $form['language']['#value'],
+        '#weight' => -10,
+        '#description' => t('A path alias set for a specific language will always be used when displaying this page in that language, and takes precedence over path aliases set for <am>All languages</em>.'),
+      );
+      break;
+
+    // Language setting for content types
+    case 'node_type_form':
+      if (isset($form['identity']['type'])) {
+        $form['workflow']['language_content_type'] = array(
+          '#type' => 'radios',
+          '#title' => t('Multilingual support'),
+          '#default_value' => variable_get('language_content_type_'. $form['#node_type']->type, 0),
+          '#options' => array(t('Disabled'), t('Enabled')),
+          '#description' => t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/settings/language'))),
+        );
+      }
+      break;
+
+    // Language field for nodes
+    default:
+      if (isset($form['#id']) && $form['#id'] == 'node-form') {
+        if (isset($form['#node']->type) && variable_get('language_content_type_'. $form['#node']->type, 0)) {
+          $form['language'] = array(
+            '#type' => 'select',
+            '#title' => t('Language'),
+            '#default_value' => (isset($form['#node']->language) ? $form['#node']->language : ''),
+            '#options' => array('' => t('Language neutral')) + locale_language_list('name'),
+          );
+        }
+        // Node type without language selector: assign the default for new nodes
+        elseif (!isset($form['#node']->nid)) {
+          $default = language_default();
+          $form['language'] = array(
+            '#type' => 'value',
+            '#value' => $default->language
+          );
+        }
+      }
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function locale_theme() {
+  return array(
+    'locale_languages_overview_form' => array(
+      'arguments' => array('form' => array()),
+    ),
+  );
+}
+
+// ---------------------------------------------------------------------------------
+// Locale core functionality
+
+/**
+ * Provides interface translation services.
+ *
+ * This function is called from t() to translate a string if needed.
+ *
+ * @param $string
+ *   A string to look up translation for. If omitted, all the
+ *   cached strings will be returned in all languages already
+ *   used on the page.
+ * @param $langcode
+ *   Language code to use for the lookup.
+ * @param $reset
+ *   Set to TRUE to reset the in-memory cache.
+ */
+function locale($string = NULL, $langcode = NULL, $reset = FALSE) {
+  global $language;
+  static $locale_t;
+
+  if ($reset) {
+    // Reset in-memory cache.
+    $locale_t = NULL;
+  }
+  
+  if (!isset($string)) {
+    // Return all cached strings if no string was specified
+    return $locale_t;
+  }
+
+  $langcode = isset($langcode) ? $langcode : $language->language;
+
+  // Store database cached translations in a static var.
+  if (!isset($locale_t[$langcode])) {
+    $locale_t[$langcode] = array();
+    // Disabling the usage of string caching allows a module to watch for
+    // the exact list of strings used on a page. From a performance
+    // perspective that is a really bad idea, so we have no user
+    // interface for this. Be careful when turning this option off!
+    if (variable_get('locale_cache_strings', 1) == 1) {
+      if ($cache = cache_get('locale:'. $langcode, 'cache')) {
+        $locale_t[$langcode] = $cache->data;
+      }
+      else {
+        // Refresh database stored cache of translations for given language.
+        // We only store short strings used in current version, to improve
+        // performance and consume less memory.
+        $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.textgroup = 'default' AND s.version = '%s' AND LENGTH(s.source) < 75", $langcode, VERSION);
+        while ($data = db_fetch_object($result)) {
+          $locale_t[$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation);
+        }
+        cache_set('locale:'. $langcode, $locale_t[$langcode]);
+      }
+    }
+  }
+
+  // If we have the translation cached, skip checking the database
+  if (!isset($locale_t[$langcode][$string])) {
+
+    // We do not have this translation cached, so get it from the DB.
+    $translation = db_fetch_object(db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.source = '%s' AND s.textgroup = 'default'", $langcode, $string));
+    if ($translation) {
+      // We have the source string at least.
+      // Cache translation string or TRUE if no translation exists.
+      $locale_t[$langcode][$string] = (empty($translation->translation) ? TRUE : $translation->translation);
+
+      if ($translation->version != VERSION) {
+        // This is the first use of this string under current Drupal version. Save version
+        // and clear cache, to include the string into caching next time. Saved version is
+        // also a string-history information for later pruning of the tables.
+        db_query("UPDATE {locales_source} SET version = '%s' WHERE lid = %d", VERSION, $translation->lid);
+        cache_clear_all('locale:', 'cache', TRUE);
+      }
+    }
+    else {
+      // We don't have the source string, cache this as untranslated.
+      db_query("INSERT INTO {locales_source} (location, source, textgroup, version) VALUES ('%s', '%s', 'default', '%s')", request_uri(), $string, VERSION);
+      $locale_t[$langcode][$string] = TRUE;
+      // Clear locale cache so this string can be added in a later request.
+      cache_clear_all('locale:', 'cache', TRUE);
+    }
+  }
+
+  return ($locale_t[$langcode][$string] === TRUE ? $string : $locale_t[$langcode][$string]);
+}
+
+/**
+ * Returns plural form index for a specific number.
+ *
+ * The index is computed from the formula of this language.
+ *
+ * @param $count
+ *   Number to return plural for.
+ * @param $langcode
+ *   Optional language code to translate to a language other than
+ *   what is used to display the page.
+ */
+function locale_get_plural($count, $langcode = NULL) {
+  global $language;
+  static $locale_formula, $plurals = array();
+
+  $langcode = $langcode ? $langcode : $language->language;
+
+  if (!isset($plurals[$langcode][$count])) {
+    if (!isset($locale_formula)) {
+      $language_list = language_list();
+      $locale_formula[$langcode] = $language_list[$langcode]->formula;
+    }
+    if ($locale_formula[$langcode]) {
+      $n = $count;
+      $plurals[$langcode][$count] = @eval('return intval('. $locale_formula[$langcode] .');');
+      return $plurals[$langcode][$count];
+    }
+    else {
+      $plurals[$langcode][$count] = -1;
+      return -1;
+    }
+  }
+  return $plurals[$langcode][$count];
+}
+
+
+/**
+ * Returns a language name
+ */
+function locale_language_name($lang) {
+  static $list = NULL;
+  if (!isset($list)) {
+    $list = locale_language_list();
+  }
+  return ($lang && isset($list[$lang])) ? $list[$lang] : t('All');
+}
+
+/**
+ * Returns array of language names
+ *
+ * @param $field
+ *   'name' => names in current language, localized
+ *   'native' => native names
+ * @param $all
+ *   Boolean to return all languages or only enabled ones
+ */
+function locale_language_list($field = 'name', $all = FALSE) {
+  if ($all) {
+    $languages = language_list();
+  }
+  else {
+    $languages = language_list('enabled');
+    $languages = $languages[1];
+  }
+  $list = array();
+  foreach ($languages as $language) {
+    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
+  }
+  return $list;
+}
+
+/**
+ * Imports translations when new modules or themes are installed or enabled.
+ *
+ * This function will either import translation for the component change
+ * right away, or start a batch if more files need to be imported.
+ *
+ * @param $components
+ *   An array of component (theme and/or module) names to import
+ *   translations for.
+ */
+function locale_system_update($components) {
+  include_once 'includes/locale.inc';
+  if ($batch = locale_batch_by_component($components)) {
+    batch_set($batch);
+  }
+}
+
+/**
+ * Update JavaScript translation file, if required, and add it to the page.
+ *
+ * This function checks all JavaScript files currently added via drupal_add_js()
+ * and invokes parsing if they have not yet been parsed for Drupal.t()
+ * and Drupal.formatPlural() calls. Also refreshes the JavaScript translation
+ * file if necessary, and adds it to the page.
+ */
+function locale_update_js_files() {
+  global $language;
+
+  $dir = file_create_path(variable_get('locale_js_directory', 'languages'));
+  $parsed = variable_get('javascript_parsed', array());
+
+  // The first three parameters are NULL in order to get an array with all
+  // scopes. This is necessary to prevent recreation of JS translation files
+  // when new files are added for example in the footer.
+  $javascript = drupal_add_js(NULL, NULL, NULL);
+  $files = $new_files = FALSE;
+
+  foreach ($javascript as $scope) {
+    foreach ($scope as $type => $data) {
+      if ($type != 'setting' && $type != 'inline') {
+        foreach ($data as $filepath => $info) {
+          $files = TRUE;
+          if (!in_array($filepath, $parsed)) {
+            // Don't parse our own translations files.
+            if (substr($filepath, 0, strlen($dir)) != $dir) {
+              locale_inc_callback('_locale_parse_js_file', $filepath);
+              watchdog('locale', 'Parsed JavaScript file %file.', array('%file' => $filepath));
+              $parsed[] = $filepath;
+              $new_files = TRUE;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // If there are any new source files we parsed, invalidate existing
+  // JavaScript translation files for all languages, adding the refresh
+  // flags into the existing array.
+  if ($new_files) {
+    $parsed += locale_inc_callback('_locale_invalidate_js');
+  }
+
+  // If necessary, rebuild the translation file for the current language.
+  if (!empty($parsed['refresh:'. $language->language])) {
+    // Don't clear the refresh flag on failure, so that another try will
+    // be performed later.
+    if (locale_inc_callback('_locale_rebuild_js')) {
+      unset($parsed['refresh:'. $language->language]);
+    }
+    // Store any changes after refresh was attempted.
+    variable_set('javascript_parsed', $parsed);
+  }
+  // If no refresh was attempted, but we have new source files, we need
+  // to store them too. This occurs if current page is in English.
+  else if ($new_files) {
+    variable_set('javascript_parsed', $parsed);
+  }
+
+  // Add the translation JavaScript file to the page.
+  if ($files && !empty($language->javascript)) {
+    drupal_add_js($dir .'/'. $language->language .'_'. $language->javascript .'.js', 'core');
+  }
+}
+
+// ---------------------------------------------------------------------------------
+// Language switcher block
+
+/**
+ * Implementation of hook_block().
+ * Displays a language switcher. Translation links may be provided by other modules.
+ */
+function locale_block($op = 'list', $delta = 0) {
+  if ($op == 'list') {
+    $block[0]['info'] = t('Language switcher');
+    // Not worth caching.
+    $block[0]['cache'] = BLOCK_NO_CACHE;
+    return $block;
+  }
+
+  // Only show if we have at least two languages and language dependent
+  // web addresses, so we can actually link to other language versions.
+  elseif ($op == 'view' && variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
+    $languages = language_list('enabled');
+    $links = array();
+    foreach ($languages[1] as $language) {
+      $links[$language->language] = array(
+        'href'       => $_GET['q'],
+        'title'      => $language->native,
+        'language'   => $language,
+        'attributes' => array('class' => 'language-link'),
+      );
+    }
+
+    // Allow modules to provide translations for specific links.
+    // A translation link may need to point to a different path or use
+    // a translated link text before going through l(), which will just
+    // handle the path aliases.
+    drupal_alter('translation_link', $links, $_GET['q']);
+
+    $block['subject'] = t('Languages');
+    $block['content'] = theme('links', $links, array());
+    return $block;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/menu/menu.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,638 @@
+<?php
+// $Id: menu.admin.inc,v 1.26.2.3 2008/02/11 15:12:53 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbaks for menu module.
+ */
+
+/**
+ * Menu callback which shows an overview page of all the custom menus and their descriptions.
+ */
+function menu_overview_page() {
+  $result = db_query("SELECT * FROM {menu_custom} ORDER BY title");
+  $content = array();
+  while ($menu = db_fetch_array($result)) {
+    $menu['href'] = 'admin/build/menu-customize/'. $menu['menu_name'];
+    $menu['localized_options'] = array();
+    $content[] = $menu;
+  }
+  return theme('admin_block_content', $content);
+}
+
+/**
+ * Form for editing an entire menu tree at once.
+ *
+ * Shows for one menu the menu items accessible to the current user and
+ * relevant operations.
+ */
+function menu_overview_form(&$form_state, $menu) {
+  global $menu_admin;
+  $sql = "
+    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+    FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+    WHERE ml.menu_name = '%s'
+    ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
+  $result = db_query($sql, $menu['menu_name']);
+  $tree = menu_tree_data($result);
+  $node_links = array();
+  menu_tree_collect_node_links($tree, $node_links);
+  // We indicate that a menu administrator is running the menu access check.
+  $menu_admin = TRUE;
+  menu_tree_check_access($tree, $node_links);
+  $menu_admin = FALSE;
+
+  $form = _menu_overview_tree_form($tree);
+  $form['#menu'] =  $menu;
+  if (element_children($form)) {
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save configuration'),
+    );
+  }
+  else {
+    $form['empty_menu'] = array('#value' => t('There are no menu items yet.'));
+  }
+  return $form;
+}
+
+/**
+ * Recursive helper function for menu_overview_form().
+ */
+function _menu_overview_tree_form($tree) {
+  static $form = array('#tree' => TRUE);
+  foreach ($tree as $data) {
+    $title = '';
+    $item = $data['link'];
+    // Don't show callbacks; these have $item['hidden'] < 0.
+    if ($item && $item['hidden'] >= 0) {
+      $mlid = 'mlid:'. $item['mlid'];
+      $form[$mlid]['#item'] = $item;
+      $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => 'menu-disabled') : array('class' => 'menu-enabled');
+      $form[$mlid]['title']['#value'] = l($item['title'], $item['href'], $item['localized_options']) . ($item['hidden'] ? ' ('. t('disabled') .')' : '');
+      $form[$mlid]['hidden'] = array(
+        '#type' => 'checkbox',
+        '#default_value' => !$item['hidden'],
+      );
+      $form[$mlid]['expanded'] = array(
+        '#type' => 'checkbox',
+        '#default_value' => $item['expanded'],
+      );
+      $form[$mlid]['weight'] = array(
+        '#type' => 'weight',
+        '#delta' => 50,
+        '#default_value' => isset($form_state[$mlid]['weight']) ? $form_state[$mlid]['weight'] : $item['weight'],
+      );
+      $form[$mlid]['mlid'] = array(
+        '#type' => 'hidden',
+        '#value' => $item['mlid'],
+      );
+      $form[$mlid]['plid'] = array(
+        '#type' => 'textfield',
+        '#default_value' => isset($form_state[$mlid]['plid']) ? $form_state[$mlid]['plid'] : $item['plid'],
+        '#size' => 6,
+      );
+      // Build a list of operations.
+      $operations = array();
+      $operations['edit'] = l(t('edit'), 'admin/build/menu/item/'. $item['mlid'] .'/edit');
+      // Only items created by the menu module can be deleted.
+      if ($item['module'] == 'menu' || $item['updated'] == 1) {
+        $operations['delete'] = l(t('delete'), 'admin/build/menu/item/'. $item['mlid'] .'/delete');
+      }
+      // Set the reset column.
+      elseif ($item['module'] == 'system' && $item['customized']) {
+        $operations['reset'] = l(t('reset'), 'admin/build/menu/item/'. $item['mlid'] .'/reset');
+      }
+
+      $form[$mlid]['operations'] = array();
+      foreach ($operations as $op => $value) {
+        $form[$mlid]['operations'][$op] = array('#value' => $value);
+      }
+    }
+
+    if ($data['below']) {
+      _menu_overview_tree_form($data['below']);
+    }
+  }
+  return $form;
+}
+
+/**
+ * Submit handler for the menu overview form.
+ *
+ * This function takes great care in saving parent items first, then items
+ * underneath them. Saving items in the incorrect order can break the menu tree.
+ *
+ * @see menu_overview_form()
+ */
+function menu_overview_form_submit($form, &$form_state) {
+  // When dealing with saving menu items, the order in which these items are
+  // saved is critical. If a changed child item is saved before its parent,
+  // the child item could be saved with an invalid path past its immediate
+  // parent. To prevent this, save items in the form in the same order they
+  // are sent by $_POST, ensuring parents are saved first, then their children.
+  // See http://drupal.org/node/181126#comment-632270
+  $order = array_flip(array_keys($form['#post'])); // Get the $_POST order.
+  $form = array_merge($order, $form); // Update our original form with the new order.
+
+  $updated_items = array();
+  $fields = array('expanded', 'weight', 'plid');
+  foreach (element_children($form) as $mlid) {
+    if (isset($form[$mlid]['#item'])) {
+      $element = $form[$mlid];
+      // Update any fields that have changed in this menu item.
+      foreach ($fields as $field) {
+        if ($element[$field]['#value'] != $element[$field]['#default_value']) {
+          $element['#item'][$field] = $element[$field]['#value'];
+          $updated_items[$mlid] = $element['#item'];
+        }
+      }
+      // Hidden is a special case, the value needs to be reversed.
+      if ($element['hidden']['#value'] != $element['hidden']['#default_value']) {
+        $element['#item']['hidden'] = !$element['hidden']['#value'];
+        $updated_items[$mlid] = $element['#item'];
+      }
+    }
+  }
+
+  // Save all our changed items to the database.
+  foreach ($updated_items as $item) {
+    $item['customized'] = 1;
+    menu_link_save($item);
+  }
+}
+
+/**
+ * Theme the menu overview form into a table.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_overview_form($form) {
+  drupal_add_tabledrag('menu-overview', 'match', 'parent', 'menu-plid', 'menu-plid', 'menu-mlid', TRUE, MENU_MAX_DEPTH - 1);
+  drupal_add_tabledrag('menu-overview', 'order', 'sibling', 'menu-weight');
+
+  $header = array(
+    t('Menu item'),
+    array('data' => t('Enabled'), 'class' => 'checkbox'),
+    array('data' => t('Expanded'), 'class' => 'checkbox'),
+    t('Weight'),
+    array('data' => t('Operations'), 'colspan' => '3'),
+  );
+
+  $rows = array();
+  foreach (element_children($form) as $mlid) {
+    if (isset($form[$mlid]['hidden'])) {
+      $element = &$form[$mlid];
+      // Build a list of operations.
+      $operations = array();
+      foreach (element_children($element['operations']) as $op) {
+        $operations[] = drupal_render($element['operations'][$op]);
+      }
+      while (count($operations) < 2) {
+        $operations[] = '';
+      }
+
+      // Add special classes to be used for tabledrag.js.
+      $element['plid']['#attributes']['class'] = 'menu-plid';
+      $element['mlid']['#attributes']['class'] = 'menu-mlid';
+      $element['weight']['#attributes']['class'] = 'menu-weight';
+
+      // Change the parent field to a hidden. This allows any value but hides the field.
+      $element['plid']['#type'] = 'hidden';
+
+      $row = array();
+      $row[] = theme('indentation', $element['#item']['depth'] - 1) . drupal_render($element['title']);
+      $row[] = array('data' => drupal_render($element['hidden']), 'class' => 'checkbox');
+      $row[] = array('data' => drupal_render($element['expanded']), 'class' => 'checkbox');
+      $row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
+      $row = array_merge($row, $operations);
+
+      $row = array_merge(array('data' => $row), $element['#attributes']);
+      $row['class'] = !empty($row['class']) ? $row['class'] .' draggable' : 'draggable';
+      $rows[] = $row;
+    }
+  }
+  $output = '';
+  if ($rows) {
+    $output .= theme('table', $header, $rows, array('id' => 'menu-overview'));
+  }
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Menu callback; Build the menu link editing form.
+ */
+function menu_edit_item(&$form_state, $type, $item, $menu) {
+
+  $form['menu'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Menu settings'),
+    '#collapsible' => FALSE,
+    '#tree' => TRUE,
+    '#weight' => -2,
+    '#attributes' => array('class' => 'menu-item-form'),
+    '#item' => $item,
+  );
+  if ($type == 'add' || empty($item)) {
+    // This is an add form, initialize the menu link.
+    $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
+  }
+  foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
+    $form['menu'][$key] = array('#type' => 'value', '#value' => $item[$key]);
+  }
+  // Any item created or edited via this interface is considered "customized".
+  $form['menu']['customized'] = array('#type' => 'value', '#value' => 1);
+  $form['menu']['original_item'] = array('#type' => 'value', '#value' => $item);
+
+  $path = $item['link_path'];
+  if (isset($item['options']['query'])) {
+    $path .= '?'. $item['options']['query'];
+  }
+  if (isset($item['options']['fragment'])) {
+    $path .= '#'. $item['options']['fragment'];
+  }
+  if ($item['module'] == 'menu') {
+    $form['menu']['link_path'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Path'),
+      '#default_value' => $path,
+      '#description' => t('The path this menu item links to. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
+      '#required' => TRUE,
+    );
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#access' => $item['mlid'],
+      '#submit' => array('menu_item_delete_submit'),
+      '#weight' => 10,
+    );
+  }
+  else {
+    $form['menu']['_path'] = array(
+      '#type' => 'item',
+      '#title' => t('Path'),
+      '#description' => l($item['link_title'], $item['href'], $item['options']),
+    );
+  }
+  $form['menu']['link_title'] = array('#type' => 'textfield',
+    '#title' => t('Menu link title'),
+    '#default_value' => $item['link_title'],
+    '#description' => t('The link text corresponding to this item that should appear in the menu.'),
+    '#required' => TRUE,
+  );
+  $form['menu']['description'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
+    '#rows' => 1,
+    '#description' => t('The description displayed when hovering over a menu item.'),
+  );
+  $form['menu']['enabled'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enabled'),
+    '#default_value' => !$item['hidden'],
+    '#description' => t('Menu items that are not enabled will not be listed in any menu.'),
+  );
+  $form['menu']['expanded'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Expanded'),
+    '#default_value' => $item['expanded'],
+    '#description' => t('If selected and this menu item has children, the menu will always appear expanded.'),
+  );
+
+  // Generate a list of possible parents (not including this item or descendants).
+  $options = menu_parent_options(menu_get_menus(), $item);
+  $default = $item['menu_name'] .':'. $item['plid'];
+  if (!isset($options[$default])) {
+    $default = 'navigation:0';
+  }
+  $form['menu']['parent'] = array(
+    '#type' => 'select',
+    '#title' => t('Parent item'),
+    '#default_value' => $default,
+    '#options' => $options,
+    '#description' => t('The maximum depth for an item and all its children is fixed at !maxdepth. Some menu items may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
+    '#attributes' => array('class' => 'menu-title-select'),
+  );
+  $form['menu']['weight'] = array(
+    '#type' => 'weight',
+    '#title' => t('Weight'),
+    '#delta' => 50,
+    '#default_value' => $item['weight'],
+    '#description' => t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'),
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+
+  return $form;
+}
+
+/**
+ * Validate form values for a menu link being added or edited.
+ */
+function menu_edit_item_validate($form, &$form_state) {
+  $item = &$form_state['values']['menu'];
+  $normal_path = drupal_get_normal_path($item['link_path']);
+  if ($item['link_path'] != $normal_path) {
+    drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $item['link_path'], '%normal_path' => $normal_path)));
+    $item['link_path'] = $normal_path;
+  }
+  if (!menu_path_is_external($item['link_path'])) {
+    $parsed_link = parse_url($item['link_path']);
+    if (isset($parsed_link['query'])) {
+      $item['options']['query'] = $parsed_link['query'];
+    }
+    if (isset($parsed_link['fragment'])) {
+      $item['options']['fragment'] = $parsed_link['fragment'];
+    }
+    if ($item['link_path'] != $parsed_link['path']) {
+      $item['link_path'] = $parsed_link['path'];
+    }
+  }
+  if (!trim($item['link_path']) || !menu_valid_path($item)) {
+    form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $item['link_path'])));
+  }
+}
+
+/**
+ * Submit function for the delete button on the menu item editing form.
+ */
+function menu_item_delete_submit($form, &$form_state) {
+  $form_state['redirect'] = 'admin/build/menu/item/'. $form_state['values']['menu']['mlid'] .'/delete';
+}
+
+/**
+ * Process menu and menu item add/edit form submissions.
+ */
+function menu_edit_item_submit($form, &$form_state) {
+  $item = $form_state['values']['menu'];
+
+  // The value of "hidden" is the opposite of the value
+  // supplied by the "enabled" checkbox.
+  $item['hidden'] = (int) !$item['enabled'];
+  unset($item['enabled']);
+
+  $item['options']['attributes']['title'] = $item['description'];
+  list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
+  if (!menu_link_save($item)) {
+    drupal_set_message(t('There was an error saving the menu link.'), 'error');
+  }
+  $form_state['redirect'] = 'admin/build/menu-customize/'. $item['menu_name'];
+}
+
+/**
+ * Menu callback; Build the form that handles the adding/editing of a custom menu.
+ */
+function menu_edit_menu(&$form_state, $type, $menu = array()) {
+  if ($type == 'edit') {
+    $form['menu_name'] = array('#type' => 'value', '#value' => $menu['menu_name']);
+    $form['#insert'] = FALSE;
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#access' => !in_array($menu['menu_name'], menu_list_system_menus()),
+      '#submit' => array('menu_custom_delete_submit'),
+      '#weight' => 10,
+    );
+  }
+  else {
+    $menu = array('menu_name' => '', 'title' => '', 'description' => '');
+    $form['menu_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Menu name'),
+      '#maxsize' => MENU_MAX_MENU_NAME_LENGTH_UI,
+      '#description' => t('The machine-readable name of this menu. This text will be used for constructing the URL of the <em>menu overview</em> page for this menu. This name must contain only lowercase letters, numbers, and hyphens, and must be unique.'),
+      '#required' => TRUE,
+    );
+    $form['#insert'] = TRUE;
+  }
+  $form['#title'] = $menu['title'];
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => $menu['title'],
+    '#required' => TRUE,
+  );
+  $form['description'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $menu['description'],
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit function for the 'Delete' button on the menu editing form.
+ */
+function menu_custom_delete_submit($form, &$form_state) {
+  $form_state['redirect'] = 'admin/build/menu-customize/'. $form_state['values']['menu_name'] .'/delete';
+}
+
+/**
+ * Menu callback; check access and get a confirm form for deletion of a custom menu.
+ */
+function menu_delete_menu_page($menu) {
+  // System-defined menus may not be deleted.
+  if (in_array($menu['menu_name'], menu_list_system_menus())) {
+    drupal_access_denied();
+    return;
+  }
+  return drupal_get_form('menu_delete_menu_confirm', $menu);
+}
+
+/**
+ * Build a confirm form for deletion of a custom menu.
+ */
+function menu_delete_menu_confirm(&$form_state, $menu) {
+  $form['#menu'] = $menu;
+  $caption = '';
+  $num_links = db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s'", $menu['menu_name']));
+  if ($num_links) {
+    $caption .= '<p>'. format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu item in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu items in %title. They will be deleted (system-defined items will be reset).', array('%title' => $menu['title'])) .'</p>';
+  }
+  $caption .= '<p>'. t('This action cannot be undone.') .'</p>';
+  return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/build/menu-customize/'. $menu['menu_name'], $caption, t('Delete'));
+}
+
+/**
+ * Delete a custom menu and all items in it.
+ */
+function menu_delete_menu_confirm_submit($form, &$form_state) {
+  $menu = $form['#menu'];
+  $form_state['redirect'] = 'admin/build/menu';
+  // System-defined menus may not be deleted - only menus defined by this module.
+  if (in_array($menu['menu_name'], menu_list_system_menus())  || !db_result(db_query("SELECT COUNT(*) FROM {menu_custom} WHERE menu_name = '%s'", $menu['menu_name']))) {
+    return;
+  }
+  // Reset all the menu links defined by the system via hook_menu.
+  $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = '%s' AND ml.module = 'system' ORDER BY m.number_parts ASC", $menu['menu_name']);
+  while ($item = db_fetch_array($result)) {
+    menu_reset_item($item);
+  }
+  // Delete all links to the overview page for this menu.
+  $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = '%s'", 'admin/build/menu-customize/'. $menu['menu_name']);
+  while ($m = db_fetch_array($result)) {
+    menu_link_delete($m['mlid']);
+  }
+  // Delete all the links in the menu and the menu from the list of custom menus.
+  db_query("DELETE FROM {menu_links} WHERE menu_name = '%s'", $menu['menu_name']);
+  db_query("DELETE FROM {menu_custom} WHERE menu_name = '%s'", $menu['menu_name']);
+  // Delete all the blocks for this menu.
+  db_query("DELETE FROM {blocks} WHERE module = 'menu' AND delta = '%s'", $menu['menu_name']);
+  db_query("DELETE FROM {blocks_roles} WHERE module = 'menu' AND delta = '%s'", $menu['menu_name']);
+  menu_cache_clear_all();
+  cache_clear_all();
+  $t_args = array('%title' => $menu['title']);
+  drupal_set_message(t('The custom menu %title has been deleted.', $t_args));
+  watchdog('menu', 'Deleted custom menu %title and all its menu items.', $t_args, WATCHDOG_NOTICE);
+}
+
+/**
+ * Validates the human and machine-readable names when adding or editing a menu.
+ */
+function menu_edit_menu_validate($form, &$form_state) {
+  $item = $form_state['values'];
+  if (preg_match('/[^a-z0-9-]/', $item['menu_name'])) {
+    form_set_error('menu_name', t('The menu name may only consist of lowercase letters, numbers, and hyphens.'));
+  }
+  if (strlen($item['menu_name']) > MENU_MAX_MENU_NAME_LENGTH_UI) {
+    form_set_error('menu_name', format_plural(MENU_MAX_MENU_NAME_LENGTH_UI, "The menu name can't be longer than 1 character.", "The menu name can't be longer than @count characters."));
+  }
+  if ($form['#insert']) {
+    // We will add 'menu-' to the menu name to help avoid name-space conflicts.
+    $item['menu_name'] = 'menu-'. $item['menu_name'];
+    if (db_result(db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = '%s'", $item['menu_name'])) ||
+      db_result(db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = '%s'", $item['menu_name'], 0, 1))) {
+      form_set_error('menu_name', t('The menu already exists.'));
+    }
+  }
+}
+
+/**
+ * Submit function for adding or editing a custom menu.
+ */
+function menu_edit_menu_submit($form, &$form_state) {
+  $menu = $form_state['values'];
+  $path = 'admin/build/menu-customize/';
+  if ($form['#insert']) {
+    // Add 'menu-' to the menu name to help avoid name-space conflicts.
+    $menu['menu_name'] = 'menu-'. $menu['menu_name'];
+    $link['link_title'] = $menu['title'];
+    $link['link_path'] = $path . $menu['menu_name'];
+    $link['router_path'] = $path .'%';
+    $link['module'] = 'menu';
+    $link['plid'] = db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s' AND module = '%s'", 'admin/build/menu', 'system'));
+    menu_link_save($link);
+    db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menu['menu_name'], $menu['title'], $menu['description']);
+  }
+  else {
+    db_query("UPDATE {menu_custom} SET title = '%s', description = '%s' WHERE menu_name = '%s'", $menu['title'], $menu['description'], $menu['menu_name']);
+    $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s'", $path . $menu['menu_name']);
+    while ($m = db_fetch_array($result)) {
+      $link = menu_link_load($m['mlid']);
+      $link['link_title'] = $menu['title'];
+      menu_link_save($link);
+    }
+  }
+  $form_state['redirect'] = $path . $menu['menu_name'];
+}
+
+/**
+ * Menu callback; Check access and present a confirm form for deleting a menu link.
+ */
+function menu_item_delete_page($item) {
+  // Links defined via hook_menu may not be deleted. Updated items are an
+  // exception, as they can be broken.
+  if ($item['module'] == 'system' && !$item['updated']) {
+    drupal_access_denied();
+    return;
+  }
+  return drupal_get_form('menu_item_delete_form', $item);
+}
+
+/**
+ * Build a confirm form for deletion of a single menu link.
+ */
+function menu_item_delete_form(&$form_state, $item) {
+  $form['#item'] = $item;
+  return confirm_form($form, t('Are you sure you want to delete the custom menu item %item?', array('%item' => $item['link_title'])), 'admin/build/menu-customize/'. $item['menu_name']);
+}
+
+/**
+ * Process menu delete form submissions.
+ */
+function menu_item_delete_form_submit($form, &$form_state) {
+  $item = $form['#item'];
+  menu_link_delete($item['mlid']);
+  $t_args = array('%title' => $item['link_title']);
+  drupal_set_message(t('The menu item %title has been deleted.', $t_args));
+  watchdog('menu', 'Deleted menu item %title.', $t_args, WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/build/menu-customize/'. $item['menu_name'];
+}
+
+/**
+ * Menu callback; reset a single modified item.
+ */
+function menu_reset_item_confirm(&$form_state, $item) {
+  $form['item'] = array('#type' => 'value', '#value' => $item);
+  return confirm_form($form, t('Are you sure you want to reset the item %item to its default values?', array('%item' => $item['link_title'])), 'admin/build/menu-customize/'. $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset'));
+}
+
+/**
+ * Process menu reset item form submissions.
+ */
+function menu_reset_item_confirm_submit($form, &$form_state) {
+  $item = $form_state['values']['item'];
+  $new_item = menu_reset_item($item);
+  drupal_set_message(t('The menu item was reset to its default settings.'));
+  $form_state['redirect'] = 'admin/build/menu-customize/'. $new_item['menu_name'];
+}
+
+/**
+ * Menu callback; Build the form presenting menu configuration options.
+ */
+function menu_configure() {
+  $form['intro'] = array(
+    '#type' => 'item',
+    '#value' => t('The menu module allows on-the-fly creation of menu links in the content authoring forms. The following option sets the default menu in which a new link will be added.'),
+  );
+
+  $menu_options = menu_get_menus();
+  $form['menu_default_node_menu'] = array(
+    '#type' => 'select',
+    '#title' => t('Default menu for content'),
+    '#default_value' => variable_get('menu_default_node_menu', 'primary-links'),
+    '#options' => $menu_options,
+    '#description' => t('Choose the menu to be the default in the menu options in the content authoring form.'),
+  );
+
+  $primary = variable_get('menu_primary_links_source', 'primary-links');
+  $primary_options = array_merge($menu_options, array('' => t('No primary links')));
+  $form['menu_primary_links_source'] = array(
+    '#type' => 'select',
+    '#title' => t('Source for the primary links'),
+    '#default_value' => $primary,
+    '#options' => $primary_options,
+    '#tree' => FALSE,
+    '#description' => t('Select what should be displayed as the primary links.'),
+  );
+
+  $secondary_options = array_merge($menu_options, array('' => t('No secondary links')));
+  $form["menu_secondary_links_source"] = array(
+    '#type' => 'select',
+    '#title' => t('Source for the secondary links'),
+    '#default_value' => variable_get('menu_secondary_links_source', 'secondary-links'),
+    '#options' => $secondary_options,
+    '#tree' => FALSE,
+    '#description' => t('Select what should be displayed as the secondary links. You can choose the same menu for secondary links as for primary links (currently %primary). If you do this, the children of the active primary menu link will be displayed as secondary links.', array('%primary' => $primary_options[$primary])),
+  );
+
+  return system_settings_form($form);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/menu/menu.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: menu.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Menu
+description = Allows administrators to customize the site navigation menu.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/menu/menu.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,58 @@
+<?php
+// $Id: menu.install,v 1.9 2008/01/30 20:27:28 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function menu_install() {
+  // Create tables.
+  drupal_install_schema('menu');
+  
+  $t = get_t();
+  db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", 'navigation', $t('Navigation'), $t('The navigation menu is provided by Drupal and is the main interactive menu for any site. It is usually the only menu that contains personalized links for authenticated users, and is often not even visible to anonymous users.'));
+  db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", 'primary-links', $t('Primary links'), $t('Primary links are often used at the theme layer to show the major sections of a site. A typical representation for primary links would be tabs along the top.'));
+  db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", 'secondary-links', $t('Secondary links'), $t('Secondary links are often used for pages like legal notices, contact details, and other secondary navigation items that play a lesser role than primary links'));
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function menu_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('menu');
+  menu_rebuild();
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function menu_schema() {
+  $schema['menu_custom'] = array(
+    'description' => t('Holds definitions for top-level custom menus (for example, Primary Links).'),
+    'fields' => array(
+      'menu_name' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Primary Key: Unique key for menu. This is used as a block delta so length is 32.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Menu title; displayed at top of block.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => t('Menu description.'),
+      ),
+    ),
+    'primary key' => array('menu_name'),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/menu/menu.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,446 @@
+<?php
+// $Id: menu.module,v 1.157.2.1 2008/02/11 15:12:53 goba Exp $
+
+/**
+ * @file
+ * Allows administrators to customize the site navigation menu.
+ */
+
+/**
+ * Maximum length of menu name as entered by the user. Database length is 32
+ * and we add a menu- prefix.
+ */
+define('MENU_MAX_MENU_NAME_LENGTH_UI', 27);
+
+/**
+ * Implementation of hook_help().
+ */
+function menu_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#menu':
+      $output = '<p>'. t("The menu module provides an interface to control and customize Drupal's powerful menu system. Menus are a hierarchical collection of links, or menu items, used to navigate a website, and are positioned and displayed using Drupal's flexible block system. By default, three menus are created during installation: <em>Navigation</em>, <em>Primary links</em>, and <em>Secondary links</em>. The <em>Navigation</em> menu contains most links necessary for working with and navigating your site, and is often displayed in either the left or right sidebar. Most Drupal themes also provide support for <em>Primary links</em> and <em>Secondary links</em>, by displaying them in either the header or footer of each page. By default, <em>Primary links</em> and <em>Secondary links</em> contain no menu items but may be configured to contain custom menu items specific to your site.") .'</p>';
+      $output .= '<p>'. t('The <a href="@menu">menus page</a> displays all menus currently available on your site. Select a menu from this list to add or edit a menu item, or to rearrange items within the menu. Create new menus using the <a href="@add-menu">add menu page</a> (the block containing a new menu must also be enabled on the <a href="@blocks">blocks administration page</a>).', array('@menu' => url('admin/build/menu'), '@add-menu' => url('admin/build/menu/add'), '@blocks' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@menu">Menu module</a>.', array('@menu' => 'http://drupal.org/handbook/modules/menu/')) .'</p>';
+      return $output;
+    case 'admin/build/menu':
+      return '<p>'. t('Menus are a collection of links (menu items) used to navigate a website. The menus currently available on your site are displayed below. Select a menu from this list to manage its menu items.') .'</p>';
+    case 'admin/build/menu/add':
+      return '<p>'. t('Enter the name for your new menu. Remember to enable the newly created block in the <a href="@blocks">blocks administration page</a>.', array('@blocks' => url('admin/build/block'))) .'</p>';
+    case 'admin/build/menu-customize/%':
+      return '<p>'. t('To rearrange menu items, grab a drag-and-drop handle under the <em>Menu item</em> column and drag the items (or group of items) to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save configuration</em> button at the bottom of the page.') .'<p>';
+    case 'admin/build/menu/item/add':
+      return '<p>'. t('Enter the title and path for your new menu item.') .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function menu_perm() {
+  return array('administer menu');
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function menu_menu() {
+  $items['admin/build/menu'] = array(
+    'title' => 'Menus',
+    'description' => "Control your site's navigation menu, primary links and secondary links. as well as rename and reorganize menu items.",
+    'page callback' => 'menu_overview_page',
+    'access callback' => 'user_access',
+    'access arguments' => array('administer menu'),
+    'file' => 'menu.admin.inc',
+  );
+
+  $items['admin/build/menu/list'] = array(
+    'title' => 'List menus',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu/add'] = array(
+    'title' => 'Add menu',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_edit_menu', 'add'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_configure'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 5,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu-customize/%menu'] = array(
+    'title' => 'Customize menu',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_overview_form', 3),
+    'title callback' => 'menu_overview_title',
+    'title arguments' => array(3),
+    'access arguments' => array('administer menu'),
+    'type' => MENU_CALLBACK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu-customize/%menu/list'] = array(
+    'title' => 'List items',
+    'weight' => -10,
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu-customize/%menu/add'] = array(
+    'title' => 'Add item',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_edit_item', 'add', NULL, 3),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu-customize/%menu/edit'] = array(
+    'title' => 'Edit menu',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_edit_menu', 'edit', 3),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu-customize/%menu/delete'] = array(
+    'title' => 'Delete menu',
+    'page callback' => 'menu_delete_menu_page',
+    'page arguments' => array(3),
+    'type' => MENU_CALLBACK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu/item/%menu_link/edit'] = array(
+    'title' => 'Edit menu item',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_edit_item', 'edit', 4, NULL),
+    'type' => MENU_CALLBACK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu/item/%menu_link/reset'] = array(
+    'title' => 'Reset menu item',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('menu_reset_item_confirm', 4),
+    'type' => MENU_CALLBACK,
+    'file' => 'menu.admin.inc',
+  );
+  $items['admin/build/menu/item/%menu_link/delete'] = array(
+    'title' => 'Delete menu item',
+    'page callback' => 'menu_item_delete_page',
+    'page arguments' => array(4),
+    'type' => MENU_CALLBACK,
+    'file' => 'menu.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implemenation of hook_theme().
+ */
+function menu_theme() {
+  return array(
+    'menu_overview_form' => array(
+      'file' => 'menu.admin.inc',
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_enable()
+ *
+ *  Add a link for each custom menu.
+ */
+function menu_enable() {
+  menu_rebuild();
+  $link = db_fetch_array(db_query("SELECT mlid AS plid, menu_name from {menu_links} WHERE link_path = 'admin/build/menu' AND module = 'system'"));
+  $link['router_path'] = 'admin/build/menu-customize/%';
+  $link['module'] = 'menu';
+  $result = db_query("SELECT * FROM {menu_custom}");
+  while ($menu = db_fetch_array($result)) {
+    $link['mlid'] = 0;
+    $link['link_title'] = $menu['title'];
+    $link['link_path'] = 'admin/build/menu-customize/'. $menu['menu_name'];
+    if (!db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s' AND plid = %d", $link['link_path'], $link['plid']))) {
+      menu_link_save($link);
+    }
+  }
+  menu_cache_clear_all();
+}
+
+/**
+ * Title callback for the menu overview page and links.
+ */
+function menu_overview_title($menu) {
+  return $menu['title'];
+}
+
+/**
+ * Load the data for a single custom menu.
+ */
+function menu_load($menu_name) {
+  return db_fetch_array(db_query("SELECT * FROM {menu_custom} WHERE menu_name = '%s'", $menu_name));
+}
+
+/**
+ * Return a list of menu items that are valid possible parents for the given menu item.
+ *
+ * @param $menus
+ *   An array of menu names and titles, such as from menu_get_menus().
+ * @param $item
+ *   The menu item for which to generate a list of parents.
+ *   If $item['mlid'] == 0 then the complete tree is returned.
+ * @return
+ *   An array of menu link titles keyed on the a string containing the menu name
+ *   and mlid. The list excludes the given item and its children.
+ */
+function menu_parent_options($menus, $item) {
+  // The menu_links table can be practically any size and we need a way to
+  // allow contrib modules to provide more scalable pattern choosers.
+  // hook_form_alter is too late in itself because all the possible parents are
+  // retrieved here, unless menu_override_parent_selector is set to TRUE.
+  if (variable_get('menu_override_parent_selector', FALSE)) {
+    return array();
+  }
+  // If the item has children, there is an added limit to the depth of valid parents.
+  if (isset($item['parent_depth_limit'])) {
+    $limit = $item['parent_depth_limit'];
+  }
+  else {
+    $limit = _menu_parent_depth_limit($item);
+  }
+
+  foreach ($menus as $menu_name => $title) {
+    $tree = menu_tree_all_data($menu_name, NULL);
+    $options[$menu_name .':0'] = '<'. $title .'>';
+    _menu_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
+  }
+  return $options;
+}
+
+/**
+ * Recursive helper function for menu_parent_options().
+ */
+function _menu_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) {
+  foreach ($tree as $data) {
+    if ($data['link']['depth'] > $depth_limit) {
+      // Don't iterate through any links on this level.
+      break;
+    }
+    if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) {
+      $title = $indent .' '. truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
+      if ($data['link']['hidden']) {
+        $title .= ' ('. t('disabled') .')';
+      }
+      $options[$menu_name .':'. $data['link']['mlid']] = $title;
+      if ($data['below']) {
+        _menu_parents_recurse($data['below'], $menu_name, $indent .'--', $options, $exclude, $depth_limit);
+      }
+    }
+  }
+}
+
+/**
+ * Reset a system-defined menu item.
+ */
+function menu_reset_item($item) {
+  $router = menu_router_build();
+  $new_item = _menu_link_build($router[$item['router_path']]);
+  foreach (array('mlid', 'has_children') as $key) {
+    $new_item[$key] = $item[$key];
+  }
+  menu_link_save($new_item);
+  return $new_item;
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function menu_block($op = 'list', $delta = 0) {
+  $menus = menu_get_menus();
+  // The Navigation menu is handled by the user module.
+  unset($menus['navigation']);
+  if ($op == 'list') {
+    $blocks = array();
+    foreach ($menus as $name => $title) {
+      // Default "Navigation" block is handled by user.module.
+      $blocks[$name]['info'] = check_plain($title);
+      // Menu blocks can't be cached because each menu item can have
+      // a custom access callback. menu.inc manages its own caching.
+      $blocks[$name]['cache'] = BLOCK_NO_CACHE;
+    }
+    return $blocks;
+  }
+  else if ($op == 'view') {
+    $data['subject'] = check_plain($menus[$delta]);
+    $data['content'] = menu_tree($delta);
+    return $data;
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function menu_nodeapi(&$node, $op) {
+  switch ($op) {
+    case 'insert':
+    case 'update':
+      if (isset($node->menu)) {
+        $item = $node->menu;
+        if (!empty($item['delete'])) {
+          menu_link_delete($item['mlid']);
+        }
+        elseif (trim($item['link_title'])) {
+          $item['link_title'] = trim($item['link_title']);
+          $item['link_path'] = "node/$node->nid";
+          if (!$item['customized']) {
+            $item['options']['attributes']['title'] = trim($node->title);
+          }
+          if (!menu_link_save($item)) {
+            drupal_set_message(t('There was an error saving the menu link.'), 'error');
+          }
+        }
+      }
+      break;
+    case 'delete':
+      // Delete all menu module links that point to this node.
+      $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu'", $node->nid);
+      while ($m = db_fetch_array($result)) {
+        menu_link_delete($m['mlid']);
+      }
+      break;
+    case 'prepare':
+      if (empty($node->menu)) {
+        // Prepare the node for the edit form so that $node->menu always exists.
+        $menu_name = variable_get('menu_default_node_menu', 'primary-links');
+        $item = array();
+        if (isset($node->nid)) {
+          // Give priority to the default menu
+          $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND menu_name = '%s' AND module = 'menu' ORDER BY mlid ASC", $node->nid, $menu_name, 0, 1));
+          // Check all menus if a link does not exist in the default menu.
+          if (!$mlid) {
+            $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu' ORDER BY mlid ASC", $node->nid, 0, 1));
+          }
+          if ($mlid) {
+            $item = menu_link_load($mlid);
+          }
+        }
+        // Set default values.
+        $node->menu = $item + array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu_name, 'weight' => 0, 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0, 'customized' => 0);
+      }
+      // Find the depth limit for the parent select.
+      if (!isset($node->menu['parent_depth_limit'])) {
+        $node->menu['parent_depth_limit'] = _menu_parent_depth_limit($node->menu);
+      }
+      break;
+  }
+}
+
+/**
+ * Find the depth limit for items in the parent select.
+ */
+function _menu_parent_depth_limit($item) {
+  return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? menu_link_children_relative_depth($item) : 0);
+}
+
+/**
+ * Implementation of hook_form_alter(). Adds menu item fields to the node form.
+ */
+function menu_form_alter(&$form, $form_state, $form_id) {
+  if (isset($form['#node']) && $form['#node']->type .'_node_form' == $form_id) {
+    // Note - doing this to make sure the delete checkbox stays in the form.
+    $form['#cache'] = TRUE;
+
+    $form['menu'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Menu settings'),
+      '#access' => user_access('administer menu'),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#tree' => TRUE,
+      '#weight' => -2,
+      '#attributes' => array('class' => 'menu-item-form'),
+    );
+    $item = $form['#node']->menu;
+
+    if ($item['mlid']) {
+      // There is an existing link.
+      $form['menu']['delete'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Delete this menu item.'),
+      );
+    }
+    if (!$item['link_title']) {
+      $form['menu']['#collapsed'] = TRUE;
+    }
+
+    foreach (array('mlid', 'module', 'hidden', 'has_children', 'customized', 'options', 'expanded', 'hidden', 'parent_depth_limit') as $key) {
+      $form['menu'][$key] = array('#type' => 'value', '#value' => $item[$key]);
+    }
+    $form['menu']['#item'] = $item;
+
+    $form['menu']['link_title'] = array('#type' => 'textfield',
+      '#title' => t('Menu link title'),
+      '#default_value' => $item['link_title'],
+      '#description' => t('The link text corresponding to this item that should appear in the menu. Leave blank if you do not wish to add this post to the menu.'),
+      '#required' => FALSE,
+    );
+    // Generate a list of possible parents (not including this item or descendants).
+    $options = menu_parent_options(menu_get_menus(), $item);
+    $default = $item['menu_name'] .':'. $item['plid'];
+    if (!isset($options[$default])) {
+      $default = 'primary-links:0';
+    }
+    $form['menu']['parent'] = array(
+      '#type' => 'select',
+      '#title' => t('Parent item'),
+      '#default_value' => $default,
+      '#options' => $options,
+      '#description' => t('The maximum depth for an item and all its children is fixed at !maxdepth. Some menu items may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
+      '#attributes' => array('class' => 'menu-title-select'),
+    );
+    $form['#submit'][] = 'menu_node_form_submit';
+
+    $form['menu']['weight'] = array(
+      '#type' => 'weight',
+      '#title' => t('Weight'),
+      '#delta' => 50,
+      '#default_value' => $item['weight'],
+      '#description' => t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'),
+    );
+  }
+}
+
+/**
+ * Decompose the selected menu parent option into the menu_name and plid.
+ */
+function menu_node_form_submit($form, &$form_state) {
+  list($form_state['values']['menu']['menu_name'], $form_state['values']['menu']['plid']) = explode(':', $form_state['values']['menu']['parent']);
+}
+
+/**
+ * Return an associative array of the custom menus names.
+ *
+ * @param $all
+ *   If FALSE return only user-added menus, or if TRUE also include
+ *   the menus defined by the system.
+ * @return
+ *   An array with the machine-readable names as the keys, and human-readable
+ *   titles as the values.
+ */
+function menu_get_menus($all = TRUE) {
+  $system_menus = menu_list_system_menus();
+  $sql = 'SELECT * FROM {menu_custom}';
+  if (!$all) {
+    $sql .= ' WHERE menu_name NOT IN ('. implode(',', array_fill(0, count($system_menus), "'%s'")) .')';
+  }
+  $sql .= ' ORDER BY title';
+  $result = db_query($sql, $system_menus);
+  $rows = array();
+  while ($r = db_fetch_array($result)) {
+    $rows[$r['menu_name']] = $r['title'];
+  }
+  return $rows;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/content_types.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,411 @@
+<?php
+// $Id: content_types.inc,v 1.50.2.1 2008/02/13 11:23:28 goba Exp $
+
+/**
+ * @file
+ * Content type editing UI.
+ */
+
+/**
+ * Displays the content type admin overview page.
+ */
+function node_overview_types() {
+  $types = node_get_types();
+  $names = node_get_types('names');
+  $header = array(t('Name'), t('Type'), t('Description'), array('data' => t('Operations'), 'colspan' => '2'));
+  $rows = array();
+
+  foreach ($names as $key => $name) {
+    $type = $types[$key];
+    if (node_hook($type, 'form')) {
+      $type_url_str = str_replace('_', '-', $type->type);
+      $row = array(
+        l($name, 'admin/content/node-type/'. $type_url_str),
+        check_plain($type->type),
+        filter_xss_admin($type->description),
+      );
+      // Set the edit column.
+      $row[] = array('data' => l(t('edit'), 'admin/content/node-type/'. $type_url_str));
+
+      // Set the delete column.
+      if ($type->custom) {
+        $row[] = array('data' => l(t('delete'), 'admin/content/node-type/'. $type_url_str .'/delete'));
+      }
+      else {
+        $row[] = array('data' => '');
+      }
+      $rows[] = $row;
+    }
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No content types available.'), 'colspan' => '5', 'class' => 'message'));
+  }
+
+  return theme('table', $header, $rows);
+}
+
+/**
+ * Generates the node type editing form.
+ */
+function node_type_form(&$form_state, $type = NULL) {
+  if (!isset($type->type)) {
+    $type = new stdClass();
+    $type->type = $type->name = $type->module = $type->description = $type->help = '';
+    $type->min_word_count = 0;
+    $type->has_title = TRUE;
+    $type->has_body = TRUE;
+    $type->title_label = t('Title');
+    $type->body_label = t('Body');
+    $type->custom = TRUE;
+    $type->modified = FALSE;
+    $type->locked = FALSE;
+  }
+
+  $form['#node_type'] = $type; // Make the type object available to implementations of hook_form_alter.
+
+  $form['identity'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Identification'),
+  );
+  $form['identity']['name'] = array(
+    '#title' => t('Name'),
+    '#type' => 'textfield',
+    '#default_value' => $type->name,
+    '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>create content</em> page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and <strong>spaces</strong>. This name must be unique.'),
+    '#required' => TRUE,
+  );
+
+  if (!$type->locked) {
+    $form['identity']['type'] = array(
+      '#title' => t('Type'),
+      '#type' => 'textfield',
+      '#default_value' => $type->type,
+      '#maxlength' => 32,
+      '#required' => TRUE,
+      '#description' => t('The machine-readable name of this content type. This text will be used for constructing the URL of the <em>create content</em> page for this content type. This name must contain only lowercase letters, numbers, and underscores. Underscores will be converted into hyphens when constructing the URL of the <em>create content</em> page. This name must be unique.'),
+    );
+  }
+  else {
+    $form['identity']['type'] = array(
+      '#type' => 'value',
+      '#value' => $type->type,
+    );
+    $form['identity']['type_display'] = array(
+      '#title' => t('Type'),
+      '#type' => 'item',
+      '#value' => theme('placeholder', $type->type),
+      '#description' => t('The machine-readable name of this content type. This field cannot be modified for system-defined content types.'),
+    );
+  }
+
+  $form['identity']['description'] = array(
+    '#title' => t('Description'),
+    '#type' => 'textarea',
+    '#default_value' => $type->description,
+    '#description' => t('A brief description of this content type. This text will be displayed as part of the list on the <em>create content</em> page.'),
+    );
+
+  $form['submission'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Submission form settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['submission']['title_label'] = array(
+    '#title' => t('Title field label'),
+    '#type' => 'textfield',
+    '#default_value' => $type->title_label,
+    '#required' => TRUE,
+  );
+  if (!$type->has_title) {
+    // Avoid overwriting a content type that intentionally does not have a
+    // title field.
+    $form['submission']['title_label']['#attributes'] = array('disabled' => 'disabled');
+    $form['submission']['title_label']['#description'] = t('This content type does not have a title field.');
+    $form['submission']['title_label']['#required'] = FALSE;
+  }
+  $form['submission']['body_label'] = array(
+    '#title' => t('Body field label'),
+    '#type' => 'textfield',
+    '#default_value' => isset($type->body_label) ? $type->body_label : '',
+    '#description' => t('To omit the body field for this content type, remove any text and leave this field blank.'),
+  );
+  $form['submission']['min_word_count'] = array(
+    '#type' => 'select',
+    '#title' => t('Minimum number of words'),
+    '#default_value' => $type->min_word_count,
+    '#options' => drupal_map_assoc(array(0, 10, 25, 50, 75, 100, 125, 150, 175, 200)),
+    '#description' => t('The minimum number of words for the body field to be considered valid for this content type. This can be useful to rule out submissions that do not meet the site\'s standards, such as short test posts.')
+  );
+  $form['submission']['help']  = array(
+    '#type' => 'textarea',
+    '#title' => t('Explanation or submission guidelines'),
+    '#default_value' => $type->help,
+    '#description' => t('This text will be displayed at the top of the submission form for this content type. It is useful for helping or instructing your users.')
+  );
+  $form['workflow'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Workflow settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['workflow']['node_options'] = array('#type' => 'checkboxes',
+    '#title' => t('Default options'),
+    '#default_value' => variable_get('node_options_'. $type->type, array('status', 'promote')),
+    '#options' => array(
+      'status' => t('Published'),
+      'promote' => t('Promoted to front page'),
+      'sticky' => t('Sticky at top of lists'),
+      'revision' => t('Create new revision'),
+    ),
+    '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
+  );
+
+  $form['old_type'] = array(
+    '#type' => 'value',
+    '#value' => $type->type,
+  );
+  $form['orig_type'] = array(
+    '#type' => 'value',
+    '#value' => isset($type->orig_type) ? $type->orig_type : '',
+  );
+  $form['module'] = array(
+    '#type' => 'value',
+    '#value' => $type->module,
+  );
+  $form['custom'] = array(
+    '#type' => 'value',
+    '#value' => $type->custom,
+  );
+  $form['modified'] = array(
+    '#type' => 'value',
+    '#value' => $type->modified,
+  );
+  $form['locked'] = array(
+    '#type' => 'value',
+    '#value' => $type->locked,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save content type'),
+    '#weight' => 40,
+  );
+
+  if ($type->custom) {
+    if (!empty($type->type)) {
+      $form['delete'] = array(
+        '#type' => 'submit',
+        '#value' => t('Delete content type'),
+        '#weight' => 45,
+      );
+    }
+  }
+  else {
+    $form['reset'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset to defaults'),
+      '#weight' => 50,
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Implementation of hook_form_validate().
+ */
+function node_type_form_validate($form, &$form_state) {
+  $type = new stdClass();
+  $type->type = trim($form_state['values']['type']);
+  $type->name = trim($form_state['values']['name']);
+
+  // Work out what the type was before the user submitted this form
+  $old_type = trim($form_state['values']['old_type']);
+
+  $types = node_get_types('names');
+
+  if (!$form_state['values']['locked']) {
+    if (isset($types[$type->type]) && $type->type != $old_type) {
+      form_set_error('type', t('The machine-readable name %type is already taken.', array('%type' => $type->type)));
+    }
+    if (!preg_match('!^[a-z0-9_]+$!', $type->type)) {
+      form_set_error('type', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+    }
+    // 'theme' conflicts with theme_node_form().
+    // '0' is invalid, since elsewhere we check it using empty().
+    if (in_array($type->type, array('0', 'theme'))) {
+      form_set_error('type', t("Invalid machine-readable name. Please enter a name other than %invalid.", array('%invalid' => $type->type)));
+    }
+  }
+
+  $names = array_flip($types);
+
+  if (isset($names[$type->name]) && $names[$type->name] != $old_type) {
+    form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name)));
+  }
+}
+
+/**
+ * Implementation of hook_form_submit().
+ */
+function node_type_form_submit($form, &$form_state) {
+  $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
+
+  $type = new stdClass();
+
+  $type->type = trim($form_state['values']['type']);
+  $type->name = trim($form_state['values']['name']);
+  $type->orig_type = trim($form_state['values']['orig_type']);
+  $type->old_type = isset($form_state['values']['old_type']) ? $form_state['values']['old_type'] : $type->type;
+
+  $type->description = $form_state['values']['description'];
+  $type->help = $form_state['values']['help'];
+  $type->min_word_count = $form_state['values']['min_word_count'];
+  $type->title_label = $form_state['values']['title_label'];
+  $type->body_label = $form_state['values']['body_label'];
+
+  // title_label is required in core; has_title will always be true, unless a
+  // module alters the title field.
+  $type->has_title = ($type->title_label != '');
+  $type->has_body = ($type->body_label != '');
+
+  $type->module = !empty($form_state['values']['module']) ? $form_state['values']['module'] : 'node';
+  $type->custom = $form_state['values']['custom'];
+  $type->modified = TRUE;
+  $type->locked = $form_state['values']['locked'];
+
+  if ($op == t('Reset to defaults')) {
+    node_type_reset($type);
+  }
+  elseif ($op == t('Delete content type')) {
+    $form_state['redirect'] = 'admin/content/node-type/'. str_replace('_', '-', $type->old_type) .'/delete';
+    return;
+  }
+
+  $status = node_type_save($type);
+
+  $variables = $form_state['values'];
+
+  // Remove everything that's been saved already - whatever's left is assumed
+  // to be a persistent variable.
+  foreach ($variables as $key => $value) {
+    if (isset($type->$key)) {
+      unset($variables[$key]);
+    }
+  }
+
+  unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id']);
+
+  // Save or reset persistent variable values.
+  foreach ($variables as $key => $value) {
+    $variable_new = $key .'_'. $type->type;
+    $variable_old = $key .'_'. $type->old_type;
+
+    if ($op == t('Reset to defaults')) {
+      variable_del($variable_old);
+    }
+    else {
+      if (is_array($value)) {
+        $value = array_keys(array_filter($value));
+      }
+      variable_set($variable_new, $value);
+
+      if ($variable_new != $variable_old) {
+        variable_del($variable_old);
+      }
+    }
+  }
+
+  node_types_rebuild();
+  menu_rebuild();
+  $t_args = array('%name' => $type->name);
+
+  if ($op == t('Reset to defaults')) {
+    drupal_set_message(t('The content type %name has been reset to its default values.', $t_args));
+    return;
+  }
+
+  if ($status == SAVED_UPDATED) {
+    drupal_set_message(t('The content type %name has been updated.', $t_args));
+  }
+  elseif ($status == SAVED_NEW) {
+    drupal_set_message(t('The content type %name has been added.', $t_args));
+    watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/content/types'));
+  }
+
+  $form_state['redirect'] = 'admin/content/types';
+  return;
+}
+
+/**
+ * Implementation of hook_node_type().
+ */
+function node_node_type($op, $info) {
+  if ($op != 'delete' && !empty($info->old_type) && $info->old_type != $info->type) {
+    $update_count = node_type_update_nodes($info->old_type, $info->type);
+
+    if ($update_count) {
+      drupal_set_message(format_plural($update_count, 'Changed the content type of 1 post from %old-type to %type.', 'Changed the content type of @count posts from %old-type to %type.', array('%old-type' => $info->old_type, '%type' => $info->type)));
+    }
+  }
+}
+
+/**
+ * Resets all of the relevant fields of a module-defined node type to their
+ * default values.
+ *
+ * @param &$type
+ *   The node type to reset. The node type is passed back by reference with its
+ *   resetted values. If there is no module-defined info for this node type,
+ *   then nothing happens.
+ */
+function node_type_reset(&$type) {
+  $info_array = module_invoke_all('node_info');
+  if (isset($info_array[$type->orig_type])) {
+    $info_array[$type->orig_type]['type'] = $type->orig_type; 
+    $info = _node_type_set_defaults($info_array[$type->orig_type]);
+
+    foreach ($info as $field => $value) {
+      $type->$field = $value;
+    }
+  }
+}
+
+/**
+ * Menu callback; delete a single content type.
+ */
+function node_type_delete_confirm(&$form_state, $type) {
+  $form['type'] = array('#type' => 'value', '#value' => $type->type);
+  $form['name'] = array('#type' => 'value', '#value' => $type->name);
+
+  $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name));
+  $caption = '';
+
+  $num_nodes = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = '%s'", $type->type));
+  if ($num_nodes) {
+    $caption .= '<p>'. format_plural($num_nodes, '<strong>Warning:</strong> there is currently 1 %type post on your site. It may not be able to be displayed or edited correctly, once you have removed this content type.', '<strong>Warning:</strong> there are currently @count %type posts on your site. They may not be able to be displayed or edited correctly, once you have removed this content type.', array('%type' => $type->name)) .'</p>';
+  }
+
+  $caption .= '<p>'. t('This action cannot be undone.') .'</p>';
+
+  return confirm_form($form, $message, 'admin/content/types', $caption, t('Delete'));
+}
+
+/**
+ * Process content type delete confirm submissions.
+ */
+function node_type_delete_confirm_submit($form, &$form_state) {
+  node_type_delete($form_state['values']['type']);
+
+  $t_args = array('%name' => $form_state['values']['name']);
+  drupal_set_message(t('The content type %name has been deleted.', $t_args));
+  watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
+
+  node_types_rebuild();
+  menu_rebuild();
+
+  $form_state['redirect'] = 'admin/content/types';
+  return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+/* $Id: node-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */
+
+#node-admin-buttons {
+  float: right;
+  margin-left: 0;
+  margin-right: 0.5em;
+  clear: left;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,634 @@
+<?php
+// $Id: node.admin.inc,v 1.19 2008/02/03 19:39:52 goba Exp $
+
+/**
+ * @file
+ * Content administration and module settings UI.
+ */
+
+/**
+ * Menu callback; presents general node configuration options.
+ */
+function node_configure() {
+  // Only show rebuild button if there is 0 or more than 2 rows in node_access table,
+  // or if there are modules that implement node_grant.
+  if (db_result(db_query('SELECT COUNT(*) FROM {node_access}')) != 1 || count(module_implements('node_grants')) > 0) {
+    $status = '<p>'. t('If the site is experiencing problems with permissions to content, you may have to rebuild the permissions cache. Possible causes for permission problems are disabling modules or configuration changes to permissions. Rebuilding will remove all privileges to posts, and replace them with permissions based on the current modules and settings.') .'</p>';
+    $status .= '<p>'. t('Rebuilding may take some time if there is a lot of content or complex permission settings. After rebuilding has completed posts will automatically use the new permissions.') .'</p>';
+
+    $form['access'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Node access status'),
+    );
+    $form['access']['status'] = array('#value' => $status);
+    $form['access']['rebuild'] = array(
+      '#type' => 'submit',
+      '#value' => t('Rebuild permissions'),
+    );
+  }
+
+  $form['default_nodes_main'] = array(
+    '#type' => 'select', '#title' => t('Number of posts on main page'), '#default_value' => variable_get('default_nodes_main', 10),
+    '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
+    '#description' => t('The default maximum number of posts to display per page on overview pages such as the main page.')
+  );
+  $form['teaser_length'] = array(
+    '#type' => 'select', '#title' => t('Length of trimmed posts'), '#default_value' => variable_get('teaser_length', 600),
+    '#options' => 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'),
+    ),
+    '#description' => 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'. Note that this setting will only affect new or updated content and will not affect existing teasers.")
+  );
+
+  $form['node_preview'] = array(
+    '#type' => 'radios',
+    '#title' => t('Preview post'),
+    '#default_value' => variable_get('node_preview', 0),
+    '#options' => array(t('Optional'), t('Required')),
+    '#description' => t('Must users preview posts before submitting?'),
+  );
+
+  $form['#validate'] = array('node_configure_validate');
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form validate callback.
+ */
+function node_configure_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Rebuild permissions')) {
+    drupal_goto('admin/content/node-settings/rebuild');
+  }
+}
+
+/**
+ * Menu callback: confirm rebuilding of permissions.
+ */
+function node_configure_rebuild_confirm() {
+  return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'),
+                  'admin/content/node-settings', t('This action rebuilds all permissions on site content, and may be a lengthy process. This action cannot be undone.'), t('Rebuild permissions'), t('Cancel'));
+}
+
+/**
+ * Handler for wipe confirmation
+ */
+function node_configure_rebuild_confirm_submit($form, &$form_state) {
+  node_access_rebuild(TRUE);
+  $form_state['redirect'] = 'admin/content/node-settings';
+  return;
+}
+
+/**
+ * Implementation of hook_node_operations().
+ */
+function node_node_operations() {
+  $operations = array(
+    'publish' => array(
+      'label' => t('Publish'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('status' => 1)),
+    ),
+    'unpublish' => array(
+      'label' => t('Unpublish'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('status' => 0)),
+    ),
+    'promote' => array(
+      'label' => t('Promote to front page'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('status' => 1, 'promote' => 1)),
+    ),
+    'demote' => array(
+      'label' => t('Demote from front page'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('promote' => 0)),
+    ),
+    'sticky' => array(
+      'label' => t('Make sticky'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('status' => 1, 'sticky' => 1)),
+    ),
+    'unsticky' => array(
+      'label' => t('Remove stickiness'),
+      'callback' => 'node_mass_update',
+      'callback arguments' => array('updates' => array('sticky' => 0)),
+    ),
+    'delete' => array(
+      'label' => t('Delete'),
+      'callback' => NULL,
+    ),
+  );
+  return $operations;
+}
+
+/**
+ * List node administration filters that can be applied.
+ */
+function node_filters() {
+  // Regular filters
+  $filters['status'] = array(
+    'title' => t('status'),
+    'options' => array(
+      'status-1' => t('published'),
+      'status-0' => t('not published'),
+      'promote-1' => t('promoted'),
+      'promote-0' => t('not promoted'),
+      'sticky-1' => t('sticky'),
+      'sticky-0' => t('not sticky'),
+    ),
+  );
+  // Include translation states if we have this module enabled
+  if (module_exists('translation')) {
+    $filters['status']['options'] += array(
+      'translate-0' => t('Up to date translation'),
+      'translate-1' => t('Outdated translation'),
+    );
+  }
+
+  $filters['type'] = array('title' => t('type'), 'options' => node_get_types('names'));
+
+  // The taxonomy filter
+  if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
+    $filters['category'] = array('title' => t('category'), 'options' => $taxonomy);
+  }
+  // Language filter if there is a list of languages
+  if ($languages = module_invoke('locale', 'language_list')) {
+    $languages = array('' => t('Language neutral')) + $languages;
+    $filters['language'] = array('title' => t('language'), 'options' => $languages);
+  }
+  return $filters;
+}
+
+/**
+ * Build query for node administration filters based on session.
+ */
+function node_build_filter_query() {
+  $filters = node_filters();
+
+  // Build query
+  $where = $args = array();
+  $join = '';
+  foreach ($_SESSION['node_overview_filter'] as $index => $filter) {
+    list($key, $value) = $filter;
+    switch ($key) {
+      case 'status':
+        // Note: no exploitable hole as $key/$value have already been checked when submitted
+        list($key, $value) = explode('-', $value, 2);
+        $where[] = 'n.'. $key .' = %d';
+        break;
+      case 'category':
+        $table = "tn$index";
+        $where[] = "$table.tid = %d";
+        $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
+        break;
+      case 'type':
+        $where[] = "n.type = '%s'";
+        break;
+      case 'language':
+        $where[] = "n.language = '%s'";
+        break;
+    }
+    $args[] = $value;
+  }
+  $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
+
+  return array('where' => $where, 'join' => $join, 'args' => $args);
+}
+
+/**
+ * Return form for node administration filters.
+ */
+function node_filter_form() {
+  $session = &$_SESSION['node_overview_filter'];
+  $session = is_array($session) ? $session : array();
+  $filters = node_filters();
+
+  $i = 0;
+  $form['filters'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Show only items where'),
+    '#theme' => 'node_filters',
+  );
+  $form['#submit'][] = 'node_filter_form_submit';
+  foreach ($session as $filter) {
+    list($type, $value) = $filter;
+    if ($type == 'category') {
+      // Load term name from DB rather than search and parse options array.
+      $value = module_invoke('taxonomy', 'get_term', $value);
+      $value = $value->name;
+    }
+    else if ($type == 'language') {
+      $value = empty($value) ? t('Language neutral') : module_invoke('locale', 'language_name', $value);
+    }
+    else {
+      $value = $filters[$type]['options'][$value];
+    }
+    if ($i++) {
+      $form['filters']['current'][] = array('#value' => t('<em>and</em> where <strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
+    }
+    else {
+      $form['filters']['current'][] = array('#value' => t('<strong>%a</strong> is <strong>%b</strong>', array('%a' => $filters[$type]['title'], '%b' => $value)));
+    }
+    if (in_array($type, array('type', 'language'))) {
+      // Remove the option if it is already being filtered on.
+      unset($filters[$type]);
+    }
+  }
+
+  foreach ($filters as $key => $filter) {
+    $names[$key] = $filter['title'];
+    $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
+  }
+
+  $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
+  $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
+  if (count($session)) {
+    $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
+    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
+  }
+
+  drupal_add_js('misc/form.js', 'core');
+
+  return $form;
+}
+
+/**
+ * Theme node administration filter form.
+ *
+ * @ingroup themeable
+ */
+function theme_node_filter_form($form) {
+  $output = '';
+  $output .= '<div id="node-admin-filter">';
+  $output .= drupal_render($form['filters']);
+  $output .= '</div>';
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Theme node administration filter selector.
+ *
+ * @ingroup themeable
+ */
+function theme_node_filters($form) {
+  $output = '';
+  $output .= '<ul class="clear-block">';
+  if (!empty($form['current'])) {
+    foreach (element_children($form['current']) as $key) {
+      $output .= '<li>'. drupal_render($form['current'][$key]) .'</li>';
+    }
+  }
+
+  $output .= '<li><dl class="multiselect">'. (!empty($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') .'<dd class="a">';
+  foreach (element_children($form['filter']) as $key) {
+    $output .= drupal_render($form['filter'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '<dt>'. t('is') .'</dt><dd class="b">';
+
+  foreach (element_children($form['status']) as $key) {
+    $output .= drupal_render($form['status'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '</dl>';
+  $output .= '<div class="container-inline" id="node-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
+  $output .= '</li></ul>';
+
+  return $output;
+}
+
+/**
+ * Process result from node administration filter form.
+ */
+function node_filter_form_submit($form, &$form_state) {
+  $filters = node_filters();
+  switch ($form_state['values']['op']) {
+    case t('Filter'):
+    case t('Refine'):
+      if (isset($form_state['values']['filter'])) {
+        $filter = $form_state['values']['filter'];
+
+        // Flatten the options array to accommodate hierarchical/nested options.
+        $flat_options = form_options_flatten($filters[$filter]['options']);
+
+        if (isset($flat_options[$form_state['values'][$filter]])) {
+          $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]);
+        }
+      }
+      break;
+    case t('Undo'):
+      array_pop($_SESSION['node_overview_filter']);
+      break;
+    case t('Reset'):
+      $_SESSION['node_overview_filter'] = array();
+      break;
+  }
+}
+
+/**
+ * Make mass update of nodes, changing all nodes in the $nodes array
+ * to update them with the field values in $updates.
+ *
+ * IMPORTANT NOTE: This function is intended to work when called
+ * from a form submit handler. Calling it outside of the form submission
+ * process may not work correctly.
+ *
+ * @param array $nodes
+ *   Array of node nids to update.
+ * @param array $updates
+ *   Array of key/value pairs with node field names and the
+ *   value to update that field to.
+ */
+function node_mass_update($nodes, $updates) {
+  // We use batch processing to prevent timeout when updating a large number
+  // of nodes.
+  if (count($nodes) > 10) {
+    $batch = array(
+      'operations' => array(
+        array('_node_mass_update_batch_process', array($nodes, $updates))
+      ),
+      'finished' => '_node_mass_update_batch_finished',
+      'title' => t('Processing'),
+      // We use a single multi-pass operation, so the default
+      // 'Remaining x of y operations' message will be confusing here.
+      'progress_message' => '',
+      'error_message' => t('The update has encountered an error.'),
+      // The operations do not live in the .module file, so we need to
+      // tell the batch engine which file to load before calling them.
+      'file' => drupal_get_path('module', 'node') .'/node.admin.inc',
+    );
+    batch_set($batch);
+  }
+  else {
+    foreach ($nodes as $nid) {
+      _node_mass_update_helper($nid, $updates);
+    }
+    drupal_set_message(t('The update has been performed.'));
+  }
+}
+
+/**
+ * Node Mass Update - helper function.
+ */
+function _node_mass_update_helper($nid, $updates) {
+  $node = node_load($nid, NULL, TRUE);
+  foreach ($updates as $name => $value) {
+    $node->$name = $value;
+  }
+  node_save($node);
+  return $node;
+}
+
+/**
+ * Node Mass Update Batch operation
+ */
+function _node_mass_update_batch_process($nodes, $updates, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max'] = count($nodes);
+    $context['sandbox']['nodes'] = $nodes;
+  }
+
+  // Process nodes by groups of 5.
+  $count = min(5, count($context['sandbox']['nodes']));
+  for ($i = 1; $i <= $count; $i++) {
+    // For each nid, load the node, reset the values, and save it.
+    $nid = array_shift($context['sandbox']['nodes']);
+    $node = _node_mass_update_helper($nid, $updates);
+
+    // Store result for post-processing in the finished callback.
+    $context['results'][] = l($node->title, 'node/'. $node->nid);
+
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Node Mass Update Batch 'finished' callback.
+ */
+function _node_mass_update_batch_finished($success, $results, $operations) {
+  if ($success) {
+    drupal_set_message(t('The update has been performed.'));
+  }
+  else {
+    drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
+    $message = format_plural(count($results), '1 item successfully processed:', '@count items successfully processed:');
+    $message .= theme('item_list', $results);
+    drupal_set_message($message);
+  }
+}
+
+/**
+ * Menu callback: content administration.
+ */
+function node_admin_content($form_state) {
+  if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
+    return node_multiple_delete_confirm($form_state, array_filter($form_state['values']['nodes']));
+  }
+  $form = node_filter_form();
+
+  $form['#theme'] = 'node_filter_form';
+  $form['admin']  = node_admin_nodes();
+
+  return $form;
+}
+
+/**
+ * Form builder: Builds the node administration overview.
+ */
+function node_admin_nodes() {
+
+  $filter = node_build_filter_query();
+
+  $result = pager_query(db_rewrite_sql('SELECT n.*, u.name FROM {node} n '. $filter['join'] .' INNER JOIN {users} u ON n.uid = u.uid '. $filter['where'] .' ORDER BY n.changed DESC'), 50, 0, NULL, $filter['args']);
+
+  // Enable language column if locale is enabled or if we have any node with language
+  $count = db_result(db_query("SELECT COUNT(*) FROM {node} n WHERE language != ''"));
+  $multilanguage = (module_exists('locale') || $count);
+
+  $form['options'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $options = array();
+  foreach (module_invoke_all('node_operations') as $operation => $array) {
+    $options[$operation] = $array['label'];
+  }
+  $form['options']['operation'] = array(
+    '#type' => 'select',
+    '#options' => $options,
+    '#default_value' => 'approve',
+  );
+  $form['options']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update'),
+    '#submit' => array('node_admin_nodes_submit'),
+  );
+
+  $languages = language_list();
+  $destination = drupal_get_destination();
+  $nodes = array();
+  while ($node = db_fetch_object($result)) {
+    $nodes[$node->nid] = '';
+    $options = empty($node->language) ? array() : array('language' => $languages[$node->language]);
+    $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid, $options) .' '. theme('mark', node_mark($node->nid, $node->changed)));
+    $form['name'][$node->nid] =  array('#value' => check_plain(node_get_types('name', $node)));
+    $form['username'][$node->nid] = array('#value' => theme('username', $node));
+    $form['status'][$node->nid] =  array('#value' => ($node->status ? t('published') : t('not published')));
+    if ($multilanguage) {
+      $form['language'][$node->nid] = array('#value' => empty($node->language) ? t('Language neutral') : t($languages[$node->language]->name));
+    }
+    $form['operations'][$node->nid] = array('#value' => l(t('edit'), 'node/'. $node->nid .'/edit', array('query' => $destination)));
+  }
+  $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
+  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+  $form['#theme'] = 'node_admin_nodes';
+  return $form;
+}
+
+/**
+ * Validate node_admin_nodes form submissions.
+ * 
+ * Check if any nodes have been selected to perform the chosen
+ * 'Update option' on.
+ */
+function node_admin_nodes_validate($form, &$form_state) {
+  $nodes = array_filter($form_state['values']['nodes']);
+  if (count($nodes) == 0) {
+    form_set_error('', t('No items selected.'));
+  }
+}
+
+/**
+ * Process node_admin_nodes form submissions.
+ * 
+ * Execute the chosen 'Update option' on the selected nodes.
+ */
+function node_admin_nodes_submit($form, &$form_state) {
+  $operations = module_invoke_all('node_operations');
+  $operation = $operations[$form_state['values']['operation']];
+  // Filter out unchecked nodes
+  $nodes = array_filter($form_state['values']['nodes']);
+  if ($function = $operation['callback']) {
+    // Add in callback arguments if present.
+    if (isset($operation['callback arguments'])) {
+      $args = array_merge(array($nodes), $operation['callback arguments']);
+    }
+    else {
+      $args = array($nodes);
+    }
+    call_user_func_array($function, $args);
+
+    cache_clear_all();
+  }
+  else {
+    // We need to rebuild the form to go to a second step.  For example, to
+    // show the confirmation form for the deletion of nodes.
+    $form_state['rebuild'] = TRUE;
+  }
+}
+
+
+/**
+ * Theme node administration overview.
+ *
+ * @ingroup themeable
+ */
+function theme_node_admin_nodes($form) {
+  // If there are rows in this form, then $form['title'] contains a list of
+  // the title form elements.
+  $has_posts = isset($form['title']) && is_array($form['title']);
+  $select_header = $has_posts ? theme('table_select_header_cell') : '';
+  $header = array($select_header, t('Title'), t('Type'), t('Author'), t('Status'));
+  if (isset($form['language'])) {
+    $header[] = t('Language');
+  }
+  $header[] = t('Operations');
+  $output = '';
+
+  $output .= drupal_render($form['options']);
+  if ($has_posts) {
+    foreach (element_children($form['title']) as $key) {
+      $row = array();
+      $row[] = drupal_render($form['nodes'][$key]);
+      $row[] = drupal_render($form['title'][$key]);
+      $row[] = drupal_render($form['name'][$key]);
+      $row[] = drupal_render($form['username'][$key]);
+      $row[] = drupal_render($form['status'][$key]);
+      if (isset($form['language'])) {
+        $row[] = drupal_render($form['language'][$key]);
+      }
+      $row[] = drupal_render($form['operations'][$key]);
+      $rows[] = $row;
+    }
+
+  }
+  else {
+    $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
+  }
+
+  $output .= theme('table', $header, $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+function node_multiple_delete_confirm(&$form_state, $nodes) {
+
+  $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
+  // array_filter returns only elements with TRUE values
+  foreach ($nodes as $nid => $value) {
+    $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
+    $form['nodes'][$nid] = array(
+      '#type' => 'hidden',
+      '#value' => $nid,
+      '#prefix' => '<li>',
+      '#suffix' => check_plain($title) ."</li>\n",
+    );
+  }
+  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+  $form['#submit'][] = 'node_multiple_delete_confirm_submit';
+  return confirm_form($form,
+                      t('Are you sure you want to delete these items?'),
+                      'admin/content/node', t('This action cannot be undone.'),
+                      t('Delete all'), t('Cancel'));
+}
+
+function node_multiple_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    foreach ($form_state['values']['nodes'] as $nid => $value) {
+      node_delete($nid);
+    }
+    drupal_set_message(t('The items have been deleted.'));
+  }
+  $form_state['redirect'] = 'admin/content/node';
+  return;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,44 @@
+/* $Id: node.css,v 1.5 2008/01/25 21:21:44 goba Exp $ */
+
+.node-unpublished {
+  background-color: #fff4f4;
+}
+.preview .node {
+  background-color: #ffffea;
+}
+#node-admin-filter ul {
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+  width: 100%;
+}
+#node-admin-buttons {
+  float: left; /* LTR */
+  margin-left: 0.5em; /* LTR */
+  clear: right; /* LTR */
+}
+td.revision-current {
+  background: #ffc;
+}
+.node-form .form-text {
+  display: block;
+  width: 95%;
+}
+.node-form .container-inline .form-text {
+  display: inline;
+  width: auto;
+}
+.node-form .standard {
+  clear: both;
+}
+.node-form textarea {
+  display: block;
+  width: 95%;
+}
+.node-form .attachments fieldset {
+  float: none;
+  display: block;
+}
+.terms-inline {
+  display: inline;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: node.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Node
+description = Allows content to be submitted to the site and displayed on pages.
+package = Core - required
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,335 @@
+<?php
+// $Id: node.install,v 1.4 2007/12/18 12:59:21 dries Exp $
+
+/**
+ * Implementation of hook_schema().
+ */
+function node_schema() {
+  $schema['node'] = array(
+    'description' => t('The base table for nodes.'),
+    'fields' => array(
+      'nid' => array(
+        'description' => t('The primary identifier for a node.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'vid' => array(
+        'description' => t('The current {node_revisions}.vid version identifier.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'type' => array(
+        'description' => t('The {node_type}.type of this node.'),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => ''),
+      'language' => array(
+        'description' => t('The {languages}.language of this node.'),
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => ''),
+      'title' => array(
+        'description' => t('The title of this node, always treated a non-markup plain text.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'uid' => array(
+        'description' => t('The {users}.uid that owns this node; initially, this is the user that created it.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'status' => array(
+        'description' => t('Boolean indicating whether the node is published (visible to non-administrators).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1),
+      'created' => array(
+        'description' => t('The Unix timestamp when the node was created.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'changed' => array(
+        'description' => t('The Unix timestamp when the node was most recently saved.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'comment' => array(
+        'description' => t('Whether comments are allowed on this node: 0 = no, 1 = read only, 2 = read/write.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'promote' => array(
+        'description' => t('Boolean indicating whether the node should displayed on the front page.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'moderate' => array(
+        'description' => t('Previously, a boolean indicating whether the node was "in moderation"; mostly no longer used.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'sticky' => array(
+        'description' => t('Boolean indicating whether the node should be displayed at the top of lists in which it appears.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'tnid' => array(
+        'description' => t('The translation set id for this node, which equals the node id of the source post in each set.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'translate' => array(
+        'description' => t('A boolean indicating whether this translation page needs to be updated.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      ),
+    'indexes' => array(
+      'node_changed'        => array('changed'),
+      'node_created'        => array('created'),
+      'node_moderate'       => array('moderate'),
+      'node_promote_status' => array('promote', 'status'),
+      'node_status_type'    => array('status', 'type', 'nid'),
+      'node_title_type'     => array('title', array('type', 4)),
+      'node_type'           => array(array('type', 4)),
+      'uid'                 => array('uid'),
+      'tnid'                => array('tnid'),
+      'translate'           => array('translate'),
+      ),
+    'unique keys' => array(
+      'vid'     => array('vid')
+      ),
+    'primary key' => array('nid'),
+    );
+
+  $schema['node_access'] = array(
+    'description' => t('Identifies which realm/grant pairs a user must possess in order to view, update, or delete specific nodes.'),
+    'fields' => array(
+      'nid' => array(
+        'description' => t('The {node}.nid this record affects.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'gid' => array(
+        'description' => t("The grant ID a user must possess in the specified realm to gain this row's privileges on the node."),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'realm' => array(
+        'description' => t('The realm in which the user must possess the grant ID. Each node access node can define one or more realms.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'grant_view' => array(
+        'description' => t('Boolean indicating whether a user with the realm/grant pair can view this node.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'grant_update' => array(
+        'description' => t('Boolean indicating whether a user with the realm/grant pair can edit this node.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'grant_delete' => array(
+        'description' => t('Boolean indicating whether a user with the realm/grant pair can delete this node.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny')
+      ),
+    'primary key' => array('nid', 'gid', 'realm'),
+    );
+
+  $schema['node_counter'] = array(
+    'description' => t('Access statistics for {node}s.'),
+    'fields' => array(
+      'nid' => array(
+        'description' => t('The {node}.nid for these statistics.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'totalcount' => array(
+        'description' => t('The total number of times the {node} has been viewed.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'big'),
+      'daycount' => array(
+        'description' => t('The total number of times the {node} has been viewed today.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'medium'),
+      'timestamp' => array(
+        'description' => t('The most recent time the {node} has been viewed.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0)
+      ),
+    'primary key' => array('nid'),
+    );
+
+  $schema['node_revisions'] = array(
+    'description' => t('Stores information about each saved version of a {node}.'),
+    'fields' => array(
+      'nid' => array(
+        'description' => t('The {node} this version belongs to.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'vid' => array(
+        'description' => t('The primary identifier for this version.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'uid' => array(
+        'description' => t('The {users}.uid that created this version.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'title' => array(
+        'description' => t('The title of this version.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'body' => array(
+        'description' => t('The body of this version.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big'),
+      'teaser' => array(
+        'description' => t('The teaser of this version.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big'),
+      'log' => array(
+        'description' => t('The log entry explaining the changes in this version.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big'),
+      'timestamp' => array(
+        'description' => t('A Unix timestamp indicating when this version was created.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'format' => array(
+        'description' => t("The input format used by this version's body."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0)
+      ),
+    'indexes' => array(
+      'nid' => array('nid'),
+      'uid' => array('uid')
+      ),
+    'primary key' => array('vid'),
+    );
+
+  $schema['node_type'] = array(
+    'description' => t('Stores information about all defined {node} types.'),
+    'fields' => array(
+      'type' => array(
+        'description' => t('The machine-readable name of this type.'),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE),
+      'name' => array(
+        'description' => t('The human-readable name of this type.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'module' => array(
+        'description' => t('The module that implements this type.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE),
+      'description'    => array(
+        'description' => t('A brief description of this type.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'medium'),
+      'help' => array(
+        'description' => t('Help information shown to the user when creating a {node} of this type.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'medium'),
+      'has_title' => array(
+        'description' => t('Boolean indicating whether this type uses the {node}.title field.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'size' => 'tiny'),
+      'title_label' => array(
+        'description' => t('The label displayed for the title field on the edit form.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'has_body' => array(
+        'description' => t('Boolean indicating whether this type uses the {node_revisions}.body field.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'size' => 'tiny'),
+      'body_label' => array(
+        'description' => t('The label displayed for the body field on the edit form.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'min_word_count' => array(
+        'description' => t('The minimum number of words the body must contain.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'size' => 'small'),
+      'custom' => array(
+        'description' => t('A boolean indicating whether this type is defined by a module (FALSE) or by a user via a module like the Content Construction Kit (TRUE).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'modified' => array(
+        'description' => t('A boolean indicating whether this type has been modified by an administrator; currently not used in any way.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'locked' => array(
+        'description' => t('A boolean indicating whether the administrator can change the machine name of this type.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'orig_type' => array(
+        'description' => t('The original machine-readable name of this node type. This may be different from the current type name if the locked field is 0.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '')
+      ),
+    'primary key' => array('type'),
+    );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2733 @@
+<?php
+// $Id: node.module,v 1.947.2.2 2008/02/13 14:10:22 goba Exp $
+
+/**
+ * @file
+ * The core that allows content to be submitted to the site. Modules and scripts may
+ * programmatically submit nodes using the usual form API pattern.
+ */
+
+define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60);
+
+define('NODE_BUILD_NORMAL', 0);
+define('NODE_BUILD_PREVIEW', 1);
+define('NODE_BUILD_SEARCH_INDEX', 2);
+define('NODE_BUILD_SEARCH_RESULT', 3);
+define('NODE_BUILD_RSS', 4);
+define('NODE_BUILD_PRINT', 5);
+
+/**
+ * Implementation of hook_help().
+ */
+function node_help($path, $arg) {
+  // Remind site administrators about the {node_access} table being flagged
+  // for rebuild. We don't need to issue the message on the confirm form, or
+  // while the rebuild is being processed.
+  if ($path != 'admin/content/node-settings/rebuild' && $path != 'batch' && strpos($path, '#') === FALSE
+      && user_access('access administration pages') && node_access_needs_rebuild()) {
+    if ($path == 'admin/content/node-settings') {
+      $message = t('The content access permissions need to be rebuilt.');
+    }
+    else {
+      $message = t('The content access permissions need to be rebuilt. Please visit <a href="@node_access_rebuild">this page</a>.', array('@node_access_rebuild' => url('admin/content/node-settings/rebuild')));
+    }
+    drupal_set_message($message, 'error');
+  }
+
+  switch ($path) {
+    case 'admin/help#node':
+      $output = '<p>'. t('The node module manages content on your site, and stores all posts (regardless of type) as a "node". In addition to basic publishing settings, including whether the post has been published, promoted to the site front page, or should remain present (or sticky) at the top of lists, the node module also records basic information about the author of a post. Optional revision control over edits is available. For additional functionality, the node module is often extended by other modules.') .'</p>';
+      $output .= '<p>'. t('Though each post on your site is a node, each post is also of a particular <a href="@content-type">content type</a>. <a href="@content-type">Content types</a> are used to define the characteristics of a post, including the title and description of the fields displayed on its add and edit pages. Each content type may have different default settings for <em>Publishing options</em> and other workflow controls. By default, the two content types in a standard Drupal installation are <em>Page</em> and <em>Story</em>. Use the <a href="@content-type">content types page</a> to add new or edit existing content types. Additional content types also become available as you enable additional core, contributed and custom modules.', array('@content-type' => url('admin/content/types'))) .'</p>';
+      $output .= '<p>'. t('The administrative <a href="@content">content page</a> allows you to review and manage your site content. The <a href="@post-settings">post settings page</a> sets certain options for the display of posts. The node module makes a number of permissions available for each content type, which may be set by role on the <a href="@permissions">permissions page</a>.', array('@content' => url('admin/content/node'), '@post-settings' => url('admin/content/node-settings'), '@permissions' => url('admin/user/permissions'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@node">Node module</a>.', array('@node' => 'http://drupal.org/handbook/modules/node/')) .'</p>';
+      return $output;
+    case 'admin/content/node':
+      return ' '; // Return a non-null value so that the 'more help' link is shown.
+    case 'admin/content/types':
+      return '<p>'. t('Below is a list of all the content types on your site. All posts that exist on your site are instances of one of these content types.') .'</p>';
+    case 'admin/content/types/add':
+      return '<p>'. t('To create a new content type, enter the human-readable name, the machine-readable name, and all other relevant fields that are on this page. Once created, users of your site will be able to create posts that are instances of this content type.') .'</p>';
+    case 'node/%/revisions':
+      return '<p>'. t('The revisions let you track differences between multiple versions of a post.') .'</p>';
+    case 'node/%/edit':
+      $node = node_load($arg[1]);
+      $type = node_get_types('type', $node->type);
+      return (!empty($type->help) ? '<p>'. filter_xss_admin($type->help) .'</p>' : '');
+  }
+
+  if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
+    $type = node_get_types('type', str_replace('-', '_', $arg[2]));
+    return (!empty($type->help) ? '<p>'. filter_xss_admin($type->help) .'</p>' : '');
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function node_theme() {
+  return array(
+    'node' => array(
+      'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE),
+      'template' => 'node',
+    ),
+    'node_list' => array(
+      'arguments' => array('items' => NULL, 'title' => NULL),
+    ),
+    'node_search_admin' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'node_filter_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.admin.inc',
+    ),
+    'node_filters' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.admin.inc',
+    ),
+    'node_admin_nodes' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.admin.inc',
+    ),
+    'node_add_list' => array(
+      'arguments' => array('content' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_preview' => array(
+      'arguments' => array('node' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_log_message' => array(
+      'arguments' => array('log' => NULL),
+    ),
+    'node_submitted' => array(
+      'arguments' => array('node' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function node_cron() {
+  db_query('DELETE FROM {history} WHERE timestamp < %d', NODE_NEW_LIMIT);
+}
+
+/**
+ * Gather a listing of links to nodes.
+ *
+ * @param $result
+ *   A DB result object from a query to fetch node objects. If your query
+ *   joins the <code>node_comment_statistics</code> table so that the
+ *   <code>comment_count</code> field is available, a title attribute will
+ *   be added to show the number of comments.
+ * @param $title
+ *   A heading for the resulting list.
+ *
+ * @return
+ *   An HTML list suitable as content for a block, or FALSE if no result can
+ *   fetch from DB result object.
+ */
+function node_title_list($result, $title = NULL) {
+  $items = array();
+  $num_rows = FALSE;
+  while ($node = db_fetch_object($result)) {
+    $items[] = l($node->title, 'node/'. $node->nid, !empty($node->comment_count) ? array('title' => format_plural($node->comment_count, '1 comment', '@count comments')) : array());
+    $num_rows = TRUE;
+  }
+
+  return $num_rows ? theme('node_list', $items, $title) : FALSE;
+}
+
+/**
+ * Format a listing of links to nodes.
+ *
+ * @ingroup themeable
+ */
+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) {
+    if (node_last_viewed($nid)) {
+      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;
+  static $history;
+
+  if (!isset($history[$nid])) {
+    $history[$nid] = db_fetch_object(db_query("SELECT timestamp FROM {history} WHERE uid = %d AND nid = %d", $user->uid, $nid));
+  }
+
+  return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0);
+}
+
+/**
+ * Decide on the type of marker to be displayed for 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.
+ * @return
+ *   One of the MARK constants.
+ */
+function node_mark($nid, $timestamp) {
+  global $user;
+  static $cache;
+
+  if (!$user->uid) {
+    return MARK_READ;
+  }
+  if (!isset($cache[$nid])) {
+    $cache[$nid] = node_last_viewed($nid);
+  }
+  if ($cache[$nid] == 0 && $timestamp > NODE_NEW_LIMIT) {
+    return MARK_NEW;
+  }
+  elseif ($timestamp > $cache[$nid] && $timestamp > NODE_NEW_LIMIT) {
+    return MARK_UPDATED;
+  }
+  return MARK_READ;
+}
+
+/**
+ * See if the user used JS to submit a teaser.
+ */
+function node_teaser_js(&$form, &$form_state) {
+  if (isset($form['#post']['teaser_js'])) {
+    // Glue the teaser to the body.
+    if (trim($form_state['values']['teaser_js'])) {
+      // Space the teaser from the body
+      $body = trim($form_state['values']['teaser_js']) ."\r\n<!--break-->\r\n". trim($form_state['values']['body']);
+    }
+    else {
+      // Empty teaser, no spaces.
+      $body = '<!--break-->'. $form_state['values']['body'];
+    }
+    // Pass updated body value on to preview/submit form processing.
+    form_set_value($form['body'], $body, $form_state);
+    // Pass updated body value back onto form for those cases
+    // in which the form is redisplayed.
+    $form['body']['#value'] = $body;
+  }
+  return $form;
+}
+
+/**
+ * Ensure value of "teaser_include" checkbox is consistent with other form data.
+ *
+ * This handles two situations in which an unchecked checkbox is rejected:
+ *
+ *   1. The user defines a teaser (summary) but it is empty;
+ *   2. The user does not define a teaser (summary) (in this case an
+ *      unchecked checkbox would cause the body to be empty, or missing
+ *      the auto-generated teaser).
+ *
+ * If JavaScript is active then it is used to force the checkbox to be
+ * checked when hidden, and so the second case will not arise.
+ *
+ * In either case a warning message is output.
+ */
+function node_teaser_include_verify(&$form, &$form_state) {
+  $message = '';
+
+  // $form['#post'] is set only when the form is built for preview/submit.
+  if (isset($form['#post']['body']) && isset($form_state['values']['teaser_include']) && !$form_state['values']['teaser_include']) {
+    // "teaser_include" checkbox is present and unchecked.
+    if (strpos($form_state['values']['body'], '<!--break-->') === 0) {
+      // Teaser is empty string.
+      $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting is ignored when the summary is empty.');
+    }
+    elseif (strpos($form_state['values']['body'], '<!--break-->') === FALSE) {
+      // Teaser delimiter is not present in the body.
+      $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting has been ignored since you have not defined a summary for the post. (To define a summary, insert the delimiter "&lt;!--break--&gt;" (without the quotes) in the Body of the post to indicate the end of the summary and the start of the main content.)');
+    }
+
+    if (!empty($message)) {
+      drupal_set_message($message, 'warning');
+      // Pass new checkbox value on to preview/submit form processing.
+      form_set_value($form['teaser_include'], 1, $form_state);
+      // Pass new checkbox value back onto form for those cases
+      // in which form is redisplayed.
+      $form['teaser_include']['#value'] = 1;
+    }
+  }
+
+  return $form;
+}
+
+/**
+ * Generate a teaser for a node body.
+ *
+ * If the end of the teaser is not indicated using the <!--break--> delimiter
+ * then we generate the teaser automatically, trying to end it at a sensible
+ * place such as the end of a paragraph, a line break, or the end of a
+ * sentence (in that order of preference).
+ *
+ * @param $body
+ *   The content for which a teaser will be generated.
+ * @param $format
+ *   The format of the content. If the content contains PHP code, we do not
+ *   split it up to prevent parse errors. If the line break filter is present
+ *   then we treat newlines embedded in $body as line breaks.
+ * @param $size
+ *   The desired character length of the teaser. If omitted, the default
+ *   value will be used. Ignored if the special delimiter is present
+ *   in $body.
+ * @return
+ *   The generated teaser.
+ */
+function node_teaser($body, $format = NULL, $size = NULL) {
+
+  if (!isset($size)) {
+    $size = variable_get('teaser_length', 600);
+  }
+
+  // Find where the delimiter is in the body
+  $delimiter = strpos($body, '<!--break-->');
+
+  // If the size is zero, and there is no delimiter, the entire body is the teaser.
+  if ($size == 0 && $delimiter === FALSE) {
+    return $body;
+  }
+
+  // If a valid delimiter has been specified, use it to chop off the teaser.
+  if ($delimiter !== FALSE) {
+    return substr($body, 0, $delimiter);
+  }
+
+  // We check for the presence of the PHP evaluator filter in the current
+  // format. If the body contains PHP code, we do not split it up to prevent
+  // parse errors.
+  if (isset($format)) {
+    $filters = filter_list_format($format);
+    if (isset($filters['php/0']) && strpos($body, '<?') !== FALSE) {
+      return $body;
+    }
+  }
+
+  // If we have a short body, the entire body is the teaser.
+  if (drupal_strlen($body) <= $size) {
+    return $body;
+  }
+
+  // If the delimiter has not been specified, try to split at paragraph or
+  // sentence boundaries.
+
+  // The teaser may not be longer than maximum length specified. Initial slice.
+  $teaser = truncate_utf8($body, $size);
+
+  // Store the actual length of the UTF8 string -- which might not be the same
+  // as $size.
+  $max_rpos = strlen($teaser);
+
+  // How much to cut off the end of the teaser so that it doesn't end in the
+  // middle of a paragraph, sentence, or word.
+  // Initialize it to maximum in order to find the minimum.
+  $min_rpos = $max_rpos;
+
+  // Store the reverse of the teaser.  We use strpos on the reversed needle and
+  // haystack for speed and convenience.
+  $reversed = strrev($teaser);
+
+  // Build an array of arrays of break points grouped by preference.
+  $break_points = array();
+
+  // A paragraph near the end of sliced teaser is most preferable.
+  $break_points[] = array('</p>' => 0);
+
+  // If no complete paragraph then treat line breaks as paragraphs.
+  $line_breaks = array('<br />' => 6, '<br>' => 4);
+  // Newline only indicates a line break if line break converter
+  // filter is present.
+  if (isset($filters['filter/1'])) {
+    $line_breaks["\n"] = 1;
+  }
+  $break_points[] = $line_breaks;
+
+  // If the first paragraph is too long, split at the end of a sentence.
+  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
+
+  // Iterate over the groups of break points until a break point is found.
+  foreach ($break_points as $points) {
+    // Look for each break point, starting at the end of the teaser.
+    foreach ($points as $point => $offset) {
+      // The teaser is already reversed, but the break point isn't.
+      $rpos = strpos($reversed, strrev($point));
+      if ($rpos !== FALSE) {
+        $min_rpos = min($rpos + $offset, $min_rpos);
+      }
+    }
+
+    // If a break point was found in this group, slice and return the teaser.
+    if ($min_rpos !== $max_rpos) {
+      // Don't slice with length 0.  Length must be <0 to slice from RHS.
+      return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
+    }
+  }
+
+  // If a break point was not found, still return a teaser.
+  return $teaser;
+}
+
+/**
+ * Builds a list of available node types, and returns all of part of this list
+ * in the specified format.
+ *
+ * @param $op
+ *   The format in which to return the list. When this is set to 'type',
+ *   'module', or 'name', only the specified node type is returned. When set to
+ *   'types' or 'names', all node types are returned.
+ * @param $node
+ *   A node object, array, or string that indicates the node type to return.
+ *   Leave at default value (NULL) to return a list of all node types.
+ * @param $reset
+ *   Whether or not to reset this function's internal cache (defaults to
+ *   FALSE).
+ *
+ * @return
+ *   Either an array of all available node types, or a single node type, in a
+ *   variable format. Returns FALSE if the node type is not found.
+ */
+function node_get_types($op = 'types', $node = NULL, $reset = FALSE) {
+  static $_node_types, $_node_names;
+
+  if ($reset || !isset($_node_types)) {
+    list($_node_types, $_node_names) = _node_types_build();
+  }
+
+  if ($node) {
+    if (is_array($node)) {
+      $type = $node['type'];
+    }
+    elseif (is_object($node)) {
+      $type = $node->type;
+    }
+    elseif (is_string($node)) {
+      $type = $node;
+    }
+    if (!isset($_node_types[$type])) {
+      return FALSE;
+    }
+  }
+  switch ($op) {
+    case 'types':
+      return $_node_types;
+    case 'type':
+      return isset($_node_types[$type]) ? $_node_types[$type] : FALSE;
+    case 'module':
+      return isset($_node_types[$type]->module) ? $_node_types[$type]->module : FALSE;
+    case 'names':
+      return $_node_names;
+    case 'name':
+      return isset($_node_names[$type]) ? $_node_names[$type] : FALSE;
+  }
+}
+
+/**
+ * Resets the database cache of node types, and saves all new or non-modified
+ * module-defined node types to the database.
+ */
+function node_types_rebuild() {
+  _node_types_build();
+
+  $node_types = node_get_types('types', NULL, TRUE);
+
+  foreach ($node_types as $type => $info) {
+    if (!empty($info->is_new)) {
+      node_type_save($info);
+    }
+    if (!empty($info->disabled)) {
+      node_type_delete($info->type);
+    }
+  }
+
+  _node_types_build();
+}
+
+/**
+ * Saves a node type to the database.
+ *
+ * @param $info
+ *   The node type to save, as an object.
+ *
+ * @return
+ *   Status flag indicating outcome of the operation.
+ */
+function node_type_save($info) {
+  $is_existing = FALSE;
+  $existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
+  $is_existing = db_result(db_query("SELECT COUNT(*) FROM {node_type} WHERE type = '%s'", $existing_type));
+  if (!isset($info->help)) {
+    $info->help = '';
+  }
+  if (!isset($info->min_word_count)) {
+    $info->min_word_count = 0;
+  }
+  if (!isset($info->body_label)) {
+    $info->body_label = '';
+  }
+
+  if ($is_existing) {
+    db_query("UPDATE {node_type} SET type = '%s', name = '%s', module = '%s', has_title = %d, title_label = '%s', has_body = %d, body_label = '%s', description = '%s', help = '%s', min_word_count = %d, custom = %d, modified = %d, locked = %d WHERE type = '%s'", $info->type, $info->name, $info->module, $info->has_title, $info->title_label, $info->has_body, $info->body_label, $info->description, $info->help, $info->min_word_count, $info->custom, $info->modified, $info->locked, $existing_type);
+
+    module_invoke_all('node_type', 'update', $info);
+    return SAVED_UPDATED;
+  }
+  else {
+    db_query("INSERT INTO {node_type} (type, name, module, has_title, title_label, has_body, body_label, description, help, min_word_count, custom, modified, locked, orig_type) VALUES ('%s', '%s', '%s', %d, '%s', %d, '%s', '%s', '%s', %d, %d, %d, %d, '%s')", $info->type, $info->name, $info->module, $info->has_title, $info->title_label, $info->has_body, $info->body_label, $info->description, $info->help, $info->min_word_count, $info->custom, $info->modified, $info->locked, $info->orig_type);
+
+    module_invoke_all('node_type', 'insert', $info);
+    return SAVED_NEW;
+  }
+}
+
+/**
+ * Deletes a node type from the database.
+ *
+ * @param $type
+ *   The machine-readable name of the node type to be deleted.
+ */
+function node_type_delete($type) {
+  $info = node_get_types('type', $type);
+  db_query("DELETE FROM {node_type} WHERE type = '%s'", $type);
+  module_invoke_all('node_type', 'delete', $info);
+}
+
+/**
+ * Updates all nodes of one type to be of another type.
+ *
+ * @param $old_type
+ *   The current node type of the nodes.
+ * @param $type
+ *   The new node type of the nodes.
+ *
+ * @return
+ *   The number of nodes whose node type field was modified.
+ */
+function node_type_update_nodes($old_type, $type) {
+  db_query("UPDATE {node} SET type = '%s' WHERE type = '%s'", $type, $old_type);
+  return db_affected_rows();
+}
+
+/**
+ * Builds and returns the list of available node types.
+ *
+ * The list of types is built by querying hook_node_info() in all modules, and
+ * by comparing this information with the node types in the {node_type} table.
+ *
+ */
+function _node_types_build() {
+  $_node_types = array();
+  $_node_names = array();
+
+  $info_array = module_invoke_all('node_info');
+  foreach ($info_array as $type => $info) {
+    $info['type'] = $type;
+    $_node_types[$type] = (object) _node_type_set_defaults($info);
+    $_node_names[$type] = $info['name'];
+  }
+
+  $type_result = db_query(db_rewrite_sql('SELECT nt.type, nt.* FROM {node_type} nt ORDER BY nt.type ASC', 'nt', 'type'));
+  while ($type_object = db_fetch_object($type_result)) {
+    // Check for node types from disabled modules and mark their types for removal.
+    // Types defined by the node module in the database (rather than by a separate
+    // module using hook_node_info) have a module value of 'node'.
+    if ($type_object->module != 'node' && empty($info_array[$type_object->type])) {
+      $type_object->disabled = TRUE;
+    }
+    if (!isset($_node_types[$type_object->type]) || $type_object->modified) {
+      $_node_types[$type_object->type] = $type_object;
+      $_node_names[$type_object->type] = $type_object->name;
+
+      if ($type_object->type != $type_object->orig_type) {
+        unset($_node_types[$type_object->orig_type]);
+        unset($_node_names[$type_object->orig_type]);
+      }
+    }
+  }
+
+  asort($_node_names);
+
+  return array($_node_types, $_node_names);
+}
+
+/**
+ * Set default values for a node type defined through hook_node_info().
+ */
+function _node_type_set_defaults($info) {
+  if (!isset($info['has_title'])) {
+    $info['has_title'] = TRUE;
+  }
+  if ($info['has_title'] && !isset($info['title_label'])) {
+    $info['title_label'] = t('Title');
+  }
+
+  if (!isset($info['has_body'])) {
+    $info['has_body'] = TRUE;
+  }
+  if ($info['has_body'] && !isset($info['body_label'])) {
+    $info['body_label'] = t('Body');
+  }
+
+  if (!isset($info['help'])) {
+    $info['help'] = '';
+  }
+  if (!isset($info['min_word_count'])) {
+    $info['min_word_count'] = 0;
+  }
+  if (!isset($info['custom'])) {
+    $info['custom'] = FALSE;
+  }
+  if (!isset($info['modified'])) {
+    $info['modified'] = FALSE;
+  }
+  if (!isset($info['locked'])) {
+    $info['locked'] = TRUE;
+  }
+
+  $info['orig_type'] = $info['type'];
+  $info['is_new'] = TRUE;
+
+  return $info;
+}
+
+/**
+ * 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) {
+  $module = node_get_types('module', $node);
+  if ($module == 'node') {
+    $module = 'node_content'; // Avoid function name collisions.
+  }
+  return module_hook($module, $hook);
+}
+
+/**
+ * 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.
+ */
+function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) {
+  if (node_hook($node, $hook)) {
+    $module = node_get_types('module', $node);
+    if ($module == 'node') {
+      $module = 'node_content'; // Avoid function name collisions.
+    }
+    $function = $module .'_'. $hook;
+    return ($function($node, $a2, $a3, $a4));
+  }
+}
+
+/**
+ * Invoke a hook_nodeapi() operation in all modules.
+ *
+ * @param &$node
+ *   A node object.
+ * @param $op
+ *   A string containing the name of the nodeapi operation.
+ * @param $a3, $a4
+ *   Arguments to pass on to the hook, after the $node and $op arguments.
+ * @return
+ *   The returned value of the invoked hooks.
+ */
+function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  $return = array();
+  foreach (module_implements('nodeapi') as $name) {
+    $function = $name .'_nodeapi';
+    $result = $function($node, $op, $a3, $a4);
+    if (isset($result) && is_array($result)) {
+      $return = array_merge($return, $result);
+    }
+    else if (isset($result)) {
+      $return[] = $result;
+    }
+  }
+  return $return;
+}
+
+/**
+ * Load a node object from the database.
+ *
+ * @param $param
+ *   Either the nid of the node or an array of conditions to match against in the database query
+ * @param $revision
+ *   Which numbered revision to load. Defaults to the current version.
+ * @param $reset
+ *   Whether to reset the internal node_load cache.
+ *
+ * @return
+ *   A fully-populated node object.
+ */
+function node_load($param = array(), $revision = NULL, $reset = NULL) {
+  static $nodes = array();
+
+  if ($reset) {
+    $nodes = array();
+  }
+
+  $cachable = ($revision == NULL);
+  $arguments = array();
+  if (is_numeric($param)) {
+    if ($cachable) {
+      // Is the node statically cached?
+      if (isset($nodes[$param])) {
+        return is_object($nodes[$param]) ? drupal_clone($nodes[$param]) : $nodes[$param];
+      }
+    }
+    $cond = 'n.nid = %d';
+    $arguments[] = $param;
+  }
+  elseif (is_array($param)) {
+    // Turn the conditions into a query.
+    foreach ($param as $key => $value) {
+      $cond[] = 'n.'. db_escape_string($key) ." = '%s'";
+      $arguments[] = $value;
+    }
+    $cond = implode(' AND ', $cond);
+  }
+  else {
+    return FALSE;
+  }
+
+  // Retrieve a field list based on the site's schema.
+  $fields = drupal_schema_fields_sql('node', 'n');
+  $fields = array_merge($fields, drupal_schema_fields_sql('node_revisions', 'r'));
+  $fields = array_merge($fields, array('u.name', 'u.picture', 'u.data'));
+  // Remove fields not needed in the query: n.vid and r.nid are redundant,
+  // n.title is unnecessary because the node title comes from the
+  // node_revisions table.  We'll keep r.vid, r.title, and n.nid.
+  $fields = array_diff($fields, array('n.vid', 'n.title', 'r.nid'));
+  $fields = implode(', ', $fields);
+  // Rename timestamp field for clarity.
+  $fields = str_replace('r.timestamp', 'r.timestamp AS revision_timestamp', $fields);
+  // Change name of revision uid so it doesn't conflict with n.uid.
+  $fields = str_replace('r.uid', 'r.uid AS revision_uid', $fields);
+
+  // Retrieve the node.
+  // No db_rewrite_sql is applied so as to get complete indexing for search.
+  if ($revision) {
+    array_unshift($arguments, $revision);
+    $node = db_fetch_object(db_query('SELECT '. $fields .' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d WHERE '. $cond, $arguments));
+  }
+  else {
+    $node = db_fetch_object(db_query('SELECT '. $fields .' FROM {node} n INNER JOIN {users} u ON u.uid = n.uid INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE '. $cond, $arguments));
+  }
+
+  if ($node && $node->nid) {
+    // 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;
+      }
+    }
+
+    if ($extra = node_invoke_nodeapi($node, 'load')) {
+      foreach ($extra as $key => $value) {
+        $node->$key = $value;
+      }
+    }
+    if ($cachable) {
+      $nodes[$node->nid] = is_object($node) ? drupal_clone($node) : $node;
+    }
+  }
+
+  return $node;
+}
+
+/**
+ * Perform validation checks on the given node.
+ */
+function node_validate($node, $form = array()) {
+  // Convert the node to an object, if necessary.
+  $node = (object)$node;
+  $type = node_get_types('type', $node);
+
+  // Make sure the body has the minimum number of words.
+  // TODO : use a better word counting algorithm that will work in other languages
+  if (!empty($type->min_word_count) && isset($node->body) && count(explode(' ', $node->body)) < $type->min_word_count) {
+    form_set_error('body', t('The body of your @type is too short. You need at least %words words.', array('%words' => $type->min_word_count, '@type' => $type->name)));
+  }
+
+  if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
+    form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
+  }
+
+  if (user_access('administer nodes')) {
+    // Validate the "authored by" field.
+    if (!empty($node->name) && !($account = user_load(array('name' => $node->name)))) {
+      // 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 anonymous user we set the user ID to 0.
+      form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
+    }
+
+    // Validate the "authored on" field. As of PHP 5.1.0, strtotime returns FALSE instead of -1 upon failure.
+    if (!empty($node->date) && strtotime($node->date) <= 0) {
+      form_set_error('date', t('You have to specify a valid date.'));
+    }
+  }
+
+  // Do node-type-specific validation checks.
+  node_invoke($node, 'validate', $form);
+  node_invoke_nodeapi($node, 'validate', $form);
+}
+
+/**
+ * Prepare node for save and allow modules to make changes.
+ */
+function node_submit($node) {
+  global $user;
+
+  // Convert the node to an object, if necessary.
+  $node = (object)$node;
+
+  // Generate the teaser, but only if it hasn't been set (e.g. by a
+  // module-provided 'teaser' form item).
+  if (!isset($node->teaser)) {
+    if (isset($node->body)) {
+      $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL);
+      // Chop off the teaser from the body if needed. The teaser_include
+      // property might not be set (eg. in Blog API postings), so only act on
+      // it, if it was set with a given value.
+      if (isset($node->teaser_include) && !$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) {
+        $node->body = substr($node->body, strlen($node->teaser));
+      }
+    }
+    else {
+      $node->teaser = '';
+    }
+  }
+
+  if (user_access('administer nodes')) {
+    // Populate the "authored by" field.
+    if ($account = user_load(array('name' => $node->name))) {
+      $node->uid = $account->uid;
+    }
+    else {
+      $node->uid = 0;
+    }
+  }
+  $node->created = !empty($node->date) ? strtotime($node->date) : time();
+  $node->validated = TRUE;
+
+  return $node;
+}
+
+/**
+ * Save a node object into the database.
+ */
+function node_save(&$node) {
+  // Let modules modify the node before it is saved to the database.
+  node_invoke_nodeapi($node, 'presave');
+  global $user;
+
+  $node->is_new = FALSE;
+
+  // Apply filters to some default node fields:
+  if (empty($node->nid)) {
+    // Insert a new node.
+    $node->is_new = TRUE;
+
+    // When inserting a node, $node->log must be set because
+    // {node_revisions}.log does not (and cannot) have a default
+    // value.  If the user does not have permission to create
+    // revisions, however, the form will not contain an element for
+    // log so $node->log will be unset at this point.
+    if (!isset($node->log)) {
+      $node->log = '';
+    }
+
+    // For the same reasons, make sure we have $node->teaser and
+    // $node->body.  We should consider making these fields nullable
+    // in a future version since node types are not required to use them.
+    if (!isset($node->teaser)) {
+      $node->teaser = '';
+    }
+    if (!isset($node->body)) {
+      $node->body = '';
+    }
+  }
+  elseif (!empty($node->revision)) {
+    $node->old_vid = $node->vid;
+  }
+  else {
+    // When updating a node, avoid clobberring an existing log entry with an empty one.
+    if (empty($node->log)) {
+      unset($node->log);
+    }
+  }
+
+  // Set some required fields:
+  if (empty($node->created)) {
+    $node->created = time();
+  }
+  // The changed timestamp is always updated for bookkeeping purposes (revisions, searching, ...)
+  $node->changed = time();
+
+  $node->timestamp = time();
+  $node->format = isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT;
+  $update_node = TRUE;
+
+  // Generate the node table query and the node_revisions table query.
+  if ($node->is_new) {
+    drupal_write_record('node', $node);
+    _node_save_revision($node, $user->uid);
+    $op = 'insert';
+  }
+  else {
+    drupal_write_record('node', $node, 'nid');
+    if (!empty($node->revision)) {
+      _node_save_revision($node, $user->uid);
+    }
+    else {
+      _node_save_revision($node, $user->uid, 'vid');
+      $update_node = FALSE;
+    }
+    $op = 'update';
+  }
+  if ($update_node) {
+    db_query('UPDATE {node} SET vid = %d WHERE nid = %d', $node->vid, $node->nid);
+  }
+
+  // Call the node specific callback (if any).
+  node_invoke($node, $op);
+  node_invoke_nodeapi($node, $op);
+
+  // Update the node access table for this node.
+  node_access_acquire_grants($node);
+
+  // Clear the page and block caches.
+  cache_clear_all();
+}
+
+/**
+ * Helper function to save a revision with the uid of the current user.
+ *
+ * Node is taken by reference, becuse drupal_write_record() updates the
+ * $node with the revision id, and we need to pass that back to the caller.
+ */
+function _node_save_revision(&$node, $uid, $update = NULL) {
+  $temp_uid = $node->uid;
+  $node->uid = $uid;
+  if (isset($update)) {
+    drupal_write_record('node_revisions', $node, $update);
+  }
+  else {
+    drupal_write_record('node_revisions', $node);
+  }
+  $node->uid = $temp_uid;
+}
+
+/**
+ * Delete a node.
+ */
+function node_delete($nid) {
+
+  $node = node_load($nid);
+
+  if (node_access('delete', $node)) {
+    db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
+    db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
+
+    // Call the node-specific callback (if any):
+    node_invoke($node, 'delete');
+    node_invoke_nodeapi($node, 'delete');
+
+    // Clear the page and block caches.
+    cache_clear_all();
+
+    // Remove this node from the search index if needed.
+    if (function_exists('search_wipe')) {
+      search_wipe($node->nid, 'node');
+    }
+    watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title));
+    drupal_set_message(t('@type %title has been deleted.', array('@type' => node_get_types('name', $node), '%title' => $node->title)));
+  }
+}
+
+/**
+ * Generate a display of the given node.
+ *
+ * @param $node
+ *   A node array or node object.
+ * @param $teaser
+ *   Whether to display the teaser only or the full form.
+ * @param $page
+ *   Whether the node is being displayed by itself as a page.
+ * @param $links
+ *   Whether or not to display node links. Links are omitted for node previews.
+ *
+ * @return
+ *   An HTML representation of the themed node.
+ */
+function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
+  $node = (object)$node;
+
+  $node = node_build_content($node, $teaser, $page);
+
+  if ($links) {
+    $node->links = module_invoke_all('link', 'node', $node, $teaser);
+    drupal_alter('link', $node->links, $node);
+  }
+
+  // Set the proper node part, then unset unused $node part so that a bad
+  // theme can not open a security hole.
+  $content = drupal_render($node->content);
+  if ($teaser) {
+    $node->teaser = $content;
+    unset($node->body);
+  }
+  else {
+    $node->body = $content;
+    unset($node->teaser);
+  }
+
+  // Allow modules to modify the fully-built node.
+  node_invoke_nodeapi($node, 'alter', $teaser, $page);
+
+  return theme('node', $node, $teaser, $page);
+}
+
+/**
+ * Apply filters and build the node's standard elements.
+ */
+function node_prepare($node, $teaser = FALSE) {
+  // First we'll overwrite the existing node teaser and body with
+  // the filtered copies! Then, we'll stick those into the content
+  // array and set the read more flag if appropriate.
+  $node->readmore = $node->teaser != $node->body;
+
+  if ($teaser == FALSE) {
+    $node->body = check_markup($node->body, $node->format, FALSE);
+  }
+  else {
+    $node->teaser = check_markup($node->teaser, $node->format, FALSE);
+  }
+
+  $node->content['body'] = array(
+    '#value' => $teaser ? $node->teaser : $node->body,
+    '#weight' => 0,
+  );
+
+  return $node;
+}
+
+/**
+ * Builds a structured array representing the node's content.
+ *
+ * @param $node
+ *   A node object.
+ * @param $teaser
+ *   Whether to display the teaser only, as on the main page.
+ * @param $page
+ *   Whether the node is being displayed by itself as a page.
+ *
+ * @return
+ *   An structured array containing the individual elements
+ *   of the node's body.
+ */
+function node_build_content($node, $teaser = FALSE, $page = FALSE) {
+
+  // The build mode identifies the target for which the node is built.
+  if (!isset($node->build_mode)) {
+    $node->build_mode = NODE_BUILD_NORMAL;
+  }
+
+  // Remove the delimiter (if any) that separates the teaser from the body.
+  $node->body = isset($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')) {
+    $node = node_invoke($node, 'view', $teaser, $page);
+  }
+  else {
+    $node = node_prepare($node, $teaser);
+  }
+
+  // Allow modules to make their own additions to the node.
+  node_invoke_nodeapi($node, 'view', $teaser, $page);
+
+  return $node;
+}
+
+/**
+ * Generate a page displaying a single node, along with its comments.
+ */
+function node_show($node, $cid, $message = FALSE) {
+  if ($message) {
+    drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))));
+  }
+  $output = node_view($node, FALSE, TRUE);
+
+  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;
+}
+
+/**
+ * Theme a log message.
+ *
+ * @ingroup themeable
+ */
+function theme_node_log_message($log) {
+  return '<div class="log"><div class="title">'. t('Log') .':</div>'. $log .'</div>';
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function node_perm() {
+  $perms = array('administer content types', 'administer nodes', 'access content', 'view revisions', 'revert revisions', 'delete revisions');
+
+  foreach (node_get_types() as $type) {
+    if ($type->module == 'node') {
+      $name = check_plain($type->type);
+      $perms[] = 'create '. $name .' content';
+      $perms[] = 'delete own '. $name .' content';
+      $perms[] = 'delete any '. $name .' content';
+      $perms[] = 'edit own '. $name .' content';
+      $perms[] = 'edit any '. $name .' content';
+    }
+  }
+
+  return $perms;
+}
+
+/**
+ * Implementation of hook_search().
+ */
+function node_search($op = 'search', $keys = NULL) {
+  switch ($op) {
+    case 'name':
+      return t('Content');
+
+    case 'reset':
+      db_query("UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", time());
+      return;
+
+    case 'status':
+      $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
+      $remaining = db_result(db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0"));
+      return array('remaining' => $remaining, 'total' => $total);
+
+    case 'admin':
+      $form = array();
+      // Output form for defining rank factor weights.
+      $form['content_ranking'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Content ranking'),
+      );
+      $form['content_ranking']['#theme'] = 'node_search_admin';
+      $form['content_ranking']['info'] = array(
+        '#value' => '<em>'. t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') .'</em>'
+      );
+
+      $ranking = array('node_rank_relevance' => t('Keyword relevance'),
+                       'node_rank_recent' => t('Recently posted'));
+      if (module_exists('comment')) {
+        $ranking['node_rank_comments'] = t('Number of comments');
+      }
+      if (module_exists('statistics') && variable_get('statistics_count_content_views', 0)) {
+        $ranking['node_rank_views'] = t('Number of views');
+      }
+
+      // Note: reversed to reflect that higher number = higher ranking.
+      $options = drupal_map_assoc(range(0, 10));
+      foreach ($ranking as $var => $title) {
+        $form['content_ranking']['factors'][$var] = array(
+          '#title' => $title,
+          '#type' => 'select',
+          '#options' => $options,
+          '#default_value' => variable_get($var, 5),
+        );
+      }
+      return $form;
+
+    case 'search':
+      // Build matching conditions
+      list($join1, $where1) = _db_rewrite_sql();
+      $arguments1 = array();
+      $conditions1 = 'n.status = 1';
+
+      if ($type = search_query_extract($keys, 'type')) {
+        $types = array();
+        foreach (explode(',', $type) as $t) {
+          $types[] = "n.type = '%s'";
+          $arguments1[] = $t;
+        }
+        $conditions1 .= ' AND ('. implode(' OR ', $types) .')';
+        $keys = search_query_insert($keys, 'type');
+      }
+
+      if ($category = search_query_extract($keys, 'category')) {
+        $categories = array();
+        foreach (explode(',', $category) as $c) {
+          $categories[] = "tn.tid = %d";
+          $arguments1[] = $c;
+        }
+        $conditions1 .= ' AND ('. implode(' OR ', $categories) .')';
+        $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid';
+        $keys = search_query_insert($keys, 'category');
+      }
+
+      // Build ranking expression (we try to map each parameter to a
+      // uniform distribution in the range 0..1).
+      $ranking = array();
+      $arguments2 = array();
+      $join2 = '';
+      // Used to avoid joining on node_comment_statistics twice
+      $stats_join = FALSE;
+      $total = 0;
+      if ($weight = (int)variable_get('node_rank_relevance', 5)) {
+        // Average relevance values hover around 0.15
+        $ranking[] = '%d * i.relevance';
+        $arguments2[] = $weight;
+        $total += $weight;
+      }
+      if ($weight = (int)variable_get('node_rank_recent', 5)) {
+        // Exponential decay with half-life of 6 months, starting at last indexed node
+        $ranking[] = '%d * POW(2, (GREATEST(MAX(n.created), MAX(n.changed), MAX(c.last_comment_timestamp)) - %d) * 6.43e-8)';
+        $arguments2[] = $weight;
+        $arguments2[] = (int)variable_get('node_cron_last', 0);
+        $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
+        $stats_join = TRUE;
+        $total += $weight;
+      }
+      if (module_exists('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
+        // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
+        $scale = variable_get('node_cron_comments_scale', 0.0);
+        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(c.comment_count) * %f))';
+        $arguments2[] = $weight;
+        $arguments2[] = $scale;
+        if (!$stats_join) {
+          $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid';
+        }
+        $total += $weight;
+      }
+      if (module_exists('statistics') && variable_get('statistics_count_content_views', 0) &&
+          $weight = (int)variable_get('node_rank_views', 5)) {
+        // Inverse law that maps the highest view count on the site to 1 and 0 to 0.
+        $scale = variable_get('node_cron_views_scale', 0.0);
+        $ranking[] = '%d * (2.0 - 2.0 / (1.0 + MAX(nc.totalcount) * %f))';
+        $arguments2[] = $weight;
+        $arguments2[] = $scale;
+        $join2 .= ' LEFT JOIN {node_counter} nc ON nc.nid = i.sid';
+        $total += $weight;
+      }
+      $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') .' AS score';
+
+      // Do search
+      $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2);
+
+      // Load results
+      $results = array();
+      foreach ($find as $item) {
+        // Build the node body.
+        $node = node_load($item->sid);
+        $node->build_mode = NODE_BUILD_SEARCH_RESULT;
+        $node = node_build_content($node, FALSE, FALSE);
+        $node->body = drupal_render($node->content);
+
+        // Fetch comments for snippet
+        $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
+        // Fetch terms for snippet
+        $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
+
+        $extra = node_invoke_nodeapi($node, 'search result');
+        $results[] = array(
+          'link' => url('node/'. $item->sid, array('absolute' => TRUE)),
+          'type' => check_plain(node_get_types('name', $node)),
+          'title' => $node->title,
+          'user' => theme('username', $node),
+          'date' => $node->changed,
+          'node' => $node,
+          'extra' => $extra,
+          'score' => $item->score / $total,
+          'snippet' => search_excerpt($keys, $node->body),
+        );
+      }
+      return $results;
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function node_user($op, &$edit, &$user) {
+  if ($op == 'delete') {
+    db_query('UPDATE {node} SET uid = 0 WHERE uid = %d', $user->uid);
+    db_query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', $user->uid);
+  }
+}
+
+/**
+ * Theme the content ranking part of the search settings admin page.
+ *
+ * @ingroup themeable
+ */
+function theme_node_search_admin($form) {
+  $output = drupal_render($form['info']);
+
+  $header = array(t('Factor'), t('Weight'));
+  foreach (element_children($form['factors']) as $key) {
+    $row = array();
+    $row[] = $form['factors'][$key]['#title'];
+    unset($form['factors'][$key]['#title']);
+    $row[] = drupal_render($form['factors'][$key]);
+    $rows[] = $row;
+  }
+  $output .= theme('table', $header, $rows);
+
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Retrieve the comment mode for the given node ID (none, read, or read/write).
+ */
+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];
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function node_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+
+  if ($type == 'node') {
+    if ($teaser == 1 && $node->teaser && !empty($node->readmore)) {
+      $links['node_read_more'] = array(
+        'title' => t('Read more'),
+        'href' => "node/$node->nid",
+        // The title attribute gets escaped when the links are processed, so
+        // there is no need to escape here.
+        'attributes' => array('title' => t('Read the rest of !title.', array('!title' => $node->title)))
+      );
+    }
+  }
+
+  return $links;
+}
+
+function _node_revision_access($node, $op = 'view') {
+  static $access = array();
+  if (!isset($access[$node->vid])) {
+    $node_current_revision = node_load($node->nid);
+    $is_current_revision = $node_current_revision->vid == $node->vid;
+    // There should be at least two revisions. If the vid of the given node
+    // and the vid of the current revision differs, then we already have two
+    // different revisions so there is no need for a separate database check.
+    // Also, if you try to revert to or delete the current revision, that's
+    // not good.
+    if ($is_current_revision && (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) == 1 || $op == 'update' || $op == 'delete')) {
+      $access[$node->vid] = FALSE;
+    }
+    elseif (user_access('administer nodes')) {
+      $access[$node->vid] = TRUE;
+    }
+    else {
+      $map = array('view' => 'view revisions', 'update' => 'revert revisions', 'delete' => 'delete revisions');
+      // First check the user permission, second check the access to the
+      // current revision and finally, if the node passed in is not the current
+      // revision then access to that, too.
+      $access[$node->vid] = isset($map[$op]) && user_access($map[$op]) && node_access($op, $node_current_revision) && ($is_current_revision || node_access($op, $node));
+    }
+  }
+  return $access[$node->vid];
+}
+
+function _node_add_access() {
+  $types = node_get_types();
+  foreach ($types as $type) {
+    if (node_hook($type->type, 'form') && node_access('create', $type->type)) {
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function node_menu() {
+  $items['admin/content/node'] = array(
+    'title' => 'Content',
+    'description' => "View, edit, and delete your site's content.",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_admin_content'),
+    'access arguments' => array('administer nodes'),
+    'file' => 'node.admin.inc',
+  );
+
+  $items['admin/content/node/overview'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+
+  $items['admin/content/node-settings'] = array(
+    'title' => 'Post settings',
+    'description' => 'Control posting behavior, such as teaser length, requiring previews before posting, and the number of posts on the front page.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_configure'),
+    'access arguments' => array('administer nodes'),
+    'file' => 'node.admin.inc',
+  );
+  $items['admin/content/node-settings/rebuild'] = array(
+    'title' => 'Rebuild permissions',
+    'page arguments' => array('node_configure_rebuild_confirm'),
+    'file' => 'node.admin.inc',
+    // Any user than can potentially trigger a node_acess_needs_rebuild(TRUE)
+    // has to be allowed access to the 'node access rebuild' confirm form.
+    'access arguments' => array('access administration pages'),
+    'type' => MENU_CALLBACK,
+  );
+
+  $items['admin/content/types'] = array(
+    'title' => 'Content types',
+    'description' => 'Manage posts by content type, including default status, front page promotion, etc.',
+    'page callback' => 'node_overview_types',
+    'access arguments' => array('administer content types'),
+    'file' => 'content_types.inc',
+  );
+  $items['admin/content/types/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/content/types/add'] = array(
+    'title' => 'Add content type',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_type_form'),
+    'file' => 'content_types.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['node'] = array(
+    'title' => 'Content',
+    'page callback' => 'node_page_default',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['node/add'] = array(
+    'title' => 'Create content',
+    'page callback' => 'node_add_page',
+    'access callback' => '_node_add_access',
+    'weight' => 1,
+    'file' => 'node.pages.inc',
+  );
+  $items['rss.xml'] = array(
+    'title' => 'RSS feed',
+    'page callback' => 'node_feed',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+  foreach (node_get_types('types', NULL, TRUE) as $type) {
+    $type_url_str = str_replace('_', '-', $type->type);
+    $items['node/add/'. $type_url_str] = array(
+      'title' => drupal_ucfirst($type->name),
+      'title callback' => 'check_plain',
+      'page callback' => 'node_add',
+      'page arguments' => array(2),
+      'access callback' => 'node_access',
+      'access arguments' => array('create', $type->type),
+      'description' => $type->description,
+      'file' => 'node.pages.inc',
+    );
+    $items['admin/content/node-type/'. $type_url_str] = array(
+      'title' => $type->name,
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('node_type_form', $type),
+      'file' => 'content_types.inc',
+      'type' => MENU_CALLBACK,
+    );
+    $items['admin/content/node-type/'. $type_url_str .'/edit'] = array(
+      'title' => 'Edit',
+      'type' => MENU_DEFAULT_LOCAL_TASK,
+    );
+    $items['admin/content/node-type/'. $type_url_str .'/delete'] = array(
+      'title' => 'Delete',
+      'page arguments' => array('node_type_delete_confirm', $type),
+      'file' => 'content_types.inc',
+      'type' => MENU_CALLBACK,
+    );
+  }
+  $items['node/%node'] = array(
+    'title callback' => 'node_page_title',
+    'title arguments' => array(1),
+    'page callback' => 'node_page_view',
+    'page arguments' => array(1),
+    'access callback' => 'node_access',
+    'access arguments' => array('view', 1),
+    'type' => MENU_CALLBACK);
+  $items['node/%node/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10);
+  $items['node/%node/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'node_page_edit',
+    'page arguments' => array(1),
+    'access callback' => 'node_access',
+    'access arguments' => array('update', 1),
+    'weight' => 1,
+    'file' => 'node.pages.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['node/%node/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_delete_confirm', 1),
+    'access callback' => 'node_access',
+    'access arguments' => array('delete', 1),
+    'file' => 'node.pages.inc',
+    'weight' => 1,
+    'type' => MENU_CALLBACK);
+  $items['node/%node/revisions'] = array(
+    'title' => 'Revisions',
+    'page callback' => 'node_revision_overview',
+    'page arguments' => array(1),
+    'access callback' => '_node_revision_access',
+    'access arguments' => array(1),
+    'weight' => 2,
+    'file' => 'node.pages.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['node/%node/revisions/%/view'] = array(
+    'title' => 'Revisions',
+    'load arguments' => array(3),
+    'page callback' => 'node_show',
+    'page arguments' => array(1, NULL, TRUE),
+    'type' => MENU_CALLBACK,
+  );
+  $items['node/%node/revisions/%/revert'] = array(
+    'title' => 'Revert to earlier revision',
+    'load arguments' => array(3),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_revision_revert_confirm', 1),
+    'access callback' => '_node_revision_access',
+    'access arguments' => array(1, 'update'),
+    'file' => 'node.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
+  $items['node/%node/revisions/%/delete'] = array(
+    'title' => 'Delete earlier revision',
+    'load arguments' => array(3),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('node_revision_delete_confirm', 1),
+    'access callback' => '_node_revision_access',
+    'access arguments' => array(1, 'delete'),
+    'file' => 'node.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Title callback.
+ */
+function node_page_title($node) {
+  return $node->title;
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function node_init() {
+  drupal_add_css(drupal_get_path('module', 'node') .'/node.css');
+}
+
+function node_last_changed($nid) {
+  $node = db_fetch_object(db_query('SELECT changed FROM {node} WHERE nid = %d', $nid));
+  return ($node->changed);
+}
+
+/**
+ * Return a list of all the existing revision numbers.
+ */
+function node_revision_list($node) {
+  $revisions = array();
+  $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revisions} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = %d ORDER BY r.timestamp DESC', $node->nid);
+  while ($revision = db_fetch_object($result)) {
+    $revisions[$revision->vid] = $revision;
+  }
+
+  return $revisions;
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function node_block($op = 'list', $delta = 0) {
+  if ($op == 'list') {
+    $blocks[0]['info'] = t('Syndicate');
+    // Not worth caching.
+    $blocks[0]['cache'] = BLOCK_NO_CACHE;
+    return $blocks;
+  }
+  else if ($op == 'view') {
+    $block['subject'] = t('Syndicate');
+    $block['content'] = theme('feed_icon', url('rss.xml'), t('Syndicate'));
+
+    return $block;
+  }
+}
+
+/**
+ * A generic function for generating RSS feeds from a set of nodes.
+ *
+ * @param $nids
+ *   An array of node IDs (nid). Defaults to FALSE so empty feeds can be
+ *   generated with passing an empty array, if no items are to be added
+ *   to the feed.
+ * @param $channel
+ *   An associative array containing title, link, description and other keys.
+ *   The link should be an absolute URL.
+ */
+function node_feed($nids = FALSE, $channel = array()) {
+  global $base_url, $language;
+
+  if ($nids === FALSE) {
+    $nids = array();
+    $result = db_query_range(db_rewrite_sql('SELECT n.nid, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0, variable_get('feed_default_items', 10));
+    while ($row = db_fetch_object($result)) {
+      $nids[] = $row->nid;
+    }
+  }
+
+  $item_length = variable_get('feed_item_length', 'teaser');
+  $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
+
+  $items = '';
+  foreach ($nids as $nid) {
+    // Load the specified node:
+    $item = node_load($nid);
+    $item->build_mode = NODE_BUILD_RSS;
+    $item->link = url("node/$nid", array('absolute' => TRUE));
+
+    if ($item_length != 'title') {
+      $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
+
+      // Filter and prepare node teaser
+      if (node_hook($item, 'view')) {
+        $item = node_invoke($item, 'view', $teaser, FALSE);
+      }
+      else {
+        $item = node_prepare($item, $teaser);
+      }
+
+      // Allow modules to change $node->teaser before viewing.
+      node_invoke_nodeapi($item, 'view', $teaser, FALSE);
+    }
+
+    // Allow modules to add additional item fields and/or modify $item
+    $extra = node_invoke_nodeapi($item, 'rss item');
+    $extra = array_merge($extra, array(array('key' => 'pubDate', 'value' => format_date($item->created, 'custom', 'r')), array('key' => 'dc:creator', 'value' => $item->name), array('key' => 'guid', 'value' => $item->nid .' at '. $base_url, 'attributes' => array('isPermaLink' => 'false'))));
+    foreach ($extra as $element) {
+      if (isset($element['namespace'])) {
+        $namespaces = array_merge($namespaces, $element['namespace']);
+      }
+    }
+
+    // Prepare the item description
+    switch ($item_length) {
+      case 'fulltext':
+        $item_text = $item->body;
+        break;
+      case 'teaser':
+        $item_text = $item->teaser;
+        if (!empty($item->readmore)) {
+          $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) .'</p>';
+        }
+        break;
+      case 'title':
+        $item_text = '';
+        break;
+    }
+
+    $items .= format_rss_item($item->title, $item->link, $item_text, $extra);
+  }
+
+  $channel_defaults = array(
+    'version'     => '2.0',
+    'title'       => variable_get('site_name', 'Drupal'),
+    'link'        => $base_url,
+    'description' => variable_get('site_mission', ''),
+    'language'    => $language->language
+  );
+  $channel = array_merge($channel_defaults, $channel);
+
+  $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+  $output .= "<rss version=\"". $channel["version"] ."\" xml:base=\"". $base_url ."\" ". drupal_attributes($namespaces) .">\n";
+  $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
+  $output .= "</rss>\n";
+
+  drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+  print $output;
+}
+
+/**
+ * Menu callback; Generate a listing of promoted nodes.
+ */
+function node_page_default() {
+  $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10));
+
+  $output = '';
+  $num_rows = FALSE;
+  while ($node = db_fetch_object($result)) {
+    $output .= node_view(node_load($node->nid), 1);
+    $num_rows = TRUE;
+  }
+
+  if ($num_rows) {
+    $feed_url = url('rss.xml', array('absolute' => TRUE));
+    drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') .' '. t('RSS'));
+    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  }
+  else {
+    $default_message = t('<h1 class="title">Welcome to your new Drupal website!</h1><p>Please follow these steps to set up and start using your website:</p>');
+    $default_message .= '<ol>';
+
+    $default_message .= '<li>'. t('<strong>Configure your website</strong> Once logged in, visit the <a href="@admin">administration section</a>, where you can <a href="@config">customize and configure</a> all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/settings'))) .'</li>';
+    $default_message .= '<li>'. t('<strong>Enable additional functionality</strong> Next, visit the <a href="@modules">module list</a> and enable features which suit your specific needs. You can find additional modules in the <a href="@download_modules">Drupal modules download section</a>.', array('@modules' => url('admin/build/modules'), '@download_modules' => 'http://drupal.org/project/modules')) .'</li>';
+    $default_message .= '<li>'. t('<strong>Customize your website design</strong> To change the "look and feel" of your website, visit the <a href="@themes">themes section</a>. You may choose from one of the included themes or download additional themes from the <a href="@download_themes">Drupal themes download section</a>.', array('@themes' => url('admin/build/themes'), '@download_themes' => 'http://drupal.org/project/themes')) .'</li>';
+    $default_message .= '<li>'. t('<strong>Start posting content</strong> Finally, you can <a href="@content">create content</a> for your website. This message will disappear once you have promoted a post to the front page.', array('@content' => url('node/add'))) .'</li>';
+    $default_message .= '</ol>';
+    $default_message .= '<p>'. t('For more information, please refer to the <a href="@help">help section</a>, or the <a href="@handbook">online Drupal handbooks</a>. You may also post at the <a href="@forum">Drupal forum</a>, or view the wide range of <a href="@support">other support options</a> available.', array('@help' => url('admin/help'), '@handbook' => 'http://drupal.org/handbooks', '@forum' => 'http://drupal.org/forum', '@support' => 'http://drupal.org/support')) .'</p>';
+
+    $output = '<div id="first-time">'. $default_message .'</div>';
+  }
+  drupal_set_title('');
+
+  return $output;
+}
+
+/**
+ * Menu callback; view a single node.
+ */
+function node_page_view($node, $cid = NULL) {
+  drupal_set_title(check_plain($node->title));
+  return node_show($node, $cid);
+}
+
+/**
+ * Implementation of hook_update_index().
+ */
+function node_update_index() {
+  $limit = (int)variable_get('search_cron_limit', 100);
+
+  // Store the maximum possible comments per thread (used for ranking by reply count)
+  variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}'))));
+  variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
+
+  $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);
+
+  while ($node = db_fetch_object($result)) {
+    _node_index_node($node);
+  }
+}
+
+/**
+ * Index a single node.
+ *
+ * @param $node
+ *   The node to index.
+ */
+function _node_index_node($node) {
+  $node = node_load($node->nid);
+
+  // save the changed time of the most recent indexed node, for the search results half-life calculation
+  variable_set('node_cron_last', $node->changed);
+
+  // Build the node body.
+  $node->build_mode = NODE_BUILD_SEARCH_INDEX;
+  $node = node_build_content($node, FALSE, FALSE);
+  $node->body = drupal_render($node->content);
+
+  $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
+
+  // Fetch extra data normally not visible
+  $extra = node_invoke_nodeapi($node, 'update index');
+  foreach ($extra as $t) {
+    $text .= $t;
+  }
+
+  // Update index
+  search_index($node->nid, 'node', $text);
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function node_form_alter(&$form, $form_state, $form_id) {
+  // Advanced node search form
+  if ($form_id == 'search_form' && $form['module']['#value'] == 'node' && user_access('use advanced search')) {
+    // Keyword boxes:
+    $form['advanced'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Advanced search'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#attributes' => array('class' => 'search-advanced'),
+    );
+    $form['advanced']['keywords'] = array(
+      '#prefix' => '<div class="criterion">',
+      '#suffix' => '</div>',
+    );
+    $form['advanced']['keywords']['or'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Containing any of the words'),
+      '#size' => 30,
+      '#maxlength' => 255,
+    );
+    $form['advanced']['keywords']['phrase'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Containing the phrase'),
+      '#size' => 30,
+      '#maxlength' => 255,
+    );
+    $form['advanced']['keywords']['negative'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Containing none of the words'),
+      '#size' => 30,
+      '#maxlength' => 255,
+    );
+
+    // Taxonomy box:
+    if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
+      $form['advanced']['category'] = array(
+        '#type' => 'select',
+        '#title' => t('Only in the category(s)'),
+        '#prefix' => '<div class="criterion">',
+        '#size' => 10,
+        '#suffix' => '</div>',
+        '#options' => $taxonomy,
+        '#multiple' => TRUE,
+      );
+    }
+
+    // Node types:
+    $types = array_map('check_plain', node_get_types('names'));
+    $form['advanced']['type'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Only of the type(s)'),
+      '#prefix' => '<div class="criterion">',
+      '#suffix' => '</div>',
+      '#options' => $types,
+    );
+    $form['advanced']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Advanced search'),
+      '#prefix' => '<div class="action">',
+      '#suffix' => '</div>',
+    );
+
+    $form['#validate'][] = 'node_search_validate';
+  }
+}
+
+/**
+ * Form API callback for the search form. Registered in node_form_alter().
+ */
+function node_search_validate($form, &$form_state) {
+  // Initialise using any existing basic search keywords.
+  $keys = $form_state['values']['processed_keys'];
+
+  // Insert extra restrictions into the search keywords string.
+  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
+    // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
+    $form_state['values']['type'] = array_filter($form_state['values']['type']);
+    if (count($form_state['values']['type'])) {
+      $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
+    }
+  }
+
+  if (isset($form_state['values']['category']) && is_array($form_state['values']['category'])) {
+    $keys = search_query_insert($keys, 'category', implode(',', $form_state['values']['category']));
+  }
+  if ($form_state['values']['or'] != '') {
+    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_state['values']['or'], $matches)) {
+      $keys .= ' '. implode(' OR ', $matches[1]);
+    }
+  }
+  if ($form_state['values']['negative'] != '') {
+    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $form_state['values']['negative'], $matches)) {
+      $keys .= ' -'. implode(' -', $matches[1]);
+    }
+  }
+  if ($form_state['values']['phrase'] != '') {
+    $keys .= ' "'. str_replace('"', ' ', $form_state['values']['phrase']) .'"';
+  }
+  if (!empty($keys)) {
+    form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
+  }
+}
+
+/**
+ * @defgroup node_access Node access rights
+ * @{
+ * The node access system determines who can do what to which nodes.
+ *
+ * In determining access rights for a node, node_access() first checks
+ * whether the user has the "administer nodes" permission. Such users have
+ * unrestricted access to all nodes. Then the node module's hook_access()
+ * is called, and a TRUE or FALSE return value will grant or deny access.
+ * This allows, for example, the blog module to always grant access to the
+ * blog author, and for the book module to always deny editing access to
+ * PHP pages.
+ *
+ * If node module does not intervene (returns NULL), then the
+ * node_access table is used to determine access. All node access
+ * modules are queried using hook_node_grants() to assemble a list of
+ * "grant IDs" for the user. This list is compared against the table.
+ * If any row contains the node ID in question (or 0, which stands for "all
+ * nodes"), one of the grant IDs returned, and a value of TRUE for the
+ * operation in question, then access is granted. Note that this table is a
+ * list of grants; any matching row is sufficient to grant access to the
+ * node.
+ *
+ * In node listings, the process above is followed except that
+ * hook_access() is not called on each node for performance reasons and for
+ * proper functioning of the pager system. When adding a node listing to your
+ * module, be sure to use db_rewrite_sql() to add
+ * the appropriate clauses to your query for access checks.
+ *
+ * To see how to write a node access module of your own, see
+ * node_access_example.module.
+ */
+
+/**
+ * Determine whether the current user may perform the given operation on the
+ * specified node.
+ *
+ * @param $op
+ *   The operation to be performed on the node. Possible values are:
+ *   - "view"
+ *   - "update"
+ *   - "delete"
+ *   - "create"
+ * @param $node
+ *   The node object (or node array) on which the operation is to be performed,
+ *   or node type (e.g. 'forum') for "create" operation.
+ * @param $account
+ *   Optional, a user object representing the user for whom the operation is to
+ *   be performed. Determines access for a user other than the current user.
+ * @return
+ *   TRUE if the operation may be performed.
+ */
+function node_access($op, $node, $account = NULL) {
+  global $user;
+
+  if (!$node) {
+    return FALSE;
+  }
+  // Convert the node to an object if necessary:
+  if ($op != 'create') {
+    $node = (object)$node;
+  }
+  // If no user object is supplied, the access check is for the current user.
+  if (empty($account)) {
+    $account = $user;
+  }
+  // If the node is in a restricted format, disallow editing.
+  if ($op == 'update' && !filter_access($node->format)) {
+    return FALSE;
+  }
+
+  if (user_access('administer nodes', $account)) {
+    return TRUE;
+  }
+
+  if (!user_access('access content', $account)) {
+    return FALSE;
+  }
+
+  // Can't use node_invoke(), because the access hook takes the $op parameter
+  // before the $node parameter.
+  $module = node_get_types('module', $node);
+  if ($module == 'node') {
+    $module = 'node_content'; // Avoid function name collisions.
+  }
+  $access = module_invoke($module, 'access', $op, $node, $account);
+  if (!is_null($access)) {
+    return $access;
+  }
+
+  // If the module did not override the access rights, use those set in the
+  // node_access table.
+  if ($op != 'create' && $node->nid && $node->status) {
+    $grants = array();
+    foreach (node_access_grants($op, $account) as $realm => $gids) {
+      foreach ($gids as $gid) {
+        $grants[] = "(gid = $gid AND realm = '$realm')";
+      }
+    }
+
+    $grants_sql = '';
+    if (count($grants)) {
+      $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
+    }
+
+    $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql AND grant_$op >= 1";
+    $result = db_query($sql, $node->nid);
+    return (db_result($result));
+  }
+
+  // Let authors view their own nodes.
+  if ($op == 'view' && $account->uid == $node->uid && $account->uid != 0) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Generate an SQL join clause for use in fetching a node listing.
+ *
+ * @param $node_alias
+ *   If the node table has been given an SQL alias other than the default
+ *   "n", that must be passed here.
+ * @param $node_access_alias
+ *   If the node_access table has been given an SQL alias other than the default
+ *   "na", that must be passed here.
+ * @return
+ *   An SQL join clause.
+ */
+function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
+  if (user_access('administer nodes')) {
+    return '';
+  }
+
+  return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
+}
+
+/**
+ * Generate an SQL where clause for use in fetching a node listing.
+ *
+ * @param $op
+ *   The operation that must be allowed to return a node.
+ * @param $node_access_alias
+ *   If the node_access table has been given an SQL alias other than the default
+ *   "na", that must be passed here.
+ * @param $account
+ *   The user object for the user performing the operation. If omitted, the
+ *   current user is used.
+ * @return
+ *   An SQL where clause.
+ */
+function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $account = NULL) {
+  if (user_access('administer nodes')) {
+    return;
+  }
+
+  $grants = array();
+  foreach (node_access_grants($op, $account) as $realm => $gids) {
+    foreach ($gids as $gid) {
+      $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
+    }
+  }
+
+  $grants_sql = '';
+  if (count($grants)) {
+    $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
+  }
+
+  $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
+  return $sql;
+}
+
+/**
+ * Fetch an array of permission IDs granted to the given user ID.
+ *
+ * The implementation here provides only the universal "all" grant. A node
+ * access module should implement hook_node_grants() to provide a grant
+ * list for the user.
+ *
+ * @param $op
+ *   The operation that the user is trying to perform.
+ * @param $account
+ *   The user object for the user performing the operation. If omitted, the
+ *   current user is used.
+ * @return
+ *   An associative array in which the keys are realms, and the values are
+ *   arrays of grants for those realms.
+ */
+function node_access_grants($op, $account = NULL) {
+
+  if (!isset($account)) {
+    $account = $GLOBALS['user'];
+  }
+
+  return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $account, $op));
+}
+
+/**
+ * Determine whether the user has a global viewing grant for all nodes.
+ */
+function node_access_view_all_nodes() {
+  static $access;
+
+  if (!isset($access)) {
+    $grants = array();
+    foreach (node_access_grants('view') as $realm => $gids) {
+      foreach ($gids as $gid) {
+        $grants[] = "(gid = $gid AND realm = '$realm')";
+      }
+    }
+
+    $grants_sql = '';
+    if (count($grants)) {
+      $grants_sql = 'AND ('. implode(' OR ', $grants) .')';
+    }
+
+    $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
+    $result = db_query($sql);
+    $access = db_result($result);
+  }
+
+  return $access;
+}
+
+/**
+ * Implementation of hook_db_rewrite_sql
+ */
+function node_db_rewrite_sql($query, $primary_table, $primary_field) {
+  if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
+    $return['join'] = _node_access_join_sql($primary_table);
+    $return['where'] = _node_access_where_sql();
+    $return['distinct'] = 1;
+    return $return;
+  }
+}
+
+/**
+ * This function will call module invoke to get a list of grants and then
+ * write them to the database. It is called at node save, and should be
+ * called by modules whenever something other than a node_save causes
+ * the permissions on a node to change.
+ *
+ * This function is the only function that should write to the node_access
+ * table.
+ *
+ * @param $node
+ *   The $node to acquire grants for.
+ */
+function node_access_acquire_grants($node) {
+  $grants = module_invoke_all('node_access_records', $node);
+  if (empty($grants)) {
+    $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
+  }
+  else {
+    // retain grants by highest priority
+    $grant_by_priority = array();
+    foreach ($grants as $g) {
+      $grant_by_priority[intval($g['priority'])][] = $g;
+    }
+    krsort($grant_by_priority);
+    $grants = array_shift($grant_by_priority);
+  }
+
+  node_access_write_grants($node, $grants);
+}
+
+/**
+ * This function will write a list of grants to the database, deleting
+ * any pre-existing grants. If a realm is provided, it will only
+ * delete grants from that realm, but it will always delete a grant
+ * from the 'all' realm. Modules which utilize node_access can
+ * use this function when doing mass updates due to widespread permission
+ * changes.
+ *
+ * @param $node
+ *   The $node being written to. All that is necessary is that it contain a nid.
+ * @param $grants
+ *   A list of grants to write. Each grant is an array that must contain the
+ *   following keys: realm, gid, grant_view, grant_update, grant_delete.
+ *   The realm is specified by a particular module; the gid is as well, and
+ *   is a module-defined id to define grant privileges. each grant_* field
+ *   is a boolean value.
+ * @param $realm
+ *   If provided, only read/write grants for that realm.
+ * @param $delete
+ *   If false, do not delete records. This is only for optimization purposes,
+ *   and assumes the caller has already performed a mass delete of some form.
+ */
+function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) {
+  if ($delete) {
+    $query = 'DELETE FROM {node_access} WHERE nid = %d';
+    if ($realm) {
+      $query .= " AND realm in ('%s', 'all')";
+    }
+    db_query($query, $node->nid, $realm);
+  }
+
+  // Only perform work when node_access modules are active.
+  if (count(module_implements('node_grants'))) {
+    foreach ($grants as $grant) {
+      if ($realm && $realm != $grant['realm']) {
+        continue;
+      }
+      // Only write grants; denies are implicit.
+      if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
+        db_query("INSERT INTO {node_access} (nid, realm, gid, grant_view, grant_update, grant_delete) VALUES (%d, '%s', %d, %d, %d, %d)", $node->nid, $grant['realm'], $grant['gid'], $grant['grant_view'], $grant['grant_update'], $grant['grant_delete']);
+      }
+    }
+  }
+}
+
+/**
+ * Flag / unflag the node access grants for rebuilding, or read the current
+ * value of the flag.
+ *
+ * When the flag is set, a message is displayed to users with 'access
+ * administration pages' permission, pointing to the 'rebuild' confirm form.
+ * This can be used as an alternative to direct node_access_rebuild calls,
+ * allowing administrators to decide when they want to perform the actual
+ * (possibly time consuming) rebuild.
+ * When unsure the current user is an adminisrator, node_access_rebuild
+ * should be used instead.
+ *
+ * @param $rebuild
+ *   (Optional) The boolean value to be written.
+  * @return
+ *   (If no value was provided for $rebuild) The current value of the flag.
+ */
+function node_access_needs_rebuild($rebuild = NULL) {
+  if (!isset($rebuild)) {
+    return variable_get('node_access_needs_rebuild', FALSE);
+  }
+  elseif ($rebuild) {
+    variable_set('node_access_needs_rebuild', TRUE);
+  }
+  else {
+    variable_del('node_access_needs_rebuild');
+  }
+}
+
+/**
+ * Rebuild the node access database. This is occasionally needed by modules
+ * that make system-wide changes to access levels.
+ *
+ * When the rebuild is required by an admin-triggered action (e.g module
+ * settings form), calling node_access_needs_rebuild(TRUE) instead of
+ * node_access_rebuild() lets the user perform his changes and actually
+ * rebuild only once he is done.
+ *
+ * Note : As of Drupal 6, node access modules are not required to (and actually
+ * should not) call node_access_rebuild() in hook_enable/disable anymore.
+ *
+ * @see node_access_needs_rebuild()
+ *
+ * @param $batch_mode
+ *   Set to TRUE to process in 'batch' mode, spawning processing over several
+ *   HTTP requests (thus avoiding the risk of PHP timeout if the site has a
+ *   large number of nodes).
+ *   hook_update_N and any form submit handler are safe contexts to use the
+ *   'batch mode'. Less decidable cases (such as calls from hook_user,
+ *   hook_taxonomy, hook_node_type...) might consider using the non-batch mode.
+ */
+function node_access_rebuild($batch_mode = FALSE) {
+  db_query("DELETE FROM {node_access}");
+  // Only recalculate if the site is using a node_access module.
+  if (count(module_implements('node_grants'))) {
+    if ($batch_mode) {
+      $batch = array(
+        'title' => t('Rebuilding content access permissions'),
+        'operations' => array(
+          array('_node_access_rebuild_batch_operation', array()),
+        ),
+        'finished' => '_node_access_rebuild_batch_finished'
+      );
+      batch_set($batch);
+    }
+    else {
+      // If not in 'safe mode', increase the maximum execution time.
+      if (!ini_get('safe_mode')) {
+        set_time_limit(240);
+      }
+      $result = db_query("SELECT nid FROM {node}");
+      while ($node = db_fetch_object($result)) {
+        $loaded_node = node_load($node->nid, NULL, TRUE);
+        // To preserve database integrity, only aquire grants if the node
+        // loads successfully.
+        if (!empty($loaded_node)) {
+          node_access_acquire_grants($loaded_node);
+        }
+      }
+    }
+  }
+  else {
+    // Not using any node_access modules. Add the default grant.
+    db_query("INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)");
+  }
+
+  if (!isset($batch)) {
+    drupal_set_message(t('Content permissions have been rebuilt.'));
+    node_access_needs_rebuild(FALSE);
+    cache_clear_all();
+  }
+}
+
+/**
+ * Batch operation for node_access_rebuild_batch.
+ *
+ * This is a mutlistep operation : we go through all nodes by packs of 20.
+ * The batch processing engine interrupts processing and sends progress
+ * feedback after 1 second execution time.
+ */
+function _node_access_rebuild_batch_operation(&$context) {
+  if (empty($context['sandbox'])) {
+    // Initiate multistep processing.
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['current_node'] = 0;
+    $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
+  }
+
+  // Process the next 20 nodes.
+  $limit = 20;
+  $result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
+  while ($row = db_fetch_array($result)) {
+    $loaded_node = node_load($row['nid'], NULL, TRUE);
+    // To preserve database integrity, only aquire grants if the node
+    // loads successfully.
+    if (!empty($loaded_node)) {
+      node_access_acquire_grants($loaded_node);
+    }
+    $context['sandbox']['progress']++;
+    $context['sandbox']['current_node'] = $loaded_node->nid;
+  }
+
+  // Multistep processing : report progress.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Post-processing for node_access_rebuild_batch.
+ */
+function _node_access_rebuild_batch_finished($success, $results, $operations) {
+  if ($success) {
+    drupal_set_message(t('The content access permissions have been rebuilt.'));
+    node_access_needs_rebuild(FALSE);
+  }
+  else {
+    drupal_set_message(t('The content access permissions have not been properly rebuilt.'), 'error');
+  }
+  cache_clear_all();
+}
+
+/**
+ * @} End of "defgroup node_access".
+ */
+
+
+/**
+ * @defgroup node_content Hook implementations for user-created content types.
+ * @{
+ */
+
+/**
+ * Implementation of hook_access().
+ *
+ * Named so as not to conflict with node_access()
+ */
+function node_content_access($op, $node, $account) {
+  $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
+
+  if ($op == 'create') {
+    return user_access('create '. $type .' content', $account);
+  }
+
+  if ($op == 'update') {
+    if (user_access('edit any '. $type .' content', $account) || (user_access('edit own '. $type .' content', $account) && ($account->uid == $node->uid))) {
+      return TRUE;
+    }
+  }
+
+  if ($op == 'delete') {
+    if (user_access('delete any '. $type .' content', $account) || (user_access('delete own '. $type .' content', $account) && ($account->uid == $node->uid))) {
+      return TRUE;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_form().
+ */
+function node_content_form($node, $form_state) {
+  $type = node_get_types('type', $node);
+  $form = array();
+
+  if ($type->has_title) {
+    $form['title'] = array(
+      '#type' => 'textfield',
+      '#title' => check_plain($type->title_label),
+      '#required' => TRUE,
+      '#default_value' => $node->title,
+      '#maxlength' => 255,
+      '#weight' => -5,
+    );
+  }
+
+  if ($type->has_body) {
+    $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
+  }
+
+  return $form;
+}
+
+/**
+ * @} End of "defgroup node_content".
+ */
+
+/**
+ * Implementation of hook_forms(). All node forms share the same form handler
+ */
+function node_forms() {
+  $forms = array();
+  if ($types = node_get_types()) {
+    foreach (array_keys($types) as $type) {
+      $forms[$type .'_node_form']['callback'] = 'node_form';
+    }
+  }
+  return $forms;
+}
+
+/**
+ * Format the "Submitted by username on date/time" for each node
+ *
+ * @ingroup themeable
+ */
+function theme_node_submitted($node) {
+  return t('Submitted by !username on @datetime',
+    array(
+      '!username' => theme('username', $node),
+      '@datetime' => format_date($node->created),
+    ));
+}
+
+/**
+ * Implementation of hook_hook_info().
+ */
+function node_hook_info() {
+  return array(
+    'node' => array(
+      'nodeapi' => array(
+        'presave' => array(
+          'runs when' => t('When either saving a new post or updating an existing post'),
+        ),
+        'insert' => array(
+          'runs when' => t('After saving a new post'),
+        ),
+        'update' => array(
+          'runs when' => t('After saving an updated post'),
+        ),
+        'delete' => array(
+          'runs when' => t('After deleting a post')
+        ),
+        'view' => array(
+          'runs when' => t('When content is viewed by an authenticated user')
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_action_info().
+ */
+function node_action_info() {
+  return array(
+    'node_publish_action' => array(
+      'type' => 'node',
+      'description' => t('Publish post'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('insert', 'update'),
+      ),
+    ),
+    'node_unpublish_action' => array(
+      'type' => 'node',
+      'description' => t('Unpublish post'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('delete', 'insert', 'update'),
+      ),
+    ),
+    'node_make_sticky_action' => array(
+      'type' => 'node',
+      'description' => t('Make post sticky'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('insert', 'update'),
+      ),
+    ),
+    'node_make_unsticky_action' => array(
+      'type' => 'node',
+      'description' => t('Make post unsticky'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('delete', 'insert', 'update'),
+      ),
+    ),
+    'node_promote_action' => array(
+      'type' => 'node',
+      'description' => t('Promote post to front page'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('insert', 'update'),
+      ),
+    ),
+    'node_unpromote_action' => array(
+      'type' => 'node',
+      'description' => t('Remove post from front page'),
+      'configurable' => FALSE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'nodeapi' => array('presave'),
+        'comment' => array('delete', 'insert', 'update'),
+      ),
+    ),
+    'node_assign_owner_action' => array(
+      'type' => 'node',
+      'description' => t('Change the author of a post'),
+      'configurable' => TRUE,
+      'behavior' => array('changes_node_property'),
+      'hooks' => array(
+        'any' => TRUE,
+        'nodeapi' => array('presave'),
+        'comment' => array('delete', 'insert', 'update'),
+      ),
+    ),
+    'node_save_action' => array(
+      'type' => 'node',
+      'description' => t('Save post'),
+      'configurable' => FALSE,
+      'hooks' => array(
+        'comment' => array('delete', 'insert', 'update'),
+      ),
+    ),
+    'node_unpublish_by_keyword_action' => array(
+      'type' => 'node',
+      'description' => t('Unpublish post containing keyword(s)'),
+      'configurable' => TRUE,
+      'hooks' => array(
+        'nodeapi' => array('presave', 'insert', 'update'),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the status of a node to 1, meaning published.
+ */
+function node_publish_action(&$node, $context = array()) {
+  $node->status = 1;
+  watchdog('action', 'Set @type %title to published.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the status of a node to 0, meaning unpublished.
+ */
+function node_unpublish_action(&$node, $context = array()) {
+  $node->status = 0;
+  watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the sticky-at-top-of-list property of a node to 1.
+ */
+function node_make_sticky_action(&$node, $context = array()) {
+  $node->sticky = 1;
+  watchdog('action', 'Set @type %title to sticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the sticky-at-top-of-list property of a node to 0.
+ */
+function node_make_unsticky_action(&$node, $context = array()) {
+  $node->sticky = 0;
+  watchdog('action', 'Set @type %title to unsticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the promote property of a node to 1.
+ */
+function node_promote_action(&$node, $context = array()) {
+  $node->promote = 1;
+  watchdog('action', 'Promoted @type %title to front page.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Sets the promote property of a node to 0.
+ */
+function node_unpromote_action(&$node, $context = array()) {
+  $node->promote = 0;
+  watchdog('action', 'Removed @type %title from front page.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Saves a node.
+ */
+function node_save_action($node) {
+  node_save($node);
+  watchdog('action', 'Saved @type %title', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+}
+
+/**
+ * Implementation of a configurable Drupal action.
+ * Assigns ownership of a node to a user.
+ */
+function node_assign_owner_action(&$node, $context) {
+  $node->uid = $context['owner_uid'];
+  $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid']));
+  watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_get_types('type', $node), '%title' => $node->title, '%name' => $owner_name));
+}
+
+function node_assign_owner_action_form($context) {
+  $description = t('The username of the user to which you would like to assign ownership.');
+  $count = db_result(db_query("SELECT COUNT(*) FROM {users}"));
+  $owner_name = '';
+  if (isset($context['owner_uid'])) {
+    $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid']));
+  }
+
+  // Use dropdown for fewer than 200 users; textbox for more than that.
+  if (intval($count) < 200) {
+    $options = array();
+    $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
+    while ($data = db_fetch_object($result)) {
+      $options[$data->name] = $data->name;
+    }
+    $form['owner_name'] = array(
+      '#type' => 'select',
+      '#title' => t('Username'),
+      '#default_value' => $owner_name,
+      '#options' => $options,
+      '#description' => $description,
+    );
+  }
+  else {
+    $form['owner_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Username'),
+      '#default_value' => $owner_name,
+      '#autocomplete_path' => 'user/autocomplete',
+      '#size' => '6',
+      '#maxlength' => '7',
+      '#description' => $description,
+    );
+  }
+  return $form;
+}
+
+function node_assign_owner_action_validate($form, $form_state) {
+  $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s'", $form_state['values']['owner_name']));
+  if (intval($count) != 1) {
+    form_set_error('owner_name', t('Please enter a valid username.'));
+  }
+}
+
+function node_assign_owner_action_submit($form, $form_state) {
+  // Username can change, so we need to store the ID, not the username.
+  $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $form_state['values']['owner_name']));
+  return array('owner_uid' => $uid);
+}
+
+function node_unpublish_by_keyword_action_form($context) {
+  $form['keywords'] = array(
+    '#title' => t('Keywords'),
+    '#type' => 'textarea',
+    '#description' => t('The post will be unpublished if it contains any of the character sequences above. Use a comma-separated list of character sequences. Example: funny, bungee jumping, "Company, Inc.". Character sequences are case-sensitive.'),
+    '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
+  );
+  return $form;
+}
+
+function node_unpublish_by_keyword_action_submit($form, $form_state) {
+  return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
+}
+
+/**
+ * Implementation of a configurable Drupal action.
+ * Unpublish a node if it contains a certain string.
+ *
+ * @param $context
+ *   An array providing more information about the context of the call to this action.
+ * @param $comment
+ *   A node object.
+ */
+function node_unpublish_by_keyword_action($node, $context) {
+  foreach ($context['keywords'] as $keyword) {
+    if (strstr(node_view(drupal_clone($node)), $keyword) || strstr($node->title, $keyword)) {
+      $node->status = 0;
+      watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
+      break;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,603 @@
+<?php
+// $Id: node.pages.inc,v 1.28 2008/02/03 19:26:10 goba Exp $
+
+/**
+ * @file
+ * Page callbacks for adding, editing, deleting, and revisions management for content.
+ */
+
+
+/**
+ * Menu callback; presents the node editing form, or redirects to delete confirmation.
+ */
+function node_page_edit($node) {
+  drupal_set_title($node->title);
+  return drupal_get_form($node->type .'_node_form', $node);
+}
+
+function node_add_page() {
+  $item = menu_get_item();
+  $content = system_admin_menu_block($item);
+  return theme('node_add_list', $content);
+}
+
+/**
+ * Display the list of available node types for node creation.
+ *
+ * @ingroup themeable
+ */
+function theme_node_add_list($content) {
+  $output = '';
+
+  if ($content) {
+    $output = '<dl class="node-type-list">';
+    foreach ($content as $item) {
+      $output .= '<dt>'. l($item['title'], $item['href'], $item['options']) .'</dt>';
+      $output .= '<dd>'. filter_xss_admin($item['description']) .'</dd>';
+    }
+    $output .= '</dl>';
+  }
+  return $output;
+}
+
+
+/**
+ * Present a node submission form or a set of links to such forms.
+ */
+function node_add($type) {
+  global $user;
+
+  $types = node_get_types();
+  $type = isset($type) ? str_replace('-', '_', $type) : NULL;
+  // If a node type has been specified, validate its existence.
+  if (isset($types[$type]) && node_access('create', $type)) {
+    // Initialize settings:
+    $node = array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => '');
+
+    drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)));
+    $output = drupal_get_form($type .'_node_form', $node);
+  }
+
+  return $output;
+}
+
+function node_form_validate($form, &$form_state) {
+  node_validate($form_state['values'], $form);
+}
+
+function node_object_prepare(&$node) {
+  // Set up default values, if required.
+  $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
+  // If this is a new node, fill in the default values.
+  if (!isset($node->nid)) {
+    foreach (array('status', 'promote', 'sticky') as $key) {
+      $node->$key = in_array($key, $node_options);
+    }
+    global $user;
+    $node->uid = $user->uid;
+    $node->created = time();
+  }
+  else {
+    $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
+  }
+  // Always use the default revision setting.
+  $node->revision = in_array('revision', $node_options);
+
+  node_invoke($node, 'prepare');
+  node_invoke_nodeapi($node, 'prepare');
+}
+
+/**
+ * Generate the node add/edit form array.
+ */
+function node_form(&$form_state, $node) {
+  global $user;
+
+  if (isset($form_state['node'])) {
+    $node = $form_state['node'] + (array)$node;
+  }
+  if (isset($form_state['node_preview'])) {
+    $form['#prefix'] = $form_state['node_preview'];
+  }
+  $node = (object)$node;
+  foreach (array('body', 'title', 'format') as $key) {
+    if (!isset($node->$key)) {
+      $node->$key = NULL;
+    }
+  }
+  if (!isset($form_state['node_preview'])) {
+    node_object_prepare($node);
+  }
+  else {
+    $node->build_mode = NODE_BUILD_PREVIEW;
+  }
+
+  // Set the id of the top-level form tag
+  $form['#id'] = 'node-form';
+
+  // Basic node information.
+  // These elements are just values so they are not even sent to the client.
+  foreach (array('nid', 'vid', 'uid', 'created', 'type', 'language') as $key) {
+    $form[$key] = array(
+      '#type' => 'value',
+      '#value' => isset($node->$key) ? $node->$key : NULL,
+    );
+  }
+
+  // Changed must be sent to the client, for later overwrite error checking.
+  $form['changed'] = array(
+    '#type' => 'hidden',
+    '#default_value' => isset($node->changed) ? $node->changed : NULL,
+  );
+  // Get the node-specific bits.
+  if ($extra = node_invoke($node, 'form', $form_state)) {
+    $form = array_merge_recursive($form, $extra);
+  }
+  if (!isset($form['title']['#weight'])) {
+    $form['title']['#weight'] = -5;
+  }
+
+  $form['#node'] = $node;
+
+  // Add a log field if the "Create new revision" option is checked, or if the
+  // current user has the ability to check that option.
+  if (!empty($node->revision) || user_access('administer nodes')) {
+    $form['revision_information'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Revision information'),
+      '#collapsible' => TRUE,
+      // Collapsed by default when "Create new revision" is unchecked
+      '#collapsed' => !$node->revision,
+      '#weight' => 20,
+    );
+    $form['revision_information']['revision'] = array(
+      '#access' => user_access('administer nodes'),
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $node->revision,
+    );
+    $form['revision_information']['log'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Log message'),
+      '#rows' => 2,
+      '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'),
+    );
+  }
+
+  // Node author information for administrators
+  $form['author'] = array(
+    '#type' => 'fieldset',
+    '#access' => user_access('administer nodes'),
+    '#title' => t('Authoring information'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => 20,
+  );
+  $form['author']['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Authored by'),
+    '#maxlength' => 60,
+    '#autocomplete_path' => 'user/autocomplete',
+    '#default_value' => $node->name ? $node->name : '',
+    '#weight' => -1,
+    '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+  );
+  $form['author']['date'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Authored on'),
+    '#maxlength' => 25,
+    '#description' => t('Format: %time. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? $node->date : format_date($node->created, 'custom', 'Y-m-d H:i:s O'))),
+  );
+
+  if (isset($node->date)) {
+    $form['author']['date']['#default_value'] = $node->date;
+  }
+
+  // Node options for administrators
+  $form['options'] = array(
+    '#type' => 'fieldset',
+    '#access' => user_access('administer nodes'),
+    '#title' => t('Publishing options'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => 25,
+  );
+  $form['options']['status'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Published'),
+    '#default_value' => $node->status,
+  );
+  $form['options']['promote'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Promoted to front page'),
+    '#default_value' => $node->promote,
+  );
+  $form['options']['sticky'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Sticky at top of lists'),
+    '#default_value' => $node->sticky,
+  );
+
+  // These values are used when the user has no administrator access.
+  foreach (array('uid', 'created') as $key) {
+    $form[$key] = array(
+      '#type' => 'value',
+      '#value' => $node->$key,
+    );
+  }
+
+  // Add the buttons.
+  $form['buttons'] = array();
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#weight' => 5,
+    '#submit' => array('node_form_submit'),
+  );
+  $form['buttons']['preview'] = array(
+    '#type' => 'submit',
+    '#value' => t('Preview'),
+    '#weight' => 10,
+    '#submit' => array('node_form_build_preview'),
+  );
+  if (!empty($node->nid) && node_access('delete', $node)) {
+    $form['buttons']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#weight' => 15,
+      '#submit' => array('node_form_delete_submit'),
+    );
+  }
+  $form['#validate'][] = 'node_form_validate';
+  $form['#theme'] = array($node->type .'_node_form', 'node_form');
+  return $form;
+}
+
+/**
+ * Return a node body field, with format and teaser.
+ */
+function node_body_field(&$node, $label, $word_count) {
+
+  // Check if we need to restore the teaser at the beginning of the body.
+  $include = !isset($node->teaser) || ($node->teaser == substr($node->body, 0, strlen($node->teaser)));
+
+  $form = array(
+    '#after_build' => array('node_teaser_js', 'node_teaser_include_verify'));
+
+  $form['#prefix'] = '<div class="body-field-wrapper">';
+  $form['#suffix'] = '</div>';
+
+  $form['teaser_js'] = array(
+    '#type' => 'textarea',
+    '#rows' => 10,
+    '#teaser' => 'edit-body',
+    '#teaser_checkbox' => 'edit-teaser-include',
+    '#disabled' => TRUE,
+  );
+
+  $form['teaser_include'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Show summary in full view'),
+    '#default_value' => $include,
+    '#prefix' => '<div class="teaser-checkbox">',
+    '#suffix' => '</div>',
+  );
+
+  $form['body'] = array(
+    '#type' => 'textarea',
+    '#title' => check_plain($label),
+    '#default_value' => $include ? $node->body : ($node->teaser . $node->body),
+    '#rows' => 20,
+    '#required' => ($word_count > 0),
+  );
+
+  $form['format'] = filter_form($node->format);
+
+  return $form;
+}
+
+/**
+ * Button sumit function: handle the 'Delete' button on the node form.
+ */
+function node_form_delete_submit($form, &$form_state) {
+  $destination = '';
+  if (isset($_REQUEST['destination'])) {
+    $destination = drupal_get_destination();
+    unset($_REQUEST['destination']);
+  }
+  $node = $form['#node'];
+  $form_state['redirect'] = array('node/'. $node->nid .'/delete', $destination);
+}
+
+
+function node_form_build_preview($form, &$form_state) {
+  $node = node_form_submit_build_node($form, $form_state);
+  $form_state['node_preview'] = node_preview($node);
+}
+
+/**
+ * Present a node submission form.
+ *
+ * @ingroup themeable
+ */
+function theme_node_form($form) {
+  $output = "\n<div class=\"node-form\">\n";
+
+  // Admin form fields and submit buttons must be rendered first, because
+  // they need to go to the bottom of the form, and so should not be part of
+  // the catch-all call to drupal_render().
+  $admin = '';
+  if (isset($form['author'])) {
+    $admin .= "    <div class=\"authored\">\n";
+    $admin .= drupal_render($form['author']);
+    $admin .= "    </div>\n";
+  }
+  if (isset($form['options'])) {
+    $admin .= "    <div class=\"options\">\n";
+    $admin .= drupal_render($form['options']);
+    $admin .= "    </div>\n";
+  }
+  $buttons = drupal_render($form['buttons']);
+
+  // Everything else gets rendered here, and is displayed before the admin form
+  // field and the submit buttons.
+  $output .= "  <div class=\"standard\">\n";
+  $output .= drupal_render($form);
+  $output .= "  </div>\n";
+
+  if (!empty($admin)) {
+    $output .= "  <div class=\"admin\">\n";
+    $output .= $admin;
+    $output .= "  </div>\n";
+  }
+  $output .= $buttons;
+  $output .= "</div>\n";
+
+  return $output;
+}
+
+/**
+ * Generate a node preview.
+ */
+function node_preview($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, because
+      // user ID 0 denotes the anonymous user.
+      if ($user = user_load(array('name' => $node->name))) {
+        $node->uid = $user->uid;
+        $node->picture = $user->picture;
+      }
+      else {
+        $node->uid = 0; // anonymous user
+      }
+    }
+    else if ($node->uid) {
+      $user = user_load(array('uid' => $node->uid));
+      $node->name = $user->name;
+      $node->picture = $user->picture;
+    }
+
+    $node->changed = time();
+
+    // Extract a teaser, if it hasn't been set (e.g. by a module-provided
+    // 'teaser' form item).
+    if (!isset($node->teaser)) {
+      $node->teaser = empty($node->body) ? '' : node_teaser($node->body, $node->format);
+      // Chop off the teaser from the body if needed.
+      if (!$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) {
+        $node->body = substr($node->body, strlen($node->teaser));
+      }
+    }
+
+    // Display a preview of the node.
+    // Previewing alters $node so it needs to be cloned.
+    if (!form_get_errors()) {
+      $cloned_node = drupal_clone($node);
+      $cloned_node->build_mode = NODE_BUILD_PREVIEW;
+      $output = theme('node_preview', $cloned_node);
+    }
+    drupal_set_title(t('Preview'));
+
+    return $output;
+  }
+}
+
+/**
+ * Display a node preview for display during node creation and editing.
+ *
+ * @param $node
+ *   The node object which is being previewed.
+ *
+ * @ingroup themeable
+ */
+function theme_node_preview($node) {
+  $output = '<div class="preview">';
+
+  $preview_trimmed_version = FALSE;
+  // Do we need to preview trimmed version of post as well as full version?
+  if (isset($node->teaser) && isset($node->body)) {
+    $teaser = trim($node->teaser);
+    $body = trim(str_replace('<!--break-->', '', $node->body));
+
+    // Preview trimmed version if teaser and body will appear different;
+    // also (edge case) if both teaser and body have been specified by the user
+    // and are actually the same.
+    if ($teaser != $body || ($body && strpos($node->body, '<!--break-->') === 0)) {
+      $preview_trimmed_version = TRUE;
+    }
+  }
+
+  if ($preview_trimmed_version) {
+    drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
+    $output .= '<h3>'. t('Preview trimmed version') .'</h3>';
+    $output .= node_view(drupal_clone($node), 1, FALSE, 0);
+    $output .= '<h3>'. t('Preview full version') .'</h3>';
+    $output .= node_view($node, 0, FALSE, 0);
+  }
+  else {
+    $output .= node_view($node, 0, FALSE, 0);
+  }
+  $output .= "</div>\n";
+
+  return $output;
+}
+
+function node_form_submit($form, &$form_state) {
+  global $user;
+
+  $node = node_form_submit_build_node($form, $form_state);
+  $insert = empty($node->nid);
+  node_save($node);
+  $node_link = l(t('view'), 'node/'. $node->nid);
+  $watchdog_args = array('@type' => $node->type, '%title' => $node->title);
+  $t_args = array('@type' => node_get_types('name', $node), '%title' => $node->title);
+
+  if ($insert) {
+    watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+    drupal_set_message(t('@type %title has been created.', $t_args));
+  }
+  else {
+    watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+    drupal_set_message(t('@type %title has been updated.', $t_args));
+  }
+  if ($node->nid) {
+    unset($form_state['rebuild']);
+    $form_state['nid'] = $node->nid;
+    $form_state['redirect'] = 'node/'. $node->nid;
+  }
+  else {
+    // In the unlikely case something went wrong on save, the node will be
+    // rebuilt and node form redisplayed the same way as in preview.
+    drupal_set_message(t('The post could not be saved.'), 'error');
+  }
+}
+
+/**
+ * Build a node by processing submitted form values and prepare for a form rebuild.
+ */
+function node_form_submit_build_node($form, &$form_state) {
+  // Unset any button-level handlers, execute all the form-level submit
+  // functions to process the form values into an updated node.
+  unset($form_state['submit_handlers']);
+  form_execute_handlers('submit', $form, $form_state);
+  $node = node_submit($form_state['values']);
+  $form_state['node'] = (array)$node;
+  $form_state['rebuild'] = TRUE;
+  return $node;
+}
+
+/**
+ * Menu callback -- ask for confirmation of node deletion
+ */
+function node_delete_confirm(&$form_state, $node) {
+  $form['nid'] = array(
+    '#type' => 'value',
+    '#value' => $node->nid,
+  );
+
+  return confirm_form($form,
+    t('Are you sure you want to delete %title?', array('%title' => $node->title)),
+    isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid,
+    t('This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Execute node deletion
+ */
+function node_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    node_delete($form_state['values']['nid']);
+  }
+
+  $form_state['redirect'] = '<front>';
+}
+
+/**
+ * Generate an overview table of older revisions of a node.
+ */
+function node_revision_overview($node) {
+  drupal_set_title(t('Revisions for %title', array('%title' => $node->title)));
+
+  $header = array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2));
+
+  $revisions = node_revision_list($node);
+
+  $rows = array();
+  $revert_permission = FALSE;
+  if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
+    $revert_permission = TRUE;
+  }
+  $delete_permission = FALSE;
+  if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
+    $delete_permission = TRUE;
+  }
+  foreach ($revisions as $revision) {
+    $row = array();
+    $operations = array();
+
+    if ($revision->current_vid > 0) {
+      $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"), '!username' => theme('username', $revision)))
+                               . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''),
+                     'class' => 'revision-current');
+      $operations[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => 2);
+    }
+    else {
+      $row[] = t('!date by !username', array('!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"), '!username' => theme('username', $revision)))
+               . (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : '');
+      if ($revert_permission) {
+        $operations[] = l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert");
+      }
+      if ($delete_permission) {
+        $operations[] = l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete");
+      }
+    }
+    $rows[] = array_merge($row, $operations);
+  }
+
+  return theme('table', $header, $rows);
+}
+
+/**
+ * Ask for confirmation of the reversion to prevent against CSRF attacks.
+ */
+function node_revision_revert_confirm($form_state, $node_revision) {
+  $form['#node_revision'] = $node_revision;
+  return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/'. $node_revision->nid .'/revisions', '', t('Revert'), t('Cancel'));
+}
+
+function node_revision_revert_confirm_submit($form, &$form_state) {
+  $node_revision = $form['#node_revision'];
+  $node_revision->revision = 1;
+  $node_revision->log = t('Copy of the revision from %date.', array('%date' => format_date($node_revision->revision_timestamp)));
+  if (module_exists('taxonomy')) {
+    $node_revision->taxonomy = array_keys($node_revision->taxonomy);
+  }
+
+  node_save($node_revision);
+
+  watchdog('content', '@type: reverted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
+  drupal_set_message(t('@type %title has been reverted back to the revision from %revision-date.', array('@type' => node_get_types('name', $node_revision), '%title' => $node_revision->title, '%revision-date' => format_date($node_revision->revision_timestamp))));
+  $form_state['redirect'] = 'node/'. $node_revision->nid .'/revisions';
+}
+
+function node_revision_delete_confirm($form_state, $node_revision) {
+  $form['#node_revision'] = $node_revision;
+  return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/'. $node_revision->nid .'/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
+}
+
+function node_revision_delete_confirm_submit($form, &$form_state) {
+  $node_revision = $form['#node_revision'];
+  db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node_revision->nid, $node_revision->vid);
+  node_invoke_nodeapi($node_revision, 'delete revision');
+  watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
+  drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_get_types('name', $node_revision), '%title' => $node_revision->title)));
+  $form_state['redirect'] = 'node/'. $node_revision->nid;
+  if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node_revision->nid)) > 1) {
+    $form_state['redirect'] .= '/revisions';
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/node/node.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,74 @@
+<?php
+// $Id: node.tpl.php,v 1.4 2008/01/25 21:21:44 goba Exp $
+
+/**
+ * @file node.tpl.php
+ *
+ * Theme implementation to display a node.
+ *
+ * Available variables:
+ * - $title: the (sanitized) title of the node.
+ * - $content: Node body or teaser depending on $teaser flag.
+ * - $picture: The authors picture of the node output from
+ *   theme_user_picture().
+ * - $date: Formatted creation date (use $created to reformat with
+ *   format_date()).
+ * - $links: Themed links like "Read more", "Add new comment", etc. output
+ *   from theme_links().
+ * - $name: Themed username of node author output from theme_user().
+ * - $node_url: Direct url of the current node.
+ * - $terms: the themed list of taxonomy term links output from theme_links().
+ * - $submitted: themed submission information output from
+ *   theme_node_submitted().
+ *
+ * Other variables:
+ * - $node: Full node object. Contains data that may not be safe.
+ * - $type: Node type, i.e. story, page, blog, etc.
+ * - $comment_count: Number of comments attached to the node.
+ * - $uid: User ID of the node author.
+ * - $created: Time the node was published formatted in Unix timestamp.
+ * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in
+ *   teaser listings.
+ * - $id: Position of the node. Increments each time it's output.
+ *
+ * Node status variables:
+ * - $teaser: Flag for the teaser state.
+ * - $page: Flag for the full page state.
+ * - $promote: Flag for front page promotion state.
+ * - $sticky: Flags for sticky post setting.
+ * - $status: Flag for published status.
+ * - $comment: State of comment settings for the node.
+ * - $readmore: Flags true if the teaser content of the node cannot hold the
+ *   main body content.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_node()
+ */
+?>
+<div id="node-<?php print $node->nid; ?>" class="node<?php if ($sticky) { print ' sticky'; } ?><?php if (!$status) { print ' node-unpublished'; } ?> clear-block">
+
+<?php print $picture ?>
+
+<?php if (!$page): ?>
+  <h2><a href="<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
+<?php endif; ?>
+
+  <div class="meta">
+  <?php if ($submitted): ?>
+    <span class="submitted"><?php print $submitted ?></span>
+  <?php endif; ?>
+
+  <?php if ($terms): ?>
+    <div class="terms terms-inline"><?php print $terms ?></div>
+  <?php endif;?>
+  </div>
+
+  <div class="content">
+    <?php print $content ?>
+  </div>
+
+  <?php print $links; ?>
+</div>
\ No newline at end of file
Binary file modules/openid/login-bg.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,43 @@
+/* $Id: openid.css,v 1.5 2008/01/30 22:11:22 goba Exp $ */
+
+#edit-openid-identifier {
+  background-image: url("login-bg.png");
+  background-position: 0% 50%;
+  background-repeat: no-repeat;
+  padding-left: 20px;
+}
+
+div#edit-openid-identifier-wrapper {
+  display: block;
+}
+
+html.js #user-login-form div#edit-openid-identifier-wrapper,
+html.js #user-login div#edit-openid-identifier-wrapper {
+  display: none;
+}
+
+html.js #user-login-form li.openid-link,
+html.js #user-login li.openid-link {
+  display : block;
+}
+
+#user-login-form ul {
+  margin-top: 0;
+}
+
+#user-login-form li.openid-link,
+#user-login-form li.user-link,
+#user-login li.openid-link,
+#user-login li.user-link {
+  display: none;
+}
+
+#user-login-form li.openid-link,
+#user-login-form li.user-link {
+  text-align : left;
+}
+
+#user-login-form li.openid-link,
+#user-login li.openid-link {
+  background: transparent url(login-bg.png) no-repeat scroll 1px 0.35em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,439 @@
+<?php
+// $Id: openid.inc,v 1.8 2008/01/30 22:11:22 goba Exp $
+
+/**
+ * @file
+ * OpenID utility functions.
+ */
+
+// Diffie-Hellman Key Exchange Default Value.
+define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
+       '966915404479707795314057629378541917580651227423698188993727816152646631'.
+       '438561595825688188889951272158842675419950341258706556549803580104870537'.
+       '681476726513255747040765857479291291572334510643245094715007229621094194'.
+       '349783925984760375594985848253359305585439638443');
+
+// Constants for Diffie-Hellman key exchange computations.
+define('OPENID_DH_DEFAULT_GEN', '2');
+define('OPENID_SHA1_BLOCKSIZE', 64);
+define('OPENID_RAND_SOURCE', '/dev/urandom');
+
+// OpenID namespace URLs
+define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
+define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
+define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
+
+/**
+ * Performs an HTTP 302 redirect (for the 1.x protocol).
+ */
+function openid_redirect_http($url, $message) {
+  $query = array();
+  foreach ($message as $key => $val) {
+    $query[] = $key .'='. urlencode($val);
+  }
+
+  $sep = (strpos($url, '?') === FALSE) ? '?' : '&';
+  header('Location: '. $url . $sep . implode('&', $query), TRUE, 302);
+  exit;
+}
+
+/**
+ * Creates a js auto-submit redirect for (for the 2.x protocol)
+ */
+function openid_redirect($url, $message) {
+  $output = '<html><head><title>'. t('OpenID redirect') ."</title></head>\n<body>";
+  $output .= drupal_get_form('openid_redirect_form', $url, $message);
+  $output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>';
+  $output .= "</body></html>\n";
+  print $output;
+  exit;
+}
+
+function openid_redirect_form(&$form_state, $url, $message) {
+  $form = array();
+  $form['#action'] = $url;
+  $form['#method'] = "post";
+  foreach ($message as $key => $value) {
+    $form[$key] = array(
+      '#type' => 'hidden',
+      '#name' => $key,
+      '#value' => $value,
+    );
+  }
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#prefix' => '<noscript>',
+    '#suffix' => '</noscript>',
+    '#value' => t('Send'),
+  );
+
+  return $form;
+}
+
+/**
+ * Determine if the given identifier is an XRI ID.
+ */
+function _openid_is_xri($identifier) {
+  $firstchar = substr($identifier, 0, 1);
+  if ($firstchar == "@" || $firstchar == "=")
+    return TRUE;
+
+  if (stristr($identifier, 'xri://') !== FALSE) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Normalize the given identifier as per spec.
+ */
+function _openid_normalize($identifier) {
+  if (_openid_is_xri($identifier)) {
+    return _openid_normalize_xri($identifier);
+  }
+  else {
+    return _openid_normalize_url($identifier);
+  }
+}
+
+function _openid_normalize_xri($xri) {
+  $normalized_xri = $xri;
+  if (stristr($xri, 'xri://') !== FALSE) {
+    $normalized_xri = substr($xri, 6);
+  }
+  return $normalized_xri;
+}
+
+function _openid_normalize_url($url) {
+  $normalized_url = $url;
+
+  if (stristr($url, '://') === FALSE) {
+    $normalized_url = 'http://'. $url;
+  }
+
+  if (substr_count($normalized_url, '/') < 3) {
+    $normalized_url .= '/';
+  }
+
+  return $normalized_url;
+}
+
+/**
+ * Create a serialized message packet as per spec: $key:$value\n .
+ */
+function _openid_create_message($data) {
+  $serialized = '';
+
+  foreach ($data as $key => $value) {
+    if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
+      return null;
+    }
+    $serialized .= "$key:$value\n";
+  }
+  return $serialized;
+}
+
+/**
+ * Encode a message from _openid_create_message for HTTP Post
+ */
+function _openid_encode_message($message) {
+  $encoded_message = '';
+
+  $items = explode("\n", $message);
+  foreach ($items as $item) {
+    $parts = explode(':', $item, 2);
+
+    if (count($parts) == 2) {
+      if ($encoded_message != '') {
+        $encoded_message .= '&';
+      }
+      $encoded_message .= rawurlencode(trim($parts[0])) .'='. rawurlencode(trim($parts[1]));
+    }
+  }
+
+  return $encoded_message;
+}
+
+/**
+ * Convert a direct communication message
+ * into an associative array.
+ */
+function _openid_parse_message($message) {
+  $parsed_message = array();
+
+  $items = explode("\n", $message);
+  foreach ($items as $item) {
+    $parts = explode(':', $item, 2);
+
+    if (count($parts) == 2) {
+      $parsed_message[$parts[0]] = $parts[1];
+    }
+  }
+
+  return $parsed_message;
+}
+
+/**
+ * Return a nonce value - formatted per OpenID spec.
+ */
+function _openid_nonce() {
+  // YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
+  return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') .
+    chr(mt_rand(0, 25) + 65) .
+    chr(mt_rand(0, 25) + 65) .
+    chr(mt_rand(0, 25) + 65) .
+    chr(mt_rand(0, 25) + 65);
+}
+
+/**
+ * Pull the href attribute out of an html link element.
+ */
+function _openid_link_href($rel, $html) {
+  $rel = preg_quote($rel);
+  preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches);
+  if (isset($matches[3])) {
+    preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
+    return trim($href[1]);
+  }
+  return FALSE;
+}
+
+/**
+ * Pull the http-equiv attribute out of an html meta element
+ */
+function _openid_meta_httpequiv($equiv, $html) {
+  preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches);
+  if (isset($matches[1])) {
+    preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
+    if (isset($content[1])) {
+      return $content[1];
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Sign certain keys in a message
+ * @param $association - object loaded from openid_association or openid_server_association table
+ *              - important fields are ->assoc_type and ->mac_key
+ * @param $message_array - array of entire message about to be sent
+ * @param $keys_to_sign - keys in the message to include in signature (without
+ *  'openid.' appended)
+ */
+function _openid_signature($association, $message_array, $keys_to_sign) {
+  $signature = '';
+  $sign_data = array();
+
+  foreach ($keys_to_sign as $key) {
+    if (isset($message_array['openid.'. $key])) {
+      $sign_data[$key] = $message_array['openid.'. $key];
+    }
+  }
+
+  $message = _openid_create_message($sign_data);
+  $secret = base64_decode($association->mac_key);
+  $signature = _openid_hmac($secret, $message);
+
+  return base64_encode($signature);
+}
+
+function _openid_hmac($key, $text) {
+  if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
+    $key = _openid_sha1($key, true);
+  }
+
+  $key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
+  $ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
+  $opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
+  $hash1 = _openid_sha1(($key ^ $ipad) . $text, true);
+  $hmac = _openid_sha1(($key ^ $opad) . $hash1, true);
+
+  return $hmac;
+}
+
+function _openid_sha1($text) {
+  $hex = sha1($text);
+  $raw = '';
+  for ($i = 0; $i < 40; $i += 2) {
+    $hexcode = substr($hex, $i, 2);
+    $charcode = (int)base_convert($hexcode, 16, 10);
+    $raw .= chr($charcode);
+  }
+  return $raw;
+}
+
+function _openid_dh_base64_to_long($str) {
+  $b64 = base64_decode($str);
+
+  return _openid_dh_binary_to_long($b64);
+}
+
+function _openid_dh_long_to_base64($str) {
+  return base64_encode(_openid_dh_long_to_binary($str));
+}
+
+function _openid_dh_binary_to_long($str) {
+  $bytes = array_merge(unpack('C*', $str));
+
+  $n = 0;
+  foreach ($bytes as $byte) {
+    $n = bcmul($n, pow(2, 8));
+    $n = bcadd($n, $byte);
+  }
+
+  return $n;
+}
+
+function _openid_dh_long_to_binary($long) {
+  $cmp = bccomp($long, 0);
+  if ($cmp < 0) {
+    return FALSE;
+  }
+
+  if ($cmp == 0) {
+    return "\x00";
+  }
+
+  $bytes = array();
+
+  while (bccomp($long, 0) > 0) {
+    array_unshift($bytes, bcmod($long, 256));
+    $long = bcdiv($long, pow(2, 8));
+  }
+
+  if ($bytes && ($bytes[0] > 127)) {
+    array_unshift($bytes, 0);
+  }
+
+  $string = '';
+  foreach ($bytes as $byte) {
+    $string .= pack('C', $byte);
+  }
+
+  return $string;
+}
+
+function _openid_dh_xorsecret($shared, $secret) {
+  $dh_shared_str = _openid_dh_long_to_binary($shared);
+  $sha1_dh_shared = _openid_sha1($dh_shared_str);
+  $xsecret = "";
+  for ($i = 0; $i < strlen($secret); $i++) {
+    $xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
+  }
+
+  return $xsecret;
+}
+
+function _openid_dh_rand($stop) {
+  static $duplicate_cache = array();
+
+  // Used as the key for the duplicate cache
+  $rbytes = _openid_dh_long_to_binary($stop);
+
+  if (array_key_exists($rbytes, $duplicate_cache)) {
+    list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+  }
+  else {
+    if ($rbytes[0] == "\x00") {
+      $nbytes = strlen($rbytes) - 1;
+    }
+    else {
+      $nbytes = strlen($rbytes);
+    }
+
+    $mxrand = bcpow(256, $nbytes);
+
+    // If we get a number less than this, then it is in the
+    // duplicated range.
+    $duplicate = bcmod($mxrand, $stop);
+
+    if (count($duplicate_cache) > 10) {
+      $duplicate_cache = array();
+    }
+
+    $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+  }
+
+  do {
+    $bytes = "\x00". _openid_get_bytes($nbytes);
+    $n = _openid_dh_binary_to_long($bytes);
+    // Keep looping if this value is in the low duplicated range.
+  } while (bccomp($n, $duplicate) < 0);
+
+  return bcmod($n, $stop);
+}
+
+function _openid_get_bytes($num_bytes) {
+  static $f = null;
+  $bytes = '';
+  if (!isset($f)) {
+    $f = @fopen(OPENID_RAND_SOURCE, "r");
+  }
+  if (!$f) {
+    // pseudorandom used
+    $bytes = '';
+    for ($i = 0; $i < $num_bytes; $i += 4) {
+      $bytes .= pack('L', mt_rand());
+    }
+    $bytes = substr($bytes, 0, $num_bytes);
+  }
+  else {
+    $bytes = fread($f, $num_bytes);
+  }
+  return $bytes;
+}
+
+function _openid_response($str = NULL) {
+  $data = array();
+  
+  if (isset($_SERVER['REQUEST_METHOD'])) {
+    $data = _openid_get_params($_SERVER['QUERY_STRING']);
+
+    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+      $str = file_get_contents('php://input');
+
+      $post = array();
+      if ($str !== false) {
+        $post = _openid_get_params($str);
+      }
+
+      $data = array_merge($data, $post);
+    }
+  }
+
+  return $data;
+}
+
+function _openid_get_params($str) {
+  $chunks = explode("&", $str);
+
+  $data = array();
+  foreach ($chunks as $chunk) {
+    $parts = explode("=", $chunk, 2);
+
+    if (count($parts) == 2) {
+      list($k, $v) = $parts;
+      $data[$k] = urldecode($v);
+    }
+  }
+  return $data;
+}
+
+/**
+ * Provide bcpowmod support for PHP4.
+ */
+if (!function_exists('bcpowmod')) {
+  function bcpowmod($base, $exp, $mod) {
+    $square = bcmod($base, $mod);
+    $result = 1;
+    while (bccomp($exp, 0) > 0) {
+      if (bcmod($exp, 2)) {
+        $result = bcmod(bcmul($result, $square), $mod);
+      }
+      $square = bcmod(bcmul($square, $square), $mod);
+      $exp = bcdiv($exp, 2);
+    }
+    return $result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: openid.info,v 1.2 2007/06/22 22:26:43 unconed Exp $
+name = OpenID
+description = "Allows users to log into your site using OpenID."
+version = VERSION
+package = Core - optional
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,70 @@
+<?php
+// $Id: openid.install,v 1.3 2007/10/10 11:39:33 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function openid_install() {
+  // Create table.
+  drupal_install_schema('openid');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function openid_uninstall() {
+  // Remove table.
+  drupal_uninstall_schema('openid');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function openid_schema() {
+  $schema['openid_association'] = array(
+    'description' => t('Stores temporary shared key association information for OpenID authentication.'),
+    'fields' => array(
+      'idp_endpoint_uri' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'description' => t('URI of the OpenID Provider endpoint.'),
+      ),
+      'assoc_handle' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Used to refer to this association in subsequent messages.'),
+      ),
+      'assoc_type' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'description' => t('The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.'),
+      ),
+      'session_type' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'description' => t('Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".'),
+      ),
+      'mac_key' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'description' => t('The MAC key (shared secret) for this association.'),
+      ),
+      'created' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('UNIX timestamp for when the association was created.'),
+      ),
+      'expires_in' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The lifetime, in seconds, of this association.'),
+      ),
+    ),
+    'primary key' => array('assoc_handle'),
+  );
+
+  return $schema;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,38 @@
+// $Id: openid.js,v 1.6 2008/01/30 22:11:22 goba Exp $
+
+Drupal.behaviors.openid = function (context) {
+  var $loginElements = $("#edit-name-wrapper, #edit-pass-wrapper, li.openid-link");
+  var $openidElements = $("#edit-openid-identifier-wrapper, li.user-link");
+
+  // This behavior attaches by ID, so is only valid once on a page.
+  if (!$("#edit-openid-identifier.openid-processed").size() && $("#edit-openid-identifier").val()) {
+    $("#edit-openid-identifier").addClass('openid-processed');
+    $loginElements.hide();
+    // Use .css("display", "block") instead of .show() to be Konqueror friendly.
+    $openidElements.css("display", "block");
+  }
+  $("li.openid-link:not(.openid-processed)", context)
+    .addClass('openid-processed')
+    .click( function() {
+       $loginElements.hide();
+       $openidElements.css("display", "block");
+      // Remove possible error message.
+      $("#edit-name, #edit-pass").removeClass("error");
+      $("div.messages.error").hide();
+      // Set focus on OpenID Identifier field.
+      $("#edit-openid-identifier")[0].focus();
+      return false;
+    });
+  $("li.user-link:not(.openid-processed)", context)
+    .addClass('openid-processed')
+    .click(function() {
+       $openidElements.hide();
+       $loginElements.css("display", "block");
+      // Clear OpenID Identifier field and remove possible error message.
+      $("#edit-openid-identifier").val('').removeClass("error");
+      $("div.messages.error").css("display", "block");
+      // Set focus on username field.
+      $("#edit-name")[0].focus();
+      return false;
+    });
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,529 @@
+<?php
+// $Id: openid.module,v 1.19 2008/01/30 22:11:22 goba Exp $
+
+/**
+ * @file
+ * Implement OpenID Relying Party support for Drupal
+ */
+
+/**
+ * Implementation of hook_menu.
+ */
+function openid_menu() {
+  $items['openid/authenticate'] = array(
+    'title' => 'OpenID Login',
+    'page callback' => 'openid_authentication_page',
+    'access callback' => 'user_is_anonymous',
+    'type' => MENU_CALLBACK,
+    'file' => 'openid.pages.inc',
+  );
+  $items['user/%user/openid'] = array(
+    'title' => 'OpenID identities',
+    'page callback' => 'openid_user_identities',
+    'page arguments' => array(1),
+    'access callback' => 'user_edit_access',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'openid.pages.inc',
+  );
+  $items['user/%user/openid/delete'] = array(
+    'title' => 'Delete OpenID',
+    'page callback' => 'openid_user_delete',
+    'page arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'file' => 'openid.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function openid_help($path, $arg) {
+  switch ($path) {
+
+  case 'user/%/openid':
+      $output = '<p>'. t('This site supports <a href="@openid-net">OpenID</a>, a secure way to log into many websites using a single username and password. OpenID can reduce the necessity of managing many usernames and passwords for many websites.', array('@openid-net' => url('http://openid.net'))) .'</p>';
+      $output .= '<p>'. t('To use OpenID you must first establish an identity on a public or private OpenID server. If you do not have an OpenID and would like one, look into one of the <a href="@openid-providers">free public providers</a>. You can find out more about OpenID at <a href="@openid-net">this website</a>.', array('@openid-providers' => url('http://openid.net/wiki/index.php/OpenIDServers'), '@openid-net' => url('http://openid.net'))) .'</p>';
+      $output .= '<p>'. t('If you already have an OpenID, enter the URL to your OpenID server below (e.g. myusername.openidprovider.com). Next time you login, you will be able to use this URL instead of a regular username and password. You can have multiple OpenID servers if you like; just keep adding them here.') .'</p>';
+      return $output;
+
+    case 'admin/help#openid':
+      $output = '<p>'. t('OpenID is a secure method for logging into many websites with a single username and password. It does not require special software, and it does not share passwords with any site to which it is associated; including your site.') .'</p>';
+      $output .= '<p>'. t('Users can create accounts using their OpenID, assign one or more OpenIDs to an existing account, and log in using an OpenID. This lowers the barrier to registration, which is good for the site, and offers convenience and security to the users. OpenID is not a trust system, so email verification is still necessary. The benefit stems from the fact that users can have a single password that they can use on many websites. This means they can easily update their single password from a centralized location, rather than having to change dozens of passwords individually.') .'</p>';
+      $output .= '<p>'. t('The basic concept is as follows: A user has an account on an OpenID server. This account provides them with a unique URL (such as myusername.openidprovider.com). When the user comes to your site, they are presented with the option of entering this URL. Your site then communicates with the OpenID server, asking it to verify the identity of the user. If the user is logged into their OpenID server, the server communicates back to your site, verifying the user. If they are not logged in, the OpenID server will ask the user for their password. At no point does your site record, or need to record the user\'s password.') .'</p>';
+      $output .= '<p>'. t('More information on OpenID is available at <a href="@openid-net">OpenID.net</a>.', array('@openid-net' => url('http://openid.net'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@handbook">OpenID module</a>.', array('@handbook' => 'http://drupal.org/handbook/modules/openid')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function openid_user($op, &$edit, &$account, $category = NULL) {
+  if ($op == 'insert' && isset($_SESSION['openid']['values'])) {
+    // The user has registered after trying to login via OpenID.
+    if (variable_get('user_email_verification', TRUE)) {
+      drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
+    }
+    unset($_SESSION['openid']);
+  }
+}
+
+/**
+ * Implementation of hook_form_alter : adds OpenID login to the login forms.
+ */
+function openid_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'user_login_block' || $form_id == 'user_login') {
+    drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
+    drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js');
+    if (!empty($form_state['post']['openid_identifier'])) {
+      $form['name']['#required'] = FALSE;
+      $form['pass']['#required'] = FALSE;
+      unset($form['#submit']);
+      $form['#validate'] = array('openid_login_validate');
+    }
+
+    $items = array();
+    $items[] = array(
+      'data' => l(t('Log in using OpenID'), '#'),
+      'class' => 'openid-link',
+    );
+    $items[] = array(
+      'data' => l(t('Cancel OpenID login'), '#'),
+      'class' => 'user-link',
+    );
+    
+    $form['openid_links'] = array(
+      '#value' => theme('item_list', $items),
+      '#weight' => 1,
+    );
+
+    $form['links']['#weight'] = 2;
+
+    $form['openid_identifier'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Log in using OpenID'),
+      '#size' => ($form_id == 'user_login') ? 58 : 13,
+      '#maxlength' => 255,
+      '#weight' => -1,
+      '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)),
+    );
+    $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => drupal_get_destination())));
+  }
+  elseif ($form_id == 'user_register' && isset($_SESSION['openid'])) {
+    // We were unable to auto-register a new user. Prefill the registration
+    // form with the values we have.
+    $form['name']['#default_value'] = $_SESSION['openid']['values']['name'];
+    $form['mail']['#default_value'] = $_SESSION['openid']['values']['mail'];
+    // If user_email_verification is off, hide the password field and just fill
+    // with random password to avoid confusion.
+    if (!variable_get('user_email_verification', TRUE)) {
+      $form['pass']['#type'] = 'hidden';
+      $form['pass']['#value'] = user_password();
+    }
+    $form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']);
+  }
+  return $form;
+}
+
+/**
+ * Login form _validate hook
+ */
+function openid_login_validate($form, &$form_state) {
+  $return_to = $form_state['values']['openid.return_to'];
+  if (empty($return_to)) {
+    $return_to = url('', array('absolute' => TRUE));
+  }
+
+  openid_begin($form_state['values']['openid_identifier'], $return_to, $form_state['values']);
+}
+
+/**
+ * The initial step of OpenID authentication responsible for the following:
+ *  - Perform discovery on the claimed OpenID.
+ *  - If possible, create an association with the Provider's endpoint.
+ *  - Create the authentication request.
+ *  - Perform the appropriate redirect.
+ *
+ * @param $claimed_id The OpenID to authenticate
+ * @param $return_to The endpoint to return to from the OpenID Provider
+ */
+function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
+  module_load_include('inc', 'openid');
+
+  $claimed_id = _openid_normalize($claimed_id);
+
+  $services = openid_discovery($claimed_id);
+  if (count($services) == 0) {
+    form_set_error('openid_identifier', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
+    return;
+  }
+
+  // Store discovered information in the users' session so we don't have to rediscover.
+  $_SESSION['openid']['service'] = $services[0];
+  // Store the claimed id
+  $_SESSION['openid']['claimed_id'] = $claimed_id;
+  // Store the login form values so we can pass them to
+  // user_exteral_login later.
+  $_SESSION['openid']['user_login_values'] = $form_values;
+
+  $op_endpoint = $services[0]['uri'];
+  // If bcmath is present, then create an association
+  $assoc_handle = '';
+  if (function_exists('bcadd')) {
+    $assoc_handle = openid_association($op_endpoint);
+  }
+
+  // Now that there is an association created, move on
+  // to request authentication from the IdP
+  // First check for LocalID. If not found, check for Delegate. Fall
+  // back to $claimed_id if neither is found.
+  if (!empty($services[0]['localid'])) {
+    $identity = $services[0]['localid'];
+  }
+  else if (!empty($services[0]['delegate'])) {
+    $identity = $services[0]['delegate'];
+  }
+  else {
+    $identity = $claimed_id;
+  }
+
+  if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
+    $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
+  }  
+  $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
+
+  if ($services[0]['version'] == 2) {
+    openid_redirect($op_endpoint, $authn_request);
+  }
+  else {
+    openid_redirect_http($op_endpoint, $authn_request);
+  }
+}
+
+/**
+ * Completes OpenID authentication by validating returned data from the OpenID
+ * Provider.
+ *
+ * @param $response Array of returned values from the OpenID Provider.
+ *
+ * @return $response Response values for further processing with
+ *   $response['status'] set to one of 'success', 'failed' or 'cancel'.
+ */
+function openid_complete($response = array()) {
+  module_load_include('inc', 'openid');
+
+  if (count($response) == 0) {
+    $response = _openid_response();
+  }
+  
+  // Default to failed response
+  $response['status'] = 'failed';
+  if (isset($_SESSION['openid']['service']['uri']) && isset($_SESSION['openid']['claimed_id'])) {
+    $service = $_SESSION['openid']['service'];
+    $claimed_id = $_SESSION['openid']['claimed_id'];
+    unset($_SESSION['openid']['service']);
+    unset($_SESSION['openid']['claimed_id']);
+    if (isset($response['openid.mode'])) {
+      if ($response['openid.mode'] == 'cancel') {
+        $response['status'] = 'cancel';
+      }
+      else {
+        if (openid_verify_assertion($service['uri'], $response)) {
+          // If the returned claimed_id is different from the session claimed_id,
+          // then we need to do discovery and make sure the op_endpoint matches.
+          if ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) {
+            $disco = openid_discovery($response['openid.claimed_id']);
+            if ($disco[0]['uri'] != $service['uri']) {
+              return $response;
+            }
+          }
+          else {
+            $response['openid.claimed_id'] = $claimed_id;
+          }
+          $response['status'] = 'success';
+        }
+      }
+    }
+  }
+  return $response;
+}
+
+/**
+ * Perform discovery on a claimed ID to determine the OpenID provider endpoint.
+ *
+ * @param $claimed_id The OpenID URL to perform discovery on.
+ *
+ * @return Array of services discovered (including OpenID version, endpoint
+ * URI, etc).
+ */
+function openid_discovery($claimed_id) {
+  module_load_include('inc', 'openid');
+  module_load_include('inc', 'openid', 'xrds');
+
+  $services = array();
+
+  $xrds_url = $claimed_id;
+  if (_openid_is_xri($claimed_id)) {
+    $xrds_url = 'http://xri.net/'. $claimed_id;
+  }
+  $url = @parse_url($xrds_url);
+  if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
+    // For regular URLs, try Yadis resolution first, then HTML-based discovery
+    $headers = array('Accept' => 'application/xrds+xml');
+    $result = drupal_http_request($xrds_url, $headers);
+
+    if (!isset($result->error)) {
+      if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
+        // Parse XML document to find URL
+        $services = xrds_parse($result->data);
+      }
+      else {
+        $xrds_url = NULL;
+        if (isset($result->headers['X-XRDS-Location'])) {
+          $xrds_url = $result->headers['X-XRDS-Location'];
+        }
+        else {
+          // Look for meta http-equiv link in HTML head
+          $xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
+        }
+        if (!empty($xrds_url)) {
+          $headers = array('Accept' => 'application/xrds+xml');
+          $xrds_result = drupal_http_request($xrds_url, $headers);
+          if (!isset($xrds_result->error)) {
+            $services = xrds_parse($xrds_result->data);
+          }
+        }
+      }
+
+      // Check for HTML delegation
+      if (count($services) == 0) {
+        // Look for 2.0 links
+        $uri = _openid_link_href('openid2.provider', $result->data);
+        $delegate = _openid_link_href('openid2.local_id', $result->data);
+        $version = 2;
+
+        // 1.0 links
+        if (empty($uri)) {
+          $uri = _openid_link_href('openid.server', $result->data);
+          $delegate = _openid_link_href('openid.delegate', $result->data);
+          $version = 1;
+        }
+        if (!empty($uri)) {
+          $services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version);
+        }
+      }
+    }
+  }
+  if (!$services) {
+    module_invoke('system', 'check_http_request');
+  }
+  return $services;
+}
+
+/**
+ * Attempt to create a shared secret with the OpenID Provider.
+ *
+ * @param $op_endpoint URL of the OpenID Provider endpoint.
+ *
+ * @return $assoc_handle The association handle.
+ */
+function openid_association($op_endpoint) {
+  module_load_include('inc', 'openid');
+
+  // Remove Old Associations:
+  db_query("DELETE FROM {openid_association} WHERE created + expires_in < %d", time());
+
+  // Check to see if we have an association for this IdP already
+  $assoc_handle = db_result(db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = '%s'", $op_endpoint));
+  if (empty($assoc_handle)) {
+    $mod = OPENID_DH_DEFAULT_MOD;
+    $gen = OPENID_DH_DEFAULT_GEN;
+    $r = _openid_dh_rand($mod);
+    $private = bcadd($r, 1);
+    $public = bcpowmod($gen, $private, $mod);
+
+    // If there is no existing association, then request one
+    $assoc_request = openid_association_request($public);
+    $assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
+    $assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
+    $assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message);
+    if (isset($assoc_result->error)) {
+      module_invoke('system', 'check_http_request');
+      return FALSE;
+    }
+
+    $assoc_response = _openid_parse_message($assoc_result->data);
+    if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') {
+      module_invoke('system', 'check_http_request');
+      return FALSE;
+    }
+
+    if ($assoc_response['session_type'] == 'DH-SHA1') {
+      $spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']);
+      $enc_mac_key = base64_decode($assoc_response['enc_mac_key']);
+      $shared = bcpowmod($spub, $private, $mod);
+      $assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key));
+    }
+    db_query("INSERT INTO {openid_association} (idp_endpoint_uri, session_type, assoc_handle, assoc_type, expires_in, mac_key, created) VALUES('%s', '%s', '%s', '%s', %d, '%s', %d)",
+             $op_endpoint, $assoc_response['session_type'], $assoc_response['assoc_handle'], $assoc_response['assoc_type'], $assoc_response['expires_in'], $assoc_response['mac_key'], time());
+
+    $assoc_handle = $assoc_response['assoc_handle'];
+  }
+
+  return $assoc_handle;
+}
+
+/**
+ * Authenticate a user or attempt registration.
+ *
+ * @param $response Response values from the OpenID Provider.
+ */
+function openid_authentication($response) {
+  module_load_include('inc', 'openid');
+
+  $identity = $response['openid.claimed_id'];
+
+  $account = user_external_load($identity);
+  if (isset($account->uid)) {
+    if (!variable_get('user_email_verification', TRUE) || $account->login) {
+      user_external_login($account, $_SESSION['openid']['user_login_values']);
+    }
+    else {
+      drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
+    }
+  }
+  elseif (variable_get('user_register', 1)) {
+    // Register new user
+    $form_state['redirect'] = NULL;
+    $form_state['values']['name'] = (empty($response['openid.sreg.nickname'])) ? $identity : $response['openid.sreg.nickname'];
+    $form_state['values']['mail'] = (empty($response['openid.sreg.email'])) ? '' : $response['openid.sreg.email'];
+    $form_state['values']['pass']  = user_password();
+    $form_state['values']['status'] = variable_get('user_register', 1) == 1;
+    $form_state['values']['response'] = $response;
+    $form_state['values']['auth_openid'] = $identity;
+    $form = drupal_retrieve_form('user_register', $form_state);
+    drupal_prepare_form('user_register', $form, $form_state);
+    drupal_validate_form('user_register', $form, $form_state);
+    if (form_get_errors()) {
+      // We were unable to register a valid new user, redirect to standard
+      // user/register and prefill with the values we received.
+      drupal_set_message(t('OpenID registration failed for the reasons listed. You may register now, or if you already have an account you can <a href="@login">log in</a> now and add your OpenID under "My Account"', array('@login' => url('user/login'))), 'error');
+      $_SESSION['openid']['values'] = $form_state['values'];
+      // We'll want to redirect back to the same place.
+      $destination = drupal_get_destination();
+      unset($_REQUEST['destination']);
+      drupal_goto('user/register', $destination);
+    }
+    else {
+      unset($form_state['values']['response']);
+      $account = user_save('', $form_state['values']);
+      // Terminate if an error occured during user_save().
+      if (!$account) {
+        drupal_set_message(t("Error saving user account."), 'error');
+        drupal_goto();
+      }
+      user_external_login($account);
+    }
+    drupal_redirect_form($form, $form_state['redirect']);
+  }
+  else {
+    drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
+  }
+  drupal_goto();
+}
+
+function openid_association_request($public) {
+  module_load_include('inc', 'openid');
+
+  $request = array(
+    'openid.ns' => OPENID_NS_2_0,
+    'openid.mode' => 'associate',
+    'openid.session_type' => 'DH-SHA1',
+    'openid.assoc_type' => 'HMAC-SHA1'
+  );
+
+  if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') {
+    $cpub = _openid_dh_long_to_base64($public);
+    $request['openid.dh_consumer_public'] = $cpub;
+  }
+
+  return $request;
+}
+
+function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
+  module_load_include('inc', 'openid');
+
+  $ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0;
+  $request =  array(
+    'openid.ns' => $ns,
+    'openid.mode' => 'checkid_setup',
+    'openid.identity' => $identity,
+    'openid.claimed_id' => $claimed_id,
+    'openid.assoc_handle' => $assoc_handle,
+    'openid.return_to' => $return_to,
+  );
+
+  if ($version == 2) {
+    $request['openid.realm'] = url('', array('absolute' => TRUE));
+  }
+  else {
+    $request['openid.trust_root'] = $realm;
+  }
+
+  // Simple Registration
+  $request['openid.sreg.required'] = 'nickname,email';
+  $request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
+
+  $request = array_merge($request, module_invoke_all('openid', 'request', $request));
+
+  return $request;
+}
+
+/**
+ * Attempt to verify the response received from the OpenID Provider.
+ *
+ * @param $op_endpoint The OpenID Provider URL.
+ * @param $response Array of repsonse values from the provider.
+ *
+ * @return boolean
+ */
+function openid_verify_assertion($op_endpoint, $response) {
+  module_load_include('inc', 'openid');
+
+  $valid = FALSE;
+
+  $association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE assoc_handle = '%s'", $response['openid.assoc_handle']));
+  if ($association && isset($association->session_type)) {
+    $keys_to_sign = explode(',', $response['openid.signed']);
+    $self_sig = _openid_signature($association, $response, $keys_to_sign);
+    if ($self_sig == $response['openid.sig']) {
+      $valid = TRUE;
+    }
+    else {
+      $valid = FALSE;
+    }
+  }
+  else {
+    $request = $response;
+    $request['openid.mode'] = 'check_authentication';
+    $message = _openid_create_message($request);
+    $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
+    $result = drupal_http_request($op_endpoint, $headers, 'POST', _openid_encode_message($message));
+    if (!isset($result->error)) {
+      $response = _openid_parse_message($result->data);
+      if (strtolower(trim($response['is_valid'])) == 'true') {
+        $valid = TRUE;
+      }
+      else {
+        $valid = FALSE;
+      }
+    }
+  }
+  if (!$valid) {
+    module_invoke('system', 'check_http_request');
+  }
+  return $valid;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/openid.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,91 @@
+<?php
+// $Id: openid.pages.inc,v 1.5 2008/01/30 22:11:22 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the openid module.
+ */
+
+/**
+ * Menu callback; Process an OpenID authentication.
+ */
+function openid_authentication_page() {
+  $result = openid_complete();
+  switch ($result['status']) {
+    case 'success':
+      return openid_authentication($result);
+    case 'failed':
+      drupal_set_message(t('OpenID login failed.'), 'error');
+      break;
+    case 'cancel':
+      drupal_set_message(t('OpenID login cancelled.'));
+      break;
+  }
+  drupal_goto();
+}
+
+/**
+ * Menu callback; Manage OpenID identities for the specified user.
+ */
+function openid_user_identities($account) {
+  drupal_set_title(check_plain($account->name));
+  drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
+
+  // Check to see if we got a response
+  $result = openid_complete();
+  if ($result['status'] == 'success') {
+    $identity = $result['openid.claimed_id'];
+    db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $identity);
+    drupal_set_message(t('Successfully added %identity', array('%identity' => $identity)));
+  }
+
+  $header = array(t('OpenID'), t('Operations'));
+  $rows = array();
+
+  $result = db_query("SELECT * FROM {authmap} WHERE module='openid' AND uid=%d", $account->uid);
+  while ($identity = db_fetch_object($result)) {
+    $rows[] = array($identity->authname, l(t('Delete'), 'user/'. $account->uid .'/openid/delete/'. $identity->aid));
+  }
+
+  $output = theme('table', $header, $rows);
+  $output .= drupal_get_form('openid_user_add');
+  return $output;
+}
+
+/**
+ * Form builder; Add an OpenID identity.
+ *
+ * @ingroup forms
+ * @see openid_user_add_validate()
+ */
+function openid_user_add() {
+  $form['openid_identifier'] = array(
+    '#type' => 'textfield',
+    '#title' => t('OpenID'),
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Add an OpenID'));
+  return $form;
+}
+
+function openid_user_add_validate($form, &$form_state) {
+  // Check for existing entries.
+  $claimed_id = _openid_normalize($form_state['values']['openid_identifier']);
+  if (db_result(db_query("SELECT authname FROM {authmap} WHERE authname='%s'", $claimed_id))) {
+    form_set_error('openid_identifier', t('That OpenID is already in use on this site.'));
+  }
+  else {
+    $return_to = url('user/'. arg(1) .'/openid', array('absolute' => TRUE));
+    openid_begin($form_state['values']['openid_identifier'], $return_to);
+  }
+}
+
+/**
+ * Menu callback; Delete the specified OpenID identity from the system.
+ */
+function openid_user_delete($account, $aid = 0) {
+  db_query("DELETE FROM {authmap} WHERE uid=%d AND aid=%d AND module='openid'", $account->uid, $aid);
+  if (db_affected_rows()) {
+    drupal_set_message(t('OpenID deleted.'));
+  }
+  drupal_goto('user/'. $account->uid .'/openid');
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/openid/xrds.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,82 @@
+<?php
+// $Id: xrds.inc,v 1.2 2007/10/15 09:40:42 goba Exp $
+
+// Global variables to track parsing state
+$xrds_open_elements = array();
+$xrds_services = array();
+$xrds_current_service = array();
+
+/**
+ * Main entry point for parsing XRDS documents
+ */
+function xrds_parse($xml) {
+  global $xrds_services;
+
+  $parser = xml_parser_create_ns();
+  xml_set_element_handler($parser, '_xrds_element_start', '_xrds_element_end');
+  xml_set_character_data_handler($parser, '_xrds_cdata');
+
+  xml_parse($parser, $xml);
+  xml_parser_free($parser);
+
+  return $xrds_services;
+}
+
+/**
+ * Parser callback functions
+ */
+function _xrds_element_start(&$parser, $name, $attribs) {
+  global $xrds_open_elements;
+
+  $xrds_open_elements[] = _xrds_strip_namespace($name);
+}
+
+function _xrds_element_end(&$parser, $name) {
+  global $xrds_open_elements, $xrds_services, $xrds_current_service;
+
+  $name = _xrds_strip_namespace($name);
+  if ($name == 'SERVICE') {
+    if (in_array(OPENID_NS_2_0 .'/signon', $xrds_current_service['types']) ||
+        in_array(OPENID_NS_2_0 .'/server', $xrds_current_service['types'])) {
+      $xrds_current_service['version'] = 2;
+    }
+    elseif (in_array(OPENID_NS_1_1, $xrds_current_service['types']) ||
+            in_array(OPENID_NS_1_0, $xrds_current_service['types'])) {
+      $xrds_current_service['version'] = 1;
+    }
+    if (!empty($xrds_current_service['version'])) {
+      $xrds_services[] = $xrds_current_service;
+    }
+    $xrds_current_service = array();
+  }
+  array_pop($xrds_open_elements);
+}
+
+function _xrds_cdata(&$parser, $data) {
+  global $xrds_open_elements, $xrds_services, $xrds_current_service;
+  $path = strtoupper(implode('/', $xrds_open_elements));
+  switch ($path) {
+    case 'XRDS/XRD/SERVICE/TYPE':
+      $xrds_current_service['types'][] = $data;
+      break;
+    case 'XRDS/XRD/SERVICE/URI':
+      $xrds_current_service['uri'] = $data;
+      break;
+    case 'XRDS/XRD/SERVICE/DELEGATE':
+      $xrds_current_service['delegate'] = $data;
+      break;
+    case 'XRDS/XRD/SERVICE/LOCALID':
+      $xrds_current_service['localid'] = $data;
+      break;
+  }
+}
+
+function _xrds_strip_namespace($name) {
+  // Strip namespacing.
+  $pos = strrpos($name, ':');
+  if ($pos !== FALSE) {
+    $name = substr($name, $pos + 1, strlen($name));
+  }
+
+  return $name;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/path/path.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,241 @@
+<?php
+// $Id: path.admin.inc,v 1.7 2008/01/08 10:35:42 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbacks for the path module.
+ */
+
+/**
+ * Return a listing of all defined URL aliases.
+ * When filter key passed, perform a standard search on the given key,
+ * and return the list of matching URL aliases.
+ */
+function path_admin_overview($keys = NULL) {
+  // Add the filter form above the overview table.
+  $output = drupal_get_form('path_admin_filter_form', $keys);
+  // Enable language column if locale is enabled or if we have any alias with language
+  $count = db_result(db_query("SELECT COUNT(*) FROM {url_alias} WHERE language != ''"));
+  $multilanguage = (module_exists('locale') || $count);
+
+  if ($keys) {
+    // Replace wildcards with MySQL/PostgreSQL wildcards.
+    $keys = preg_replace('!\*+!', '%', $keys);
+    $sql = "SELECT * FROM {url_alias} WHERE dst LIKE '%%%s%%'";
+  }
+  else {
+    $sql = 'SELECT * FROM {url_alias}';
+  }
+  $header = array(
+    array('data' => t('Alias'), 'field' => 'dst', 'sort' => 'asc'),
+    array('data' => t('System'), 'field' => 'src'),
+    array('data' => t('Operations'), 'colspan' => '2')
+  );
+  if ($multilanguage) {
+    $header[3] = $header[2];
+    $header[2] = array('data' => t('Language'), 'field' => 'language');
+  }
+  $sql .= tablesort_sql($header);
+  $result = pager_query($sql, 50, 0 , NULL, $keys);
+
+  $rows = array();
+  $destination = drupal_get_destination();
+  while ($data = db_fetch_object($result)) {
+    $row = array(check_plain($data->dst), check_plain($data->src), l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)), l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination)));
+    if ($multilanguage) {
+      $row[4] = $row[3];
+      $row[3] = $row[2];
+      $row[2] = module_invoke('locale', 'language_name', $data->language);
+    }
+    $rows[] = $row;
+  }
+
+  if (empty($rows)) {
+    $empty_message = $keys ? t('No URL aliases found.') : t('No URL aliases available.') ;
+    $rows[] = array(array('data' => $empty_message, 'colspan' => ($multilanguage ? 5 : 4)));
+  }
+
+  $output .= theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 50, 0);
+
+  return $output;
+}
+
+/**
+ * Menu callback; handles pages for creating and editing URL aliases.
+ */
+function path_admin_edit($pid = 0) {
+  if ($pid) {
+    $alias = path_load($pid);
+    drupal_set_title(check_plain($alias['dst']));
+    $output = drupal_get_form('path_admin_form', $alias);
+  }
+  else {
+    $output = drupal_get_form('path_admin_form');
+  }
+
+  return $output;
+}
+
+/**
+ * Return a form for editing or creating an individual URL alias.
+ *
+ * @ingroup forms
+ * @see path_admin_form_validate()
+ * @see path_admin_form_submit()
+ */
+function path_admin_form(&$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
+
+  $form['#alias'] = $edit;
+
+  $form['src'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Existing system path'),
+    '#default_value' => $edit['src'],
+    '#maxlength' => 64,
+    '#size' => 45,
+    '#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1+2.'),
+    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+    '#required' => TRUE,
+  );
+  $form['dst'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Path alias'),
+    '#default_value' => $edit['dst'],
+    '#maxlength' => 64,
+    '#size' => 45,
+    '#description' => t('Specify an alternative path by which this data can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
+    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+    '#required' => TRUE,
+  );
+  // This will be a hidden value unless locale module is enabled
+  $form['language'] = array(
+    '#type' => 'value',
+    '#value' => $edit['language']
+  );
+  if ($edit['pid']) {
+    $form['pid'] = array('#type' => 'hidden', '#value' => $edit['pid']);
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Update alias'));
+  }
+  else {
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Create new alias'));
+  }
+
+  return $form;
+}
+
+
+/**
+ * Verify that a new URL alias is valid
+ */
+function path_admin_form_validate($form, &$form_state) {
+  $src = $form_state['values']['src'];
+  $dst = $form_state['values']['dst'];
+  $pid = isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0;
+  // Language is only set if locale module is enabled, otherwise save for all languages.
+  $language = isset($form_state['values']['language']) ? $form_state['values']['language'] : '';
+
+  if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE pid != %d AND dst = '%s' AND language = '%s'", $pid, $dst, $language))) {
+    form_set_error('dst', t('The alias %alias is already in use in this language.', array('%alias' => $dst)));
+  }
+  $item = menu_get_item($src);
+  if (!$item || !$item['access']) {
+    form_set_error('src', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $src)));
+  }
+}
+
+/**
+ * Save a new URL alias to the database.
+ */
+function path_admin_form_submit($form, &$form_state) {
+  // Language is only set if locale module is enabled
+  path_set_alias($form_state['values']['src'], $form_state['values']['dst'], isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0, isset($form_state['values']['language']) ? $form_state['values']['language'] : '');
+
+  drupal_set_message(t('The alias has been saved.'));
+  $form_state['redirect'] = 'admin/build/path';
+  return;
+}
+
+/**
+ * Menu callback; confirms deleting an URL alias
+ */
+function path_admin_delete_confirm($form_state, $pid) {
+  $path = path_load($pid);
+  if (user_access('administer url aliases')) {
+    $form['pid'] = array('#type' => 'value', '#value' => $pid);
+    $output = confirm_form($form,
+      t('Are you sure you want to delete path alias %title?', array('%title' => $path['dst'])),
+      isset($_GET['destination']) ? $_GET['destination'] : 'admin/build/path');
+  }
+  return $output;
+}
+
+/**
+ * Execute URL alias deletion
+ */
+function path_admin_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    path_admin_delete($form_state['values']['pid']);
+    $form_state['redirect'] = 'admin/build/path';
+    return;
+  }
+}
+
+
+/**
+ * Return a form to filter URL aliases.
+ *
+ * @ingroup forms
+ * @see path_admin_filter_form_submit()
+ */
+function path_admin_filter_form(&$form_state, $keys = '') {
+  $form['#attributes'] = array('class' => 'search-form');
+  $form['basic'] = array('#type' => 'fieldset',
+    '#title' => t('Filter aliases')
+  );
+  $form['basic']['inline'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
+  $form['basic']['inline']['filter'] = array(
+    '#type' => 'textfield',
+    '#title' => '',
+    '#default_value' => $keys,
+    '#maxlength' => 64,
+    '#size' => 25,
+  );
+  $form['basic']['inline']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Filter'),
+    '#submit' => array('path_admin_filter_form_submit_filter'),
+    );
+  if ($keys) {
+    $form['basic']['inline']['reset'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset'),
+      '#submit' => array('path_admin_filter_form_submit_reset'),
+    );
+  }
+  return $form;
+}
+
+/**
+ * Process filter form submission when the Filter button is pressed.
+ */
+function path_admin_filter_form_submit_filter($form, &$form_state) {
+  $form_state['redirect'] = 'admin/build/path/list/'. trim($form_state['values']['filter']);
+}
+
+/**
+ * Process filter form submission when the Reset button is pressed.
+ */
+function path_admin_filter_form_submit_reset($form, &$form_state) {
+  $form_state['redirect'] = 'admin/build/path/list';
+}
+
+
+/**
+ * Helper function for grabbing filter keys.
+ */
+function path_admin_filter_get_keys() {
+  // Extract keys as remainder of path
+  $path = explode('/', $_GET['q'], 5);
+  return count($path) == 5 ? $path[4] : '';
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/path/path.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: path.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Path
+description = Allows users to rename URLs.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/path/path.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,217 @@
+<?php
+// $Id: path.module,v 1.138 2008/02/03 19:20:35 goba Exp $
+
+/**
+ * @file
+ * Enables users to rename URLs.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function path_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#path':
+      $output = '<p>'. t('The path module allows you to specify aliases for Drupal URLs. Such aliases improve readability of URLs for your users and may help internet search engines to index your content more effectively. More than one alias may be created for a given page.') .'</p>';
+      $output .= t('<p>Some examples of URL aliases are:</p>
+<ul>
+<li>user/login =&gt; login</li>
+<li>image/tid/16 =&gt; store</li>
+<li>taxonomy/term/7+19+20+21 =&gt; store/products/whirlygigs</li>
+<li>node/3 =&gt; contact</li>
+</ul>
+');
+      $output .= '<p>'. t('The path module enables appropriately permissioned users to specify an optional alias in all node input and editing forms, and provides an interface to view and edit all URL aliases. The two permissions related to URL aliasing are <em>administer url aliases</em> and <em>create url aliases</em>. ') .'</p>';
+      $output .= '<p>'. t('This module also provides user-defined mass URL aliasing capabilities, which is useful if you wish to uniformly use URLs different from the default. For example, you may want to have your URLs presented in a different language. Access to the Drupal source code on the web server is required to set up mass URL aliasing. ') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@path">Path module</a>.', array('@path' => 'http://drupal.org/handbook/modules/path/')) .'</p>';
+      return $output;
+    case 'admin/build/path':
+      return '<p>'. t("Drupal provides complete control over URLs through aliasing, which is often used to make URLs more readable or easy to remember. For example, the alias 'about' may be mapped onto the post at the system path 'node/1', creating a more meaningful URL. Each system path can have multiple aliases.") .'</p>';
+    case 'admin/build/path/add':
+      return '<p>'. t('Enter the path you wish to create the alias for, followed by the name of the new alias.') .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function path_menu() {
+  $items['admin/build/path'] = array(
+    'title' => 'URL aliases',
+    'description' => "Change your site's URL paths by aliasing them.",
+    'page callback' => 'path_admin_overview',
+    'access arguments' => array('administer url aliases'),
+    'file' => 'path.admin.inc',
+  );
+  $items['admin/build/path/edit'] = array(
+    'title' => 'Edit alias',
+    'page callback' => 'path_admin_edit',
+    'type' => MENU_CALLBACK,
+    'file' => 'path.admin.inc',
+  );
+  $items['admin/build/path/delete'] = array(
+    'title' => 'Delete alias',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('path_admin_delete_confirm'),
+    'type' => MENU_CALLBACK,
+    'file' => 'path.admin.inc',
+  );
+  $items['admin/build/path/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/build/path/add'] = array(
+    'title' => 'Add alias',
+    'page callback' => 'path_admin_edit',
+    'access arguments' => array('administer url aliases'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'path.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Post-confirmation; delete an URL alias.
+ */
+function path_admin_delete($pid = 0) {
+  db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
+  drupal_set_message(t('The alias has been deleted.'));
+}
+
+/**
+ * Set an aliased path for a given Drupal path, preventing duplicates.
+ */
+function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = '') {
+  $path = urldecode($path);
+  $alias = urldecode($alias);
+  // First we check if we deal with an existing alias and delete or modify it based on pid.
+  if ($pid) {
+    // An existing alias.
+    if (!$path || !$alias) {
+      // Delete the alias based on pid.
+      db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
+    }
+    else {
+      // Update the existing alias.
+      db_query("UPDATE {url_alias} SET src = '%s', dst = '%s', language = '%s' WHERE pid = %d", $path, $alias, $language, $pid);
+    }
+  }
+  else if ($path && $alias) {
+    // Check for existing aliases.
+    if ($alias == drupal_get_path_alias($path, $language)) {
+      // There is already such an alias, neutral or in this language.
+      // Update the alias based on alias; setting the language if not yet done.
+      db_query("UPDATE {url_alias} SET src = '%s', dst = '%s', language = '%s' WHERE dst = '%s'", $path, $alias, $language, $alias);
+    }
+    else {
+      // A new alias. Add it to the database.
+      db_query("INSERT INTO {url_alias} (src, dst, language) VALUES ('%s', '%s', '%s')", $path, $alias, $language);
+    }
+  }
+  else {
+    // Delete the alias.
+    if ($alias) {
+      db_query("DELETE FROM {url_alias} WHERE dst = '%s'", $alias);
+    }
+    else {
+      db_query("DELETE FROM {url_alias} WHERE src = '%s'", $path);
+    }
+  }
+  drupal_clear_path_cache();
+}
+
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * Allows URL aliases for nodes to be specified at node edit time rather
+ * than through the administrative interface.
+ */
+function path_nodeapi(&$node, $op, $arg) {
+  // Permissions are required for everything except node loading.
+  if (user_access('create url aliases') || user_access('administer url aliases') || ($op == 'load')) {
+    $language = isset($node->language) ? $node->language : '';
+    switch ($op) {
+      case 'validate':
+        $node->path = trim($node->path);
+        if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s' AND language = '%s'", $node->path, "node/$node->nid", $language))) {
+          form_set_error('path', t('The path is already in use.'));
+        }
+        break;
+
+      case 'load':
+        $path = 'node/'. $node->nid;
+        $alias = drupal_get_path_alias($path, $language);
+        if ($path != $alias) {
+          $node->path = $alias;
+        }
+        break;
+
+      case 'insert':
+        // Don't try to insert if path is NULL. We may have already set
+        // the alias ahead of time.
+        if (isset($node->path)) {
+          path_set_alias('node/'. $node->nid, $node->path, NULL, $language);
+        }
+        break;
+
+      case 'update':
+        path_set_alias('node/'. $node->nid, isset($node->path) ? $node->path : NULL, isset($node->pid) ? $node->pid : NULL, $language);
+        break;
+
+      case 'delete':
+        $path = 'node/'. $node->nid;
+        if (drupal_get_path_alias($path) != $path) {
+          path_set_alias($path);
+        }
+        break;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function path_form_alter(&$form, $form_state, $form_id) {
+  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
+    $path = isset($form['#node']->path) ? $form['#node']->path : NULL;
+    $form['path'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('URL path settings'),
+      '#collapsible' => TRUE,
+      '#collapsed' => empty($path),
+      '#access' => user_access('create url aliases'),
+      '#weight' => 30,
+    );
+    $form['path']['path'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $path,
+      '#maxlength' => 250,
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#description' => t('Optionally specify an alternative URL by which this node can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
+    );
+    if ($path) {
+      $form['path']['pid'] = array(
+        '#type' => 'value',
+        '#value' => db_result(db_query("SELECT pid FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $path, $form['#node']->language))
+      );
+    }
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function path_perm() {
+  return array('create url aliases', 'administer url aliases');
+}
+
+/**
+ * Fetch a specific URL alias from the database.
+ */
+function path_load($pid) {
+  return db_fetch_array(db_query('SELECT * FROM {url_alias} WHERE pid = %d', $pid));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/php/php.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: php.info,v 1.2 2007/06/08 05:50:55 dries Exp $
+name = PHP filter
+description = Allows embedded PHP code/snippets to be evaluated.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/php/php.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,29 @@
+<?php
+// $Id: php.install,v 1.1 2007/04/24 10:54:34 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function php_install() {
+  $format_exists = db_result(db_query("SELECT COUNT(*) FROM {filter_formats} WHERE name = 'PHP code'"));
+  // Add a PHP code input format, if it does not exist. Do this only for the
+  // first install (or if the format has been manually deleted) as there is no
+  // reliable method to identify the format in an uninstall hook or in
+  // subsequent clean installs.
+  if (!$format_exists) {
+    db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('PHP code', '', 0)");
+    $format = db_result(db_query("SELECT MAX(format) FROM {filter_formats}"));
+
+    // Enable the PHP evaluator filter.
+    db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, 'php', 0, 0)", $format);
+
+    drupal_set_message(t('A !php-code input format has been created.', array('!php-code' => l('PHP code', 'admin/settings/filters/'. $format))));
+  }
+}
+
+/**
+ * Implementation of hook_disable().
+ */
+function php_disable() {
+  drupal_set_message(t('The PHP module has been disabled. Please note that any existing content that was using the PHP filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.'));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/php/php.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,89 @@
+<?php
+// $Id: php.module,v 1.8.2.1 2008/02/05 09:29:50 goba Exp $
+
+/**
+ * @file
+ * Additional filter for PHP input.
+ */
+
+
+/**
+ * Implementation of hook_help().
+ */
+function php_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#php':
+      $output = '<p>'. t('The PHP filter adds the ability to include PHP code in posts. PHP is a general-purpose scripting language widely-used for web development; the content management system used by this website has been developed using PHP.') .'</p>';
+      $output .= '<p>'. t('Through the PHP filter, users with the proper permission may include custom PHP code within a page of the site. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use.') .'</p>';
+      $output .= '<p>'. t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/handbook/customization/php-snippets'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@php">PHP module</a>.', array('@php' => 'http://drupal.org/handbook/modules/php/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_filter_tips().
+ */
+function php_filter_tips($delta, $format, $long = false) {
+  global $base_url;
+  if ($delta == 0) {
+    switch ($long) {
+      case 0:
+        return t('You may post PHP code. You should include &lt;?php ?&gt; tags.');
+      case 1:
+        $output = '<h4>'. t('Using custom PHP code') .'</h4>';
+        $output .= '<p>'. t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') .'</p>';
+        $output .= '<p>'. t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') .'</p>';
+        $output .= '<p>'. t('Notes:') .'</p>';
+        $output .= '<ul><li>'. t('Remember to double-check each line for syntax and logic errors <strong>before</strong> saving.') .'</li>';
+        $output .= '<li>'. t('Statements must be correctly terminated with semicolons.') .'</li>';
+        $output .= '<li>'. t('Global variables used within your PHP code retain their values after your script executes.') .'</li>';
+        $output .= '<li>'. t('<code>register_globals</code> is <strong>turned off</strong>. If you need to use forms, understand and use the functions in <a href="@formapi">the Drupal Form API</a>.', array('@formapi' => url('http://api.drupal.org/api/group/form_api/6'))) .'</li>';
+        $output .= '<li>'. t('Use a <code>print</code> or <code>return</code> statement in your code to output content.') .'</li>';
+        $output .= '<li>'. t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') .'</li>';
+        $output .= '<li>'. t('Consider including your custom PHP code within a site-specific module or <code>template.php</code> file rather than embedding it directly into a post or block.') .'</li>';
+        $output .= '<li>'. t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') .'</li></ul>';
+        $output .= '<p>'. t('A basic example: <em>Creating a "Welcome" block that greets visitors with a simple message.</em>') .'</p>';
+        $output .= '<ul><li>'. t('<p>Add a custom block to your site, named "Welcome". With its input format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:</p>
+<pre>
+print t(\'Welcome visitor! Thank you for visiting.\');
+</pre>') .'</li>';
+        $output .= '<li>'. t('<p>To display the name of a registered user, use this instead:</p>
+<pre>
+global $user;
+if ($user->uid) {
+  print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => $user->name));
+}
+else {
+  print t(\'Welcome visitor! Thank you for visiting.\');
+}
+</pre>') .'</li></ul>';
+        $output .= '<p>'. t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/handbook/customization/php-snippets'))) .'</p>';
+        return $output;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_filter(). Contains a basic PHP evaluator.
+ *
+ * Executes PHP code. Use with care.
+ */
+function php_filter($op, $delta = 0, $format = -1, $text = '') {
+  switch ($op) {
+    case 'list':
+      return array(0 => t('PHP evaluator'));
+    case 'no cache':
+      // No caching for the PHP evaluator.
+      return $delta == 0;
+    case 'description':
+      return t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!');
+    case 'process':
+      return drupal_eval($text);
+    default:
+      return $text;
+  }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/ping/ping.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: ping.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Ping
+description = Alerts other sites when your site has been updated.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/ping/ping.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,59 @@
+<?php
+// $Id: ping.module,v 1.52 2007/12/19 17:45:42 goba Exp $
+
+/**
+ * @file
+ * Alerts other sites that your site has been updated.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function ping_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#ping':
+      $output = '<p>'. t('The ping module is useful for notifying interested sites that your site has changed. It automatically sends notifications, or "pings", to the <a href="@external-http-pingomatic-com">pingomatic</a> service about new or updated content. In turn, <a href="@external-http-pingomatic-com">pingomatic</a> notifies other popular services, including weblogs.com, Technorati, blo.gs, BlogRolling, Feedster.com, and Moreover.', array('@external-http-pingomatic-com' => 'http://pingomatic.com/')) .'</p>';
+      $output .= '<p>'. t('The ping module requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@ping">Ping module</a>.', array('@ping' => 'http://drupal.org/handbook/modules/ping/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Fire off notifications of updates to remote sites.
+ */
+function ping_cron() {
+  global $base_url;
+
+  if (variable_get('site_name', 0)) {
+    if (db_result(db_query("SELECT COUNT(*) FROM {node} WHERE status = 1 AND (created > '". variable_get('cron_last', time()) ."' OR changed > '". variable_get('cron_last', time()) ."')"))) {
+      _ping_notify(variable_get('site_name', ''), $base_url);
+    }
+  }
+}
+
+/**
+ * Call hook_ping() in all modules to notify remote sites that there is
+ * new content at this one.
+ */
+function _ping_notify($name, $url) {
+  module_invoke_all('ping', $name, $url);
+}
+
+/**
+ * Implementation of hook_ping().
+ *
+ * Notifies pingomatic.com, blo.gs, and technorati.com of changes at this site.
+ */
+function ping_ping($name = '', $url = '') {
+
+  $result = xmlrpc('http://rpc.pingomatic.com', 'weblogUpdates.ping', $name, $url);
+
+  if ($result === FALSE) {
+    watchdog('directory ping', 'Failed to notify pingomatic.com (site).', array(), WATCHDOG_WARNING);
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-bar-block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,26 @@
+<?php
+// $Id: poll-bar-block.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file poll-bar-block.tpl.php
+ * Display the bar for a single choice in a poll
+ *
+ * Variables available:
+ * - $title: The title of the poll.
+ * - $votes: The number of votes for this choice
+ * - $total_votes: The number of votes for this choice
+ * - $percentage: The percentage of votes for this choice.
+ * - $vote: The choice number of the current user's vote.
+ * - $voted: Set to TRUE if the user voted for this choice.
+ *
+ * @see template_preprocess_poll_bar()
+ */
+?>
+
+<div class="text"><?php print $title; ?></div>
+<div class="bar">
+  <div style="width: <?php print $percentage; ?>%;" class="foreground"></div>
+</div>
+<div class="percent">
+  <?php print $percentage; ?>%
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-bar.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,26 @@
+<?php
+// $Id: poll-bar.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file poll-bar.tpl.php
+ * Display the bar for a single choice in a poll
+ *
+ * Variables available:
+ * - $title: The title of the poll.
+ * - $votes: The number of votes for this choice
+ * - $total_votes: The number of votes for this choice
+ * - $percentage: The percentage of votes for this choice.
+ * - $vote: The choice number of the current user's vote.
+ * - $voted: Set to TRUE if the user voted for this choice.
+ *
+ * @see template_preprocess_poll_bar()
+ */
+?>
+
+<div class="text"><?php print $title; ?></div>
+<div class="bar">
+  <div style="width: <?php print $percentage; ?>%;" class="foreground"></div>
+</div>
+<div class="percent">
+  <?php print $percentage; ?>% (<?php print format_plural($votes, '1 vote', '@count votes'); ?>)
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-results-block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,29 @@
+<?php
+// $Id: poll-results-block.tpl.php,v 1.2 2007/08/02 20:08:53 dries Exp $
+/**
+ * @file poll-results-block.tpl.php
+ * Display the poll results in a block.
+ *
+ * Variables available:
+ * - $title: The title of the poll.
+ * - $results: The results of the poll.
+ * - $votes: The total results in the poll.
+ * - $links: Links in the poll.
+ * - $nid: The nid of the poll
+ * - $cancel_form: A form to cancel the user's vote, if allowed.
+ * - $raw_links: The raw array of links. Should be run through theme('links')
+ *   if used.
+ * - $vote: The choice number of the current user's vote.
+ *
+ * @see template_preprocess_poll_results()
+ */
+?>
+
+<div class="poll">
+  <div class="title"><?php print $title ?></div>
+  <?php print $results ?>
+  <div class="total">
+    <?php print t('Total votes: @votes', array('@votes' => $votes)); ?>
+  </div>
+</div>
+<div class="links"><?php print $links; ?></div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-results.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,29 @@
+<?php
+// $Id: poll-results.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file poll-results-block.tpl.php
+ * Display the poll results in a block.
+ *
+ * Variables available:
+ * - $title: The title of the poll.
+ * - $results: The results of the poll.
+ * - $votes: The total results in the poll.
+ * - $links: Links in the poll.
+ * - $nid: The nid of the poll
+ * - $cancel_form: A form to cancel the user's vote, if allowed.
+ * - $raw_links: The raw array of links.
+ * - $vote: The choice number of the current user's vote.
+ *
+ * @see template_preprocess_poll_results()
+ */
+?>
+<div class="poll">
+  <?php print $results; ?>
+  <div class="total">
+    <?php print t('Total votes: @votes', array('@votes' => $votes)); ?>
+  </div>
+  <?php if (!empty($cancel_form)): ?>
+    <?php print $cancel_form; ?>
+  <?php endif; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+/* $Id: poll-rtl.css,v 1.2 2007/05/30 18:28:13 goba Exp $ */
+
+.poll .bar .foreground {
+  float: right;
+}
+.poll .percent {
+  text-align: left;
+}
+.poll .vote-form .choices {
+  text-align: right;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll-vote.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,30 @@
+<?php
+// $Id: poll-vote.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file poll-vote.tpl.php
+ * Voting form for a poll.
+ *
+ * - $choice: The radio buttons for the choices in the poll.
+ * - $title: The title of the poll.
+ * - $block: True if this is being displayed as a block.
+ * - $vote: The vote button
+ * - $rest: Anything else in the form that may have been added via
+ *   form_alter hooks.
+ *
+ * @see template_preprocess_poll_vote()
+ */
+?>
+<div class="poll">
+  <div class="vote-form">
+    <div class="choices">
+      <?php if ($block): ?>
+        <div class="title"><?php print $title; ?>:</div>
+      <?php endif; ?>
+      <?php print $choice; ?>
+    </div>
+    <?php print $vote; ?>
+  </div>
+  <?php // This is the 'rest' of the form, in case items have been added. ?>
+  <?php print $rest ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,41 @@
+/* $Id: poll.css,v 1.6 2008/01/22 07:47:57 goba Exp $ */
+
+.poll .bar {
+  height: 1em;
+  margin: 1px 0;
+  background-color: #ddd;
+}
+.poll .bar .foreground {
+  background-color: #000;
+  height: 1em;
+  float: left; /* LTR */
+}
+.poll .links {
+  text-align: center;
+}
+.poll .percent {
+  text-align: right; /* LTR */
+}
+.poll .total {
+  text-align: center;
+}
+.poll .vote-form {
+  text-align: center;
+}
+.poll .vote-form .choices {
+  text-align: left; /* LTR */
+  margin: 0 auto;
+  display: table;
+}
+.poll .vote-form .choices .title {
+  font-weight: bold;
+}
+.node-form #edit-poll-more {
+  margin: 0;
+}
+td.poll-chtext {
+  width: 80%;
+}
+td.poll-chvotes .form-text {
+  width: 85%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: poll.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Poll
+description = Allows your site to capture votes on different topics in the form of multiple choice questions.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,132 @@
+<?php
+// $Id: poll.install,v 1.13 2007/12/18 12:59:21 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function poll_install() {
+  // Create tables.
+  drupal_install_schema('poll');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function poll_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('poll');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function poll_schema() {
+  $schema['poll'] = array(
+    'description' => t('Stores poll-specific information for poll nodes.'),
+    'fields' => array(
+      'nid'     => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("The poll's {node}.nid.")
+        ),
+      'runtime' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The number of seconds past {node}.created during which the poll is open.')
+        ),
+      'active'  => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Boolean indicating whether or not the poll is open.'),
+        ),
+      ),
+    'primary key' => array('nid'),
+    );
+
+  $schema['poll_choices'] = array(
+    'description' => t('Stores information about all choices for all {poll}s.'),
+    'fields' => array(
+      'chid'    => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Unique identifier for a poll choice.'),
+        ),
+      'nid'     => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid this choice belongs to.'),
+        ),
+      'chtext'  => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The text for this choice.'),
+        ),
+      'chvotes' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The total number of votes this choice has received by all users.'),
+        ),
+      'chorder' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The sort order of this choice among all choices for the same node.'),
+        )
+      ),
+    'indexes' => array(
+      'nid' => array('nid')
+      ),
+    'primary key' => array('chid'),
+    );
+
+  $schema['poll_votes'] = array(
+    'description' => t('Stores per-{users} votes for each {poll}.'),
+    'fields' => array(
+      'nid'      => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('The {poll} node this vote is for.'),
+        ),
+      'uid'      => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {users}.uid this vote is from unless the voter was anonymous.'),
+        ),
+      'chorder'  => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => -1,
+        'description' => t("The {users}'s vote for this poll."),
+        ),
+      'hostname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The IP address this vote is from unless the voter was logged in.'),
+        ),
+      ),
+    'primary key' => array('nid', 'uid', 'hostname'),
+    'indexes' => array(
+      'hostname' => array('hostname'),
+      'uid'      => array('uid'),
+      ),
+    );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,793 @@
+<?php
+// $Id: poll.module,v 1.263 2008/01/15 07:57:46 dries Exp $
+
+/**
+ * @file
+ * Enables your site to capture votes on different topics in the form of multiple
+ * choice questions.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function poll_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#poll':
+      $output = '<p>'. t('The poll module can be used to create simple polls for site users. A poll is a simple, multiple choice questionnaire which displays the cumulative results of the answers to the poll. Having polls on the site is a good way to receive feedback from community members.') .'</p>';
+      $output .= '<p>'. t('When creating a poll, enter the question being posed, as well as the potential choices (and beginning vote counts for each choice). The status and duration (length of time the poll remains active for new votes) can also be specified. Use the <a href="@poll">poll</a> menu item to view all current polls. To vote in or view the results of a specific poll, click on the poll itself.', array('@poll' => url('poll'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@poll">Poll module</a>.', array('@poll' => 'http://drupal.org/handbook/modules/poll/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function poll_init() {
+  drupal_add_css(drupal_get_path('module', 'poll') .'/poll.css');
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function poll_theme() {
+  return array(
+    'poll_vote' => array(
+      'template' => 'poll-vote',
+      'arguments' => array('form' => NULL),
+    ),
+    'poll_choices' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'poll_results' => array(
+      'template' => 'poll-results',
+      'arguments' => array('raw_title' => NULL, 'results' => NULL, 'votes' => NULL, 'raw_links' => NULL, 'block' => NULL, 'nid' => NULL, 'vote' => NULL),
+    ),
+    'poll_bar' => array(
+      'template' => 'poll-bar',
+      'arguments' => array('title' => NULL, 'votes' => NULL, 'total_votes' => NULL, 'vote' => NULL, 'block' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function poll_perm() {
+  return array('create poll content', 'delete own poll content', 'delete any poll content', 'edit any poll content', 'edit own poll content', 'vote on polls', 'cancel own vote', 'inspect all votes');
+}
+
+/**
+ * Implementation of hook_access().
+ */
+function poll_access($op, $node, $account) {
+  switch ($op) {
+    case 'create':
+      return user_access('create poll content', $account);
+    case 'update':
+      return user_access('edit any poll content', $account) || (user_access('edit own poll content', $account) && ($node->uid == $account->uid));
+    case 'delete':
+      return user_access('delete any poll content', $account) || (user_access('delete own poll content', $account) && ($node->uid == $account->uid));
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function poll_menu() {
+  $items['poll'] = array(
+    'title' => 'Polls',
+    'page callback' => 'poll_page',
+    'access arguments' => array('access content'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'poll.pages.inc',
+  );
+
+  $items['node/%node/votes'] = array(
+    'title' => 'Votes',
+    'page callback' => 'poll_votes',
+    'page arguments' => array(1),
+    'access callback' => '_poll_menu_access',
+    'access arguments' => array(1, 'inspect all votes', FALSE),
+    'weight' => 3,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'poll.pages.inc',
+  );
+
+  $items['node/%node/results'] = array(
+    'title' => 'Results',
+    'page callback' => 'poll_results',
+    'page arguments' => array(1),
+    'access callback' => '_poll_menu_access',
+    'access arguments' => array(1, 'access content', TRUE),
+    'weight' => 3,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'poll.pages.inc',
+  );
+
+  $items['poll/js'] = array(
+    'title' => 'Javascript Choice Form',
+    'page callback' => 'poll_choice_js',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Callback function to see if a node is acceptable for poll menu items.
+ */
+function _poll_menu_access($node, $perm, $inspect_allowvotes) {
+  return user_access($perm) && ($node->type == 'poll') && ($node->allowvotes || !$inspect_allowvotes);
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generates a block containing the latest poll.
+ */
+function poll_block($op = 'list', $delta = 0) {
+  if (user_access('access content')) {
+    if ($op == 'list') {
+      $blocks[0]['info'] = t('Most recent poll');
+      return $blocks;
+    }
+    else if ($op == 'view') {
+      // Retrieve the latest poll.
+      $sql = db_rewrite_sql("SELECT MAX(n.created) FROM {node} n INNER JOIN {poll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1");
+      $timestamp = db_result(db_query($sql));
+      if ($timestamp) {
+        $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'status' => 1));
+
+        if ($poll->nid) {
+          $poll = poll_view($poll, TRUE, FALSE, TRUE);
+        }
+      }
+      $block['subject'] = t('Poll');
+      $block['content'] = drupal_render($poll->content);
+      return $block;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Closes polls that have exceeded their allowed runtime.
+ */
+function poll_cron() {
+  $result = db_query('SELECT p.nid FROM {poll} p INNER JOIN {node} n ON p.nid = n.nid WHERE (n.created + p.runtime) < '. time() .' AND p.active = 1 AND p.runtime != 0');
+  while ($poll = db_fetch_object($result)) {
+    db_query("UPDATE {poll} SET active = 0 WHERE nid = %d", $poll->nid);
+  }
+}
+
+/**
+ * Implementation of hook_node_info().
+ */
+function poll_node_info() {
+  return array(
+    'poll' => array(
+      'name' => t('Poll'),
+      'module' => 'poll',
+      'description' => t('A <em>poll</em> is a question with a set of possible responses. A <em>poll</em>, once created, automatically provides a simple running count of the number of votes received for each response.'),
+      'title_label' => t('Question'),
+      'has_body' => FALSE,
+    )
+  );
+}
+
+/**
+ * Implementation of hook_form().
+ */
+function poll_form(&$node, $form_state) {
+  global $user;
+
+  $admin = user_access('administer nodes') || user_access('edit any poll content') || (user_access('edit own poll content') && $user->uid == $node->uid);
+
+  $type = node_get_types('type', $node);
+
+  $form = array(
+    '#cache' => TRUE,
+  );
+
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => check_plain($type->title_label),
+    '#required' => TRUE,
+    '#default_value' => $node->title,
+    '#weight' => -5,
+  );
+
+  if (isset($form_state['choice_count'])) {
+    $choice_count = $form_state['choice_count'];
+  }
+  else {
+    $choice_count = max(2, empty($node->choice) ? 2 : count($node->choice));
+  }
+
+  // Add a wrapper for the choices and more button.
+  $form['choice_wrapper'] = array(
+    '#tree' => FALSE,
+    '#weight' => -4,
+    '#prefix' => '<div class="clear-block" id="poll-choice-wrapper">',
+    '#suffix' => '</div>',
+  );
+
+  // Container for just the poll choices.
+  $form['choice_wrapper']['choice'] = array(
+    '#prefix' => '<div id="poll-choices">',
+    '#suffix' => '</div>',
+    '#theme' => 'poll_choices',
+  );
+
+  // Add the current choices to the form.
+  for ($delta = 0; $delta < $choice_count; $delta++) {
+    $text = isset($node->choice[$delta]['chtext']) ? $node->choice[$delta]['chtext'] : '';
+    $votes = isset($node->choice[$delta]['chvotes']) ? $node->choice[$delta]['chvotes'] : 0;
+
+    $form['choice_wrapper']['choice'][$delta] = _poll_choice_form($delta, $text, $votes);
+  }
+
+  // We name our button 'poll_more' to avoid conflicts with other modules using
+  // AHAH-enabled buttons with the id 'more'.
+  $form['choice_wrapper']['poll_more'] = array(
+    '#type' => 'submit',
+    '#value' => t('More choices'),
+    '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."),
+    '#weight' => 1,
+    '#submit' => array('poll_more_choices_submit'), // If no javascript action.
+    '#ahah' => array(
+      'path' => 'poll/js',
+      'wrapper' => 'poll-choices',
+      'method' => 'replace',
+      'effect' => 'fade',
+    ),
+  );
+
+  // Poll attributes
+  $_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
+  $_active = array(0 => t('Closed'), 1 => t('Active'));
+
+  if ($admin) {
+    $form['settings'] = array(
+      '#type' => 'fieldset',
+      '#collapsible' => TRUE,
+      '#title' => t('Poll settings'),
+      '#weight' => -3,
+    );
+
+    $form['settings']['active'] = array(
+      '#type' => 'radios',
+      '#title' => t('Poll status'),
+      '#default_value' => isset($node->active) ? $node->active : 1,
+      '#options' => $_active,
+      '#description' => t('When a poll is closed, visitors can no longer vote for it.')
+    );
+  }
+  $form['settings']['runtime'] = array(
+    '#type' => 'select',
+    '#title' => t('Poll duration'),
+    '#default_value' => isset($node->runtime) ? $node->runtime : 0,
+    '#options' => $_duration,
+    '#description' => t('After this period, the poll will be closed automatically.'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler to add more choices to a poll form. This handler is used when
+ * javascript is not available. It makes changes to the form state and the
+ * entire form is rebuilt during the page reload.
+ */
+function poll_more_choices_submit($form, &$form_state) {
+  // Set the form to rebuild and run submit handlers.
+  node_form_submit_build_node($form, $form_state);
+
+  // Make the changes we want to the form state.
+  if ($form_state['values']['poll_more']) {
+    $form_state['choice_count'] = count($form_state['values']['choice']) + 5;
+  }
+}
+
+function _poll_choice_form($delta, $value = '', $votes = 0) {
+  $admin = user_access('administer nodes');
+
+  $form = array(
+    '#tree' => TRUE,
+  );
+
+  // We'll manually set the #parents property of these fields so that
+  // their values appear in the $form_state['values']['choice'] array.
+  $form['chtext'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Choice @n', array('@n' => ($delta + 1))),
+    '#default_value' => $value,
+    '#parents' => array('choice', $delta, 'chtext'),
+  );
+
+  if ($admin) {
+    $form['chvotes'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Votes for choice @n', array('@n' => ($delta + 1))),
+      '#default_value' => $votes,
+      '#size' => 5,
+      '#maxlength' => 7,
+      '#parents' => array('choice', $delta, 'chvotes'),
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Menu callback for AHAH additions.
+ */
+function poll_choice_js() {
+  $delta = count($_POST['choice']);
+
+  // Build our new form element.
+  $form_element = _poll_choice_form($delta);
+  drupal_alter('form', $form_element, array(), 'poll_choice_js');
+
+  // Build the new form.
+  $form_state = array('submitted' => FALSE);
+  $form_build_id = $_POST['form_build_id'];
+  // Add the new element to the stored form. Without adding the element to the
+  // form, Drupal is not aware of this new elements existence and will not
+  // process it. We retreive the cached form, add the element, and resave.
+  $form = form_get_cache($form_build_id, $form_state);
+  $form['choice_wrapper']['choice'][$delta] = $form_element;
+  form_set_cache($form_build_id, $form, $form_state);
+  $form += array(
+    '#post' => $_POST,
+    '#programmed' => FALSE,
+  );
+
+  // Rebuild the form.
+  $form = form_builder('poll_node_form', $form, $form_state);
+
+  // Render the new output.
+  $choice_form = $form['choice_wrapper']['choice'];
+  unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
+  $choice_form[$delta]['#attributes']['class'] = empty($choice_form[$delta]['#attributes']['class']) ? 'ahah-new-content' : $choice_form[$delta]['#attributes']['class'] .' ahah-new-content';
+  $choice_form[$delta]['chvotes']['#value'] = 0;
+  $output = theme('status_messages') . drupal_render($choice_form);
+
+  drupal_json(array('status' => TRUE, 'data' => $output));
+}
+
+/**
+ * Implementation of hook_submit().
+ */
+function poll_node_form_submit(&$form, &$form_state) {
+  // Renumber fields
+  $form_state['values']['choice'] = array_values($form_state['values']['choice']);
+  $form_state['values']['teaser'] = poll_teaser((object)$form_state['values']);
+}
+
+/**
+ * Implementation of hook_validate().
+ */
+function poll_validate($node) {
+  if (isset($node->title)) {
+    // Check for at least two options and validate amount of votes:
+    $realchoices = 0;
+    // Renumber fields
+    $node->choice = array_values($node->choice);
+    foreach ($node->choice as $i => $choice) {
+      if ($choice['chtext'] != '') {
+        $realchoices++;
+      }
+      if (isset($choice['chvotes']) && $choice['chvotes'] < 0) {
+        form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
+      }
+    }
+
+    if ($realchoices < 2) {
+      form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
+    }
+  }
+}
+
+/**
+ * Implementation of hook_load().
+ */
+function poll_load($node) {
+  global $user;
+
+  $poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
+
+  // Load the appropriate choices into the $poll object.
+  $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
+  while ($choice = db_fetch_array($result)) {
+    $poll->choice[$choice['chorder']] = $choice;
+  }
+
+  // Determine whether or not this user is allowed to vote.
+  $poll->allowvotes = FALSE;
+  if (user_access('vote on polls') && $poll->active) {
+    if ($user->uid) {
+      $result = db_fetch_object(db_query('SELECT chorder FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
+    }
+    else {
+      $result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
+    }
+    if (isset($result->chorder)) {
+      $poll->vote = $result->chorder;
+    }
+    else {
+      $poll->vote = -1;
+      $poll->allowvotes = TRUE;
+    }
+  }
+  return $poll;
+}
+
+/**
+ * Implementation of hook_insert().
+ */
+function poll_insert($node) {
+  if (!user_access('administer nodes')) {
+    // Make sure all votes are 0 initially
+    foreach ($node->choice as $i => $choice) {
+      $node->choice[$i]['chvotes'] = 0;
+    }
+    $node->active = 1;
+  }
+
+  db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
+
+  $i = 0;
+  foreach ($node->choice as $choice) {
+    if ($choice['chtext'] != '') {
+      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_update().
+ */
+function poll_update($node) {
+  // Update poll settings.
+  db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
+
+  // Clean poll choices.
+  db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
+
+  // Poll choices come in the same order with the same numbers as they are in
+  // the database, but some might have an empty title, which signifies that
+  // they should be removed. We remove all votes to the removed options, so
+  // people who voted on them can vote again.
+  $new_chorder = 0;
+  foreach ($node->choice as $old_chorder => $choice) {
+    $chvotes = isset($choice['chvotes']) ? (int)$choice['chvotes'] : 0;
+    $chtext = $choice['chtext'];
+
+    if (!empty($chtext)) {
+      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $new_chorder);
+      if ($new_chorder != $old_chorder) {
+        // We can only remove items in the middle, not add, so
+        // new_chorder is always <= old_chorder, making this safe.
+        db_query("UPDATE {poll_votes} SET chorder = %d WHERE nid = %d AND chorder = %d", $new_chorder, $node->nid, $old_chorder);
+      }
+      $new_chorder++;
+    }
+    else {
+      db_query("DELETE FROM {poll_votes} WHERE nid = %d AND chorder = %d", $node->nid, $old_chorder);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_delete().
+ */
+function poll_delete($node) {
+  db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
+}
+
+/**
+ * Implementation of hook_view().
+ *
+ * @param $block
+ *   An extra parameter that adapts the hook to display a block-ready
+ *   rendering of the poll.
+ */
+function poll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
+  global $user;
+  $output = '';
+
+  // Special display for side-block
+  if ($block) {
+    // No 'read more' link
+    $node->readmore = FALSE;
+
+    $links = module_invoke_all('link', 'node', $node, 1);
+    $links[] = array('title' => t('Older polls'), 'href' => 'poll', 'attributes' => array('title' => t('View the list of polls on this site.')));
+    if ($node->allowvotes && $block) {
+      $links[] = array('title' => t('Results'), 'href' => 'node/'. $node->nid .'/results', 'attributes' => array('title' => t('View the current poll results.')));
+    }
+
+    $node->links = $links;
+  }
+
+  if (!empty($node->allowvotes) && ($block || empty($node->show_results))) {
+    $node->content['body'] = array(
+      '#value' => drupal_get_form('poll_view_voting', $node, $block),
+    );
+  }
+  else {
+    $node->content['body'] = array(
+      '#value' => poll_view_results($node, $teaser, $page, $block),
+    );
+  }
+  return $node;
+}
+
+/**
+ * Creates a simple teaser that lists all the choices.
+ *
+ * This is primarily used for RSS.
+ */
+function poll_teaser($node) {
+  $teaser = NULL;
+  if (is_array($node->choice)) {
+    foreach ($node->choice as $k => $choice) {
+      if ($choice['chtext'] != '') {
+        $teaser .= '* '. check_plain($choice['chtext']) ."\n";
+      }
+    }
+  }
+  return $teaser;
+}
+
+/**
+ * Generates the voting form for a poll.
+ *
+ * @ingroup forms
+ * @see poll_vote()
+ * @see phptemplate_preprocess_poll_vote()
+ */
+function poll_view_voting(&$form_state, $node, $block) {
+  if ($node->choice) {
+    $list = array();
+    foreach ($node->choice as $i => $choice) {
+      $list[$i] = check_plain($choice['chtext']);
+    }
+    $form['choice'] = array(
+      '#type' => 'radios',
+      '#default_value' => -1,
+      '#options' => $list,
+    );
+  }
+
+  $form['vote'] = array(
+    '#type' => 'submit',
+    '#value' => t('Vote'),
+    '#submit' => array('poll_vote'),
+  );
+
+  // Store the node so we can get to it in submit functions.
+  $form['#node'] = $node;
+  $form['#block'] = $block;
+
+  // Set form caching because we could have multiple of these forms on
+  // the same page, and we want to ensure the right one gets picked.
+  $form['#cache'] = TRUE;
+
+  // Provide a more cleanly named voting form theme.
+  $form['#theme'] = 'poll_vote';
+  return $form;
+}
+
+/**
+ * Validation function for processing votes
+ */
+function poll_view_voting_validate($form, &$form_state) {
+  if ($form_state['values']['choice'] == -1) {
+    form_set_error( 'choice', t('Your vote could not be recorded because you did not select any of the choices.'));
+  }
+}
+
+/**
+ * Submit handler for processing a vote
+ */
+function poll_vote($form, &$form_state) {
+  $node = $form['#node'];
+  $choice = $form_state['values']['choice'];
+
+  global $user;
+  if ($user->uid) {
+    db_query('INSERT INTO {poll_votes} (nid, chorder, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
+  }
+  else {
+    db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, ip_address());
+  }
+
+  // Add one to the votes.
+  db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
+
+  cache_clear_all();
+  drupal_set_message(t('Your vote was recorded.'));
+
+  // Return the user to whatever page they voted from.
+}
+
+/**
+ * Themes the voting form for a poll.
+ *
+ * Inputs: $form
+ */
+function template_preprocess_poll_vote(&$variables) {
+  $form = $variables['form'];
+  $variables['choice'] = drupal_render($form['choice']);
+  $variables['title'] = check_plain($form['#node']->title);
+  $variables['vote'] = drupal_render($form['vote']);
+  $variables['rest'] = drupal_render($form);
+  $variables['block'] = $form['#block'];
+  // If this is a block, allow a different tpl.php to be used.
+  if ($variables['block']) {
+    $variables['template_files'][] = 'poll-vote-block';
+  }
+}
+
+/**
+ * Generates a graphical representation of the results of a poll.
+ */
+function poll_view_results(&$node, $teaser, $page, $block) {
+  // Count the votes and find the maximum
+  $total_votes = 0;
+  $max_votes = 0;
+  foreach ($node->choice as $choice) {
+    if (isset($choice['chvotes'])) {
+      $total_votes += $choice['chvotes'];
+      $max_votes = max($max_votes, $choice['chvotes']);
+    }
+  }
+
+  $poll_results = '';
+  foreach ($node->choice as $i => $choice) {
+    if (!empty($choice['chtext'])) {
+      $chvotes = isset($choice['chvotes']) ? $choice['chvotes'] : NULL;
+      $poll_results .= theme('poll_bar', $choice['chtext'], $chvotes, $total_votes, isset($node->vote) && $node->vote == $i, $block);
+    }
+  }
+
+  return theme('poll_results', $node->title, $poll_results, $total_votes, isset($node->links) ? $node->links : array(), $block, $node->nid, isset($node->vote) ? $node->vote : NULL);
+}
+
+
+/**
+ * Theme the admin poll form for choices.
+ *
+ * @ingroup themeable
+ */
+function theme_poll_choices($form) {
+  // Change the button title to reflect the behavior when using JavaScript.
+  drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-poll-more").val("'. t('Add another choice') .'"); }); }', 'inline');
+
+  $rows = array();
+  $headers = array(
+    t('Choice'),
+    t('Vote count'),
+  );
+
+  foreach (element_children($form) as $key) {
+    // No need to print the field title every time.
+    unset($form[$key]['chtext']['#title'], $form[$key]['chvotes']['#title']);
+
+    // Build the table row.
+    $row = array(
+      'data' => array(
+        array('data' => drupal_render($form[$key]['chtext']), 'class' => 'poll-chtext'),
+        array('data' => drupal_render($form[$key]['chvotes']), 'class' => 'poll-chvotes'),
+      ),
+    );
+
+    // Add additional attributes to the row, such as a class for this row.
+    if (isset($form[$key]['#attributes'])) {
+      $row = array_merge($row, $form[$key]['#attributes']);
+    }
+    $rows[] = $row;
+  }
+
+  $output = theme('table', $headers, $rows);
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Preprocess the poll_results theme hook.
+ *
+ * Inputs: $raw_title, $results, $votes, $raw_links, $block, $nid, $vote. The
+ * $raw_* inputs to this are naturally unsafe; often safe versions are
+ * made to simply overwrite the raw version, but in this case it seems likely
+ * that the title and the links may be overridden by the theme layer, so they
+ * are left in with a different name for that purpose.
+ *
+ * @see poll-results.tpl.php
+ * @see poll-results-block.tpl.php
+ * @see theme_poll_results()
+ */
+function template_preprocess_poll_results(&$variables) {
+  $variables['links'] = theme('links', $variables['raw_links']);
+  if (isset($variables['vote']) && $variables['vote'] > -1 && user_access('cancel own vote')) {
+    $variables['cancel_form'] = drupal_get_form('poll_cancel_form', $variables['nid']);
+  }
+  $variables['title'] = check_plain($variables['raw_title']);
+
+  // If this is a block, allow a different tpl.php to be used.
+  if ($variables['block']) {
+    $variables['template_files'][] = 'poll-results-block';
+  }
+}
+
+/**
+ * Preprocess the poll_bar theme hook.
+ *
+ * Inputs: $title, $votes, $total_votes, $voted, $block
+ *
+ * @see poll-bar.tpl.php
+ * @see poll-bar-block.tpl.php
+ * @see theme_poll_bar()
+ */
+function template_preprocess_poll_bar(&$variables) {
+  if ($variables['block']) {
+    $variables['template_files'][] = 'poll-bar-block';
+  }
+  $variables['title'] = check_plain($variables['title']);
+  $variables['percentage'] = round($variables['votes'] * 100 / max($variables['total_votes'], 1));
+}
+
+/**
+ * Builds the cancel form for a poll.
+ *
+ * @ingroup forms
+ * @see poll_cancel()
+ */
+function poll_cancel_form(&$form_state, $nid) {
+  // Store the nid so we can get to it in submit functions.
+  $form['#nid'] = $nid;
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Cancel your vote'),
+    '#submit' => array('poll_cancel')
+  );
+
+  $form['#cache'] = TRUE;
+
+  return $form;
+}
+
+/**
+ * Submit callback for poll_cancel_form
+ */
+function poll_cancel($form, &$form_state) {
+  $node = node_load($form['#nid']);
+  global $user;
+
+  if ($user->uid) {
+    db_query('DELETE FROM {poll_votes} WHERE nid = %d and uid = %d', $node->nid, $user->uid);
+  }
+  else {
+    db_query("DELETE FROM {poll_votes} WHERE nid = %d and hostname = '%s'", $node->nid, ip_address());
+  }
+
+  // Subtract from the votes.
+  db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE nid = %d AND chorder = %d", $node->nid, $node->vote);
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function poll_user($op, &$edit, &$user) {
+  if ($op == 'delete') {
+    db_query('UPDATE {poll_votes} SET uid = 0 WHERE uid = %d', $user->uid);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/poll/poll.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,56 @@
+<?php
+// $Id: poll.pages.inc,v 1.4 2007/12/14 09:50:41 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the poll module.
+ */
+
+/**
+ * Menu callback to provide a simple list of all polls available.
+ */
+function poll_page() {
+  // List all polls.
+  $sql = db_rewrite_sql("SELECT n.nid, n.title, p.active, n.created, SUM(c.chvotes) AS votes FROM {node} n INNER JOIN {poll} p ON n.nid = p.nid INNER JOIN {poll_choices} c ON n.nid = c.nid WHERE n.status = 1 GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC");
+  // Count all polls for the pager.
+  $count_sql = db_rewrite_sql('SELECT COUNT(*) FROM {node} n INNER JOIN {poll} p ON n.nid = p.nid WHERE n.status = 1');
+  $result = pager_query($sql, 15, 0, $count_sql);
+  $output = '<ul>';
+  while ($node = db_fetch_object($result)) {
+    $output .= '<li>'. l($node->title, "node/$node->nid") .' - '. format_plural($node->votes, '1 vote', '@count votes') .' - '. ($node->active ? t('open') : t('closed')) .'</li>';
+  }
+  $output .= '</ul>';
+  $output .= theme("pager", NULL, 15);
+  return $output;
+}
+
+/**
+ * Callback for the 'votes' tab for polls you can see other votes on
+ */
+function poll_votes($node) {
+  drupal_set_title(check_plain($node->title));
+  $output = t('This table lists all the recorded votes for this poll. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.');
+
+  $header[] = array('data' => t('Visitor'), 'field' => 'u.name');
+  $header[] = array('data' => t('Vote'), 'field' => 'pv.chorder');
+
+  $result = pager_query("SELECT pv.chorder, pv.uid, pv.hostname, u.name FROM {poll_votes} pv LEFT JOIN {users} u ON pv.uid = u.uid WHERE pv.nid = %d". tablesort_sql($header), 20, 0, NULL, $node->nid);
+  $rows = array();
+  while ($vote = db_fetch_object($result)) {
+    $rows[] = array(
+      $vote->name ? theme('username', $vote) : check_plain($vote->hostname),
+      check_plain($node->choice[$vote->chorder]['chtext']));
+  }
+  $output .= theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 20, 0);
+  return $output;
+}
+
+/**
+ * Callback for the 'results' tab for polls you can vote on
+ */
+function poll_results($node) {
+  drupal_set_title(check_plain($node->title));
+  $node->show_results = TRUE;
+  return node_show($node, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile-block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,42 @@
+<?php
+// $Id: profile-block.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file profile-block.tpl.php
+ * Default theme implementation for displaying a users profile within a
+ * block. It only shows in relation to a node displayed as a full page.
+ *
+ * Available variables:
+ * - $picture: Image configured for the account linking to the users page.
+ * - $profile: Keyed array of all profile fields that have a value.
+ *
+ * Each $field in $profile contains:
+ * - $field->title: Title of the profile field.
+ * - $field->value: Value of the profile field.
+ * - $field->type: Type of the profile field, i.e., checkbox, textfield,
+ *   textarea, selection, list, url or date.
+ *
+ * Since $profile is keyed, a direct print of the field is possible. Not
+ * all accounts may have a value for a profile so do a check first. If a field
+ * of "last_name" was set for the site, the following can be used.
+ *
+ *  <?php if (isset($profile['last_name'])): ?>
+ *    <div class="field last-name">
+ *      <?php print $profile['last_name']->title; ?>:<br />
+ *      <?php print $profile['last_name']->value; ?>
+ *    </div>
+ *  <?php endif; ?>
+ *
+ * @see template_preprocess_profile_block()
+ */
+?>
+<?php print $picture; ?>
+
+<?php foreach ($profile as $field) : ?>
+  <p>
+    <?php if ($field->type != 'checkbox') : ?>
+      <strong><?php print $field->title; ?></strong><br />
+    <?php endif; ?>
+    <?php print $field->value; ?>
+  </p>
+<?php endforeach; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile-listing.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,52 @@
+<?php
+// $Id: profile-listing.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file profile-listing.tpl.php
+ * Default theme implementation for displaying a user and their profile data
+ * for member listing pages.
+ *
+ * @see profile-wrapper.tpl.php
+ *      where all the data is collected and printed out.
+ *
+ * Available variables:
+ * - $picture: Image configured for the account linking to the users page.
+ * - $name: User's account name linking to the users page.
+ * - $profile: Keyed array of all profile fields that are set as visible
+ *   in member list pages (configured by site administrators). It also needs
+ *   to have a value in order to be present.
+ *
+ * Each $field in $profile contains:
+ * - $field->title: Title of the profile field.
+ * - $field->value: Value of the profile field.
+ * - $field->type: Type of the profile field, i.e., checkbox, textfield,
+ *   textarea, selection, list, url or date.
+ *
+ * Since $profile is keyed, a direct print of the field is possible. Not
+ * all accounts may have a value for a profile so do a check first. If a field
+ * of "last_name" was set for the site, the following can be used.
+ *
+ *  <?php if (isset($profile['last_name'])): ?>
+ *    <div class="field last-name">
+ *      <?php print $profile['last_name']->title; ?>:<br />
+ *      <?php print $profile['last_name']->value; ?>
+ *    </div>
+ *  <?php endif; ?>
+ *
+ * @see template_preprocess_profile_listing()
+ */
+?>
+<div class="profile">
+  <?php print $picture; ?>
+
+  <div class="name">
+    <?php print $name; ?>
+  </div>
+
+  <?php foreach ($profile as $field) : ?>
+    <div class="field">
+      <?php print $field->value; ?>
+    </div>
+  <?php endforeach; ?>
+
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile-wrapper.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,25 @@
+<?php
+// $Id: profile-wrapper.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $
+
+/**
+ * @file profile-wrapper.tpl.php
+ * Default theme implementation for wrapping member listings and their
+ * profiles.
+ *
+ * This template is used when viewing a list of users. It can be a general
+ * list for viewing all users with the url of "example.com/profile" or when
+ * viewing a set of users who share a specific value for a profile such
+ * as "example.com/profile/country/belgium".
+ *
+ * Available variables:
+ * - $content: User account profiles iterated through profile-listing.tpl.php.
+ * - $current_field: The named field being browsed. Provided here for context.
+ *   The above example would result in "last_name". An alternate template name
+ *   is also based on this, e.g., "profile-wrapper-last_name.tpl.php".
+ *
+ * @see template_preprocess_profile_wrapper()
+ */
+?>
+<div id="profile">
+  <?php print $content; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,404 @@
+<?php
+// $Id: profile.admin.inc,v 1.8 2008/01/21 15:20:43 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbacks for the profile module.
+ */
+
+/**
+ * Form builder to display a listing of all editable profile fields.
+ *
+ * @ingroup forms
+ * @see profile_admin_overview_submit()
+ */
+function profile_admin_overview() {
+  $result = db_query('SELECT title, name, type, category, fid, weight FROM {profile_fields} ORDER BY category, weight');
+
+  $form = array();
+  $categories = array();
+  while ($field = db_fetch_object($result)) {
+    // Collect all category information
+    $categories[] = $field->category;
+
+    // Save all field information
+    $form[$field->fid]['name'] = array('#value' => check_plain($field->name));
+    $form[$field->fid]['title'] = array('#value' => check_plain($field->title));
+    $form[$field->fid]['type'] = array('#value' => $field->type);
+    $form[$field->fid]['category'] = array('#type' => 'select', '#default_value' => $field->category, '#options' => array());
+    $form[$field->fid]['weight'] = array('#type' => 'weight', '#default_value' => $field->weight);
+    $form[$field->fid]['edit'] = array('#value' => l(t('edit'), "admin/user/profile/edit/$field->fid"));
+    $form[$field->fid]['delete'] = array('#value' => l(t('delete'), "admin/user/profile/delete/$field->fid"));
+  }
+
+  // Add the cateogory combo boxes
+  $categories = array_unique($categories);
+  foreach ($form as $fid => $field) {
+    foreach ($categories as $cat => $category) {
+      $form[$fid]['category']['#options'][$category] = $category;
+    }
+  }
+
+  // Display the submit button only when there's more than one field
+  if (count($form) > 1) {
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+  }
+  else {
+    // Disable combo boxes when there isn't a submit button
+    foreach ($form as $fid => $field) {
+      unset($form[$fid]['weight']);
+      $form[$fid]['category']['#type'] = 'value';
+    }
+  }
+  $form['#tree'] = TRUE;
+
+  $addnewfields = '<h2>'. t('Add new field') .'</h2>';
+  $addnewfields .= '<ul>';
+  foreach (_profile_field_types() as $key => $value) {
+    $addnewfields .= '<li>'. l($value, "admin/user/profile/add/$key") .'</li>';
+  }
+  $addnewfields .= '</ul>';
+  $form['addnewfields'] = array('#value' => $addnewfields);
+
+  return $form;
+}
+
+/**
+ * Submit handler to update changed profile field weights and categories.
+ *
+ * @see profile_admin_overview()
+ */
+function profile_admin_overview_submit($form, &$form_state) {
+  foreach (element_children($form_state['values']) as $fid) {
+    if (is_numeric($fid)) {
+      $weight = $form_state['values'][$fid]['weight'];
+      $category = $form_state['values'][$fid]['category'];
+      if ($weight != $form[$fid]['weight']['#default_value'] || $category != $form[$fid]['category']['#default_value']) {
+        db_query("UPDATE {profile_fields} SET weight = %d, category = '%s' WHERE fid = %d", $weight, $category, $fid);
+      }
+    }
+  }
+
+  drupal_set_message(t('Profile fields have been updated.'));
+  cache_clear_all();
+  menu_rebuild();
+}
+
+/**
+ * Theme the profile field overview into a drag and drop enabled table.
+ *
+ * @ingroup themeable
+ * @see profile_admin_overview()
+ */
+function theme_profile_admin_overview($form) {
+  drupal_add_css(drupal_get_path('module', 'profile') .'/profile.css');
+  // Add javascript if there's more than one field.
+  if (isset($form['submit'])) {
+    drupal_add_js(drupal_get_path('module', 'profile') .'/profile.js');
+  }
+
+  $rows = array();
+  $categories = array();
+  $category_number = 0;
+  foreach (element_children($form) as $key) {
+    // Don't take form control structures.
+    if (array_key_exists('category', $form[$key])) {
+      $field = &$form[$key];
+      $category = $field['category']['#default_value'];
+
+      if (!isset($categories[$category])) {
+        // Category classes are given numeric IDs because there's no guarantee
+        // class names won't contain invalid characters.
+        $categories[$category] = $category_number;
+        $category_field['#attributes']['class'] = 'profile-category profile-category-'. $category_number;
+        $rows[] = array(array('data' => $category, 'colspan' => 7, 'class' => 'category'));
+        $rows[] = array('data' => array(array('data' => '<em>'. t('No fields in this category. If this category remains empty when saved, it will be removed.') .'</em>', 'colspan' => 7)), 'class' => 'category-'. $category_number .'-message category-message category-populated');
+
+        // Make it dragable only if there is more than one field
+        if (isset($form['submit'])) {
+          drupal_add_tabledrag('profile-fields', 'order', 'sibling', 'profile-weight', 'profile-weight-'. $category_number);
+          drupal_add_tabledrag('profile-fields', 'match', 'sibling', 'profile-category', 'profile-category-'. $category_number);
+        }
+        $category_number++;
+      }
+
+      // Add special drag and drop classes that group fields together.
+      $field['weight']['#attributes']['class'] = 'profile-weight profile-weight-'. $categories[$category];
+      $field['category']['#attributes']['class'] = 'profile-category profile-category-'. $categories[$category];
+
+      // Add the row
+      $row = array();
+      $row[] = drupal_render($field['title']);
+      $row[] = drupal_render($field['name']);
+      $row[] = drupal_render($field['type']);
+      if (isset($form['submit'])) {
+        $row[] = drupal_render($field['category']);
+        $row[] = drupal_render($field['weight']);
+      }
+      $row[] = drupal_render($field['edit']);
+      $row[] = drupal_render($field['delete']);
+      $rows[] = array('data' => $row, 'class' => 'draggable');
+    }
+  }
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => 7));
+  }
+
+  $header = array(t('Title'), t('Name'), t('Type'));
+  if (isset($form['submit'])) {
+    $header[] = t('Category');
+    $header[] = t('Weight');
+  }
+  $header[] = array('data' => t('Operations'), 'colspan' => 2);
+
+  $output = theme('table', $header, $rows, array('id' => 'profile-fields'));
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Menu callback: Generate a form to add/edit a user profile field.
+ *
+ * @ingroup forms
+ * @see profile_field_form_validate()
+ * @see profile_field_form_submit()
+ */
+function profile_field_form(&$form_state, $arg = NULL) {
+  if (arg(3) == 'edit') {
+    if (is_numeric($arg)) {
+      $fid = $arg;
+
+      $edit = db_fetch_array(db_query('SELECT * FROM {profile_fields} WHERE fid = %d', $fid));
+
+      if (!$edit) {
+        drupal_not_found();
+        return;
+      }
+      drupal_set_title(t('edit %title', array('%title' => $edit['title'])));
+      $form['fid'] = array('#type' => 'value',
+        '#value' => $fid,
+      );
+      $type = $edit['type'];
+    }
+    else {
+      drupal_not_found();
+      return;
+    }
+  }
+  else {
+    $types = _profile_field_types();
+    if (!isset($types[$arg])) {
+      drupal_not_found();
+      return;
+    }
+    $type = $arg;
+    drupal_set_title(t('add new %type', array('%type' => $types[$type])));
+    $edit = array('name' => 'profile_');
+    $form['type'] = array('#type' => 'value', '#value' => $type);
+  }
+  $edit += array(
+    'category' => '',
+    'title' => '',
+    'explanation' => '',
+    'weight' => 0,
+    'page' => '',
+    'autocomplete' => '',
+    'required' => '',
+    'register' => '',
+  );
+  $form['fields'] = array('#type' => 'fieldset',
+    '#title' => t('Field settings'),
+  );
+  $form['fields']['category'] = array('#type' => 'textfield',
+    '#title' => t('Category'),
+    '#default_value' => $edit['category'],
+    '#autocomplete_path' => 'admin/user/profile/autocomplete',
+    '#description' => t('The category the new field should be part of. Categories are used to group fields logically. An example category is "Personal information".'),
+    '#required' => TRUE,
+  );
+  $form['fields']['title'] = array('#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => $edit['title'],
+    '#description' => t('The title of the new field. The title will be shown to the user. An example title is "Favorite color".'),
+    '#required' => TRUE,
+  );
+  $form['fields']['name'] = array('#type' => 'textfield',
+    '#title' => t('Form name'),
+    '#default_value' => $edit['name'],
+    '#description' => t('The name of the field. The form name is not shown to the user but used internally in the HTML code and URLs.
+Unless you know what you are doing, it is highly recommended that you prefix the form name with <code>profile_</code> to avoid name clashes with other fields. Spaces or any other special characters except dash (-) and underscore (_) are not allowed. An example name is "profile_favorite_color" or perhaps just "profile_color".'),
+    '#required' => TRUE,
+  );
+  $form['fields']['explanation'] = array('#type' => 'textarea',
+    '#title' => t('Explanation'),
+    '#default_value' => $edit['explanation'],
+    '#description' => t('An optional explanation to go with the new field. The explanation will be shown to the user.'),
+  );
+  if ($type == 'selection') {
+    $form['fields']['options'] = array('#type' => 'textarea',
+      '#title' => t('Selection options'),
+      '#default_value' => isset($edit['options']) ? $edit['options'] : '',
+      '#description' => t('A list of all options. Put each option on a separate line. Example options are "red", "blue", "green", etc.'),
+    );
+  }
+  $form['fields']['visibility'] = array('#type' => 'radios',
+    '#title' => t('Visibility'),
+    '#default_value' => isset($edit['visibility']) ? $edit['visibility'] : PROFILE_PUBLIC,
+    '#options' => array(PROFILE_HIDDEN => t('Hidden profile field, only accessible by administrators, modules and themes.'), PROFILE_PRIVATE => t('Private field, content only available to privileged users.'), PROFILE_PUBLIC => t('Public field, content shown on profile page but not used on member list pages.'), PROFILE_PUBLIC_LISTINGS => t('Public field, content shown on profile page and on member list pages.')),
+  );
+  if ($type == 'selection' || $type == 'list' || $type == 'textfield') {
+    $form['fields']['page'] = array('#type' => 'textfield',
+      '#title' => t('Page title'),
+      '#default_value' => $edit['page'],
+      '#description' => t('To enable browsing this field by value, enter a title for the resulting page. The word <code>%value</code> will be substituted with the corresponding value. An example page title is "People whose favorite color is %value". This is only applicable for a public field.'),
+    );
+  }
+  else if ($type == 'checkbox') {
+    $form['fields']['page'] = array('#type' => 'textfield',
+      '#title' => t('Page title'),
+      '#default_value' => $edit['page'],
+      '#description' => t('To enable browsing this field by value, enter a title for the resulting page. An example page title is "People who are employed". This is only applicable for a public field.'),
+    );
+  }
+  $form['fields']['weight'] = array('#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $edit['weight'],
+    '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'),
+  );
+  $form['fields']['autocomplete'] = array('#type' => 'checkbox',
+    '#title' => t('Form will auto-complete while user is typing.'),
+    '#default_value' => $edit['autocomplete'],
+  );
+  $form['fields']['required'] = array('#type' => 'checkbox',
+    '#title' => t('The user must enter a value.'),
+    '#default_value' => $edit['required'],
+  );
+  $form['fields']['register'] = array('#type' => 'checkbox',
+    '#title' => t('Visible in user registration form.'),
+    '#default_value' => $edit['register'],
+  );
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Save field'),
+  );
+  return $form;
+}
+
+/**
+ * Validate profile_field_form submissions.
+ */
+function profile_field_form_validate($form, &$form_state) {
+  // Validate the 'field name':
+  if (preg_match('/[^a-zA-Z0-9_-]/', $form_state['values']['name'])) {
+    form_set_error('name', t('The specified form name contains one or more illegal characters. Spaces or any other special characters except dash (-) and underscore (_) are not allowed.'));
+  }
+
+  if (in_array($form_state['values']['name'], user_fields())) {
+    form_set_error('name', t('The specified form name is reserved for use by Drupal.'));
+  }
+  // Validate the category:
+  if (!$form_state['values']['category']) {
+    form_set_error('category', t('You must enter a category.'));
+  }
+  if (strtolower($form_state['values']['category']) == 'account') {
+    form_set_error('category', t('The specified category name is reserved for use by Drupal.'));
+  }
+  $args1 = array($form_state['values']['title'], $form_state['values']['category']);
+  $args2 = array($form_state['values']['name']);
+  $query_suffix = '';
+
+  if (isset($form_state['values']['fid'])) {
+    $args1[] = $args2[] = $form_state['values']['fid'];
+    $query_suffix = ' AND fid != %d';
+  }
+
+  if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s' AND category = '%s'". $query_suffix, $args1))) {
+    form_set_error('title', t('The specified title is already in use.'));
+  }
+  if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE name = '%s'". $query_suffix, $args2))) {
+    form_set_error('name', t('The specified name is already in use.'));
+  }
+  if ($form_state['values']['visibility'] == PROFILE_HIDDEN) {
+    if ($form_state['values']['required']) {
+      form_set_error('required', t('A hidden field cannot be required.'));
+    }
+    if ($form_state['values']['register']) {
+      form_set_error('register', t('A hidden field cannot be set to visible on the user registration form.'));
+    }
+  }
+}
+
+/**
+ * Process profile_field_form submissions.
+ */
+function profile_field_form_submit($form, &$form_state) {
+  if (!isset($form_state['values']['options'])) {
+    $form_state['values']['options'] = '';
+  }
+  if (!isset($form_state['values']['page'])) {
+    $form_state['values']['page'] = '';
+  }
+  if (!isset($form_state['values']['fid'])) {
+    db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, register, visibility, autocomplete, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, '%s', '%s')", $form_state['values']['title'], $form_state['values']['name'], $form_state['values']['explanation'], $form_state['values']['category'], $form_state['values']['type'], $form_state['values']['weight'], $form_state['values']['required'], $form_state['values']['register'], $form_state['values']['visibility'], $form_state['values']['autocomplete'], $form_state['values']['options'], $form_state['values']['page']);
+
+    drupal_set_message(t('The field has been created.'));
+    watchdog('profile', 'Profile field %field added under category %category.', array('%field' => $form_state['values']['title'], '%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile'));
+  }
+  else {
+    db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, register = %d, visibility = %d, autocomplete = %d, options = '%s', page = '%s' WHERE fid = %d", $form_state['values']['title'], $form_state['values']['name'], $form_state['values']['explanation'], $form_state['values']['category'], $form_state['values']['weight'], $form_state['values']['required'], $form_state['values']['register'], $form_state['values']['visibility'], $form_state['values']['autocomplete'], $form_state['values']['options'], $form_state['values']['page'], $form_state['values']['fid']);
+
+    drupal_set_message(t('The field has been updated.'));
+  }
+  cache_clear_all();
+  menu_rebuild();
+
+  $form_state['redirect'] = 'admin/user/profile';
+  return;
+}
+
+/**
+ * Menu callback; deletes a field from all user profiles.
+ */
+function profile_field_delete(&$form_state, $fid) {
+  $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid));
+  if (!$field) {
+    drupal_not_found();
+    return;
+  }
+  $form['fid'] = array('#type' => 'value', '#value' => $fid);
+  $form['title'] = array('#type' => 'value', '#value' => $field->title);
+
+  return confirm_form($form,
+    t('Are you sure you want to delete the field %field?', array('%field' => $field->title)), 'admin/user/profile',
+    t('This action cannot be undone. If users have entered values into this field in their profile, these entries will also be deleted. If you want to keep the user-entered data, instead of deleting the field you may wish to <a href="@edit-field">edit this field</a> and change it to a hidden profile field so that it may only be accessed by administrators.', array('@edit-field' => url('admin/user/profile/edit/'. $fid))),
+    t('Delete'), t('Cancel'));
+}
+
+/**
+ * Process a field delete form submission.
+ */
+function profile_field_delete_submit($form, &$form_state) {
+  db_query('DELETE FROM {profile_fields} WHERE fid = %d', $form_state['values']['fid']);
+  db_query('DELETE FROM {profile_values} WHERE fid = %d', $form_state['values']['fid']);
+
+  cache_clear_all();
+
+  drupal_set_message(t('The field %field has been deleted.', array('%field' => $form_state['values']['title'])));
+  watchdog('profile', 'Profile field %field deleted.', array('%field' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile'));
+
+  $form_state['redirect'] = 'admin/user/profile';
+  return;
+}
+
+/**
+ * Retrieve a pipe delimited string of autocomplete suggestions for profile categories
+ */
+function profile_admin_settings_autocomplete($string) {
+  $matches = array();
+  $result = db_query_range("SELECT category FROM {profile_fields} WHERE LOWER(category) LIKE LOWER('%s%%')", $string, 0, 10);
+  while ($data = db_fetch_object($result)) {
+    $matches[$data->category] = check_plain($data->category);
+  }
+  print drupal_to_js($matches);
+  exit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+/* $Id: profile.css,v 1.3 2007/11/30 09:02:51 goba Exp $ */
+
+#profile-fields td.category {
+  font-weight: bold;
+}
+#profile-fields tr.category-message {
+  color: #999;
+}
+#profile-fields tr.category-populated {
+  display: none;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: profile.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Profile
+description = Supports configurable user profiles.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,147 @@
+<?php
+// $Id: profile.install,v 1.12 2007/12/18 12:59:22 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function profile_install() {
+  // Create tables.
+  drupal_install_schema('profile');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function profile_uninstall() {
+  // Remove tables
+  drupal_uninstall_schema('profile');
+
+  variable_del('profile_block_author_fields');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function profile_schema() {
+  $schema['profile_fields'] = array(
+    'description' => t('Stores profile field information.'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique profile field ID.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Title of the field shown to the end user.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Internal name of the field used in the form HTML and URLs.'),
+      ),
+      'explanation' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => t('Explanation of the field to end users.'),
+      ),
+      'category' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Profile category that the field will be grouped under.'),
+      ),
+      'page' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t("Title of page used for browsing by the field's value"),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+        'description' => t('Type of form field.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Weight of field in relation to other profile fields.'),
+      ),
+      'required' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether the user is required to enter a value. (0 = no, 1 = yes)'),
+      ),
+      'register' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether the field is visible in the user registration form. (1 = yes, 0 = no)'),
+      ),
+      'visibility' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The level of visibility for the field. (0 = hidden, 1 = private, 2 = public on profile but not member list pages, 3 = public on profile and list pages)'),
+      ),
+      'autocomplete' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether form auto-completion is enabled. (0 = disabled, 1 = enabled)'),
+      ),
+      'options' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => t('List of options to be used in a list selection field.'),
+      ),
+    ),
+    'indexes' => array('category' => array('category')),
+    'unique keys' => array('name' => array('name')),
+    'primary key' => array('fid'),
+  );
+
+  $schema['profile_values'] = array(
+    'description' => t('Stores values for profile fields.'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {profile_fields}.fid of the field.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {users}.uid of the profile user.'),
+      ),
+      'value' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => t('The value for the field.'),
+      ),
+    ),
+    'primary key' => array('uid', 'fid'),
+    'indexes' => array(
+      'fid' => array('fid'),
+    ),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,54 @@
+// $Id: profile.js,v 1.2 2007/12/08 14:06:22 goba Exp $
+
+/**
+ * Add functionality to the profile drag and drop table.
+ *
+ * This behavior is dependent on the tableDrag behavior, since it uses the
+ * objects initialized in that behavior to update the row. It shows and hides
+ * a warning message when removing the last field from a profile category.
+ */
+Drupal.behaviors.profileDrag = function(context) {
+  var table = $('#profile-fields');
+  var tableDrag = Drupal.tableDrag['profile-fields']; // Get the profile tableDrag object.
+
+  // Add a handler for when a row is swapped, update empty categories.
+  tableDrag.row.prototype.onSwap = function(swappedRow) {
+    var rowObject = this;
+    $('tr.category-message', table).each(function() {
+      // If the dragged row is in this category, but above the message row, swap it down one space.
+      if ($(this).prev('tr').get(0) == rowObject.element) {
+        // Prevent a recursion problem when using the keyboard to move rows up.
+        if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
+          rowObject.swap('after', this);
+        }
+      }
+      // This category has become empty
+      if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').size() == 0) {
+        $(this).removeClass('category-populated').addClass('category-empty');
+      }
+      // This category has become populated.
+      else if ($(this).is('.category-empty')) {
+        $(this).removeClass('category-empty').addClass('category-populated');
+      }
+    });
+  };
+
+  // Add a handler so when a row is dropped, update fields dropped into new categories.
+  tableDrag.onDrop = function() {
+    dragObject = this;
+    if ($(dragObject.rowObject.element).prev('tr').is('.category-message')) {
+      var categoryRow = $(dragObject.rowObject.element).prev('tr').get(0);
+      var categoryNum = categoryRow.className.replace(/([^ ]+[ ]+)*category-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
+      var categoryField = $('select.profile-category', dragObject.rowObject.element);
+      var weightField = $('select.profile-weight', dragObject.rowObject.element);
+      var oldcategoryNum = weightField[0].className.replace(/([^ ]+[ ]+)*profile-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
+
+      if (!categoryField.is('.profile-category-'+ categoryNum)) {
+        categoryField.removeClass('profile-category-' + oldcategoryNum).addClass('profile-category-' + categoryNum);
+        weightField.removeClass('profile-weight-' + oldcategoryNum).addClass('profile-weight-' + categoryNum);
+
+        categoryField.val(categoryField[0].options[categoryNum].value);
+      }
+    }
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,575 @@
+<?php
+// $Id: profile.module,v 1.236 2008/02/03 19:36:46 goba Exp $
+
+/**
+ * @file
+ * Support for configurable user profiles.
+ */
+
+/**
+ * Private field, content only available to privileged users.
+ */
+define('PROFILE_PRIVATE', 1);
+
+/**
+ * Public field, content shown on profile page but not used on member list pages.
+ */
+define('PROFILE_PUBLIC', 2);
+
+/**
+ * Public field, content shown on profile page and on member list pages.
+ */
+define('PROFILE_PUBLIC_LISTINGS', 3);
+
+/**
+ * Hidden profile field, only accessible by administrators, modules and themes.
+ */
+define('PROFILE_HIDDEN', 4);
+
+/**
+ * Implementation of hook_help().
+ */
+function profile_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#profile':
+      $output = '<p>'. t('The profile module allows custom fields (such as country, full name, or age) to be defined and displayed in the <em>My Account</em> section. This permits users of a site to share more information about themselves, and can help community-based sites organize users around specific information.') .'</p>';
+      $output .= '<p>'. t('The following types of fields can be added to a user profile:') .'</p>';
+      $output .= '<ul><li>'. t('single-line textfield') .'</li>';
+      $output .= '<li>'. t('multi-line textfield') .'</li>';
+      $output .= '<li>'. t('checkbox') .'</li>';
+      $output .= '<li>'. t('list selection') .'</li>';
+      $output .= '<li>'. t('freeform list') .'</li>';
+      $output .= '<li>'. t('URL') .'</li>';
+      $output .= '<li>'. t('date') .'</li></ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@profile">Profile module</a>.', array('@profile' => 'http://drupal.org/handbook/modules/profile/')) .'</p>';
+      return $output;
+    case 'admin/user/profile':
+      return '<p>'. t("This page displays a list of the existing custom profile fields to be displayed on a user's <em>My Account</em> page. To provide structure, similar or related fields may be placed inside a category. To add a new category (or edit an existing one), edit a profile field and provide a new category name. To change the category of a field or the order of fields within a category, grab a drag-and-drop handle under the Title column and drag the field to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save configuration</em> button at the bottom of the page.") .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function profile_theme() {
+  return array(
+    'profile_block' => array(
+      'arguments' => array('account' => NULL, 'fields' => array()),
+      'template' => 'profile-block',
+    ),
+    'profile_listing' => array(
+      'arguments' => array('account' => NULL, 'fields' => array()),
+      'template' => 'profile-listing',
+    ),
+    'profile_wrapper' => array(
+      'arguments' => array('content' => NULL),
+      'template' => 'profile-wrapper',
+    ),
+    'profile_admin_overview' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'profile.admin.inc',
+    )
+  );
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function profile_menu() {
+  $items['profile'] = array(
+    'title' => 'User list',
+    'page callback' => 'profile_browse',
+    'access arguments' => array('access user profiles'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'profile.pages.inc',
+  );
+  $items['admin/user/profile'] = array(
+    'title' => 'Profiles',
+    'description' => 'Create customizable fields for your users.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile_admin_overview'),
+    'access arguments' => array('administer users'),
+    'file' => 'profile.admin.inc',
+  );
+  $items['admin/user/profile/add'] = array(
+    'title' => 'Add field',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile_field_form'),
+    'type' => MENU_CALLBACK,
+    'file' => 'profile.admin.inc',
+  );
+  $items['admin/user/profile/autocomplete'] = array(
+    'title' => 'Profile category autocomplete',
+    'page callback' => 'profile_admin_settings_autocomplete',
+    'type' => MENU_CALLBACK,
+    'file' => 'profile.admin.inc',
+  );
+  $items['admin/user/profile/edit'] = array(
+    'title' => 'Edit field',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile_field_form'),
+    'type' => MENU_CALLBACK,
+    'file' => 'profile.admin.inc',
+  );
+  $items['admin/user/profile/delete'] = array(
+    'title' => 'Delete field',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('profile_field_delete'),
+    'type' => MENU_CALLBACK,
+    'file' => 'profile.admin.inc',
+  );
+  $items['profile/autocomplete'] = array(
+    'title' => 'Profile autocomplete',
+    'page callback' => 'profile_autocomplete',
+    'access arguments' => array('access user profiles'),
+    'type' => MENU_CALLBACK,
+    'file' => 'profile.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function profile_block($op = 'list', $delta = 0, $edit = array()) {
+
+  if ($op == 'list') {
+    $blocks[0]['info'] = t('Author information');
+    $blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
+    return $blocks;
+  }
+  else if ($op == 'configure' && $delta == 0) {
+    // Compile a list of fields to show
+    $fields = array();
+    $result = db_query('SELECT name, title, weight, visibility FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
+    while ($record = db_fetch_object($result)) {
+      $fields[$record->name] = check_plain($record->title);
+    }
+    $fields['user_profile'] = t('Link to full user profile');
+    $form['profile_block_author_fields'] = array('#type' => 'checkboxes',
+      '#title' => t('Profile fields to display'),
+      '#default_value' => variable_get('profile_block_author_fields', NULL),
+      '#options' => $fields,
+      '#description' => t('Select which profile fields you wish to display in the block. Only fields designated as public in the <a href="@profile-admin">profile field configuration</a> are available.', array('@profile-admin' => url('admin/user/profile'))),
+    );
+    return $form;
+  }
+  else if ($op == 'save' && $delta == 0) {
+    variable_set('profile_block_author_fields', $edit['profile_block_author_fields']);
+  }
+  else if ($op == 'view') {
+    if (user_access('access user profiles')) {
+      $output = '';
+      if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
+        $node = node_load(arg(1));
+        $account = user_load(array('uid' => $node->uid));
+
+        if ($use_fields = variable_get('profile_block_author_fields', array())) {
+          // Compile a list of fields to show.
+          $fields = array();
+          $result = db_query('SELECT name, title, type, visibility, weight FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
+          while ($record = db_fetch_object($result)) {
+            // Ensure that field is displayed only if it is among the defined block fields and, if it is private, the user has appropriate permissions.
+            if (isset($use_fields[$record->name]) && $use_fields[$record->name]) {
+              $fields[] = $record;
+            }
+          }
+        }
+
+        if (!empty($fields)) {
+          $profile = _profile_update_user_fields($fields, $account);
+          $output .= theme('profile_block', $account, $profile, TRUE);
+        }
+
+        if (isset($use_fields['user_profile']) && $use_fields['user_profile']) {
+          $output .= '<div>'. l(t('View full user profile'), 'user/'. $account->uid) .'</div>';
+        }
+      }
+
+      if ($output) {
+        $block['subject'] = t('About %name', array('%name' => $account->name));
+        $block['content'] = $output;
+        return $block;
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function profile_user($type, &$edit, &$user, $category = NULL) {
+  switch ($type) {
+    case 'load':
+      return profile_load_profile($user);
+    case 'register':
+      return profile_form_profile($edit, $user, $category, TRUE);
+    case 'update':
+    return profile_save_profile($edit, $user, $category);
+    case 'insert':
+      return profile_save_profile($edit, $user, $category, TRUE);
+    case 'view':
+      return profile_view_profile($user);
+    case 'form':
+      return profile_form_profile($edit, $user, $category);
+    case 'validate':
+      return profile_validate_profile($edit, $category);
+    case 'categories':
+      return profile_categories();
+    case 'delete':
+      db_query('DELETE FROM {profile_values} WHERE uid = %d', $user->uid);
+  }
+}
+
+function profile_load_profile(&$user) {
+  $result = db_query('SELECT f.name, f.type, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid);
+  while ($field = db_fetch_object($result)) {
+    if (empty($user->{$field->name})) {
+      $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value;
+    }
+  }
+}
+
+function profile_save_profile(&$edit, &$user, $category, $register = FALSE) {
+  $result = _profile_get_fields($category, $register);
+  while ($field = db_fetch_object($result)) {
+    if (_profile_field_serialize($field->type)) {
+      $edit[$field->name] = serialize($edit[$field->name]);
+    }
+    db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
+    db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
+    // Mark field as handled (prevents saving to user->data).
+    $edit[$field->name] = NULL;
+  }
+}
+
+function profile_view_field($user, $field) {
+  // Only allow browsing of private fields for admins, if browsing is enabled,
+  // and if a user has permission to view profiles. Note that this check is
+  // necessary because a user may always see their own profile.
+  $browse = user_access('access user profiles')
+         && (user_access('administer users') || $field->visibility != PROFILE_PRIVATE)
+         && !empty($field->page);
+
+  if (isset($user->{$field->name}) && $value = $user->{$field->name}) {
+    switch ($field->type) {
+      case 'textarea':
+        return check_markup($value);
+      case 'textfield':
+      case 'selection':
+        return $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
+      case 'checkbox':
+        return $browse ? l($field->title, 'profile/'. $field->name) : check_plain($field->title);
+      case 'url':
+        return '<a href="'. check_url($value) .'">'. check_plain($value) .'</a>';
+      case 'date':
+        $format = substr(variable_get('date_format_short', 'm/d/Y - H:i'), 0, 5);
+        // Note: Avoid PHP's date() because it does not handle dates before
+        // 1970 on Windows. This would make the date field useless for e.g.
+        // birthdays.
+        $replace = array(
+          'd' => sprintf('%02d', $value['day']),
+          'j' => $value['day'],
+          'm' => sprintf('%02d', $value['month']),
+          'M' => map_month($value['month']),
+          'Y' => $value['year'],
+          'H:i' => NULL,
+          'g:ia' => NULL,
+        );
+        return strtr($format, $replace);
+      case 'list':
+        $values = split("[,\n\r]", $value);
+        $fields = array();
+        foreach ($values as $value) {
+          if ($value = trim($value)) {
+            $fields[] = $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
+          }
+        }
+        return implode(', ', $fields);
+    }
+  }
+}
+
+function profile_view_profile(&$user) {
+
+  profile_load_profile($user);
+
+  // Show private fields to administrators and people viewing their own account.
+  if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) {
+    $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_HIDDEN);
+  }
+  else {
+    $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND visibility != %d ORDER BY category, weight', PROFILE_PRIVATE, PROFILE_HIDDEN);
+  }
+
+  $fields = array();
+  while ($field = db_fetch_object($result)) {
+    if ($value = profile_view_field($user, $field)) {
+      $title = ($field->type != 'checkbox') ? check_plain($field->title) : NULL;
+
+      // Create a single fieldset for each category.
+      if (!isset($user->content[$field->category])) {
+        $user->content[$field->category] = array(
+          '#type' => 'user_profile_category',
+          '#title' => $field->category,
+        );
+      }
+
+      $user->content[$field->category][$field->name] = array(
+        '#type' => 'user_profile_item',
+        '#title' => $title,
+        '#value' => $value,
+        '#weight' => $field->weight,
+        '#attributes' => array('class' => 'profile-'. $field->name),
+      );
+    }
+  }
+}
+
+function _profile_form_explanation($field) {
+  $output = $field->explanation;
+
+  if ($field->type == 'list') {
+    $output .= ' '. t('Put each item on a separate line or separate them by commas. No HTML allowed.');
+  }
+
+  if ($field->visibility == PROFILE_PRIVATE) {
+    $output .= ' '. t('The content of this field is kept private and will not be shown publicly.');
+  }
+
+  return $output;
+}
+
+function profile_form_profile($edit, $user, $category, $register = FALSE) {
+  $result = _profile_get_fields($category, $register);
+  $weight = 1;
+  $fields = array();
+  while ($field = db_fetch_object($result)) {
+    $category = $field->category;
+    if (!isset($fields[$category])) {
+      $fields[$category] = array('#type' => 'fieldset', '#title' => check_plain($category), '#weight' => $weight++);
+    }
+    switch ($field->type) {
+      case 'textfield':
+      case 'url':
+        $fields[$category][$field->name] = array('#type' => 'textfield',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#maxlength' => 255,
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        if ($field->autocomplete) {
+          $fields[$category][$field->name]['#autocomplete_path'] = "profile/autocomplete/". $field->fid;
+        }
+        break;
+      case 'textarea':
+        $fields[$category][$field->name] = array('#type' => 'textarea',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        break;
+      case 'list':
+        $fields[$category][$field->name] = array('#type' => 'textarea',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        break;
+      case 'checkbox':
+        $fields[$category][$field->name] = array('#type' => 'checkbox',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        break;
+      case 'selection':
+        $options = $field->required ? array() : array('--');
+        $lines = split("[,\n\r]", $field->options);
+        foreach ($lines as $line) {
+          if ($line = trim($line)) {
+            $options[$line] = $line;
+          }
+        }
+        $fields[$category][$field->name] = array('#type' => 'select',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#options' => $options,
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        break;
+      case 'date':
+        $fields[$category][$field->name] = array('#type' => 'date',
+          '#title' => check_plain($field->title),
+          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
+          '#description' => _profile_form_explanation($field),
+          '#required' => $field->required,
+        );
+        break;
+    }
+  }
+  return $fields;
+}
+
+/**
+ * Helper function: update an array of user fields by calling profile_view_field
+ */
+function _profile_update_user_fields($fields, $account) {
+  foreach ($fields as $key => $field) {
+    $fields[$key]->value = profile_view_field($account, $field);
+  }
+  return $fields;
+}
+
+function profile_validate_profile($edit, $category) {
+  $result = _profile_get_fields($category);
+  while ($field = db_fetch_object($result)) {
+    if ($edit[$field->name]) {
+      if ($field->type == 'url') {
+        if (!valid_url($edit[$field->name], TRUE)) {
+          form_set_error($field->name, t('The value provided for %field is not a valid URL.', array('%field' => $field->title)));
+        }
+      }
+    }
+    else if ($field->required && !user_access('administer users')) {
+      form_set_error($field->name, t('The field %field is required.', array('%field' => $field->title)));
+    }
+  }
+
+  return $edit;
+}
+
+function profile_categories() {
+  $result = db_query("SELECT DISTINCT(category) FROM {profile_fields}");
+  $data = array();
+  while ($category = db_fetch_object($result)) {
+    $data[] = array(
+      'name' => $category->category,
+      'title' => $category->category,
+      'weight' => 3,
+      'access callback' => 'profile_category_access',
+      'access arguments' => array($category->category)
+    );
+  }
+  return $data;
+}
+
+/**
+ * Menu item access callback - check if a user has access to a profile category.
+ */
+function profile_category_access($category) {
+  if (user_access('administer users')) {
+    return TRUE;
+  }
+  else {
+    return db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE category = '%s' AND visibility <> %d", $category, PROFILE_HIDDEN));
+  }
+}
+
+/**
+ * Process variables for profile-block.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $account
+ * - $fields
+ *
+ * @see profile-block.tpl.php
+ */
+function template_preprocess_profile_block(&$variables) {
+
+  $variables['picture'] = theme('user_picture', $variables['account']);
+  $variables['profile'] = array();
+  // Supply filtered version of $fields that have values.
+  foreach ($variables['fields'] as $field) {
+    if ($field->value) {
+      $variables['profile'][$field->name]->title = check_plain($field->title);
+      $variables['profile'][$field->name]->value = $field->value;
+      $variables['profile'][$field->name]->type = $field->type;
+    }
+  }
+
+}
+
+/**
+ * Process variables for profile-listing.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $account
+ * - $fields
+ *
+ * @see profile-listing.tpl.php
+ */
+function template_preprocess_profile_listing(&$variables) {
+
+  $variables['picture'] = theme('user_picture', $variables['account']);
+  $variables['name'] = theme('username', $variables['account']);
+  $variables['profile'] = array();
+  // Supply filtered version of $fields that have values.
+  foreach ($variables['fields'] as $field) {
+    if ($field->value) {
+      $variables['profile'][$field->name]->title = $field->title;
+      $variables['profile'][$field->name]->value = $field->value;
+      $variables['profile'][$field->name]->type = $field->type;
+    }
+  }
+
+}
+
+/**
+ * Process variables for profile-wrapper.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $content
+ *
+ * @see profile-wrapper.tpl.php
+ */
+function template_preprocess_profile_wrapper(&$variables) {
+  $variables['current_field'] = '';
+  if ($field = arg(1)) {
+    $variables['current_field'] = $field;
+    // Supply an alternate template suggestion based on the browsable field.
+    $variables['template_files'][] = 'profile-wrapper-'. $field;
+  }
+}
+
+function _profile_field_types($type = NULL) {
+  $types = array('textfield' => t('single-line textfield'),
+                 'textarea' => t('multi-line textfield'),
+                 'checkbox' => t('checkbox'),
+                 'selection' => t('list selection'),
+                 'list' => t('freeform list'),
+                 'url' => t('URL'),
+                 'date' => t('date'));
+  return isset($type) ? $types[$type] : $types;
+}
+
+function _profile_field_serialize($type = NULL) {
+  return $type == 'date';
+}
+
+function _profile_get_fields($category, $register = FALSE) {
+  $args = array();
+  $sql = 'SELECT * FROM {profile_fields} WHERE ';
+  $filters = array();
+  if ($register) {
+    $filters[] = 'register = 1';
+  }
+  else {
+    // Use LOWER('%s') instead of PHP's strtolower() to avoid UTF-8 conversion issues.
+    $filters[] = "LOWER(category) = LOWER('%s')";
+    $args[] = $category;
+  }
+  if (!user_access('administer users')) {
+    $filters[] = 'visibility != %d';
+    $args[] = PROFILE_HIDDEN;
+  }
+  $sql .= implode(' AND ', $filters);
+  $sql .= ' ORDER BY category, weight';
+  return db_query($sql, $args);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/profile/profile.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,120 @@
+<?php
+// $Id: profile.pages.inc,v 1.2 2007/12/08 14:06:22 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the profile module.
+ */
+
+/**
+ * Menu callback; display a list of user information.
+ */
+function profile_browse() {
+  // Ensure that the path is converted to 3 levels always.
+  list(, $name, $value) = array_pad(explode('/', $_GET['q'], 3), 3, '');
+
+  $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name));
+
+  if ($name && $field->fid) {
+    // Only allow browsing of fields that have a page title set.
+    if (empty($field->page)) {
+      drupal_not_found();
+      return;
+    }
+    // Do not allow browsing of private and hidden fields by non-admins.
+    if (!user_access('administer users') && ($field->visibility == PROFILE_PRIVATE || $field->visibility == PROFILE_HIDDEN)) {
+      drupal_access_denied();
+      return;
+    }
+
+    // Compile a list of fields to show.
+    $fields = array();
+    $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE fid != %d AND visibility = %d ORDER BY weight', $field->fid, PROFILE_PUBLIC_LISTINGS);
+    while ($record = db_fetch_object($result)) {
+      $fields[] = $record;
+    }
+
+    // Determine what query to use:
+    $arguments = array($field->fid);
+    switch ($field->type) {
+      case 'checkbox':
+        $query = 'v.value = 1';
+        break;
+      case 'textfield':
+      case 'selection':
+        $query = "v.value = '%s'";
+        $arguments[] = $value;
+        break;
+      case 'list':
+        $query = "v.value LIKE '%%%s%%'";
+        $arguments[] = $value;
+        break;
+      default:
+        drupal_not_found();
+        return;
+    }
+
+    // Extract the affected users:
+    $result = pager_query("SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_values} v ON u.uid = v.uid WHERE v.fid = %d AND $query AND u.access != 0 AND u.status != 0 ORDER BY u.access DESC", 20, 0, NULL, $arguments);
+
+    $content = '';
+    while ($account = db_fetch_object($result)) {
+      $account = user_load(array('uid' => $account->uid));
+      $profile = _profile_update_user_fields($fields, $account);
+      $content .= theme('profile_listing', $account, $profile);
+    }
+    $output = theme('profile_wrapper', $content);
+    $output .= theme('pager', NULL, 20);
+
+    if ($field->type == 'selection' || $field->type == 'list' || $field->type == 'textfield') {
+      $title = strtr(check_plain($field->page), array('%value' => theme('placeholder', $value)));
+    }
+    else {
+      $title = check_plain($field->page);
+    }
+
+    drupal_set_title($title);
+    return $output;
+  }
+  else if ($name && !$field->fid) {
+    drupal_not_found();
+  }
+  else {
+    // Compile a list of fields to show.
+    $fields = array();
+    $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE visibility = %d ORDER BY category, weight', PROFILE_PUBLIC_LISTINGS);
+    while ($record = db_fetch_object($result)) {
+      $fields[] = $record;
+    }
+
+    // Extract the affected users:
+    $result = pager_query('SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 AND access != 0 ORDER BY access DESC', 20, 0, NULL);
+
+    $content = '';
+    while ($account = db_fetch_object($result)) {
+      $account = user_load(array('uid' => $account->uid));
+      $profile = _profile_update_user_fields($fields, $account);
+      $content .= theme('profile_listing', $account, $profile);
+    }
+    $output = theme('profile_wrapper', $content);
+    $output .= theme('pager', NULL, 20);
+
+    drupal_set_title(t('User list'));
+    return $output;
+  }
+}
+
+/**
+ * Callback to allow autocomplete of profile text fields.
+ */
+function profile_autocomplete($field, $string) {
+  $matches = array();
+  if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) {
+    $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10);
+    while ($data = db_fetch_object($result)) {
+      $matches[$data->value] = check_plain($data->value);
+    }
+  }
+
+  drupal_json($matches);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search-block-form.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,37 @@
+<?php
+// $Id: search-block-form.tpl.php,v 1.1 2007/10/31 18:06:38 dries Exp $
+
+/**
+ * @file search-block-form.tpl.php
+ * Default theme implementation for displaying a search form within a block region.
+ *
+ * Available variables:
+ * - $search_form: The complete search form ready for print.
+ * - $search: Array of keyed search elements. Can be used to print each form
+ *   element separately.
+ *
+ * Default keys within $search:
+ * - $search['search_block_form']: Text input area wrapped in a div.
+ * - $search['submit']: Form submit button.
+ * - $search['hidden']: Hidden form elements. Used to validate forms when submitted.
+ *
+ * Since $search is keyed, a direct print of the form element is possible.
+ * Modules can add to the search form so it is recommended to check for their
+ * existance before printing. The default keys will always exist.
+ *
+ *   <?php if (isset($search['extra_field'])): ?>
+ *     <div class="extra-field">
+ *       <?php print $search['extra_field']; ?>
+ *     </div>
+ *   <?php endif; ?>
+ *
+ * To check for all available data within $search, use the code below.
+ *
+ *   <?php print '<pre>'. check_plain(print_r($search, 1)) .'</pre>'; ?>
+ *
+ * @see template_preprocess_search_block_form()
+ */
+?>
+<div class="container-inline">
+  <?php print $search_form; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search-result.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,59 @@
+<?php
+// $Id: search-result.tpl.php,v 1.1 2007/10/31 18:06:38 dries Exp $
+
+/**
+ * @file search-result.tpl.php
+ * Default theme implementation for displaying a single search result.
+ *
+ * This template renders a single search result and is collected into
+ * search-results.tpl.php. This and the parent template are
+ * dependent to one another sharing the markup for definition lists.
+ *
+ * Available variables:
+ * - $url: URL of the result.
+ * - $title: Title of the result.
+ * - $snippet: A small preview of the result. Does not apply to user searches.
+ * - $info: String of all the meta information ready for print. Does not apply
+ *   to user searches.
+ * - $info_split: Contains same data as $info split into a keyed array.
+ * - $type: The type of search, e.g., "node" or "user".
+ *
+ * Default keys within $info_split:
+ * - $info_split['type']: Node type.
+ * - $info_split['user']: Author of the node linked to users profile. Depends
+ *   on permission.
+ * - $info_split['date']: Last update of the node. Short formatted.
+ * - $info_split['comment']: Number of comments output as "% comments", %
+ *   being the count. Depends on comment.module.
+ * - $info_split['upload']: Number of attachments output as "% attachments", %
+ *   being the count. Depends on upload.module.
+ *
+ * Since $info_split is keyed, a direct print of the item is possible.
+ * This array does not apply to user searches so it is recommended to check
+ * for their existance before printing. The default keys of 'type', 'user' and
+ * 'date' always exist for node searches. Modules may provide other data.
+ *
+ *   <?php if (isset($info_split['comment'])) : ?>
+ *     <span class="info-comment">
+ *       <?php print $info_split['comment']; ?>
+ *     </span>
+ *   <?php endif; ?>
+ *
+ * To check for all available data within $info_split, use the code below.
+ *
+ *   <?php print '<pre>'. check_plain(print_r($info_split, 1)) .'</pre>'; ?>
+ *
+ * @see template_preprocess_search_result()
+ */
+?>
+<dt class="title">
+  <a href="<?php print $url; ?>"><?php print $title; ?></a>
+</dt>
+<dd>
+  <?php if ($snippet) : ?>
+    <p class="search-snippet"><?php print $snippet; ?></p>
+  <?php endif; ?>
+  <?php if ($info) : ?>
+  <p class="search-info"><?php print $info; ?></p>
+  <?php endif; ?>
+</dd>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search-results.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,27 @@
+<?php
+// $Id: search-results.tpl.php,v 1.1 2007/10/31 18:06:38 dries Exp $
+
+/**
+ * @file search-results.tpl.php
+ * Default theme implementation for displaying search results.
+ *
+ * This template collects each invocation of theme_search_result(). This and
+ * the child template are dependant to one another sharing the markup for
+ * definition lists.
+ *
+ * Note that modules may implement their own search type and theme function
+ * completely bypassing this template.
+ *
+ * Available variables:
+ * - $search_results: All results as it is rendered through
+ *   search-result.tpl.php
+ * - $type: The type of search, e.g., "node" or "user".
+ *
+ *
+ * @see template_preprocess_search_results()
+ */
+?>
+<dl class="search-results <?php print $type; ?>-results">
+  <?php print $search_results; ?>
+</dl>
+<?php print $pager; ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+/* $Id: search-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */
+
+.search-advanced .criterion {
+  float: right;
+  margin-right: 0;
+  margin-left: 2em;
+}
+.search-advanced .action {
+  float: right;
+  clear: right;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search-theme-form.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,38 @@
+<?php
+// $Id: search-theme-form.tpl.php,v 1.1 2007/10/31 18:06:38 dries Exp $
+
+/**
+ * @file search-theme-form.tpl.php
+ * Default theme implementation for displaying a search form directly into the
+ * theme layout. Not to be confused with the search block or the search page.
+ *
+ * Available variables:
+ * - $search_form: The complete search form ready for print.
+ * - $search: Array of keyed search elements. Can be used to print each form
+ *   element separately.
+ *
+ * Default keys within $search:
+ * - $search['search_theme_form']: Text input area wrapped in a div.
+ * - $search['submit']: Form submit button.
+ * - $search['hidden']: Hidden form elements. Used to validate forms when submitted.
+ *
+ * Since $search is keyed, a direct print of the form element is possible.
+ * Modules can add to the search form so it is recommended to check for their
+ * existance before printing. The default keys will always exist.
+ *
+ *   <?php if (isset($search['extra_field'])): ?>
+ *     <div class="extra-field">
+ *       <?php print $search['extra_field']; ?>
+ *     </div>
+ *   <?php endif; ?>
+ *
+ * To check for all available data within $search, use the code below.
+ *
+ *   <?php print '<pre>'. check_plain(print_r($search, 1)) .'</pre>'; ?>
+ *
+ * @see template_preprocess_search_theme_form()
+ */
+?>
+<div id="search" class="container-inline">
+  <?php print $search_form; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,85 @@
+<?php
+// $Id: search.admin.inc,v 1.4 2008/01/08 10:35:42 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the search module.
+ */
+
+/**
+ * Menu callback: confirm wiping of the index.
+ */
+function search_wipe_confirm() {
+  return confirm_form(array(), t('Are you sure you want to re-index the site?'),
+                  'admin/settings/search', t(' The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed. This action cannot be undone.'), t('Re-index site'), t('Cancel'));
+}
+
+/**
+ * Handler for wipe confirmation
+ */
+function search_wipe_confirm_submit(&$form, &$form_state) {
+  if ($form['confirm']) {
+    search_wipe();
+    drupal_set_message(t('The index will be rebuilt.'));
+    $form_state['redirect'] = 'admin/settings/search';
+    return;
+  }
+}
+
+/**
+ * Menu callback; displays the search module settings page.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ * @see search_admin_settings_validate()
+ */
+function search_admin_settings() {
+  // Collect some stats
+  $remaining = 0;
+  $total = 0;
+  foreach (module_list() as $module) {
+    if (module_hook($module, 'search')) {
+      $status = module_invoke($module, 'search', 'status');
+      $remaining += $status['remaining'];
+      $total += $status['total'];
+    }
+  }
+  $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
+  $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) .'%';
+  $status = '<p><strong>'. t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) .' '. $count .'</strong></p>';
+  $form['status'] = array('#type' => 'fieldset', '#title' => t('Indexing status'));
+  $form['status']['status'] = array('#value' => $status);
+  $form['status']['wipe'] = array('#type' => 'submit', '#value' => t('Re-index site'));
+
+  $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500));
+
+  // Indexing throttle:
+  $form['indexing_throttle'] = array('#type' => 'fieldset', '#title' => t('Indexing throttle'));
+  $form['indexing_throttle']['search_cron_limit'] = array('#type' => 'select', '#title' => t('Number of items to index per cron run'), '#default_value' => variable_get('search_cron_limit', 100), '#options' => $items, '#description' => t('The maximum number of items indexed in each pass of a <a href="@cron">cron maintenance task</a>. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))));
+  // Indexing settings:
+  $form['indexing_settings'] = array('#type' => 'fieldset', '#title' => t('Indexing settings'));
+  $form['indexing_settings']['info'] = array('#value' => t('<p><em>Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.</em></p><p><em>The default settings should be appropriate for the majority of sites.</em></p>'));
+  $form['indexing_settings']['minimum_word_size'] = array('#type' => 'textfield', '#title' => t('Minimum word length to index'), '#default_value' => variable_get('minimum_word_size', 3), '#size' => 5, '#maxlength' => 3, '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).'));
+  $form['indexing_settings']['overlap_cjk'] = array('#type' => 'checkbox', '#title' => t('Simple CJK handling'), '#default_value' => variable_get('overlap_cjk', TRUE), '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.'));
+
+  $form['#validate'] = array('search_admin_settings_validate');
+
+  // Per module settings
+  $form = array_merge($form, module_invoke_all('search', 'admin'));
+  return system_settings_form($form);
+}
+
+/**
+ * Validate callback.
+ */
+function search_admin_settings_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Re-index site')) {
+    drupal_goto('admin/settings/search/wipe');
+  }
+  // If these settings change, the index needs to be rebuilt.
+  if ((variable_get('minimum_word_size', 3) != $form_state['values']['minimum_word_size']) ||
+      (variable_get('overlap_cjk', TRUE) != $form_state['values']['overlap_cjk'])) {
+    drupal_set_message(t('The index will be rebuilt.'));
+    search_wipe();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,29 @@
+/* $Id: search.css,v 1.3 2007/10/31 18:06:38 dries Exp $ */
+
+.search-form {
+  margin-bottom: 1em;
+}
+.search-form input {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+.search-results p {
+  margin-top: 0;
+}
+.search-results dt {
+  font-size: 1.1em;
+}
+.search-results dd {
+  margin-bottom: 1em;
+}
+.search-results .search-info {
+  font-size: 0.85em;
+}
+.search-advanced .criterion {
+  float: left; /* LTR */
+  margin-right: 2em; /* LTR */
+}
+.search-advanced .action {
+  float: left; /* LTR */
+  clear: left; /* LTR */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: search.info,v 1.4 2007/06/08 05:50:55 dries Exp $
+name = Search
+description = Enables site-wide keyword searching.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,153 @@
+<?php
+// $Id: search.install,v 1.14 2007/12/28 10:53:27 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function search_install() {
+  // Create tables.
+  drupal_install_schema('search');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function search_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('search');
+
+  variable_del('minimum_word_size');
+  variable_del('overlap_cjk');
+  variable_del('search_cron_limit');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function search_schema() {
+  $schema['search_dataset'] = array(
+    'description' => t('Stores items that will be searched.'),
+    'fields' => array(
+      'sid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Search item ID, e.g. node ID for nodes.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 16,
+        'not null' => FALSE,
+        'description' => t('Type of item, e.g. node.'),
+      ),
+      'data' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => t('List of space-separated words from the item.'),
+      ),
+      'reindex' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Set to force node reindexing.'),
+      ),
+    ),
+    'unique keys' => array('sid_type' => array('sid', 'type')),
+  );
+
+  $schema['search_index'] = array(
+    'description' => t('Stores the search index, associating words, items and scores.'),
+    'fields' => array(
+      'word' => array(
+        'type' => 'varchar',
+        'length' => 50,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The {search_total}.word that is associated with the search item.'),
+      ),
+      'sid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {search_dataset}.sid of the searchable item to which the word belongs.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 16,
+        'not null' => FALSE,
+        'description' => t('The {search_dataset}.type of the searchable item to which the word belongs.'),
+      ),
+      'score' => array(
+        'type' => 'float',
+        'not null' => FALSE,
+        'description' => t('The numeric score of the word, higher being more important.'),
+      ),
+    ),
+    'indexes' => array(
+      'sid_type' => array('sid', 'type'),
+      'word' => array('word')
+    ),
+    'unique keys' => array('word_sid_type' => array('word', 'sid', 'type')),
+  );
+
+  $schema['search_total'] = array(
+    'description' => t('Stores search totals for words.'),
+    'fields' => array(
+      'word' => array(
+        'description' => t('Primary Key: Unique word in the search index.'),
+        'type' => 'varchar',
+        'length' => 50,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'count' => array(
+        'description' => t("The count of the word in the index using Zipf's law to equalize the probability distribution."),
+        'type' => 'float',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('word'),
+  );
+
+  $schema['search_node_links'] = array(
+    'description' => t('Stores items (like nodes) that link to other nodes, used to improve search scores for nodes that are frequently linked to.'),
+    'fields' => array(
+      'sid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {search_dataset}.sid of the searchable item containing the link to the node.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 16,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The {search_dataset}.type of the searchable item containing the link to the node.'),
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid that this item links to.'),
+      ),
+      'caption' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => FALSE,
+        'description' => t('The text used to link to the {node}.nid.'),
+      ),
+    ),
+    'primary key' => array('sid', 'type', 'nid'),
+    'indexes' => array('nid' => array('nid')),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1290 @@
+<?php
+// $Id: search.module,v 1.250.2.1 2008/02/07 16:42:03 goba Exp $
+
+/**
+ * @file
+ * Enables site-wide keyword searching.
+ */
+
+/**
+ * Matches Unicode character classes to exclude from the search index.
+ *
+ * See: http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values
+ *
+ * The index only contains the following character classes:
+ * Lu     Letter, Uppercase
+ * Ll     Letter, Lowercase
+ * Lt     Letter, Titlecase
+ * Lo     Letter, Other
+ * Nd     Number, Decimal Digit
+ * No     Number, Other
+ */
+define('PREG_CLASS_SEARCH_EXCLUDE',
+'\x{0}-\x{2f}\x{3a}-\x{40}\x{5b}-\x{60}\x{7b}-\x{bf}\x{d7}\x{f7}\x{2b0}-'.
+'\x{385}\x{387}\x{3f6}\x{482}-\x{489}\x{559}-\x{55f}\x{589}-\x{5c7}\x{5f3}-'.
+'\x{61f}\x{640}\x{64b}-\x{65e}\x{66a}-\x{66d}\x{670}\x{6d4}\x{6d6}-\x{6ed}'.
+'\x{6fd}\x{6fe}\x{700}-\x{70f}\x{711}\x{730}-\x{74a}\x{7a6}-\x{7b0}\x{901}-'.
+'\x{903}\x{93c}\x{93e}-\x{94d}\x{951}-\x{954}\x{962}-\x{965}\x{970}\x{981}-'.
+'\x{983}\x{9bc}\x{9be}-\x{9cd}\x{9d7}\x{9e2}\x{9e3}\x{9f2}-\x{a03}\x{a3c}-'.
+'\x{a4d}\x{a70}\x{a71}\x{a81}-\x{a83}\x{abc}\x{abe}-\x{acd}\x{ae2}\x{ae3}'.
+'\x{af1}-\x{b03}\x{b3c}\x{b3e}-\x{b57}\x{b70}\x{b82}\x{bbe}-\x{bd7}\x{bf0}-'.
+'\x{c03}\x{c3e}-\x{c56}\x{c82}\x{c83}\x{cbc}\x{cbe}-\x{cd6}\x{d02}\x{d03}'.
+'\x{d3e}-\x{d57}\x{d82}\x{d83}\x{dca}-\x{df4}\x{e31}\x{e34}-\x{e3f}\x{e46}-'.
+'\x{e4f}\x{e5a}\x{e5b}\x{eb1}\x{eb4}-\x{ebc}\x{ec6}-\x{ecd}\x{f01}-\x{f1f}'.
+'\x{f2a}-\x{f3f}\x{f71}-\x{f87}\x{f90}-\x{fd1}\x{102c}-\x{1039}\x{104a}-'.
+'\x{104f}\x{1056}-\x{1059}\x{10fb}\x{10fc}\x{135f}-\x{137c}\x{1390}-\x{1399}'.
+'\x{166d}\x{166e}\x{1680}\x{169b}\x{169c}\x{16eb}-\x{16f0}\x{1712}-\x{1714}'.
+'\x{1732}-\x{1736}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17db}\x{17dd}'.
+'\x{17f0}-\x{180e}\x{1843}\x{18a9}\x{1920}-\x{1945}\x{19b0}-\x{19c0}\x{19c8}'.
+'\x{19c9}\x{19de}-\x{19ff}\x{1a17}-\x{1a1f}\x{1d2c}-\x{1d61}\x{1d78}\x{1d9b}-'.
+'\x{1dc3}\x{1fbd}\x{1fbf}-\x{1fc1}\x{1fcd}-\x{1fcf}\x{1fdd}-\x{1fdf}\x{1fed}-'.
+'\x{1fef}\x{1ffd}-\x{2070}\x{2074}-\x{207e}\x{2080}-\x{2101}\x{2103}-\x{2106}'.
+'\x{2108}\x{2109}\x{2114}\x{2116}-\x{2118}\x{211e}-\x{2123}\x{2125}\x{2127}'.
+'\x{2129}\x{212e}\x{2132}\x{213a}\x{213b}\x{2140}-\x{2144}\x{214a}-\x{2b13}'.
+'\x{2ce5}-\x{2cff}\x{2d6f}\x{2e00}-\x{3005}\x{3007}-\x{303b}\x{303d}-\x{303f}'.
+'\x{3099}-\x{309e}\x{30a0}\x{30fb}-\x{30fe}\x{3190}-\x{319f}\x{31c0}-\x{31cf}'.
+'\x{3200}-\x{33ff}\x{4dc0}-\x{4dff}\x{a015}\x{a490}-\x{a716}\x{a802}\x{a806}'.
+'\x{a80b}\x{a823}-\x{a82b}\x{d800}-\x{f8ff}\x{fb1e}\x{fb29}\x{fd3e}\x{fd3f}'.
+'\x{fdfc}-\x{fe6b}\x{feff}-\x{ff0f}\x{ff1a}-\x{ff20}\x{ff3b}-\x{ff40}\x{ff5b}-'.
+'\x{ff65}\x{ff70}\x{ff9e}\x{ff9f}\x{ffe0}-\x{fffd}');
+
+/**
+ * Matches all 'N' Unicode character classes (numbers)
+ */
+define('PREG_CLASS_NUMBERS',
+'\x{30}-\x{39}\x{b2}\x{b3}\x{b9}\x{bc}-\x{be}\x{660}-\x{669}\x{6f0}-\x{6f9}'.
+'\x{966}-\x{96f}\x{9e6}-\x{9ef}\x{9f4}-\x{9f9}\x{a66}-\x{a6f}\x{ae6}-\x{aef}'.
+'\x{b66}-\x{b6f}\x{be7}-\x{bf2}\x{c66}-\x{c6f}\x{ce6}-\x{cef}\x{d66}-\x{d6f}'.
+'\x{e50}-\x{e59}\x{ed0}-\x{ed9}\x{f20}-\x{f33}\x{1040}-\x{1049}\x{1369}-'.
+'\x{137c}\x{16ee}-\x{16f0}\x{17e0}-\x{17e9}\x{17f0}-\x{17f9}\x{1810}-\x{1819}'.
+'\x{1946}-\x{194f}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2153}-\x{2183}'.
+'\x{2460}-\x{249b}\x{24ea}-\x{24ff}\x{2776}-\x{2793}\x{3007}\x{3021}-\x{3029}'.
+'\x{3038}-\x{303a}\x{3192}-\x{3195}\x{3220}-\x{3229}\x{3251}-\x{325f}\x{3280}-'.
+'\x{3289}\x{32b1}-\x{32bf}\x{ff10}-\x{ff19}');
+
+/**
+ * Matches all 'P' Unicode character classes (punctuation)
+ */
+define('PREG_CLASS_PUNCTUATION',
+'\x{21}-\x{23}\x{25}-\x{2a}\x{2c}-\x{2f}\x{3a}\x{3b}\x{3f}\x{40}\x{5b}-\x{5d}'.
+'\x{5f}\x{7b}\x{7d}\x{a1}\x{ab}\x{b7}\x{bb}\x{bf}\x{37e}\x{387}\x{55a}-\x{55f}'.
+'\x{589}\x{58a}\x{5be}\x{5c0}\x{5c3}\x{5f3}\x{5f4}\x{60c}\x{60d}\x{61b}\x{61f}'.
+'\x{66a}-\x{66d}\x{6d4}\x{700}-\x{70d}\x{964}\x{965}\x{970}\x{df4}\x{e4f}'.
+'\x{e5a}\x{e5b}\x{f04}-\x{f12}\x{f3a}-\x{f3d}\x{f85}\x{104a}-\x{104f}\x{10fb}'.
+'\x{1361}-\x{1368}\x{166d}\x{166e}\x{169b}\x{169c}\x{16eb}-\x{16ed}\x{1735}'.
+'\x{1736}\x{17d4}-\x{17d6}\x{17d8}-\x{17da}\x{1800}-\x{180a}\x{1944}\x{1945}'.
+'\x{2010}-\x{2027}\x{2030}-\x{2043}\x{2045}-\x{2051}\x{2053}\x{2054}\x{2057}'.
+'\x{207d}\x{207e}\x{208d}\x{208e}\x{2329}\x{232a}\x{23b4}-\x{23b6}\x{2768}-'.
+'\x{2775}\x{27e6}-\x{27eb}\x{2983}-\x{2998}\x{29d8}-\x{29db}\x{29fc}\x{29fd}'.
+'\x{3001}-\x{3003}\x{3008}-\x{3011}\x{3014}-\x{301f}\x{3030}\x{303d}\x{30a0}'.
+'\x{30fb}\x{fd3e}\x{fd3f}\x{fe30}-\x{fe52}\x{fe54}-\x{fe61}\x{fe63}\x{fe68}'.
+'\x{fe6a}\x{fe6b}\x{ff01}-\x{ff03}\x{ff05}-\x{ff0a}\x{ff0c}-\x{ff0f}\x{ff1a}'.
+'\x{ff1b}\x{ff1f}\x{ff20}\x{ff3b}-\x{ff3d}\x{ff3f}\x{ff5b}\x{ff5d}\x{ff5f}-'.
+'\x{ff65}');
+
+/**
+ * Matches all CJK characters that are candidates for auto-splitting
+ * (Chinese, Japanese, Korean).
+ * Contains kana and BMP ideographs.
+ */
+define('PREG_CLASS_CJK', '\x{3041}-\x{30ff}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}'.
+'\x{4e00}-\x{9fbb}\x{f900}-\x{fad9}');
+
+/**
+ * Implementation of hook_help().
+ */
+function search_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#search':
+      $output = '<p>'. t('The search module adds the ability to search for content by keywords. Search is often the only practical way to find content on a large site, and is useful for finding both users and posts.') .'</p>';
+      $output .= '<p>'. t('To provide keyword searching, the search engine maintains an index of words found in your site\'s content. To build and maintain this index, a correctly configured <a href="@cron">cron maintenance task</a> is required. Indexing behavior can be adjusted using the <a href="@searchsettings">search settings page</a>; for example, the <em>Number of items to index per cron run</em> sets the maximum number of items indexed in each pass of a <a href="@cron">cron maintenance task</a>. If necessary, reduce this number to prevent timeouts and memory errors when indexing.', array('@cron' => url('admin/reports/status'), '@searchsettings' => url('admin/settings/search'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@search">Search module</a>.', array('@search' => 'http://drupal.org/handbook/modules/search/')) .'</p>';
+      return $output;
+    case 'admin/settings/search':
+      return '<p>'. t('The search engine maintains an index of words found in your site\'s content. To build and maintain this index, a correctly configured <a href="@cron">cron maintenance task</a> is required. Indexing behavior can be adjusted using the settings below.', array('@cron' => url('admin/reports/status'))) .'</p>';
+    case 'search#noresults':
+      return t('<ul>
+<li>Check if your spelling is correct.</li>
+<li>Remove quotes around phrases to match each word individually: <em>"blue smurf"</em> will match less than <em>blue smurf</em>.</li>
+<li>Consider loosening your query with <em>OR</em>: <em>blue smurf</em> will match less than <em>blue OR smurf</em>.</li>
+</ul>');
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function search_theme() {
+  return array(
+    'search_theme_form' => array(
+      'arguments' => array('form' => NULL),
+      'template' => 'search-theme-form',
+    ),
+    'search_block_form' => array(
+      'arguments' => array('form' => NULL),
+      'template' => 'search-block-form',
+    ),
+    'search_result' => array(
+      'arguments' => array('result' => NULL, 'type' => NULL),
+      'file' => 'search.pages.inc',
+      'template' => 'search-result',
+    ),
+    'search_results' => array(
+      'arguments' => array('results' => NULL, 'type' => NULL),
+      'file' => 'search.pages.inc',
+      'template' => 'search-results',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function search_perm() {
+  return array('search content', 'use advanced search', 'administer search');
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function search_block($op = 'list', $delta = 0) {
+  if ($op == 'list') {
+    $blocks[0]['info'] = t('Search form');
+    // Not worth caching.
+    $blocks[0]['cache'] = BLOCK_NO_CACHE;
+    return $blocks;
+  }
+  else if ($op == 'view' && user_access('search content')) {
+    $block['content'] = drupal_get_form('search_block_form');
+    $block['subject'] = t('Search');
+    return $block;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function search_menu() {
+  $items['search'] = array(
+    'title' => 'Search',
+    'page callback' => 'search_view',
+    'access arguments' => array('search content'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'file' => 'search.pages.inc',
+  );
+  $items['admin/settings/search'] = array(
+    'title' => 'Search settings',
+    'description' => 'Configure relevance settings for search and other indexing options',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('search_admin_settings'),
+    'access arguments' => array('administer search'),
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'search.admin.inc',
+  );
+  $items['admin/settings/search/wipe'] = array(
+    'title' => 'Clear index',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('search_wipe_confirm'),
+    'access arguments' => array('administer search'),
+    'type' => MENU_CALLBACK,
+    'file' => 'search.admin.inc',
+  );
+  $items['admin/reports/search'] = array(
+    'title' => 'Top search phrases',
+    'description' => 'View most popular search phrases.',
+    'page callback' => 'dblog_top',
+    'page arguments' => array('search'),
+    'file' => 'dblog.admin.inc',
+    'file path' => drupal_get_path('module', 'dblog'),
+  );
+
+  foreach (module_implements('search') as $name) {
+    $items['search/'. $name .'/%menu_tail'] = array(
+      'title callback' => 'module_invoke',
+      'title arguments' => array($name, 'search', 'name', TRUE),
+      'page callback' => 'search_view',
+      'page arguments' => array($name),
+      'access callback' => '_search_menu',
+      'access arguments' => array($name),
+      'type' => MENU_LOCAL_TASK,
+      'parent' => 'search',
+      'file' => 'search.pages.inc',
+    );
+  }
+  return $items;
+}
+
+function _search_menu($name) {
+  return user_access('search content') && module_invoke($name, 'search', 'name');
+}
+
+/**
+ * Wipes a part of or the entire search index.
+ *
+ * @param $sid
+ *  (optional) The SID of the item to wipe. If specified, $type must be passed
+ *  too.
+ * @param $type
+ *  (optional) The type of item to wipe.
+ */
+function search_wipe($sid = NULL, $type = NULL, $reindex = FALSE) {
+  if ($type == NULL && $sid == NULL) {
+    module_invoke_all('search', 'reset');
+  }
+  else {
+    db_query("DELETE FROM {search_dataset} WHERE sid = %d AND type = '%s'", $sid, $type);
+    db_query("DELETE FROM {search_index} WHERE sid = %d AND type = '%s'", $sid, $type);
+    // Don't remove links if re-indexing.
+    if (!$reindex) {
+      db_query("DELETE FROM {search_node_links} WHERE sid = %d AND type = '%s'", $sid, $type);
+    }
+  }
+}
+
+/**
+ * Marks a word as dirty (or retrieves the list of dirty words). This is used
+ * during indexing (cron). Words which are dirty have outdated total counts in
+ * the search_total table, and need to be recounted.
+ */
+function search_dirty($word = NULL) {
+  static $dirty = array();
+  if ($word !== NULL) {
+    $dirty[$word] = TRUE;
+  }
+  else {
+    return $dirty;
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Fires hook_update_index() in all modules and cleans up dirty words (see
+ * search_dirty).
+ */
+function search_cron() {
+  // We register a shutdown function to ensure that search_total is always up
+  // to date.
+  register_shutdown_function('search_update_totals');
+
+  // Update word index
+  foreach (module_list() as $module) {
+    module_invoke($module, 'update_index');
+  }
+}
+
+/**
+ * This function is called on shutdown to ensure that search_total is always
+ * up to date (even if cron times out or otherwise fails).
+ */
+function search_update_totals() {
+  // Update word IDF (Inverse Document Frequency) counts for new/changed words
+  foreach (search_dirty() as $word => $dummy) {
+    // Get total count
+    $total = db_result(db_query("SELECT SUM(score) FROM {search_index} WHERE word = '%s'", $word));
+    // Apply Zipf's law to equalize the probability distribution
+    $total = log10(1 + 1/(max(1, $total)));
+    db_query("UPDATE {search_total} SET count = %f WHERE word = '%s'", $total, $word);
+    if (!db_affected_rows()) {
+      db_query("INSERT INTO {search_total} (word, count) VALUES ('%s', %f)", $word, $total);
+    }
+  }
+  // Find words that were deleted from search_index, but are still in
+  // search_total. We use a LEFT JOIN between the two tables and keep only the
+  // rows which fail to join.
+  $result = db_query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL");
+  while ($word = db_fetch_object($result)) {
+    db_query("DELETE FROM {search_total} WHERE word = '%s'", $word->realword);
+  }
+}
+
+/**
+ * Simplifies a string according to indexing rules.
+ */
+function search_simplify($text) {
+  // Decode entities to UTF-8
+  $text = decode_entities($text);
+
+  // Lowercase
+  $text = drupal_strtolower($text);
+
+  // Call an external processor for word handling.
+  search_invoke_preprocess($text);
+
+  // Simple CJK handling
+  if (variable_get('overlap_cjk', TRUE)) {
+    $text = preg_replace_callback('/['. PREG_CLASS_CJK .']+/u', 'search_expand_cjk', $text);
+  }
+
+  // To improve searching for numerical data such as dates, IP addresses
+  // or version numbers, we consider a group of numerical characters
+  // separated only by punctuation characters to be one piece.
+  // This also means that searching for e.g. '20/03/1984' also returns
+  // results with '20-03-1984' in them.
+  // Readable regexp: ([number]+)[punctuation]+(?=[number])
+  $text = preg_replace('/(['. PREG_CLASS_NUMBERS .']+)['. PREG_CLASS_PUNCTUATION .']+(?=['. PREG_CLASS_NUMBERS .'])/u', '\1', $text);
+
+  // The dot, underscore and dash are simply removed. This allows meaningful
+  // search behavior with acronyms and URLs.
+  $text = preg_replace('/[._-]+/', '', $text);
+
+  // With the exception of the rules above, we consider all punctuation,
+  // marks, spacers, etc, to be a word boundary.
+  $text = preg_replace('/['. PREG_CLASS_SEARCH_EXCLUDE .']+/u', ' ', $text);
+
+  return $text;
+}
+
+/**
+ * Basic CJK tokenizer. Simply splits a string into consecutive, overlapping
+ * sequences of characters ('minimum_word_size' long).
+ */
+function search_expand_cjk($matches) {
+  $min = variable_get('minimum_word_size', 3);
+  $str = $matches[0];
+  $l = drupal_strlen($str);
+  // Passthrough short words
+  if ($l <= $min) {
+    return ' '. $str .' ';
+  }
+  $tokens = ' ';
+  // FIFO queue of characters
+  $chars = array();
+  // Begin loop
+  for ($i = 0; $i < $l; ++$i) {
+    // Grab next character
+    $current = drupal_substr($str, 0, 1);
+    $str = substr($str, strlen($current));
+    $chars[] = $current;
+    if ($i >= $min - 1) {
+      $tokens .= implode('', $chars) .' ';
+      array_shift($chars);
+    }
+  }
+  return $tokens;
+}
+
+/**
+ * Splits a string into tokens for indexing.
+ */
+function search_index_split($text) {
+  static $last = NULL;
+  static $lastsplit = NULL;
+
+  if ($last == $text) {
+    return $lastsplit;
+  }
+  // Process words
+  $text = search_simplify($text);
+  $words = explode(' ', $text);
+  array_walk($words, '_search_index_truncate');
+
+  // Save last keyword result
+  $last = $text;
+  $lastsplit = $words;
+
+  return $words;
+}
+
+/**
+ * Helper function for array_walk in search_index_split.
+ */
+function _search_index_truncate(&$text) {
+  $text = truncate_utf8($text, 50);
+}
+
+/**
+ * Invokes hook_search_preprocess() in modules.
+ */
+function search_invoke_preprocess(&$text) {
+  foreach (module_implements('search_preprocess') as $module) {
+    $text = module_invoke($module, 'search_preprocess', $text);
+  }
+}
+
+/**
+ * Update the full-text search index for a particular item.
+ *
+ * @param $sid
+ *   A number identifying this particular item (e.g. node id).
+ *
+ * @param $type
+ *   A string defining this type of item (e.g. 'node')
+ *
+ * @param $text
+ *   The content of this item. Must be a piece of HTML text.
+ *
+ * @ingroup search
+ */
+function search_index($sid, $type, $text) {
+  $minimum_word_size = variable_get('minimum_word_size', 3);
+
+  // Link matching
+  global $base_url;
+  $node_regexp = '@href=[\'"]?(?:'. preg_quote($base_url, '@') .'/|'. preg_quote(base_path(), '@') .')(?:\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i';
+
+  // Multipliers for scores of words inside certain HTML tags.
+  // Note: 'a' must be included for link ranking to work.
+  $tags = array('h1' => 25,
+                'h2' => 18,
+                'h3' => 15,
+                'h4' => 12,
+                'h5' => 9,
+                'h6' => 6,
+                'u' => 3,
+                'b' => 3,
+                'i' => 3,
+                'strong' => 3,
+                'em' => 3,
+                'a' => 10);
+
+  // Strip off all ignored tags to speed up processing, but insert space before/after
+  // them to keep word boundaries.
+  $text = str_replace(array('<', '>'), array(' <', '> '), $text);
+  $text = strip_tags($text, '<'. implode('><', array_keys($tags)) .'>');
+
+  // Split HTML tags from plain text.
+  $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+  // Note: PHP ensures the array consists of alternating delimiters and literals
+  // and begins and ends with a literal (inserting $null as required).
+
+  $tag = FALSE; // Odd/even counter. Tag or no tag.
+  $link = FALSE; // State variable for link analyser
+  $score = 1; // Starting score per word
+  $accum = ' '; // Accumulator for cleaned up data
+  $tagstack = array(); // Stack with open tags
+  $tagwords = 0; // Counter for consecutive words
+  $focus = 1; // Focus state
+
+  $results = array(0 => array()); // Accumulator for words for index
+
+  foreach ($split as $value) {
+    if ($tag) {
+      // Increase or decrease score per word based on tag
+      list($tagname) = explode(' ', $value, 2);
+      $tagname = drupal_strtolower($tagname);
+      // Closing or opening tag?
+      if ($tagname[0] == '/') {
+        $tagname = substr($tagname, 1);
+        // If we encounter unexpected tags, reset score to avoid incorrect boosting.
+        if (!count($tagstack) || $tagstack[0] != $tagname) {
+          $tagstack = array();
+          $score = 1;
+        }
+        else {
+          // Remove from tag stack and decrement score
+          $score = max(1, $score - $tags[array_shift($tagstack)]);
+        }
+        if ($tagname == 'a') {
+          $link = FALSE;
+        }
+      }
+      else {
+        if (isset($tagstack[0]) && $tagstack[0] == $tagname) {
+          // None of the tags we look for make sense when nested identically.
+          // If they are, it's probably broken HTML.
+          $tagstack = array();
+          $score = 1;
+        }
+        else {
+          // Add to open tag stack and increment score
+          array_unshift($tagstack, $tagname);
+          $score += $tags[$tagname];
+        }
+        if ($tagname == 'a') {
+          // Check if link points to a node on this site
+          if (preg_match($node_regexp, $value, $match)) {
+            $path = drupal_get_normal_path($match[1]);
+            if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) {
+              $linknid = $match[1];
+              if ($linknid > 0) {
+                // Note: ignore links to uncachable nodes to avoid redirect bugs.
+                $node = db_fetch_object(db_query('SELECT n.title, n.nid, n.vid, r.format FROM {node} n INNER JOIN {node_revisions} r ON n.vid = r.vid WHERE n.nid = %d', $linknid));
+                if (filter_format_allowcache($node->format)) {
+                  $link = TRUE;
+                  $linktitle = $node->title;
+                }
+              }
+            }
+          }
+        }
+      }
+      // A tag change occurred, reset counter.
+      $tagwords = 0;
+    }
+    else {
+      // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values
+      if ($value != '') {
+        if ($link) {
+          // Check to see if the node link text is its URL. If so, we use the target node title instead.
+          if (preg_match('!^https?://!i', $value)) {
+            $value = $linktitle;
+          }
+        }
+        $words = search_index_split($value);
+        foreach ($words as $word) {
+          // Add word to accumulator
+          $accum .= $word .' ';
+          $num = is_numeric($word);
+          // Check wordlength
+          if ($num || drupal_strlen($word) >= $minimum_word_size) {
+            // Normalize numbers
+            if ($num) {
+              $word = (int)ltrim($word, '-0');
+            }
+
+            // Links score mainly for the target.
+            if ($link) {
+              if (!isset($results[$linknid])) {
+                $results[$linknid] = array();
+              }
+              $results[$linknid][] = $word;
+              // Reduce score of the link caption in the source.
+              $focus *= 0.2;
+            }
+            // Fall-through
+            if (!isset($results[0][$word])) {
+              $results[0][$word] = 0;
+            }
+            $results[0][$word] += $score * $focus;
+
+            // Focus is a decaying value in terms of the amount of unique words up to this point.
+            // From 100 words and more, it decays, to e.g. 0.5 at 500 words and 0.3 at 1000 words.
+            $focus = min(1, .01 + 3.5 / (2 + count($results[0]) * .015));
+          }
+          $tagwords++;
+          // Too many words inside a single tag probably mean a tag was accidentally left open.
+          if (count($tagstack) && $tagwords >= 15) {
+            $tagstack = array();
+            $score = 1;
+          }
+        }
+      }
+    }
+    $tag = !$tag;
+  }
+
+  search_wipe($sid, $type, TRUE);
+
+  // Insert cleaned up data into dataset
+  db_query("INSERT INTO {search_dataset} (sid, type, data, reindex) VALUES (%d, '%s', '%s', %d)", $sid, $type, $accum, 0);
+
+  // Insert results into search index
+  foreach ($results[0] as $word => $score) {
+    // The database will collate similar words (accented and non-accented forms, etc.),
+    // and the score is additive, so first add and then insert.
+    db_query("UPDATE {search_index} SET score = score + %d WHERE word = '%s' AND sid = '%d' AND type = '%s'", $score, $word, $sid, $type);
+    if (!db_affected_rows()) {
+      db_query("INSERT INTO {search_index} (word, sid, type, score) VALUES ('%s', %d, '%s', %f)", $word, $sid, $type, $score);
+    }
+    search_dirty($word);
+  }
+  unset($results[0]);
+
+  // Get all previous links from this item.
+  $result = db_query("SELECT nid, caption FROM {search_node_links} WHERE sid = %d AND type = '%s'", $sid, $type);
+  $links = array();
+  while ($link = db_fetch_object($result)) {
+    $links[$link->nid] = $link->caption;
+  }
+
+  // Now store links to nodes.
+  foreach ($results as $nid => $words) {
+    $caption = implode(' ', $words);
+    if (isset($links[$nid])) {
+      if ($links[$nid] != $caption) {
+        // Update the existing link and mark the node for reindexing.
+        db_query("UPDATE {search_node_links} SET caption = '%s' WHERE sid = %d AND type = '%s' AND nid = %d", $caption, $sid, $type, $nid);
+        search_touch_node($nid);
+      }
+      // Unset the link to mark it as processed.
+      unset($links[$nid]);
+    }
+    else {
+      // Insert the existing link and mark the node for reindexing.
+      db_query("INSERT INTO {search_node_links} (caption, sid, type, nid) VALUES ('%s', %d, '%s', %d)", $caption, $sid, $type, $nid);
+      search_touch_node($nid);
+    }
+  }
+  // Any left-over links in $links no longer exist. Delete them and mark the nodes for reindexing.
+  foreach ($links as $nid => $caption) {
+    db_query("DELETE FROM {search_node_links} WHERE sid = %d AND type = '%s' AND nid = %d", $sid, $type, $nid);
+    search_touch_node($nid);
+  }
+}
+
+/**
+ * Change a node's changed timestamp to 'now' to force reindexing.
+ *
+ * @param $nid
+ *   The nid of the node that needs reindexing.
+ */
+function search_touch_node($nid) {
+  db_query("UPDATE {search_dataset} SET reindex = %d WHERE sid = %d AND type = 'node'", time(), $nid);
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function search_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
+  switch ($op) {
+    // Transplant links to a node into the target node.
+    case 'update index':
+      $result = db_query("SELECT caption FROM {search_node_links} WHERE nid = %d", $node->nid);
+      $output = array();
+      while ($link = db_fetch_object($result)) {
+        $output[] = $link->caption;
+      }
+      return '<a>('. implode(', ', $output) .')</a>';
+    // Reindex the node when it is updated.  The node is automatically indexed
+    // when it is added, simply by being added to the node table.
+    case 'update':
+      search_touch_node($node->nid);
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_comment().
+ */
+function search_comment($a1, $op) {
+  switch ($op) {
+    // Reindex the node when comments are added or changed
+    case 'insert':
+    case 'update':
+    case 'delete':
+    case 'publish':
+    case 'unpublish':
+      search_touch_node(is_array($a1) ? $a1['nid'] : $a1->nid);
+      break;
+  }
+}
+
+/**
+ * Extract a module-specific search option from a search query. e.g. 'type:book'
+ */
+function search_query_extract($keys, $option) {
+  if (preg_match('/(^| )'. $option .':([^ ]*)( |$)/i', $keys, $matches)) {
+    return $matches[2];
+  }
+}
+
+/**
+ * Return a query with the given module-specific search option inserted in.
+ * e.g. 'type:book'.
+ */
+function search_query_insert($keys, $option, $value = '') {
+  if (search_query_extract($keys, $option)) {
+    $keys = trim(preg_replace('/(^| )'. $option .':[^ ]*/i', '', $keys));
+  }
+  if ($value != '') {
+    $keys .= ' '. $option .':'. $value;
+  }
+  return $keys;
+}
+
+/**
+ * Parse a search query into SQL conditions.
+ *
+ * We build two queries that matches the dataset bodies. @See do_search for
+ * more about these.
+ *
+ * @param $text
+ *   The search keys.
+ * @return
+ *   A list of six elements.
+ *    * A series of statements AND'd together which will be used to provide all
+ *      possible matches.
+ *    * Arguments for this query part.
+ *    * A series of exact word matches OR'd together.
+ *    * Arguments for this query part.
+ *    * A bool indicating whether this is a simple query or not. Negative
+ *      terms, presence of both AND / OR make this FALSE.
+ *    * A bool indicating the presence of a lowercase or. Maybe the user
+ *      wanted to use OR.
+ */
+function search_parse_query($text) {
+  $keys = array('positive' => array(), 'negative' => array());
+
+  // Tokenize query string
+  preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $text, $matches, PREG_SET_ORDER);
+
+  if (count($matches) < 1) {
+    return NULL;
+  }
+
+  // Classify tokens
+  $or = FALSE;
+  $warning = '';
+  $simple = TRUE;
+  foreach ($matches as $match) {
+    $phrase = FALSE;
+    // Strip off phrase quotes
+    if ($match[2]{0} == '"') {
+      $match[2] = substr($match[2], 1, -1);
+      $phrase = TRUE;
+      $simple = FALSE;
+    }
+    // Simplify keyword according to indexing rules and external preprocessors
+    $words = search_simplify($match[2]);
+    // Re-explode in case simplification added more words, except when matching a phrase
+    $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
+    // Negative matches
+    if ($match[1] == '-') {
+      $keys['negative'] = array_merge($keys['negative'], $words);
+    }
+    // OR operator: instead of a single keyword, we store an array of all
+    // OR'd keywords.
+    elseif ($match[2] == 'OR' && count($keys['positive'])) {
+      $last = array_pop($keys['positive']);
+      // Starting a new OR?
+      if (!is_array($last)) {
+        $last = array($last);
+      }
+      $keys['positive'][] = $last;
+      $or = TRUE;
+      continue;
+    }
+    // AND operator: implied, so just ignore it
+    elseif ($match[2] == 'AND' || $match[2] == 'and') {
+      $warning = $match[2];
+      continue;
+    }
+
+    // Plain keyword
+    else {
+      if ($match[2] == 'or') {
+        $warning = $match[2];
+      }
+      if ($or) {
+        // Add to last element (which is an array)
+        $keys['positive'][count($keys['positive']) - 1] = array_merge($keys['positive'][count($keys['positive']) - 1], $words);
+      }
+      else {
+        $keys['positive'] = array_merge($keys['positive'], $words);
+      }
+    }
+    $or = FALSE;
+  }
+
+  // Convert keywords into SQL statements.
+  $query = array();
+  $query2 = array();
+  $arguments = array();
+  $arguments2 = array();
+  $matches = 0;
+  $simple_and = FALSE;
+  $simple_or = FALSE;
+  // Positive matches
+  foreach ($keys['positive'] as $key) {
+    // Group of ORed terms
+    if (is_array($key) && count($key)) {
+      $simple_or = TRUE;
+      $queryor = array();
+      $any = FALSE;
+      foreach ($key as $or) {
+        list($q, $num_new_scores) = _search_parse_query($or, $arguments2);
+        $any |= $num_new_scores;
+        if ($q) {
+          $queryor[] = $q;
+          $arguments[] = $or;
+        }
+      }
+      if (count($queryor)) {
+        $query[] = '('. implode(' OR ', $queryor) .')';
+        // A group of OR keywords only needs to match once
+        $matches += ($any > 0);
+      }
+    }
+    // Single ANDed term
+    else {
+      $simple_and = TRUE;
+      list($q, $num_new_scores, $num_valid_words) = _search_parse_query($key, $arguments2);
+      if ($q) {
+        $query[] = $q;
+        $arguments[] = $key;
+        if (!$num_valid_words) {
+          $simple = FALSE;
+        }
+        // Each AND keyword needs to match at least once
+        $matches += $num_new_scores;
+      }
+    }
+  }
+  if ($simple_and && $simple_or) {
+    $simple = FALSE;
+  }
+  // Negative matches
+  foreach ($keys['negative'] as $key) {
+    list($q) = _search_parse_query($key, $arguments2, TRUE);
+    if ($q) {
+      $query[] = $q;
+      $arguments[] = $key;
+      $simple = FALSE;
+    }
+  }
+  $query = implode(' AND ', $query);
+
+  // Build word-index conditions for the first pass
+  $query2 = substr(str_repeat("i.word = '%s' OR ", count($arguments2)), 0, -4);
+
+  return array($query, $arguments, $query2, $arguments2, $matches, $simple, $warning);
+}
+
+/**
+ * Helper function for search_parse_query();
+ */
+function _search_parse_query(&$word, &$scores, $not = FALSE) {
+  $num_new_scores = 0;
+  $num_valid_words = 0;
+  // Determine the scorewords of this word/phrase
+  if (!$not) {
+    $split = explode(' ', $word);
+    foreach ($split as $s) {
+      $num = is_numeric($s);
+      if ($num || drupal_strlen($s) >= variable_get('minimum_word_size', 3)) {
+        $s = $num ? ((int)ltrim($s, '-0')) : $s;
+        if (!isset($scores[$s])) {
+          $scores[$s] = $s;
+          $num_new_scores++;
+        }
+        $num_valid_words++;
+      }
+    }
+  }
+  // Return matching snippet and number of added words
+  return array("d.data ". ($not ? 'NOT ' : '') ."LIKE '%% %s %%'", $num_new_scores, $num_valid_words);
+}
+
+/**
+ * Do a query on the full-text search index for a word or words.
+ *
+ * This function is normally only called by each module that support the
+ * indexed search (and thus, implements hook_update_index()).
+ *
+ * Results are retrieved in two logical passes. However, the two passes are
+ * joined together into a single query.  And in the case of most simple
+ * queries the second pass is not even used.
+ *
+ * The first pass selects a set of all possible matches, which has the benefit
+ * of also providing the exact result set for simple "AND" or "OR" searches.
+ *
+ * The second portion of the query further refines this set by verifying
+ * advanced text conditions (such negative or phrase matches)
+ *
+ * @param $keywords
+ *   A search string as entered by the user.
+ *
+ * @param $type
+ *   A string identifying the calling module.
+ *
+ * @param $join1
+ *   (optional) Inserted into the JOIN part of the first SQL query.
+ *   For example "INNER JOIN {node} n ON n.nid = i.sid".
+ *
+ * @param $where1
+ *   (optional) Inserted into the WHERE part of the first SQL query.
+ *   For example "(n.status > %d)".
+ *
+ * @param $arguments1
+ *   (optional) Extra SQL arguments belonging to the first query.
+ *
+ * @param $columns2
+ *   (optional) Inserted into the SELECT pat of the second query. Must contain
+ *   a column selected as 'score'.
+ *   defaults to 'i.relevance AS score'
+ *
+ * @param $join2
+ *   (optional) Inserted into the JOIN par of the second SQL query.
+ *   For example "INNER JOIN {node_comment_statistics} n ON n.nid = i.sid"
+ *
+ * @param $arguments2
+ *   (optional) Extra SQL arguments belonging to the second query parameter.
+ *
+ * @param $sort_parameters
+ *   (optional) SQL arguments for sorting the final results.
+ *              Default: 'ORDER BY score DESC'
+ *
+ * @return
+ *   An array of SIDs for the search results.
+ *
+ * @ingroup search
+ */
+function do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $columns2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC') {
+  $query = search_parse_query($keywords);
+
+  if ($query[2] == '') {
+    form_set_error('keys', t('You must include at least one positive keyword with @count characters or more.', array('@count' => variable_get('minimum_word_size', 3))));
+  }
+  if ($query[6]) {
+    if ($query[6] == 'or') {
+      drupal_set_message(t('Search for either of the two terms with uppercase <strong>OR</strong>. For example, <strong>cats OR dogs</strong>.'));
+    }
+  }
+  if ($query === NULL || $query[0] == '' || $query[2] == '') {
+    return array();
+  }
+
+  // Build query for keyword normalization.
+  $conditions = "$where1 AND ($query[2]) AND i.type = '%s'";
+  $arguments1 = array_merge($arguments1, $query[3], array($type));
+  $join = "INNER JOIN {search_total} t ON i.word = t.word $join1";
+  if (!$query[5]) {
+    $conditions .= " AND ($query[0])";
+    $arguments1 = array_merge($arguments1, $query[1]);
+    $join .= " INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type";
+  }
+
+  // Calculate maximum keyword relevance, to normalize it.
+  $select = "SELECT SUM(i.score * t.count) AS score FROM {search_index} i $join WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d ORDER BY score DESC";
+  $arguments = array_merge($arguments1, array($query[4]));
+  $normalize = db_result(db_query_range($select, $arguments, 0, 1));
+  if (!$normalize) {
+    return array();
+  }
+  $columns2 = str_replace('i.relevance', '('. (1.0 / $normalize) .' * SUM(i.score * t.count))', $columns2);
+
+  // Build query to retrieve results.
+  $select = "SELECT i.type, i.sid, $columns2 FROM {search_index} i $join $join2 WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d";
+  $count_select =  "SELECT COUNT(*) FROM ($select) n1";
+  $arguments = array_merge($arguments2, $arguments1, array($query[4]));
+
+  // Do actual search query
+  $result = pager_query("$select $sort_parameters", 10, 0, $count_select, $arguments);
+  $results = array();
+  while ($item = db_fetch_object($result)) {
+    $results[] = $item;
+  }
+  return $results;
+}
+
+/**
+ * Helper function for grabbing search keys.
+ */
+function search_get_keys() {
+  static $return;
+  if (!isset($return)) {
+    // Extract keys as remainder of path
+    // Note: support old GET format of searches for existing links.
+    $path = explode('/', $_GET['q'], 3);
+    $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
+    $return = count($path) == 3 ? $path[2] : $keys;
+  }
+  return $return;
+}
+
+/**
+ * @defgroup search Search interface
+ * @{
+ * The Drupal search interface manages a global search mechanism.
+ *
+ * Modules may plug into this system to provide searches of different types of
+ * data. Most of the system is handled by search.module, so this must be enabled
+ * for all of the search features to work.
+ *
+ * There are three ways to interact with the search system:
+ * - Specifically for searching nodes, you can implement nodeapi('update index')
+ *   and nodeapi('search result'). However, note that the search system already
+ *   indexes all visible output of a node, i.e. everything displayed normally
+ *   by hook_view() and hook_nodeapi('view'). This is usually sufficient.
+ *   You should only use this mechanism if you want additional, non-visible data
+ *   to be indexed.
+ * - Implement hook_search(). This will create a search tab for your module on
+ *   the /search page with a simple keyword search form. You may optionally
+ *   implement hook_search_item() to customize the display of your results.
+ * - Implement hook_update_index(). This allows your module to use Drupal's
+ *   HTML indexing mechanism for searching full text efficiently.
+ *
+ * If your module needs to provide a more complicated search form, then you need
+ * to implement it yourself without hook_search(). In that case, you should
+ * define it as a local task (tab) under the /search page (e.g. /search/mymodule)
+ * so that users can easily find it.
+ */
+
+/**
+ * Render a search form.
+ *
+ * @param $action
+ *   Form action. Defaults to "search".
+ * @param $keys
+ *   The search string entered by the user, containing keywords for the search.
+ * @param $type
+ *   The type of search to render the node for. Must be the name of module
+ *   which implements hook_search(). Defaults to 'node'.
+ * @param $prompt
+ *   A piece of text to put before the form (e.g. "Enter your keywords")
+ * @return
+ *   An HTML string containing the search form.
+ */
+function search_form(&$form_state, $action = '', $keys = '', $type = NULL, $prompt = NULL) {
+
+  // Add CSS
+  drupal_add_css(drupal_get_path('module', 'search') .'/search.css', 'module', 'all', FALSE);
+
+  if (!$action) {
+    $action = url('search/'. $type);
+  }
+  if (is_null($prompt)) {
+    $prompt = t('Enter your keywords');
+  }
+
+  $form = array(
+    '#action' => $action,
+    '#attributes' => array('class' => 'search-form'),
+  );
+  $form['module'] = array('#type' => 'value', '#value' => $type);
+  $form['basic'] = array('#type' => 'item', '#title' => $prompt);
+  $form['basic']['inline'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
+  $form['basic']['inline']['keys'] = array(
+    '#type' => 'textfield',
+    '#title' => '',
+    '#default_value' => $keys,
+    '#size' => $prompt ? 40 : 20,
+    '#maxlength' => 255,
+  );
+  // processed_keys is used to coordinate keyword passing between other forms
+  // that hook into the basic search form.
+  $form['basic']['inline']['processed_keys'] = array('#type' => 'value', '#value' => array());
+  $form['basic']['inline']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
+
+  return $form;
+}
+
+/**
+ * Form builder; Output a search form for the search block and the theme's search box.
+ *
+ * @ingroup forms
+ * @see search_box_form_submit()
+ * @see theme_search_box_form()
+ */
+function search_box(&$form_state, $form_id) {
+  $form[$form_id] = array(
+    '#title' => t('Search this site'),
+    '#type' => 'textfield',
+    '#size' => 15,
+    '#default_value' => '',
+    '#attributes' => array('title' => t('Enter the terms you wish to search for.')),
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Search'));
+  $form['#submit'][] = 'search_box_form_submit';
+  $form['#validate'][] = 'search_box_form_validate';
+
+  return $form;
+}
+
+/**
+ * Process a block search form submission.
+ */
+function search_box_form_submit($form, &$form_state) {
+  $form_id = $form['form_id']['#value'];
+  $form_state['redirect'] = 'search/node/'. trim($form_state['values'][$form_id]);
+}
+
+/**
+ * Process variables for search-theme-form.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $form
+ *
+ * @see search-theme-form.tpl.php
+ */
+function template_preprocess_search_theme_form(&$variables) {
+  $variables['search'] = array();
+  $hidden = array();
+  // Provide variables named after form keys so themers can print each element independently.
+  foreach (element_children($variables['form']) as $key) {
+    $type = $variables['form'][$key]['#type'];
+    if ($type == 'hidden' || $type == 'token') {
+      $hidden[] = drupal_render($variables['form'][$key]);
+    }
+    else {
+      $variables['search'][$key] = drupal_render($variables['form'][$key]);
+    }
+  }
+  // Hidden form elements have no value to themers. No need for separation.
+  $variables['search']['hidden'] = implode($hidden);
+  // Collect all form elements to make it easier to print the whole form.
+  $variables['search_form'] = implode($variables['search']);
+}
+
+/**
+ * Process variables for search-block-form.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $form
+ *
+ * @see search-block-form.tpl.php
+ */
+function template_preprocess_search_block_form(&$variables) {
+  $variables['search'] = array();
+  $hidden = array();
+  // Provide variables named after form keys so themers can print each element independently.
+  foreach (element_children($variables['form']) as $key) {
+    $type = $variables['form'][$key]['#type'];
+    if ($type == 'hidden' || $type == 'token') {
+      $hidden[] = drupal_render($variables['form'][$key]);
+    }
+    else {
+      $variables['search'][$key] = drupal_render($variables['form'][$key]);
+    }
+  }
+  // Hidden form elements have no value to themers. No need for separation.
+  $variables['search']['hidden'] = implode($hidden);
+  // Collect all form elements to make it easier to print the whole form.
+  $variables['search_form'] = implode($variables['search']);
+}
+
+/**
+ * Perform a standard search on the given keys, and return the formatted results.
+ */
+function search_data($keys = NULL, $type = 'node') {
+
+  if (isset($keys)) {
+    if (module_hook($type, 'search')) {
+      $results = module_invoke($type, 'search', 'search', $keys);
+      if (isset($results) && is_array($results) && count($results)) {
+        if (module_hook($type, 'search_page')) {
+          return module_invoke($type, 'search_page', $results);
+        }
+        else {
+          return theme('search_results', $results, $type);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Returns snippets from a piece of text, with certain keywords highlighted.
+ * Used for formatting search results.
+ *
+ * @param $keys
+ *   A string containing a search query.
+ *
+ * @param $text
+ *   The text to extract fragments from.
+ *
+ * @return
+ *   A string containing HTML for the excerpt.
+ */
+function search_excerpt($keys, $text) {
+  // We highlight around non-indexable or CJK characters.
+  $boundary = '(?:(?<=['. PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK .'])|(?=['. PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK .']))';
+
+  // Extract positive keywords and phrases
+  preg_match_all('/ ("([^"]+)"|(?!OR)([^" ]+))/', ' '. $keys, $matches);
+  $keys = array_merge($matches[2], $matches[3]);
+
+  // Prepare text
+  $text = ' '. strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text)) .' ';
+  array_walk($keys, '_search_excerpt_replace');
+  $workkeys = $keys;
+
+  // Extract a fragment per keyword for at most 4 keywords.
+  // First we collect ranges of text around each keyword, starting/ending
+  // at spaces.
+  // If the sum of all fragments is too short, we look for second occurrences.
+  $ranges = array();
+  $included = array();
+  $length = 0;
+  while ($length < 256 && count($workkeys)) {
+    foreach ($workkeys as $k => $key) {
+      if (strlen($key) == 0) {
+        unset($workkeys[$k]);
+        unset($keys[$k]);
+        continue;
+      }
+      if ($length >= 256) {
+        break;
+      }
+      // Remember occurrence of key so we can skip over it if more occurrences
+      // are desired.
+      if (!isset($included[$key])) {
+        $included[$key] = 0;
+      }
+      // Locate a keyword (position $p), then locate a space in front (position
+      // $q) and behind it (position $s)
+      if (preg_match('/'. $boundary . $key . $boundary .'/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
+        $p = $match[0][1];
+        if (($q = strpos($text, ' ', max(0, $p - 60))) !== FALSE) {
+          $end = substr($text, $p, 80);
+          if (($s = strrpos($end, ' ')) !== FALSE) {
+            $ranges[$q] = $p + $s;
+            $length += $p + $s - $q;
+            $included[$key] = $p + 1;
+          }
+          else {
+            unset($workkeys[$k]);
+          }
+        }
+        else {
+          unset($workkeys[$k]);
+        }
+      }
+      else {
+        unset($workkeys[$k]);
+      }
+    }
+  }
+
+  // If we didn't find anything, return the beginning.
+  if (count($ranges) == 0) {
+    return truncate_utf8($text, 256) .' ...';
+  }
+
+  // Sort the text ranges by starting position.
+  ksort($ranges);
+
+  // Now we collapse overlapping text ranges into one. The sorting makes it O(n).
+  $newranges = array();
+  foreach ($ranges as $from2 => $to2) {
+    if (!isset($from1)) {
+      $from1 = $from2;
+      $to1 = $to2;
+      continue;
+    }
+    if ($from2 <= $to1) {
+      $to1 = max($to1, $to2);
+    }
+    else {
+      $newranges[$from1] = $to1;
+      $from1 = $from2;
+      $to1 = $to2;
+    }
+  }
+  $newranges[$from1] = $to1;
+
+  // Fetch text
+  $out = array();
+  foreach ($newranges as $from => $to) {
+    $out[] = substr($text, $from, $to - $from);
+  }
+  $text = (isset($newranges[0]) ? '' : '... ') . implode(' ... ', $out) .' ...';
+
+  // Highlight keywords. Must be done at once to prevent conflicts ('strong' and '<strong>').
+  $text = preg_replace('/'. $boundary .'('. implode('|', $keys) .')'. $boundary .'/iu', '<strong>\0</strong>', $text);
+  return $text;
+}
+
+/**
+ * @} End of "defgroup search".
+ */
+
+/**
+ * Helper function for array_walk in search_except.
+ */
+function _search_excerpt_replace(&$text) {
+  $text = preg_quote($text, '/');
+}
+
+function search_forms() {
+  $forms['search_theme_form']= array(
+    'callback' => 'search_box',
+    'callback arguments' => array('search_theme_form'),
+  );
+  $forms['search_block_form']= array(
+    'callback' => 'search_box',
+    'callback arguments' => array('search_block_form'),
+  );
+  return $forms;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/search/search.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,130 @@
+<?php
+// $Id: search.pages.inc,v 1.4 2007/12/06 09:51:01 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the search module.
+ */
+
+/**
+ * Menu callback; presents the search form and/or search results.
+ */
+function search_view($type = 'node') {
+  // Search form submits with POST but redirects to GET. This way we can keep
+  // the search query URL clean as a whistle:
+  // search/type/keyword+keyword
+  if (!isset($_POST['form_id'])) {
+    if ($type == '') {
+      // Note: search/node can not be a default tab because it would take on the
+      // path of its parent (search). It would prevent remembering keywords when
+      // switching tabs. This is why we drupal_goto to it from the parent instead.
+      drupal_goto('search/node');
+    }
+
+    $keys = search_get_keys();
+    // Only perform search if there is non-whitespace search term:
+    $results = '';
+    if (trim($keys)) {
+      // Log the search keys:
+      watchdog('search', '%keys (@type).', array('%keys' => $keys, '@type' => module_invoke($type, 'search', 'name')), WATCHDOG_NOTICE, l(t('results'), 'search/'. $type .'/'. $keys));
+
+      // Collect the search results:
+      $results = search_data($keys, $type);
+
+      if ($results) {
+        $results = theme('box', t('Search results'), $results);
+      }
+      else {
+        $results = theme('box', t('Your search yielded no results'), search_help('search#noresults', drupal_help_arg()));
+      }
+    }
+
+    // Construct the search form.
+    $output = drupal_get_form('search_form', NULL, $keys, $type);
+    $output .= $results;
+
+    return $output;
+  }
+
+  return drupal_get_form('search_form', NULL, empty($keys) ? '' : $keys, $type);
+}
+
+/**
+ * Process variables for search-results.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $results
+ * - $type
+ *
+ * @see search-results.tpl.php
+ */
+function template_preprocess_search_results(&$variables) {
+  $variables['search_results'] = '';
+  foreach ($variables['results'] as $result) {
+    $variables['search_results'] .= theme('search_result', $result, $variables['type']);
+  }
+  $variables['pager'] = theme('pager', NULL, 10, 0);
+  // Provide alternate search results template.
+  $variables['template_files'][] = 'search-results-'. $variables['type'];
+}
+
+/**
+ * Process variables for search-result.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $result
+ * - $type
+ *
+ * @see search-result.tpl.php
+ */
+function template_preprocess_search_result(&$variables) {
+  $result = $variables['result'];
+  $variables['url'] = check_url($result['link']);
+  $variables['title'] = check_plain($result['title']);
+
+  $info = array();
+  if (!empty($result['type'])) {
+    $info['type'] = check_plain($result['type']);
+  }
+  if (!empty($result['user'])) {
+    $info['user'] = $result['user'];
+  }
+  if (!empty($result['date'])) {
+    $info['date'] = format_date($result['date'], 'small');
+  }
+  if (isset($result['extra']) && is_array($result['extra'])) {
+    $info = array_merge($info, $result['extra']);
+  }
+  // Check for existence. User search does not include snippets.
+  $variables['snippet'] = isset($result['snippet']) ? $result['snippet'] : '';
+  // Provide separated and grouped meta information..
+  $variables['info_split'] = $info;
+  $variables['info'] = implode(' - ', $info);
+  // Provide alternate search result template.
+  $variables['template_files'][] = 'search-result-'. $variables['type'];
+}
+
+/**
+ * As the search form collates keys from other modules hooked in via
+ * hook_form_alter, the validation takes place in _submit.
+ * search_form_validate() is used solely to set the 'processed_keys' form
+ * value for the basic search form.
+ */
+function search_form_validate($form, &$form_state) {
+  form_set_value($form['basic']['inline']['processed_keys'], trim($form_state['values']['keys']), $form_state);
+}
+
+/**
+ * Process a search form submission.
+ */
+function search_form_submit($form, &$form_state) {
+  $keys = $form_state['values']['processed_keys'];
+  if ($keys == '') {
+    form_set_error('keys', t('Please enter some keywords.'));
+    // Fall through to the drupal_goto() call.
+  }
+
+  $type = $form_state['values']['module'] ? $form_state['values']['module'] : 'node';
+  $form_state['redirect'] = 'search/'. $type .'/'. $keys;
+  return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/statistics/statistics.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,215 @@
+<?php
+// $Id: statistics.admin.inc,v 1.6 2008/01/08 10:35:42 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the statistics module.
+ */
+
+/**
+ * Menu callback; presents the "recent hits" page.
+ */
+function statistics_recent_hits() {
+  $header = array(
+    array('data' => t('Timestamp'), 'field' => 'a.timestamp', 'sort' => 'desc'),
+    array('data' => t('Page'), 'field' => 'a.path'),
+    array('data' => t('User'), 'field' => 'u.name'),
+    array('data' => t('Operations'))
+  );
+
+  $sql = 'SELECT a.aid, a.path, a.title, a.uid, u.name, a.timestamp FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid'. tablesort_sql($header);
+
+  $result = pager_query($sql, 30);
+  $rows = array();
+  while ($log = db_fetch_object($result)) {
+    $rows[] = array(
+      array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
+      _statistics_format_item($log->title, $log->path),
+      theme('username', $log),
+      l(t('details'), "admin/reports/access/$log->aid"));
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
+  }
+
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 30, 0);
+  return $output;
+}
+
+/**
+ * Menu callback; presents the "top pages" page.
+ */
+function statistics_top_pages() {
+  // MAX(title) avoids having empty node titles which otherwise causes duplicates in the top pages list
+  $sql = "SELECT COUNT(path) AS hits, path, MAX(title) AS title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog} GROUP BY path";
+  $sql_cnt = "SELECT COUNT(DISTINCT(path)) FROM {accesslog}";
+
+  $header = array(
+    array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
+    array('data' => t('Page'), 'field' => 'path'),
+    array('data' => t('Average page generation time'), 'field' => 'average_time'),
+    array('data' => t('Total page generation time'), 'field' => 'total_time')
+  );
+  $sql .= tablesort_sql($header);
+  $result = pager_query($sql, 30, 0, $sql_cnt);
+
+  $rows = array();
+  while ($page = db_fetch_object($result)) {
+    $rows[] = array($page->hits, _statistics_format_item($page->title, $page->path), t('%time ms', array('%time' => round($page->average_time))), format_interval(round($page->total_time / 1000)));
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
+  }
+
+  drupal_set_title(t('Top pages in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 30, 0);
+  return $output;
+}
+
+/**
+ * Menu callback; presents the "top visitors" page.
+ */
+function statistics_top_visitors() {
+
+  $header = array(
+    array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
+    array('data' => t('Visitor'), 'field' => 'u.name'),
+    array('data' => t('Total page generation time'), 'field' => 'total'),
+    array('data' => t('Operations'))
+  );
+
+  $sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, ac.aid". tablesort_sql($header);
+  $sql_cnt = "SELECT COUNT(DISTINCT(CONCAT(uid, hostname))) FROM {accesslog}";
+  $result = pager_query($sql, 30, 0, $sql_cnt);
+
+  $rows = array();
+  while ($account = db_fetch_object($result)) {
+    $qs = drupal_get_destination();
+    $ban_link = $account->aid ? l(t('unban'), "admin/user/rules/delete/$account->aid", array('query' => $qs)) : l(t('ban'), "admin/user/rules/add/$account->hostname/host", array('query' => $qs));
+    $rows[] = array($account->hits, ($account->uid ? theme('username', $account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link);
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
+  }
+
+  drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 30, 0);
+  return $output;
+}
+
+/**
+ * Menu callback; presents the "referrer" page.
+ */
+function statistics_top_referrers() {
+  $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE '%%%s%%' AND url <> '' GROUP BY url";
+  $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE '%%%s%%'";
+  drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
+
+  $header = array(
+    array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
+    array('data' => t('Url'), 'field' => 'url'),
+    array('data' => t('Last visit'), 'field' => 'last'),
+  );
+
+  $query .= tablesort_sql($header);
+  $result = pager_query($query, 30, 0, $query_cnt, $_SERVER['HTTP_HOST']);
+
+  $rows = array();
+  while ($referrer = db_fetch_object($result)) {
+    $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval(time() - $referrer->last))));
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3));
+  }
+
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 30, 0);
+  return $output;
+}
+
+/**
+ * Menu callback; Displays recent page accesses.
+ */
+function statistics_access_log($aid) {
+  $result = db_query('SELECT a.*, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE aid = %d', $aid);
+  if ($access = db_fetch_object($result)) {
+    $rows[] = array(
+      array('data' => t('URL'), 'header' => TRUE),
+      l(url($access->path, array('absolute' => TRUE)), $access->path)
+    );
+    // It is safe to avoid filtering $access->title through check_plain because
+    // it comes from drupal_get_title().
+    $rows[] = array(
+      array('data' => t('Title'), 'header' => TRUE),
+      $access->title
+    );
+    $rows[] = array(
+      array('data' => t('Referrer'), 'header' => TRUE),
+      ($access->url ? l($access->url, $access->url) : '')
+    );
+    $rows[] = array(
+      array('data' => t('Date'), 'header' => TRUE),
+      format_date($access->timestamp, 'large')
+    );
+    $rows[] = array(
+      array('data' => t('User'), 'header' => TRUE),
+      theme('username', $access)
+    );
+    $rows[] = array(
+      array('data' => t('Hostname'), 'header' => TRUE),
+      check_plain($access->hostname)
+    );
+
+    return theme('table', array(), $rows);
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+/**
+ * Form builder; Configure access logging.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function statistics_access_logging_settings() {
+  // Access log settings:
+  $options = array('1' => t('Enabled'), '0' => t('Disabled'));
+  $form['access'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Access log settings'));
+  $form['access']['statistics_enable_access_log'] = array(
+    '#type' => 'radios',
+    '#title' => t('Enable access log'),
+    '#default_value' => variable_get('statistics_enable_access_log', 0),
+    '#options' => $options,
+    '#description' => t('Log each page access. Required for referrer statistics.'));
+  $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+  $form['access']['statistics_flush_accesslog_timer'] = array(
+    '#type' => 'select',
+    '#title' => t('Discard access logs older than'),
+    '#default_value'   => variable_get('statistics_flush_accesslog_timer', 259200),
+    '#options' => $period,
+    '#description' => t('Older access log entries (including referrer statistics) will be automatically discarded. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))));
+
+  // count content views settings
+  $form['content'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Content viewing counter settings'));
+  $form['content']['statistics_count_content_views'] = array(
+    '#type' => 'radios',
+    '#title' => t('Count content views'),
+    '#default_value' => variable_get('statistics_count_content_views', 0),
+    '#options' => $options,
+    '#description' => t('Increment a counter each time content is viewed.'));
+
+  return system_settings_form($form);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/statistics/statistics.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: statistics.info,v 1.4 2007/06/08 05:50:56 dries Exp $
+name = Statistics
+description = Logs access statistics for your site.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/statistics/statistics.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,121 @@
+<?php
+// $Id: statistics.install,v 1.13 2007/12/18 12:59:22 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function statistics_install() {
+  // Create tables.
+  drupal_install_schema('statistics');
+}
+
+/**
+ * Changes session ID  field to VARCHAR(64) to add support for SHA-1 hashes.
+ */
+function statistics_update_1000() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {accesslog} CHANGE COLUMN sid sid varchar(64) NOT NULL default ''");
+      break;
+    case 'pgsql':
+      db_change_column($ret, 'accesslog', 'sid', 'sid', 'varchar(64)', array('not null' => TRUE, 'default' => "''"));
+      break;
+  }
+
+  return $ret;
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function statistics_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('statistics');
+
+  variable_del('statistics_count_content_views');
+  variable_del('statistics_enable_access_log');
+  variable_del('statistics_flush_accesslog_timer');
+  variable_del('statistics_day_timestamp');
+  variable_del('statistics_block_top_day_num');
+  variable_del('statistics_block_top_all_num');
+  variable_del('statistics_block_top_last_num');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function statistics_schema() {
+  $schema['accesslog'] = array(
+    'description' => t('Stores site access information for statistics.'),
+    'fields' => array(
+      'aid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique accesslog ID.'),
+      ),
+      'sid' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Browser session ID of user that visited page.'),
+      ),
+      'title' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Title of page visited.'),
+      ),
+      'path' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Internal path to page visited (relative to Drupal root.)'),
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => t('Referrer URI.'),
+      ),
+      'hostname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+        'description' => t('Hostname of user that visited the page.'),
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'default' => 0,
+        'description' => t('User {users}.uid that visited the page.'),
+      ),
+      'timer' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Time in milliseconds that the page took to load.'),
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Timestamp of when the page was visited.'),
+      ),
+    ),
+    'indexes' => array(
+      'accesslog_timestamp' => array('timestamp'),
+      'uid' => array('uid'),
+    ),
+    'primary key' => array('aid'),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/statistics/statistics.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,321 @@
+<?php
+// $Id: statistics.module,v 1.272 2008/01/04 09:31:48 goba Exp $
+
+/**
+ * @file
+ * Logs access statistics for your site.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function statistics_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#statistics':
+      $output = '<p>'. t('The statistics module keeps track of numerous site usage statistics, including the number of times, and from where, each of your posts is viewed. These statistics are useful in determining how users are interacting with each other and with your site, and are required for the display of some Drupal blocks.') .'</p>';
+      $output .= '<p>'. t('The statistics module provides:') .'</p>';
+      $output .= '<ul><li>'. t('a counter for each post on your site that increments each time the post is viewed. (Enable <em>Count content views</em> on the <a href="@accesslog">access log settings page</a>, and determine if the post access counters should be visible to any user roles on the <a href="@permissions">permissions page</a>.)', array('@accesslog' => url('admin/reports/settings'), '@permissions' => url('admin/user/permissions'))) .'</li>';
+      $output .= '<li>'. t('a <a href="@recent-hits">recent hits</a> log that displays information about the latest activity on your site, including the URL and title of the page accessed, the user name (if available) and IP address of the accessing party.', array('@recent-hits' => url('admin/reports/hits'))) .'</li>';
+      $output .= '<li>'. t('a <a href="@top-referrers">top referrers</a> log that displays the referring parties for your site visits (where your visitors came from).', array('@top-referrers' => url('admin/reports/referrers'))) .'</li>';
+      $output .= '<li>'. t('a <a href="@top-pages">top pages</a> log that displays site content in descending order by number of views.', array('@top-pages' => url('admin/reports/pages'))) .'</li>';
+      $output .= '<li>'. t('a <a href="@top-visitors">top visitors</a> log that displays the most active users on your site.', array('@top-visitors' => url('admin/reports/visitors'))) .'</li>';
+      $output .= '<li>'. t('a <em>Popular content</em> block that displays the day\'s most viewed content, the all-time most viewed content, and the last content viewed. (Enable the <em>Popular content</em> block on the <a href="@blocks">blocks administration page</a>.)', array('@blocks' => url('admin/build/block'))) .'</li></ul>';
+      $output .= '<p>'. t('Configuring the statistics module') .'</p>';
+      $output .= '<ul><li>'. t('When the <em>Enable access log</em> setting on the <a href="@accesslog">access log settings page</a> is enabled, data about every page accessed (including the remote host\'s IP address, referrer, node accessed, and user name) is stored in the access log. The access log must be enabled for the <a href="@recent-hits">recent hits</a>, <a href="@top-referrers">top referrers</a>, <a href="@top-pages">top pages</a>, and <a href="@top-visitors">top visitors</a> log pages to function. Enabling the access log adds one additional database call per page displayed by Drupal.', array('@accesslog' => url('admin/reports/settings'), '@recent-hits' => url('admin/reports/hits'), '@top-referrers' => url('admin/reports/referrers'), '@top-pages' => url('admin/reports/pages'), '@top-visitors' => url('admin/reports/visitors'))) .'</li>';
+      $output .= '<li>'. t('The <em>Discard access logs older than</em> setting on the <a href="@accesslog">access log settings page</a> specifies the length of time entries are retained in the access log before they are deleted. Automatic access log entry deletion requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@accesslog' => url('admin/reports/settings'), '@cron' => url('admin/reports/status'))) .'</li>';
+      $output .= '<li>'. t('The <em>Count content views</em> setting on the <a href="@accesslog">access log settings page</a> enables a counter for each post on your site that increments each time the post is viewed. This option must be enabled to provide post-specific access counts. Enabling this option adds one additional database call per each post displayed by Drupal.', array('@accesslog' => url('admin/reports/settings'))) .'</li></ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@statistics">Statistics module</a>.', array('@statistics' => 'http://drupal.org/handbook/modules/statistics/')) .'</p>';
+      return $output;
+    case 'admin/reports/settings':
+      return '<p>'. t('Settings for the statistical information that Drupal will keep about the site. See <a href="@statistics">site statistics</a> for the actual information.', array('@statistics' => url('admin/reports/hits'))) .'</p>';
+    case 'admin/reports/hits':
+      return '<p>'. t("This page displays the site's most recent hits.") .'</p>';
+    case 'admin/reports/referrers':
+      return '<p>'. t('This page displays all external referrers, or external references to your website.') .'</p>';
+    case 'admin/reports/visitors':
+      return '<p>'. t("When you ban a visitor, you prevent the visitor's IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. This is most commonly used to block resource-intensive bots or web crawlers.") .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_exit().
+ *
+ * This is where statistics are gathered on page accesses.
+ */
+function statistics_exit() {
+  global $user, $recent_activity;
+
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
+
+  if (variable_get('statistics_count_content_views', 0)) {
+    // We are counting content views.
+    if ((arg(0) == 'node') && is_numeric(arg(1)) && arg(2) == '') {
+      // A node has been viewed, so update the node's counters.
+      db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1));
+      // If we affected 0 rows, this is the first time viewing the node.
+      if (!db_affected_rows()) {
+        // We must create a new row to store counters for the new node.
+        db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', arg(1), time());
+      }
+    }
+  }
+  if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) {
+    // Log this page access.
+    db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), $_GET['q'], referer_uri(), ip_address(), $user->uid, session_id(), timer_read('page'), time());
+  }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function statistics_perm() {
+  return array('access statistics', 'view post access counter');
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function statistics_link($type, $node = NULL, $teaser = FALSE) {
+  global $id;
+  $links = array();
+
+  if ($type != 'comment' && user_access('view post access counter')) {
+    $statistics = statistics_get($node->nid);
+    if ($statistics) {
+      $links['statistics_counter']['title'] = format_plural($statistics['totalcount'], '1 read', '@count reads');
+    }
+  }
+
+  return $links;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function statistics_menu() {
+  $items['admin/reports/hits'] = array(
+    'title' => 'Recent hits',
+    'description' => 'View pages that have recently been visited.',
+    'page callback' => 'statistics_recent_hits',
+    'access arguments' => array('access statistics'),
+    'file' => 'statistics.admin.inc',
+  );
+  $items['admin/reports/pages'] = array(
+    'title' => 'Top pages',
+    'description' => 'View pages that have been hit frequently.',
+    'page callback' => 'statistics_top_pages',
+    'access arguments' => array('access statistics'),
+    'weight' => 1,
+    'file' => 'statistics.admin.inc',
+  );
+  $items['admin/reports/visitors'] = array(
+    'title' => 'Top visitors',
+    'description' => 'View visitors that hit many pages.',
+    'page callback' => 'statistics_top_visitors',
+    'access arguments' => array('access statistics'),
+    'weight' => 2,
+    'file' => 'statistics.admin.inc',
+  );
+  $items['admin/reports/referrers'] = array(
+    'title' => 'Top referrers',
+    'description' => 'View top referrers.',
+    'page callback' => 'statistics_top_referrers',
+    'access arguments' => array('access statistics'),
+    'file' => 'statistics.admin.inc',
+  );
+  $items['admin/reports/access/%'] = array(
+    'title' => 'Details',
+    'description' => 'View access log.',
+    'page callback' => 'statistics_access_log',
+    'page arguments' => array(3),
+    'access arguments' => array('access statistics'),
+    'type' => MENU_CALLBACK,
+    'file' => 'statistics.admin.inc',
+  );
+  $items['admin/reports/settings'] = array(
+    'title' => 'Access log settings',
+    'description' => 'Control details about what and how your site logs.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('statistics_access_logging_settings'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 3,
+    'file' => 'statistics.admin.inc',
+  );
+  $items['user/%user/track/navigation'] = array(
+    'title' => 'Track page visits',
+    'page callback' => 'statistics_user_tracker',
+    'access callback' => 'user_access',
+    'access arguments' => array('access statistics'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'statistics.pages.inc',
+  );
+  $items['node/%node/track'] = array(
+    'title' => 'Track',
+    'page callback' => 'statistics_node_tracker',
+    'access callback' => 'user_access',
+    'access arguments' => array('access statistics'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'statistics.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function statistics_user($op, &$edit, &$user) {
+  if ($op == 'delete') {
+    db_query('UPDATE {accesslog} SET uid = 0 WHERE uid = %d', $user->uid);
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function statistics_cron() {
+  $statistics_timestamp = variable_get('statistics_day_timestamp', '');
+
+  if ((time() - $statistics_timestamp) >= 86400) {
+    // Reset day counts.
+    db_query('UPDATE {node_counter} SET daycount = 0');
+    variable_set('statistics_day_timestamp', time());
+  }
+
+  // Clean up expired access logs.
+  db_query('DELETE FROM {accesslog} WHERE timestamp < %d', time() - variable_get('statistics_flush_accesslog_timer', 259200));
+}
+
+/**
+ * Returns all time or today top or last viewed node(s).
+ *
+ * @param $dbfield
+ *   one of
+ *   - 'totalcount': top viewed content of all time.
+ *   - 'daycount': top viewed content for today.
+ *   - 'timestamp': last viewed node.
+ *
+ * @param $dbrows
+ *   number of rows to be returned.
+ *
+ * @return
+ *   A query result containing n.nid, n.title, u.uid, u.name of the selected node(s)
+ *   or FALSE if the query could not be executed correctly.
+ */
+function statistics_title_list($dbfield, $dbrows) {
+  if (in_array($dbfield, array('totalcount', 'daycount', 'timestamp'))) {
+    return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE s.". $dbfield ." != 0 AND n.status = 1 ORDER BY s.". $dbfield ." DESC"), 0, $dbrows);
+  }
+  return FALSE;
+}
+
+
+/**
+ * Retrieves a node's "view statistics".
+ *
+ * @param $nid
+ *   node ID
+ *
+ * @return
+ *   An array with three entries: [0]=totalcount, [1]=daycount, [2]=timestamp
+ *   - totalcount: count of the total number of times that node has been viewed.
+ *   - daycount: count of the total number of times that node has been viewed "today".
+ *     For the daycount to be reset, cron must be enabled.
+ *   - timestamp: timestamp of when that node was last viewed.
+ */
+function statistics_get($nid) {
+
+  if ($nid > 0) {
+    // Retrieve an array with both totalcount and daycount.
+    $statistics = db_fetch_array(db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = %d', $nid));
+  }
+
+  return $statistics;
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function statistics_block($op = 'list', $delta = 0, $edit = array()) {
+  switch ($op) {
+    case 'list':
+      if (variable_get('statistics_count_content_views', 0)) {
+        $blocks[0]['info'] = t('Popular content');
+        // Too dynamic to cache.
+        $blocks[0]['cache'] = BLOCK_NO_CACHE;
+        return $blocks;
+      }
+      break;
+
+    case 'configure':
+      // Popular content block settings
+      $numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40));
+      $form['statistics_block_top_day_num'] = array('#type' => 'select', '#title' => t("Number of day's top views to display"), '#default_value' => variable_get('statistics_block_top_day_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "day" list.'));
+      $form['statistics_block_top_all_num'] = array('#type' => 'select', '#title' => t('Number of all time views to display'), '#default_value' => variable_get('statistics_block_top_all_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "all time" list.'));
+      $form['statistics_block_top_last_num'] = array('#type' => 'select', '#title' => t('Number of most recent views to display'), '#default_value' => variable_get('statistics_block_top_last_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "recently viewed" list.'));
+      return $form;
+
+    case 'save':
+      variable_set('statistics_block_top_day_num', $edit['statistics_block_top_day_num']);
+      variable_set('statistics_block_top_all_num', $edit['statistics_block_top_all_num']);
+      variable_set('statistics_block_top_last_num', $edit['statistics_block_top_last_num']);
+      break;
+
+    case 'view':
+      if (user_access('access content')) {
+        $content = array();
+
+        $daytop = variable_get('statistics_block_top_day_num', 0);
+        if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && ($node_title_list = node_title_list($result, t("Today's:")))) {
+          $content[] = $node_title_list;
+        }
+
+        $alltimetop = variable_get('statistics_block_top_all_num', 0);
+        if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && ($node_title_list = node_title_list($result, t('All time:')))) {
+          $content[] = $node_title_list;
+        }
+
+        $lasttop = variable_get('statistics_block_top_last_num', 0);
+        if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && ($node_title_list = node_title_list($result, t('Last viewed:')))) {
+          $content[] = $node_title_list;
+        }
+
+        if (count($content)) {
+          $block['content'] = implode('<br />', $content);
+          $block['subject'] = t('Popular content');
+          return $block;
+        }
+      }
+  }
+}
+
+/**
+ * It is possible to adjust the width of columns generated by the
+ * statistics module.
+ */
+function _statistics_link($path, $width = 35) {
+  $title = drupal_get_path_alias($path);
+  $title = truncate_utf8($title, $width, FALSE, TRUE);
+  return l($title, $path);
+}
+
+function _statistics_format_item($title, $path) {
+  $path = ($path ? $path : '/');
+  $output  = ($title ? "$title<br />" : '');
+  $output .= _statistics_link($path);
+  return $output;
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function statistics_nodeapi(&$node, $op, $arg = 0) {
+  switch ($op) {
+    case 'delete':
+      // clean up statistics table when node is deleted
+      db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/statistics/statistics.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,71 @@
+<?php
+// $Id: statistics.pages.inc,v 1.2 2007/10/20 21:57:50 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the statistics module.
+ */
+
+function statistics_node_tracker() {
+  if ($node = node_load(arg(1))) {
+
+    $header = array(
+        array('data' => t('Time'), 'field' => 'a.timestamp', 'sort' => 'desc'),
+        array('data' => t('Referrer'), 'field' => 'a.url'),
+        array('data' => t('User'), 'field' => 'u.name'),
+        array('data' => t('Operations')));
+
+    $result = pager_query('SELECT a.aid, a.timestamp, a.url, a.uid, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE a.path LIKE \'node/%d%%\''. tablesort_sql($header), 30, 0, NULL, $node->nid);
+    $rows = array();
+    while ($log = db_fetch_object($result)) {
+      $rows[] = array(
+        array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
+        _statistics_link($log->url),
+        theme('username', $log),
+        l(t('details'), "admin/reports/access/$log->aid"));
+    }
+
+    if (empty($rows)) {
+      $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
+    }
+
+    drupal_set_title(check_plain($node->title));
+    $output = theme('table', $header, $rows);
+    $output .= theme('pager', NULL, 30, 0);
+    return $output;
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+function statistics_user_tracker() {
+  if ($account = user_load(array('uid' => arg(1)))) {
+
+    $header = array(
+        array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'),
+        array('data' => t('Page'), 'field' => 'path'),
+        array('data' => t('Operations')));
+
+    $result = pager_query('SELECT aid, timestamp, path, title FROM {accesslog} WHERE uid = %d'. tablesort_sql($header), 30, 0, NULL, $account->uid);
+    $rows = array();
+    while ($log = db_fetch_object($result)) {
+      $rows[] = array(
+        array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
+        _statistics_format_item($log->title, $log->path),
+        l(t('details'), "admin/reports/access/$log->aid"));
+    }
+
+    if (empty($rows)) {
+      $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3));
+    }
+
+    drupal_set_title(check_plain($account->name));
+    $output = theme('table', $header, $rows);
+    $output .= theme('pager', NULL, 30, 0);
+    return $output;
+  }
+  else {
+    drupal_not_found();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/syslog/syslog.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: syslog.info,v 1.2 2007/06/08 05:50:56 dries Exp $
+name = Syslog
+description = Logs and records system events to syslog.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/syslog/syslog.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,110 @@
+<?php
+// $Id: syslog.module,v 1.14 2007/12/14 18:08:48 goba Exp $
+
+/**
+ * @file
+ * Redirects logging messages to syslog.
+ */
+
+if (defined('LOG_LOCAL0')) {
+  define('DEFAULT_SYSLOG_FACILITY', LOG_LOCAL0);
+}
+else {
+  define('DEFAULT_SYSLOG_FACILITY', LOG_USER);
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function syslog_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#syslog':
+      $output = '<p>'. t("The syslog module enables Drupal to send messages to the operating system's logging facility.") .'</p>';
+      $output .= '<p>'. t('Syslog is an operating system administrative logging tool, and provides valuable information for use in system management and security auditing. Most suited to medium and large sites, syslog provides filtering tools that allow messages to be routed by type and severity. On UNIX/Linux systems, the file /etc/syslog.conf defines this routing configuration; on Microsoft Windows, all messages are sent to the Event Log. For more information on syslog facilities, severity levels, and how to set up a syslog.conf file, see <a href="@syslog_conf">UNIX/Linux syslog.conf</a> and PHP\'s <a href="@php_openlog">openlog</a> and <a href="@php_syslog">syslog</a> functions.', array('@syslog_conf' => url('http://www.rt.com/man/syslog.5.html'), '@php_openlog' => url('http://www.php.net/manual/en/function.openlog.php'), '@php_syslog' => url('http://www.php.net/manual/en/function.syslog.php'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@syslog">Syslog module</a>.', array('@syslog' => 'http://drupal.org/handbook/modules/syslog')) .'</p>';
+      return $output;
+  }
+}
+
+function syslog_menu() {
+  $items['admin/settings/logging/syslog'] = array(
+    'title'          => 'Syslog',
+    'description'    => 'Settings for syslog logging. Syslog is an operating system administrative logging tool used in systems management and security auditing. Most suited to medium and large sites, syslog provides filtering tools that allow messages to be routed by type and severity.',
+    'page callback'  => 'drupal_get_form',
+    'page arguments' => array('syslog_admin_settings'),
+  );
+  return $items;
+}
+
+function syslog_admin_settings() {
+  $form['syslog_facility'] = array(
+    '#type'          => 'select',
+    '#title'         => t('Send events to this syslog facility'),
+    '#default_value' => variable_get('syslog_facility', DEFAULT_SYSLOG_FACILITY),
+    '#options'       => syslog_facility_list(),
+    '#description'   => t('Select the syslog facility code under which Drupal\'s messages should be sent. On UNIX/Linux systems, Drupal can flag its messages with the code LOG_LOCAL0 through LOG_LOCAL7; for Microsoft Windows, all messages are flagged with the code LOG_USER. Depending on the system configuration, syslog and other logging tools use this code to identify or filter Drupal messages from within the entire system log. For more information on syslog, see <a href="@syslog_help">Syslog help</a>.', array(
+      '@syslog_help' => url('admin/help/syslog'),
+      '!php'         => l("PHP's syslog", 'http://www.php.net/manual/en/function.openlog.php', array('external' => TRUE)),
+      '!syslog_conf' => l('UNIX/Linux syslog.conf', 'http://www.rt.com/man/syslog.5.html', array('external' => TRUE)))),
+  );
+  return system_settings_form($form);
+}
+
+function syslog_facility_list() {
+  $facility_list = array(
+    LOG_USER   => t('LOG_USER - User level messages. Use this for Windows.'),
+  );
+  if (defined('LOG_LOCAL0')) {
+    $facility_list += array(
+      LOG_LOCAL0 => t('LOG_LOCAL0 - Local 0'),
+      LOG_LOCAL1 => t('LOG_LOCAL1 - Local 1'),
+      LOG_LOCAL2 => t('LOG_LOCAL2 - Local 2'),
+      LOG_LOCAL3 => t('LOG_LOCAL3 - Local 3'),
+      LOG_LOCAL4 => t('LOG_LOCAL4 - Local 4'),
+      LOG_LOCAL5 => t('LOG_LOCAL5 - Local 5'),
+      LOG_LOCAL6 => t('LOG_LOCAL6 - Local 6'),
+      LOG_LOCAL7 => t('LOG_LOCAL7 - Local 7'),
+    );
+  }
+  return $facility_list;
+}
+
+function syslog_watchdog($entry) {
+  static $log_init = FALSE;
+
+  if (!$log_init) {
+    $log_init = TRUE;
+    openlog('drupal', LOG_NDELAY, variable_get('syslog_facility', DEFAULT_SYSLOG_FACILITY));
+  }
+
+  syslog($entry['severity'], theme('syslog_format', $entry));
+}
+
+function syslog_theme() {
+  return array(
+    'syslog_format' => array(
+      'arguments' => array('entry' => NULL),
+    ),
+  );
+}
+
+/**
+ * Format a system log entry.
+ *
+ * @ingroup themeable
+ */
+function theme_syslog_format($entry) {
+  global $base_url;
+
+  $message  = $base_url;
+  $message .= '|'. $entry['timestamp'];
+  $message .= '|'. $entry['type'];
+  $message .= '|'. $entry['ip'];
+  $message .= '|'. $entry['request_uri'];
+  $message .= '|'. $entry['referer'];
+  $message .= '|'. $entry['user']->uid;
+  $message .= '|'. strip_tags($entry['link']);
+  $message .= '|'. strip_tags(is_null($entry['variables']) ? $entry['message'] : strtr($entry['message'], $entry['variables']));
+
+  return $message;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/admin-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,37 @@
+/* $Id: admin-rtl.css,v 1.5 2007/12/22 23:24:25 goba Exp $ */
+
+div.admin-panel .body {
+  padding: 0 8px 2px 4px;
+}
+
+div.admin .expert-link {
+  text-align: left;
+  margin-right: 0;
+  margin-left: 1em;
+  padding-right: 0;
+  padding-left: 4px;
+}
+
+table.system-status-report th, table.system-status-report tr.merge-up td {
+  padding-right: 30px;
+}
+
+table.system-status-report th {
+  background-position: 95% 50%;
+}
+
+table.screenshot {
+  margin-left: 1em;
+}
+
+.date-container {
+  clear: right;
+}
+.date-container .select-container, .date-container .custom-container {
+  float: right;
+}
+.date-container .custom-container {
+  margin-left: 0;
+  margin-right: 15px;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/admin.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,134 @@
+/* $Id: admin.css,v 1.18 2007/12/22 23:24:25 goba Exp $ */
+
+/*
+** Formatting for administration page
+*/
+div.admin-panel {
+  margin: 0;
+  padding: 5px 5px 15px 5px;
+}
+
+div.admin-panel .description {
+  margin: 0 0 3px;
+  padding: 2px 0 3px 0;
+}
+
+div.admin-panel .body {
+  padding: 0 4px 2px 8px; /* LTR */
+}
+
+div.admin {
+  padding-top: 15px;
+}
+
+div.admin .left {
+  float: left;
+  width: 47%;
+  margin-left: 1em;
+}
+div.admin .right {
+  float: right;
+  width: 47%;
+  margin-right: 1em;
+}
+
+div.admin .expert-link {
+  text-align: right; /* LTR */
+  margin-right: 1em; /* LTR */
+  padding-right: 4px; /* LTR */
+}
+
+table.package {
+  width: 100%;
+}
+table.package .description {
+  width: 100%;
+}
+div.admin-dependencies, div.admin-required {
+  font-size: 0.9em;
+  color: #444;
+}
+span.admin-disabled {
+  color: #800;
+}
+span.admin-enabled {
+  color: #080;
+}
+span.admin-missing {
+  color: #f00;
+}
+
+/**
+ * Formatting for status report
+ */
+table.system-status-report th {
+  border-bottom: 1px solid #ccc;
+}
+table.system-status-report th, table.system-status-report tr.merge-up td {
+  padding-left: 30px; /* LTR */
+}
+table.system-status-report th {
+  background-repeat: no-repeat;
+  background-position: 5px 50%; /* LTR */
+  padding-top: 6px;
+  padding-bottom: 6px;
+}
+table.system-status-report tr.error th {
+  background-image: url(../../misc/watchdog-error.png);
+}
+table.system-status-report tr.warning th {
+  background-image: url(../../misc/watchdog-warning.png);
+}
+table.system-status-report tr.ok th {
+  background-image: url(../../misc/watchdog-ok.png);
+}
+
+/**
+ * Formatting for theme configuration
+ */
+.theme-settings-left {
+  float: left;
+  width: 49%;
+}
+.theme-settings-right {
+  float: right;
+  width: 49%;
+}
+.theme-settings-bottom {
+  clear: both;
+}
+
+/**
+ * Formatting for theme overview
+ */
+table.screenshot {
+  margin-right: 1em; /* LTR */
+}
+.theme-info h2 {
+  margin-bottom: 0;
+}
+.theme-info p {
+  margin-top: 0;
+}
+
+
+/**
+ * Date and time settings page
+ */
+.date-container {
+  overflow: auto;
+  clear: left; /* LTR */
+}
+.date-container .form-item {
+  margin-top: 0;
+}
+.date-container .select-container, .date-container .custom-container {
+  float: left; /* LTR */
+}
+.date-container .custom-container {
+  margin-left: 15px; /* LTR */
+  width: 50%;
+}
+html.js .custom-container label {
+  visibility: hidden;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,37 @@
+<?php
+// $Id: block.tpl.php,v 1.4 2007/09/01 05:42:48 dries Exp $
+
+/**
+ * @file block.tpl.php
+ *
+ * Theme implementation to display a block.
+ *
+ * Available variables:
+ * - $block->subject: Block title.
+ * - $block->content: Block content.
+ * - $block->module: Module that generated the block.
+ * - $block->delta: This is a numeric id connected to each module.
+ * - $block->region: The block region embedding the current block.
+ *
+ * Helper variables:
+ * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region.
+ * - $zebra: Same output as $block_zebra but independent of any block region.
+ * - $block_id: Counter dependent on each block region.
+ * - $id: Same output as $block_id but independent of any block region.
+ * - $is_front: Flags true when presented in the front page.
+ * - $logged_in: Flags true when the current user is a logged-in member.
+ * - $is_admin: Flags true when the current user is an administrator.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_block()
+ */
+?>
+<div id="block-<?php print $block->module .'-'. $block->delta; ?>" class="block block-<?php print $block->module ?>">
+<?php if ($block->subject): ?>
+  <h2><?php print $block->subject ?></h2>
+<?php endif;?>
+
+  <div class="content">
+    <?php print $block->content ?>
+  </div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/box.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,23 @@
+<?php
+// $Id: box.tpl.php,v 1.3 2007/12/16 21:01:45 goba Exp $
+
+/**
+ * @file box.tpl.php
+ *
+ * Theme implementation to display a box.
+ *
+ * Available variables:
+ * - $title: Box title.
+ * - $content: Box content.
+ *
+ * @see template_preprocess()
+ */
+?>
+<div class="box">
+
+<?php if ($title): ?>
+  <h2><?php print $title ?></h2>
+<?php endif; ?>
+
+  <div class="content"><?php print $content ?></div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/defaults-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+/* $Id: defaults-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */
+
+th {
+  text-align: right;
+  padding-right: 0;
+  padding-left: 1em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/defaults.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,53 @@
+/* $Id: defaults.css,v 1.5 2007/10/02 12:10:40 dries Exp $ */
+
+/*
+** HTML elements
+*/
+fieldset {
+  margin-bottom: 1em;
+  padding: .5em;
+}
+form {
+  margin: 0;
+  padding: 0;
+}
+hr {
+  height: 1px;
+  border: 1px solid gray;
+}
+img {
+  border: 0;
+}
+table {
+  border-collapse: collapse;
+}
+th {
+  text-align: left; /* LTR */
+  padding-right: 1em; /* LTR */
+  border-bottom: 3px solid #ccc;
+}
+
+/*
+** Markup free clearing
+** Details: http://www.positioniseverything.net/easyclearing.html
+*/
+.clear-block:after {
+  content: ".";
+  display: block;
+  height: 0;
+  clear: both;
+  visibility: hidden;
+}
+
+.clear-block {
+  display: inline-block;
+}
+
+/* Hides from IE-mac \*/
+* html .clear-block {
+  height: 1%;
+}
+.clear-block {
+  display: block;
+}
+/* End hide from IE-mac */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/maintenance-page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,98 @@
+<?php
+// $Id: maintenance-page.tpl.php,v 1.2 2008/01/24 09:42:51 goba Exp $
+
+/**
+ * @file maintenance-page.tpl.php
+ *
+ * Theme implementation to display a single Drupal page while off-line.
+ *
+ * All the available variables are mirrored in page.tpl.php. Some may be left
+ * blank but they are provided for consistency.
+ *
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_maintenance_page()
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+
+<head>
+  <title><?php print $head_title; ?></title>
+  <?php print $head; ?>
+  <?php print $styles; ?>
+  <?php print $scripts; ?>
+  <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyled Content in IE */ ?> </script>
+</head>
+<body class="<?php print $body_classes; ?>">
+  <div id="page">
+    <div id="header">
+      <div id="logo-title">
+
+        <?php if (!empty($logo)): ?>
+          <a href="<?php print $base_path; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">
+            <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
+          </a>
+        <?php endif; ?>
+
+        <div id="name-and-slogan">
+          <?php if (!empty($site_name)): ?>
+            <h1 id="site-name">
+              <a href="<?php print $base_path ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
+            </h1>
+          <?php endif; ?>
+
+          <?php if (!empty($site_slogan)): ?>
+            <div id="site-slogan"><?php print $site_slogan; ?></div>
+          <?php endif; ?>
+        </div> <!-- /name-and-slogan -->
+      </div> <!-- /logo-title -->
+
+      <?php if (!empty($header)): ?>
+        <div id="header-region">
+          <?php print $header; ?>
+        </div>
+      <?php endif; ?>
+
+    </div> <!-- /header -->
+
+    <div id="container" class="clear-block">
+
+      <?php if (!empty($left)): ?>
+        <div id="sidebar-left" class="column sidebar">
+          <?php print $left; ?>
+        </div> <!-- /sidebar-left -->
+      <?php endif; ?>
+
+      <div id="main" class="column"><div id="main-squeeze">
+
+        <div id="content">
+          <?php if (!empty($title)): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+          <?php if (!empty($messages)): print $messages; endif; ?>
+          <div id="content-content" class="clear-block">
+            <?php print $content; ?>
+          </div> <!-- /content-content -->
+        </div> <!-- /content -->
+
+      </div></div> <!-- /main-squeeze /main -->
+
+      <?php if (!empty($right)): ?>
+        <div id="sidebar-right" class="column sidebar">
+          <?php print $right; ?>
+        </div> <!-- /sidebar-right -->
+      <?php endif; ?>
+
+    </div> <!-- /container -->
+
+    <div id="footer-wrapper">
+      <div id="footer">
+        <?php print $footer_message; ?>
+        <?php if (!empty($footer)): print $footer; endif; ?>
+      </div> <!-- /footer -->
+    </div> <!-- /footer-wrapper -->
+
+  </div> <!-- /page -->
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/maintenance.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,23 @@
+/* $Id: maintenance.css,v 1.1 2007/11/30 12:19:10 goba Exp $ */
+
+/* Update styles */
+#update-results {
+  margin-top: 3em;
+  padding: 0.25em;
+  border: 1px solid #ccc;
+  background: #eee;
+  font-size: smaller;
+}
+#update-results h2 {
+  margin-top: 0.25em;
+}
+#update-results h4 {
+  margin-bottom: 0.25em;
+}
+#update-results li.none {
+  color: #888;
+  font-style: italic;
+}
+#update-results li.failure strong {
+  color: #b63300;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,184 @@
+<?php
+// $Id: page.tpl.php,v 1.11 2008/01/24 09:42:51 goba Exp $
+
+/**
+ * @file page.tpl.php
+ *
+ * Theme implementation to display a single Drupal page.
+ *
+ * Available variables:
+ *
+ * General utility variables:
+ * - $base_path: The base URL path of the Drupal installation. At the very
+ *   least, this will always default to /.
+ * - $css: An array of CSS files for the current page.
+ * - $directory: The directory the theme is located in, e.g. themes/garland or
+ *   themes/garland/minelli.
+ * - $is_front: TRUE if the current page is the front page. Used to toggle the mission statement.
+ * - $logged_in: TRUE if the user is registered and signed in.
+ * - $is_admin: TRUE if the user has permission to access administration pages.
+ *
+ * Page metadata:
+ * - $language: (object) The language the site is being displayed in.
+ *   $language->language contains its textual representation.
+ *   $language->dir contains the language direction. It will either be 'ltr' or 'rtl'.
+ * - $head_title: A modified version of the page title, for use in the TITLE tag.
+ * - $head: Markup for the HEAD section (including meta tags, keyword tags, and
+ *   so on).
+ * - $styles: Style tags necessary to import all CSS files for the page.
+ * - $scripts: Script tags necessary to load the JavaScript files and settings
+ *   for the page.
+ * - $body_classes: A set of CSS classes for the BODY tag. This contains flags
+ *   indicating the current layout (multiple columns, single column), the current
+ *   path, whether the user is logged in, and so on.
+ *
+ * Site identity:
+ * - $front_page: The URL of the front page. Use this instead of $base_path,
+ *   when linking to the front page. This includes the language domain or prefix.
+ * - $logo: The path to the logo image, as defined in theme configuration.
+ * - $site_name: The name of the site, empty when display has been disabled
+ *   in theme settings.
+ * - $site_slogan: The slogan of the site, empty when display has been disabled
+ *   in theme settings.
+ * - $mission: The text of the site mission, empty when display has been disabled
+ *   in theme settings.
+ *
+ * Navigation:
+ * - $search_box: HTML to display the search box, empty if search has been disabled.
+ * - $primary_links (array): An array containing primary navigation links for the
+ *   site, if they have been configured.
+ * - $secondary_links (array): An array containing secondary navigation links for
+ *   the site, if they have been configured.
+ *
+ * Page content (in order of occurrance in the default page.tpl.php):
+ * - $left: The HTML for the left sidebar.
+ *
+ * - $breadcrumb: The breadcrumb trail for the current page.
+ * - $title: The page title, for use in the actual HTML content.
+ * - $help: Dynamic help text, mostly for admin pages.
+ * - $messages: HTML for status and error messages. Should be displayed prominently.
+ * - $tabs: Tabs linking to any sub-pages beneath the current page (e.g., the view
+ *   and edit tabs when displaying a node).
+ *
+ * - $content: The main content of the current Drupal page.
+ *
+ * - $right: The HTML for the right sidebar.
+ *
+ * Footer/closing data:
+ * - $feed_icons: A string of all feed icons for the current page.
+ * - $footer_message: The footer message as defined in the admin settings.
+ * - $footer : The footer region.
+ * - $closure: Final closing markup from any modules that have altered the page.
+ *   This variable should always be output last, after all other dynamic content.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_page()
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+
+<head>
+  <title><?php print $head_title; ?></title>
+  <?php print $head; ?>
+  <?php print $styles; ?>
+  <?php print $scripts; ?>
+  <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyled Content in IE */ ?> </script>
+</head>
+<body class="<?php print $body_classes; ?>">
+  <div id="page">
+    <div id="header">
+      <div id="logo-title">
+
+        <?php if (!empty($logo)): ?>
+          <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo">
+            <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" />
+          </a>
+        <?php endif; ?>
+
+        <div id="name-and-slogan">
+          <?php if (!empty($site_name)): ?>
+            <h1 id="site-name">
+              <a href="<?php print $front_page ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a>
+            </h1>
+          <?php endif; ?>
+
+          <?php if (!empty($site_slogan)): ?>
+            <div id="site-slogan"><?php print $site_slogan; ?></div>
+          <?php endif; ?>
+        </div> <!-- /name-and-slogan -->
+      </div> <!-- /logo-title -->
+
+      <?php if (!empty($search_box)): ?>
+        <div id="search-box"><?php print $search_box; ?></div>
+      <?php endif; ?>
+
+      <?php if (!empty($header)): ?>
+        <div id="header-region">
+          <?php print $header; ?>
+        </div>
+      <?php endif; ?>
+
+    </div> <!-- /header -->
+
+    <div id="container" class="clear-block">
+
+      <div id="navigation" class="menu <?php if (!empty($primary_links)) { print "withprimary"; } if (!empty($secondary_links)) { print " withsecondary"; } ?> ">
+        <?php if (!empty($primary_links)): ?>
+          <div id="primary" class="clear-block">
+            <?php print theme('links', $primary_links, array('class' => 'links primary-links')); ?>
+          </div>
+        <?php endif; ?>
+
+        <?php if (!empty($secondary_links)): ?>
+          <div id="secondary" class="clear-block">
+            <?php print theme('links', $secondary_links, array('class' => 'links secondary-links')); ?>
+          </div>
+        <?php endif; ?>
+      </div> <!-- /navigation -->
+
+      <?php if (!empty($left)): ?>
+        <div id="sidebar-left" class="column sidebar">
+          <?php print $left; ?>
+        </div> <!-- /sidebar-left -->
+      <?php endif; ?>
+
+      <div id="main" class="column"><div id="main-squeeze">
+        <?php if (!empty($breadcrumb)): ?><div id="breadcrumb"><?php print $breadcrumb; ?></div><?php endif; ?>
+        <?php if (!empty($mission)): ?><div id="mission"><?php print $mission; ?></div><?php endif; ?>
+
+        <div id="content">
+          <?php if (!empty($title)): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
+          <?php if (!empty($tabs)): ?><div class="tabs"><?php print $tabs; ?></div><?php endif; ?>
+          <?php if (!empty($messages)): print $messages; endif; ?>
+          <?php if (!empty($help)): print $help; endif; ?>
+          <div id="content-content" class="clear-block">
+            <?php print $content; ?>
+          </div> <!-- /content-content -->
+          <?php print $feed_icons; ?>
+        </div> <!-- /content -->
+
+      </div></div> <!-- /main-squeeze /main -->
+
+      <?php if (!empty($right)): ?>
+        <div id="sidebar-right" class="column sidebar">
+          <?php print $right; ?>
+        </div> <!-- /sidebar-right -->
+      <?php endif; ?>
+
+    </div> <!-- /container -->
+
+    <div id="footer-wrapper">
+      <div id="footer">
+        <?php print $footer_message; ?>
+        <?php if (!empty($footer)): print $footer; endif; ?>
+      </div> <!-- /footer -->
+    </div> <!-- /footer-wrapper -->
+
+    <?php print $closure; ?>
+
+  </div> <!-- /page -->
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system-menus-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,18 @@
+/* $Id: system-menus-rtl.css,v 1.1 2007/10/05 14:50:25 goba Exp $ */
+
+ul.menu {
+  text-align:right;
+}
+ul.menu li {
+  margin: 0 0.5em 0 0;
+}
+li.expanded {
+  padding: 0.2em 0 0 0.5em;
+}
+li.collapsed {
+  list-style-image: url(../../misc/menu-collapsed-rtl.png);
+  padding: 0.2em 0 0 0.5em;
+}
+li.leaf {
+  padding: 0.2em 0 0 0.5em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system-menus.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,50 @@
+/* $Id: system-menus.css,v 1.1 2007/10/05 14:50:25 goba Exp $ */
+
+ul.menu {
+  list-style: none;
+  border: none;
+  text-align:left; /* LTR */
+}
+ul.menu li {
+  margin: 0 0 0 0.5em; /* LTR */
+}
+li.expanded {
+  list-style-type: circle;
+  list-style-image: url(../../misc/menu-expanded.png);
+  padding: 0.2em 0.5em 0 0; /* LTR */
+  margin: 0;
+}
+li.collapsed {
+  list-style-type: disc;
+  list-style-image: url(../../misc/menu-collapsed.png); /* LTR */
+  padding: 0.2em 0.5em 0 0; /* LTR */
+  margin: 0;
+}
+li.leaf {
+  list-style-type: square;
+  list-style-image: url(../../misc/menu-leaf.png);
+  padding: 0.2em 0.5em 0 0; /* LTR */
+  margin: 0;
+}
+li a.active {
+  color: #000;
+}
+td.menu-disabled {
+  background: #ccc;
+}
+ul.links {
+  margin: 0;
+  padding: 0;
+}
+ul.links.inline {
+  display: inline;
+}
+ul.links li {
+  display: inline;
+  list-style-type: none;
+  padding: 0 0.5em;
+}
+.block ul {
+  margin: 0;
+  padding: 0 0 0.25em 1em; /* LTR */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,107 @@
+/* $Id: system-rtl.css,v 1.12 2008/01/10 18:08:19 goba Exp $ */
+
+thead th {
+  text-align: right;
+  padding-left: 1em;
+  padding-right: 0.5em;
+}
+
+.item-list .icon {
+  float: left;
+  padding-left: 0;
+  padding-right: 0.25em;
+  clear: left;
+}
+.item-list ul li {
+  margin: 0 1.5em 0.25em 0;
+}
+
+.more-link {
+  text-align: left;
+}
+.more-help-link {
+  text-align: left;
+}
+
+dl.multiselect dt, dl.multiselect dd {
+  float: right;
+  margin: 0 0 0 1em;
+}
+
+.block ul {
+  padding: 0 1em 0.25em 0;
+}
+
+ul.primary {
+  padding: 0 1em 0 0;
+}
+ul.primary li a {
+  margin-right: 5px;
+  margin-left: 0.5em;
+}
+ul.secondary li {
+  display: inline;
+  padding: 0 1em;
+  border-right: none;
+  border-left: 1px solid #ccc;
+}
+html.js input.form-autocomplete {
+  background-position: 0% 2px;
+}
+html.js input.throbbing {
+  background-position: 0% -18px;
+}
+
+html.js fieldset.collapsible legend a {
+  padding-left: 0;
+  padding-right: 15px;
+  background-position: 98% 75%;
+}
+html.js fieldset.collapsed legend a {
+  background-image: url(../../misc/menu-collapsed-rtl.png);
+  background-position: 98% 50%;
+}
+
+div.teaser-button-wrapper {
+  float: left;
+  padding-right: 0;
+  padding-left: 5%;
+}
+.teaser-checkbox div.form-item {
+  float: left;
+  margin: 0 0 0 5%;
+}
+.progress .percentage {
+  float: left;
+}
+.progess-disabled {
+  float: right;
+}
+.ahah-progress {
+  float: right;
+}
+.ahah-progress .throbber {
+  float: right;
+}
+input.password-field {
+  margin-left: 10px;
+  margin-right: 0;
+}
+input.password-confirm {
+  margin-left: 10px;
+  margin-right: 0;
+}
+
+.draggable a.tabledrag-handle {
+  float: right;
+  margin: -0.4em -0.5em -0.4em 0;
+  padding: 0.42em 0.5em 0.42em 1.5em;
+}
+div.indentation {
+  margin: -0.4em -0.4em -0.4em 0.2em;
+  padding: 0.42em 0.6em 0.42em 0;
+  float: right;
+}
+div.tree-child, div.tree-child-last {
+  background-position: -65px center;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2211 @@
+<?php
+// $Id: system.admin.inc,v 1.63 2008/02/04 12:35:48 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the system module.
+ */
+
+/**
+ * Menu callback; Provide the administration overview page.
+ */
+function system_main_admin_page($arg = NULL) {
+  // If we received an argument, they probably meant some other page.
+  // Let's 404 them since the menu system cannot be told we do not
+  // accept arguments.
+  if (isset($arg) && substr($arg, 0, 3) != 'by-') {
+    return drupal_not_found();
+  }
+
+  // Check for status report errors.
+  if (system_status(TRUE) && user_access('administer site configuration')) {
+    drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
+  }
+  $blocks = array();
+  if ($admin = db_fetch_array(db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin' AND module = 'system'"))) {
+    $result = db_query("
+      SELECT m.*, ml.*
+      FROM {menu_links} ml
+      INNER JOIN {menu_router} m ON ml.router_path = m.path
+      WHERE ml.link_path != 'admin/help' AND menu_name = '%s' AND ml.plid = %d AND hidden = 0", $admin);
+    while ($item = db_fetch_array($result)) {
+      _menu_link_translate($item);
+      if (!$item['access']) {
+        continue;
+      }
+      // The link 'description' either derived from the hook_menu 'description'
+      // or entered by the user via menu module is saved as the title attribute.
+      if (!empty($item['localized_options']['attributes']['title'])) {
+        $item['description'] = $item['localized_options']['attributes']['title'];
+      }
+      $block = $item;
+      $block['content'] = '';
+      if ($item['block_callback'] && function_exists($item['block_callback'])) {
+        $function = $item['block_callback'];
+        $block['content'] .= $function();
+      }
+      $block['content'] .= theme('admin_block_content', system_admin_menu_block($item));
+      // Prepare for sorting as in function _menu_tree_check_access().
+      // The weight is offset so it is always positive, with a uniform 5-digits.
+      $blocks[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $block;
+    }
+  }
+  if ($blocks) {
+    ksort($blocks);
+    return theme('admin_page', $blocks);
+  }
+  else {
+    return t('You do not have any administrative items.');
+  }
+}
+
+
+/**
+ * Provide a single block from the administration menu as a page.
+ * This function is often a destination for these blocks.
+ * For example, 'admin/content/types' needs to have a destination to be valid
+ * in the Drupal menu system, but too much information there might be
+ * hidden, so we supply the contents of the block.
+ *
+ * @return
+ *   The output HTML.
+ */
+function system_admin_menu_block_page() {
+  $item = menu_get_item();
+  if ($content = system_admin_menu_block($item)) {
+    $output = theme('admin_block_content', $content);
+  }
+  else {
+    $output = t('You do not have any administrative items.');
+  }
+  return $output;
+}
+
+/**
+ * Menu callback; Sets whether the admin menu is in compact mode or not.
+ *
+ * @param $mode
+ *   Valid values are 'on' and 'off'.
+ */
+function system_admin_compact_page($mode = 'off') {
+  global $user;
+  user_save($user, array('admin_compact_mode' => ($mode == 'on')));
+  drupal_goto('admin');
+}
+
+/**
+ * Menu callback; prints a listing of admin tasks for each installed module.
+ */
+function system_admin_by_module() {
+
+  $modules = module_rebuild_cache();
+  $menu_items = array();
+  $help_arg = module_exists('help') ? drupal_help_arg() : FALSE;
+
+  foreach ($modules as $file) {
+    $module = $file->name;
+    if ($module == 'help') {
+      continue;
+    }
+
+    $admin_tasks = system_get_module_admin_tasks($module);
+
+    // Only display a section if there are any available tasks.
+    if (count($admin_tasks)) {
+
+      // Check for help links.
+      if ($help_arg && module_invoke($module, 'help', "admin/help#$module", $help_arg)) {
+        $admin_tasks[100] = l(t('Get help'), "admin/help/$module");
+      }
+
+      // Sort.
+      ksort($admin_tasks);
+
+      $menu_items[$file->info['name']] = array($file->info['description'], $admin_tasks);
+    }
+  }
+  return theme('system_admin_by_module', $menu_items);
+}
+
+/**
+ * Menu callback; displays a module's settings page.
+ */
+function system_settings_overview() {
+  // Check database setup if necessary
+  if (function_exists('db_check_setup') && empty($_POST)) {
+    db_check_setup();
+  }
+
+  $item = menu_get_item('admin/settings');
+  $content = system_admin_menu_block($item);
+
+  $output = theme('admin_block_content', $content);
+
+  return $output;
+}
+
+/**
+ * Form builder; This function allows selection of the theme to show in administration sections.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_admin_theme_settings() {
+  $themes = system_theme_data();
+
+  uasort($themes, 'system_sort_modules_by_info_name');
+
+  $options[0] = '<'. t('System default') .'>';
+  foreach ($themes as $theme) {
+    $options[$theme->name] = $theme->info['name'];
+  }
+
+  $form['admin_theme'] = array(
+    '#type' => 'select',
+    '#options' => $options,
+    '#title' => t('Administration theme'),
+    '#description' => t('Choose which theme the administration pages should display in. If you choose "System default" the administration pages will use the same theme as the rest of the site.'),
+    '#default_value' => variable_get('admin_theme', '0'),
+  );
+
+  $form['node_admin_theme'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use administration theme for content editing'),
+    '#description' => t('Use the administration theme when editing existing posts or creating new ones.'),
+    '#default_value' => variable_get('node_admin_theme', '0'),
+  );
+
+  $form['#submit'][] = 'system_admin_theme_submit';
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback; displays a listing of all themes.
+ *
+ * @ingroup forms
+ * @see system_themes_form_submit()
+ */
+function system_themes_form() {
+
+  drupal_clear_css_cache();
+  $themes = system_theme_data();
+
+  uasort($themes, 'system_sort_modules_by_info_name');
+
+  $status = array();
+  $incompatible_core = array();
+  $incompatible_php = array();
+
+  foreach ($themes as $theme) {
+    $screenshot = NULL;
+    $theme_key = $theme->name;
+    while ($theme_key) {
+      if (file_exists($themes[$theme_key]->info['screenshot'])) {
+        $screenshot = $themes[$theme_key]->info['screenshot'];
+        break;
+      }
+      $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL;
+    }
+    $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
+
+    $form[$theme->name]['screenshot'] = array('#value' => $screenshot);
+    $form[$theme->name]['info'] = array(
+      '#type' => 'value',
+      '#value' => $theme->info,
+    );
+    $options[$theme->name] = '';
+
+    if (!empty($theme->status) || $theme->name == variable_get('admin_theme', '0')) {
+      $form[$theme->name]['operations'] = array('#value' => l(t('configure'), 'admin/build/themes/settings/'. $theme->name) );
+    }
+    else {
+      // Dummy element for drupal_render. Cleaner than adding a check in the theme function.
+      $form[$theme->name]['operations'] = array();
+    }
+    if (!empty($theme->status)) {
+      $status[] = $theme->name;
+    }
+    else {
+      // Ensure this theme is compatible with this version of core.
+      if (!isset($theme->info['core']) || $theme->info['core'] != DRUPAL_CORE_COMPATIBILITY) {
+        $incompatible_core[] = $theme->name;
+      }
+      if (version_compare(phpversion(), $theme->info['php']) < 0) {
+        $incompatible_php[$theme->name] = $theme->info['php'];
+      }
+    }
+  }
+
+  $form['status'] = array(
+    '#type' => 'checkboxes',
+    '#options' => $options,
+    '#default_value' => $status,
+    '#incompatible_themes_core' => drupal_map_assoc($incompatible_core),
+    '#incompatible_themes_php' => $incompatible_php,
+  );
+  $form['theme_default'] = array(
+    '#type' => 'radios',
+    '#options' => $options,
+    '#default_value' => variable_get('theme_default', 'garland'),
+  );
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save configuration'),
+  );
+  $form['buttons']['reset'] = array(
+    '#type' => 'submit',
+    '#value' => t('Reset to defaults'),
+  );
+  return $form;
+}
+
+/**
+ * Process system_themes_form form submissions.
+ */
+function system_themes_form_submit($form, &$form_state) {
+
+  // Store list of previously enabled themes and disable all themes
+  $old_theme_list = $new_theme_list = array();
+  foreach (list_themes() as $theme) {
+    if ($theme->status) {
+      $old_theme_list[] = $theme->name;
+    }
+  }
+  db_query("UPDATE {system} SET status = 0 WHERE type = 'theme'");
+
+  if ($form_state['values']['op'] == t('Save configuration')) {
+    if (is_array($form_state['values']['status'])) {
+      foreach ($form_state['values']['status'] as $key => $choice) {
+        // Always enable the default theme, despite its status checkbox being checked:
+        if ($choice || $form_state['values']['theme_default'] == $key) {
+          system_initialize_theme_blocks($key);
+          $new_theme_list[] = $key;
+          db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = '%s'", $key);
+        }
+      }
+    }
+    if (($admin_theme = variable_get('admin_theme', '0')) != '0' && $admin_theme != $form_state['values']['theme_default']) {
+      drupal_set_message(t('Please note that the <a href="!admin_theme_page">administration theme</a> is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', array(
+        '!admin_theme_page' => url('admin/settings/admin'),
+        '%admin_theme' => $admin_theme,
+        '%selected_theme' => $form_state['values']['theme_default'],
+      )));
+    }
+    variable_set('theme_default', $form_state['values']['theme_default']);
+  }
+  else {
+    // Revert to defaults: only Garland is enabled.
+    variable_del('theme_default');
+    db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name = 'garland'");
+    $new_theme_list = array('garland');
+  }
+
+  list_themes(TRUE);
+  menu_rebuild();
+  drupal_rebuild_theme_registry();
+  drupal_set_message(t('The configuration options have been saved.'));
+  $form_state['redirect'] = 'admin/build/themes';
+
+  // Notify locale module about new themes being enabled, so translations can
+  // be imported. This might start a batch, and only return to the redirect
+  // path after that.
+  module_invoke('locale', 'system_update', array_diff($new_theme_list, $old_theme_list));
+
+  return;
+}
+
+/**
+ * Form builder; display theme configuration for entire site and individual themes.
+ *
+ * @param $key
+ *   A theme name.
+ * @return
+ *   The form structure.
+ * @ingroup forms
+ * @see system_theme_settings_submit()
+ */
+function system_theme_settings(&$form_state, $key = '') {
+  $directory_path = file_directory_path();
+  file_check_directory($directory_path, FILE_CREATE_DIRECTORY, 'file_directory_path');
+
+  // Default settings are defined in theme_get_settings() in includes/theme.inc
+  if ($key) {
+    $settings = theme_get_settings($key);
+    $var = str_replace('/', '_', 'theme_'. $key .'_settings');
+    $themes = system_theme_data();
+    $features = $themes[$key]->info['features'];
+  }
+  else {
+    $settings = theme_get_settings('');
+    $var = 'theme_settings';
+  }
+
+  $form['var'] = array('#type' => 'hidden', '#value' => $var);
+
+  // Check for a new uploaded logo, and use that instead.
+  if ($file = file_save_upload('logo_upload', array('file_validate_is_image' => array()))) {
+    $parts = pathinfo($file->filename);
+    $filename = ($key) ? str_replace('/', '_', $key) .'_logo.'. $parts['extension'] : 'logo.'. $parts['extension'];
+
+    // The image was saved using file_save_upload() and was added to the
+    // files table as a temporary file. We'll make a copy and let the garbage
+    // collector delete the original upload.
+    if (file_copy($file, $filename, FILE_EXISTS_REPLACE)) {
+      $_POST['default_logo'] = 0;
+      $_POST['logo_path'] = $file->filepath;
+      $_POST['toggle_logo'] = 1;
+    }
+  }
+
+  // Check for a new uploaded favicon, and use that instead.
+  if ($file = file_save_upload('favicon_upload')) {
+    $parts = pathinfo($file->filename);
+    $filename = ($key) ? str_replace('/', '_', $key) .'_favicon.'. $parts['extension'] : 'favicon.'. $parts['extension'];
+
+    // The image was saved using file_save_upload() and was added to the
+    // files table as a temporary file. We'll make a copy and let the garbage
+    // collector delete the original upload.
+    if (file_copy($file, $filename)) {
+      $_POST['default_favicon'] = 0;
+      $_POST['favicon_path'] = $file->filepath;
+      $_POST['toggle_favicon'] = 1;
+    }
+  }
+
+  // Toggle settings
+  $toggles = array(
+    'logo'                 => t('Logo'),
+    'name'                 => t('Site name'),
+    'slogan'               => t('Site slogan'),
+    'mission'              => t('Mission statement'),
+    'node_user_picture'    => t('User pictures in posts'),
+    'comment_user_picture' => t('User pictures in comments'),
+    'search'               => t('Search box'),
+    'favicon'              => t('Shortcut icon'),
+    'primary_links'        => t('Primary links'),
+    'secondary_links'      => t('Secondary links'),
+  );
+
+  // Some features are not always available
+  $disabled = array();
+  if (!variable_get('user_pictures', 0)) {
+    $disabled['toggle_node_user_picture'] = TRUE;
+    $disabled['toggle_comment_user_picture'] = TRUE;
+  }
+  if (!module_exists('search')) {
+    $disabled['toggle_search'] = TRUE;
+  }
+
+  $form['theme_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Toggle display'),
+    '#description' => t('Enable or disable the display of certain page elements.'),
+  );
+  foreach ($toggles as $name => $title) {
+    if ((!$key) || in_array($name, $features)) {
+      $form['theme_settings']['toggle_'. $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => $settings['toggle_'. $name]);
+      // Disable checkboxes for features not supported in the current configuration.
+      if (isset($disabled['toggle_'. $name])) {
+        $form['theme_settings']['toggle_'. $name]['#disabled'] = TRUE;
+      }
+    }
+  }
+
+  // System wide only settings.
+  if (!$key) {
+    // Create neat 2-column layout for the toggles
+    $form['theme_settings'] += array(
+      '#prefix' => '<div class="theme-settings-left">',
+      '#suffix' => '</div>',
+    );
+
+    // Toggle node display.
+    $node_types = node_get_types('names');
+    if ($node_types) {
+      $form['node_info'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Display post information on'),
+        '#description' => t('Enable or disable the <em>submitted by Username on date</em> text when displaying posts of the following type.'),
+        '#prefix' => '<div class="theme-settings-right">',
+        '#suffix' => '</div>',
+      );
+      foreach ($node_types as $type => $name) {
+        $form['node_info']["toggle_node_info_$type"] = array('#type' => 'checkbox', '#title' => check_plain($name), '#default_value' => $settings["toggle_node_info_$type"]);
+      }
+    }
+  }
+  elseif (!element_children($form['theme_settings'])) {
+    // If there is no element in the theme settings fieldset then do not show
+    // it -- but keep it in the form if another module wants to alter.
+    $form['theme_settings']['#access'] = FALSE;
+  }
+
+  // Logo settings
+  if ((!$key) || in_array('logo', $features)) {
+    $form['logo'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Logo image settings'),
+      '#description' => t('If toggled on, the following logo will be displayed.'),
+      '#attributes' => array('class' => 'theme-settings-bottom'),
+    );
+    $form['logo']["default_logo"] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use the default logo'),
+      '#default_value' => $settings['default_logo'],
+      '#tree' => FALSE,
+      '#description' => t('Check here if you want the theme to use the logo supplied with it.')
+    );
+    $form['logo']['logo_path'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Path to custom logo'),
+      '#default_value' => $settings['logo_path'],
+      '#description' => t('The path to the file you would like to use as your logo file instead of the default logo.'));
+
+    $form['logo']['logo_upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Upload logo image'),
+      '#maxlength' => 40,
+      '#description' => t("If you don't have direct file access to the server, use this field to upload your logo.")
+    );
+  }
+
+  if ((!$key) || in_array('favicon', $features)) {
+    $form['favicon'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Shortcut icon settings'),
+      '#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers.")
+    );
+    $form['favicon']['default_favicon'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use the default shortcut icon.'),
+      '#default_value' => $settings['default_favicon'],
+      '#description' => t('Check here if you want the theme to use the default shortcut icon.')
+    );
+    $form['favicon']['favicon_path'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Path to custom icon'),
+      '#default_value' => $settings['favicon_path'],
+      '#description' => t('The path to the image file you would like to use as your custom shortcut icon.')
+    );
+
+    $form['favicon']['favicon_upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Upload icon image'),
+      '#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.")
+    );
+  }
+
+  if ($key) {
+    // Include the theme's theme-settings.php file
+    $filename = './'. str_replace("/$key.info", '', $themes[$key]->filename) .'/theme-settings.php';
+    if (!file_exists($filename) and !empty($themes[$key]->info['base theme'])) {
+      // If the theme doesn't have a theme-settings.php file, use the base theme's.
+      $base = $themes[$key]->info['base theme'];
+      $filename = './'. str_replace("/$base.info", '', $themes[$base]->filename) .'/theme-settings.php';
+    }
+    if (file_exists($filename)) {
+      require_once $filename;
+    }
+
+    // Call engine-specific settings.
+    $function = $themes[$key]->prefix .'_engine_settings';
+    if (function_exists($function)) {
+      $group = $function($settings);
+      if (!empty($group)) {
+        $form['engine_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-engine-specific settings'), '#description' => t('These settings only exist for all the templates and styles based on the %engine theme engine.', array('%engine' => $themes[$key]->prefix)));
+        $form['engine_specific'] = array_merge($form['engine_specific'], $group);
+      }
+    }
+    // Call theme-specific settings.
+    $function = $key .'_settings';
+    if (!function_exists($function)) {
+      $function = $themes[$key]->prefix .'_settings';
+    }
+    if (function_exists($function)) {
+      $group = $function($settings);
+      if (!empty($group)) {
+        $form['theme_specific'] = array('#type' => 'fieldset', '#title' => t('Theme-specific settings'), '#description' => t('These settings only exist for the %theme theme and all the styles based on it.', array('%theme' => $themes[$key]->info['name'])));
+        $form['theme_specific'] = array_merge($form['theme_specific'], $group);
+      }
+    }
+  }
+  $form['#attributes'] = array('enctype' => 'multipart/form-data');
+
+  $form = system_settings_form($form);
+  // We don't want to call system_settings_form_submit(), so change #submit.
+  $form['#submit'] = array('system_theme_settings_submit');
+  return $form;
+}
+
+/**
+ * Process system_theme_settings form submissions.
+ */
+function system_theme_settings_submit($form, &$form_state) {
+  $values = $form_state['values'];
+  $key = $values['var'];
+
+  if ($values['op'] == t('Reset to defaults')) {
+    variable_del($key);
+    drupal_set_message(t('The configuration options have been reset to their default values.'));
+  }
+  else {
+    // Exclude unnecessary elements before saving.
+    unset($values['var'], $values['submit'], $values['reset'], $values['form_id'], $values['op'], $values['form_build_id'], $values['form_token']);
+    variable_set($key, $values);
+    drupal_set_message(t('The configuration options have been saved.'));
+  }
+
+  cache_clear_all();
+}
+
+/**
+ * Recursively check compatibility.
+ *
+ * @param $incompatible
+ *   An associative array which at the end of the check contains all incompatible files as the keys, their values being TRUE.
+ * @param $files
+ *   The set of files that will be tested.
+ * @param $file
+ *   The file at which the check starts.
+ * @return
+ *   Returns TRUE if an incompatible file is found, NULL (no return value) otherwise.
+ */
+function _system_is_incompatible(&$incompatible, $files, $file) {
+  static $seen;
+  // We need to protect ourselves in case of a circular dependency.
+  if (isset($seen[$file->name])) {
+    return isset($incompatible[$file->name]);
+  }
+  $seen[$file->name] = TRUE;
+  if (isset($incompatible[$file->name])) {
+    return TRUE;
+  }
+  // The 'dependencies' key in .info files was a string in Drupal 5, but changed
+  // to an array in Drupal 6. If it is not an array, the module is not
+  // compatible and we can skip the check below which requires an array.
+  if (!is_array($file->info['dependencies'])) {
+    $file->info['dependencies'] = array();
+    $incompatible[$file->name] = TRUE;
+    return TRUE;
+  }
+  // Recursively traverse the dependencies, looking for incompatible modules
+  foreach ($file->info['dependencies'] as $dependency) {
+    if (isset($files[$dependency]) && _system_is_incompatible($incompatible, $files, $files[$dependency])) {
+      $incompatible[$file->name] = TRUE;
+      return TRUE;
+    }
+  }
+}
+
+/**
+ * Menu callback; provides module enable/disable interface.
+ *
+ * Modules can be enabled or disabled and set for throttling if the throttle module is enabled.
+ * The list of modules gets populated by module.info files, which contain each module's name,
+ * description and dependencies.
+ * @see drupal_parse_info_file for information on module.info descriptors.
+ *
+ * Dependency checking is performed to ensure that a module cannot be enabled if the module has
+ * disabled dependencies and also to ensure that the module cannot be disabled if the module has
+ * enabled dependents.
+ *
+ * @param $form_state
+ *   An associative array containing the current state of the form.
+ * @ingroup forms
+ * @see theme_system_modules()
+ * @see system_modules_submit()
+ * @return
+ *   The form array.
+ */
+function system_modules($form_state = array()) {
+  drupal_rebuild_theme_registry();
+  node_types_rebuild();
+  menu_rebuild();
+  cache_clear_all('schema', 'cache');
+  // Get current list of modules.
+  $files = module_rebuild_cache();
+
+  uasort($files, 'system_sort_modules_by_info_name');
+
+  if (!empty($form_state['storage'])) {
+    return system_modules_confirm_form($files, $form_state['storage']);
+  }
+  $dependencies = array();
+
+  // Store module list for validation callback.
+  $form['validation_modules'] = array('#type' => 'value', '#value' => $files);
+
+  // Create storage for disabled modules as browser will disable checkboxes.
+  $form['disabled_modules'] = array('#type' => 'value', '#value' => array());
+
+  // Traverse the files, checking for compatibility
+  $incompatible_core = array();
+  $incompatible_php = array();
+  foreach ($files as $filename => $file) {
+    // Ensure this module is compatible with this version of core.
+    if (!isset($file->info['core']) || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY) {
+      $incompatible_core[$file->name] = $file->name;
+    }
+    // Ensure this module is compatible with the currently installed version of PHP.
+    if (version_compare(phpversion(), $file->info['php']) < 0) {
+      $incompatible_php[$file->name] = $file->info['php'];
+    }
+  }
+
+  // Array for disabling checkboxes in callback system_module_disable.
+  $disabled = array();
+  $throttle = array();
+  // Traverse the files retrieved and build the form.
+  foreach ($files as $filename => $file) {
+    $form['name'][$filename] = array('#value' => $file->info['name']);
+    $form['version'][$filename] = array('#value' => $file->info['version']);
+    $form['description'][$filename] = array('#value' => t($file->info['description']));
+    $options[$filename] = '';
+    // Ensure this module is compatible with this version of core and php.
+    if (_system_is_incompatible($incompatible_core, $files, $file) || _system_is_incompatible($incompatible_php, $files, $file)) {
+      $disabled[] = $file->name;
+      // Nothing else in this loop matters, so move to the next module.
+      continue;
+    }
+    if ($file->status) {
+      $status[] = $file->name;
+    }
+    if ($file->throttle) {
+      $throttle[] = $file->name;
+    }
+
+    $dependencies = array();
+    // Check for missing dependencies.
+    if (is_array($file->info['dependencies'])) {
+      foreach ($file->info['dependencies'] as $dependency) {
+        if (!isset($files[$dependency]) || !$files[$dependency]->status) {
+          if (isset($files[$dependency])) {
+            $dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
+          }
+          else {
+            $dependencies[] = drupal_ucfirst($dependency) . t(' (<span class="admin-missing">missing</span>)');
+            $disabled[] = $filename;
+            $form['disabled_modules']['#value'][$filename] = FALSE;
+          }
+        }
+        else {
+          $dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
+        }
+      }
+
+      // Add text for dependencies.
+      if (!empty($dependencies)) {
+        $form['description'][$filename]['dependencies'] = array(
+          '#value' => t('Depends on: !dependencies', array('!dependencies' => implode(', ', $dependencies))),
+          '#prefix' => '<div class="admin-dependencies">',
+          '#suffix' => '</div>',
+        );
+      }
+    }
+
+    // Mark dependents disabled so user can not remove modules being depended on.
+    $dependents = array();
+    foreach ($file->info['dependents'] as $dependent) {
+      if ($files[$dependent]->status == 1) {
+        $dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
+        $disabled[] = $filename;
+        $form['disabled_modules']['#value'][$filename] = TRUE;
+      }
+      else {
+        $dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
+      }
+    }
+
+    // Add text for enabled dependents.
+    if (!empty($dependents)) {
+      $form['description'][$filename]['required'] = array(
+        '#value' => t('Required by: !required', array('!required' => implode(', ', $dependents))),
+        '#prefix' => '<div class="admin-required">',
+        '#suffix' => '</div>',
+      );
+    }
+  }
+
+  $modules_required = drupal_required_modules();
+  // Merge in required modules.
+  foreach ($modules_required as $required) {
+    $disabled[] = $required;
+    $form['disabled_modules']['#value'][$required] = TRUE;
+  }
+
+  // Handle status checkboxes, including overriding
+  // the generated checkboxes for required modules.
+  $form['status'] = array(
+    '#type' => 'checkboxes',
+    '#default_value' => $status,
+    '#options' => $options,
+    '#process' => array(
+      'expand_checkboxes',
+      'system_modules_disable',
+    ),
+    '#disabled_modules' => $disabled,
+    '#incompatible_modules_core' => $incompatible_core,
+    '#incompatible_modules_php' => $incompatible_php,
+  );
+
+  // Handle throttle checkboxes, including overriding the
+  // generated checkboxes for required modules.
+  if (module_exists('throttle')) {
+    $form['throttle'] = array(
+      '#type' => 'checkboxes',
+      '#default_value' => $throttle,
+      '#options' => $options,
+      '#process' => array(
+        'expand_checkboxes',
+        'system_modules_disable',
+      ),
+      '#disabled_modules' => array_merge($modules_required, array('throttle')),
+    );
+  }
+
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save configuration'),
+  );
+  $form['#action'] = url('admin/build/modules/list/confirm');
+
+  return $form;
+}
+
+/**
+ * Array sorting callback; sorts modules or themes by their name.
+ */
+function system_sort_modules_by_info_name($a, $b) {
+  return strcasecmp($a->info['name'], $b->info['name']);
+}
+
+/**
+ * Form process callback function to disable check boxes.
+ *
+ * @param $form
+ *   The form structure.
+ * @param $edit
+ *   Not used.
+ * @ingroup forms
+ * @return
+ *   The form structure.
+ */
+function system_modules_disable($form, $edit) {
+  foreach ($form['#disabled_modules'] as $key) {
+    $form[$key]['#attributes']['disabled'] = 'disabled';
+  }
+  return $form;
+}
+
+/**
+ * Display confirmation form for dependencies.
+ *
+ * @param $modules
+ *   Array of module file objects as returned from module_rebuild_cache().
+ * @param $storage
+ *   The contents of $form_state['storage']; an array with two
+ *   elements: the list of dependencies and the list of status
+ *   form field values from the previous screen.
+ * @ingroup forms
+ */
+function system_modules_confirm_form($modules, $storage) {
+  $form = array();
+  $items = array();
+
+  list($dependencies, $status) = $storage;
+  $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
+  $form['status']['#tree'] = TRUE;
+  // Remember list of modules selected on the module listing page already.
+  foreach ($status as $key => $choice) {
+    $form['status'][$key] = array('#type' => 'value', '#value' => $choice);
+  }
+  foreach ($dependencies as $name => $missing_dependencies) {
+    $form['status'][$name] = array('#type' => 'hidden', '#value' => 1);
+    foreach ($missing_dependencies as $k => $dependency) {
+      $form['status'][$dependency] = array('#type' => 'hidden', '#value' => 1);
+      $info = $modules[$dependency]->info;
+      $missing_dependencies[$k] = $info['name'] ? $info['name'] : drupal_ucfirst($dependency);
+    }
+    $t_argument = array(
+      '@module' => $modules[$name]->info['name'],
+      '@dependencies' => implode(', ', $missing_dependencies),
+    );
+    $items[] = format_plural(count($missing_dependencies), 'You must enable the @dependencies module to install @module.', 'You must enable the @dependencies modules to install @module.', $t_argument);
+  }
+  $form['text'] = array('#value' => theme('item_list', $items));
+
+  if ($form) {
+    // Set some default form values
+    $form = confirm_form(
+      $form,
+      t('Some required modules must be enabled'),
+      'admin/build/modules',
+      t('Would you like to continue with enabling the above?'),
+      t('Continue'),
+      t('Cancel'));
+    return $form;
+  }
+}
+
+/**
+ * Submit callback; handles modules form submission.
+ */
+function system_modules_submit($form, &$form_state) {
+  include_once './includes/install.inc';
+  $new_modules = array();
+
+  // If we are coming from the confirm form...
+  if (!isset($form_state['storage'])) {
+    // Merge in disabled active modules since they should be enabled.
+    // They don't appear because disabled checkboxes are not submitted
+    // by browsers.
+    $form_state['values']['status'] = array_merge($form_state['values']['status'], $form_state['values']['disabled_modules']);
+
+    // Check values for dependency that we can't install.
+    if ($dependencies = system_module_build_dependencies($form_state['values']['validation_modules'], $form_state['values'])) {
+      // These are the modules that depend on existing modules.
+      foreach (array_keys($dependencies) as $name) {
+        $form_state['values']['status'][$name] = 0;
+      }
+    }
+  }
+  else {
+    $dependencies = NULL;
+  }
+
+  // Update throttle settings, if present
+  if (isset($form_state['values']['throttle'])) {
+    foreach ($form_state['values']['throttle'] as $key => $choice) {
+      db_query("UPDATE {system} SET throttle = %d WHERE type = 'module' and name = '%s'", $choice ? 1 : 0, $key);
+    }
+  }
+
+  // If there where unmet dependencies and they haven't confirmed don't process
+  // the submission yet. Store the form submission data needed later.
+  if ($dependencies) {
+    if (!isset($form_state['values']['confirm'])) {
+      $form_state['storage'] = array($dependencies, $form_state['values']['status']);
+      return;
+    }
+    else {
+      $form_state['values']['status'] = array_merge($form_state['values']['status'], $form_storage[1]);
+    }
+  }
+  // If we have no dependencies, or the dependencies are confirmed
+  // to be installed, we don't need the temporary storage anymore.
+  unset($form_state['storage']);
+
+  $enable_modules = array();
+  $disable_modules = array();
+  foreach ($form_state['values']['status'] as $key => $choice) {
+    if ($choice) {
+      if (drupal_get_installed_schema_version($key) == SCHEMA_UNINSTALLED) {
+        $new_modules[] = $key;
+      }
+      else {
+        $enable_modules[] = $key;
+      }
+    }
+    else {
+      $disable_modules[] = $key;
+    }
+  }
+
+  $old_module_list = module_list();
+
+  if (!empty($enable_modules)) {
+    module_enable($enable_modules);
+  }
+  if (!empty($disable_modules)) {
+    module_disable($disable_modules);
+  }
+
+  // Install new modules.
+  foreach ($new_modules as $key => $module) {
+    if (!drupal_check_module($module)) {
+      unset($new_modules[$key]);
+    }
+  }
+  drupal_install_modules($new_modules);
+
+  $current_module_list = module_list(TRUE, FALSE);
+  if ($old_module_list != $current_module_list) {
+    drupal_set_message(t('The configuration options have been saved.'));
+  }
+
+  drupal_clear_css_cache();
+  drupal_clear_js_cache();
+
+  $form_state['redirect'] = 'admin/build/modules';
+
+  // Notify locale module about module changes, so translations can be
+  // imported. This might start a batch, and only return to the redirect
+  // path after that.
+  module_invoke('locale', 'system_update', $new_modules);
+
+  // Synchronize to catch any actions that were added or removed.
+  actions_synchronize();
+
+  return;
+}
+
+
+/**
+ * Generate a list of dependencies for modules that are going to be switched on.
+ *
+ * @param $modules
+ *   The list of modules to check.
+ * @param $form_values
+ *   Submitted form values used to determine what modules have been enabled.
+ * @return
+ *   An array of dependencies.
+ */
+function system_module_build_dependencies($modules, $form_values) {
+  static $dependencies;
+
+  if (!isset($dependencies) && isset($form_values)) {
+    $dependencies = array();
+    foreach ($modules as $name => $module) {
+      // If the module is disabled, will be switched on and it has dependencies.
+      if (!$module->status && $form_values['status'][$name] && isset($module->info['dependencies'])) {
+        foreach ($module->info['dependencies'] as $dependency) {
+          if (!$form_values['status'][$dependency] && isset($modules[$dependency])) {
+            if (!isset($dependencies[$name])) {
+              $dependencies[$name] = array();
+            }
+            $dependencies[$name][] = $dependency;
+          }
+        }
+      }
+    }
+  }
+  return $dependencies;
+}
+
+/**
+ * Uninstall functions
+ */
+
+/**
+ * Builds a form of currently disabled modules.
+ *
+ * @ingroup forms
+ * @see system_modules_uninstall_validate()
+ * @see system_modules_uninstall_submit()
+ * @param $form_state['values']
+ *   Submitted form values.
+ * @return
+ *   A form array representing the currently disabled modules.
+ */
+function system_modules_uninstall($form_state = NULL) {
+  // Make sure the install API is available.
+  include_once './includes/install.inc';
+
+  // Display the confirm form if any modules have been submitted.
+  if (isset($form_state) && $confirm_form = system_modules_uninstall_confirm_form($form_state['storage'])) {
+    return $confirm_form;
+  }
+
+  $form = array();
+
+  // Pull all disabled modules from the system table.
+  $disabled_modules = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > %d ORDER BY name", SCHEMA_UNINSTALLED);
+  while ($module = db_fetch_object($disabled_modules)) {
+
+    // Grab the module info
+    $info = unserialize($module->info);
+
+    // Load the .install file, and check for an uninstall hook.
+    // If the hook exists, the module can be uninstalled.
+    module_load_install($module->name);
+    if (module_hook($module->name, 'uninstall')) {
+      $form['modules'][$module->name]['name'] = array('#value' => $info['name'] ? $info['name'] : $module->name);
+      $form['modules'][$module->name]['description'] = array('#value' => t($info['description']));
+      $options[$module->name] = '';
+    }
+  }
+
+  // Only build the rest of the form if there are any modules available to uninstall.
+  if (!empty($options)) {
+    $form['uninstall'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $options,
+    );
+    $form['buttons']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Uninstall'),
+    );
+    $form['#action'] = url('admin/build/modules/uninstall/confirm');
+  }
+  else {
+    $form['modules'] = array();
+  }
+
+  return $form;
+}
+
+/**
+ * Confirm uninstall of selected modules.
+ *
+ * @ingroup forms
+ * @param $storage
+ *   An associative array of modules selected to be uninstalled.
+ * @return
+ *   A form array representing modules to confirm.
+ */
+function system_modules_uninstall_confirm_form($storage) {
+  // Nothing to build.
+  if (!isset($storage)) {
+    return;
+  }
+
+  // Construct the hidden form elements and list items.
+  foreach (array_filter($storage['uninstall']) as $module => $value) {
+    $info = drupal_parse_info_file(dirname(drupal_get_filename('module', $module)) .'/'. $module .'.info');
+    $uninstall[] = $info['name'];
+    $form['uninstall'][$module] = array('#type' => 'hidden',
+      '#value' => 1,
+    );
+  }
+
+  // Display a confirm form if modules have been selected.
+  if (isset($uninstall)) {
+    $form['#confirmed'] = TRUE;
+    $form['uninstall']['#tree'] = TRUE;
+    $form['modules'] = array('#value' => '<p>'. t('The following modules will be completely uninstalled from your site, and <em>all data from these modules will be lost</em>!') .'</p>'. theme('item_list', $uninstall));
+    $form = confirm_form(
+      $form,
+      t('Confirm uninstall'),
+      'admin/build/modules/uninstall',
+      t('Would you like to continue with uninstalling the above?'),
+      t('Uninstall'),
+      t('Cancel'));
+    return $form;
+  }
+}
+
+/**
+ * Validates the submitted uninstall form.
+ */
+function system_modules_uninstall_validate($form, &$form_state) {
+  // Form submitted, but no modules selected.
+  if (!count(array_filter($form_state['values']['uninstall']))) {
+    drupal_set_message(t('No modules selected.'), 'error');
+    drupal_goto('admin/build/modules/uninstall');
+  }
+}
+
+/**
+ * Processes the submitted uninstall form.
+ */
+function system_modules_uninstall_submit($form, &$form_state) {
+  // Make sure the install API is available.
+  include_once './includes/install.inc';
+
+  if (!empty($form['#confirmed'])) {
+    // Call the uninstall routine for each selected module.
+    foreach (array_filter($form_state['values']['uninstall']) as $module => $value) {
+      drupal_uninstall_module($module);
+    }
+    drupal_set_message(t('The selected modules have been uninstalled.'));
+
+    unset($form_state['storage']);
+    $form_state['redirect'] = 'admin/build/modules/uninstall';
+  }
+  else {
+    $form_state['storage'] = $form_state['values'];
+  }
+}
+
+/**
+ * Form builder; The general site information form.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_site_information_settings() {
+  $form['site_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Name'),
+    '#default_value' => variable_get('site_name', 'Drupal'),
+    '#description' => t('The name of this website.'),
+    '#required' => TRUE
+  );
+  $form['site_mail'] = array(
+    '#type' => 'textfield',
+    '#title' => t('E-mail address'),
+    '#default_value' => variable_get('site_mail', ini_get('sendmail_from')),
+    '#description' => t("The <em>From</em> address in automated e-mails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this e-mail being flagged as spam.)"),
+    '#required' => TRUE,
+  );
+  $form['site_slogan'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Slogan'),
+    '#default_value' => variable_get('site_slogan', ''),
+    '#description' => t("Your site's motto, tag line, or catchphrase (often displayed alongside the title of the site).")
+  );
+  $form['site_mission'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Mission'),
+    '#default_value' => variable_get('site_mission', ''),
+    '#description' => t("Your site's mission or focus statement (often prominently displayed on the front page).")
+  );
+  $form['site_footer'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Footer message'),
+    '#default_value' => variable_get('site_footer', ''),
+    '#description' => t('This text will be displayed at the bottom of each page. Useful for adding a copyright notice to your pages.')
+  );
+  $form['anonymous'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Anonymous user'),
+    '#default_value' => variable_get('anonymous', t('Anonymous')),
+    '#description' => t('The name used to indicate anonymous users.'),
+    '#required' => TRUE,
+  );
+  $form['site_frontpage'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default front page'),
+    '#default_value' => variable_get('site_frontpage', 'node'),
+    '#size' => 40,
+    '#description' => t('The home page displays content from this relative URL. If unsure, specify "node".'),
+    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
+    '#required' => TRUE,
+  );
+  $form['#validate'][] = 'system_site_information_settings_validate';
+
+  return system_settings_form($form);
+}
+
+/**
+ * Validate the submitted site-information form.
+ */
+function system_site_information_settings_validate($form, &$form_state) {
+  // Validate the e-mail address.
+  if ($error = user_validate_mail($form_state['values']['site_mail'])) {
+    form_set_error('site_mail', $error);
+  }
+  // Validate front page path.
+  $item = array('link_path' => $form_state['values']['site_frontpage']);
+  $normal_path = drupal_get_normal_path($item['link_path']);
+  if ($item['link_path'] != $normal_path) {
+    drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $item['link_path'], '%normal_path' => $normal_path)));
+    $item['link_path'] = $normal_path;
+  }
+  if (!empty($item) && !menu_valid_path($item)) {
+    form_set_error('site_frontpage', t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $item['link_path'])));
+  }
+}
+
+/**
+ * Form builder; Configure error reporting settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_error_reporting_settings() {
+
+  $form['site_403'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default 403 (access denied) page'),
+    '#default_value' => variable_get('site_403', ''),
+    '#size' => 40,
+    '#description' => t('This page is displayed when the requested document is denied to the current user. If unsure, specify nothing.'),
+    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
+  );
+
+  $form['site_404'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default 404 (not found) page'),
+    '#default_value' => variable_get('site_404', ''),
+    '#size' => 40,
+    '#description' => t('This page is displayed when no other content matches the requested document. If unsure, specify nothing.'),
+    '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
+  );
+
+  $form['error_level'] = array(
+    '#type' => 'select', '#title' => t('Error reporting'), '#default_value' => variable_get('error_level', 1),
+    '#options' => array(t('Write errors to the log'), t('Write errors to the log and to the screen')),
+    '#description' => t('Specify where Drupal, PHP and SQL errors are logged. While it is recommended that a site running in a production environment write errors to the log only, in a development or testing environment it may be helpful to write errors both to the log and to the screen.')
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback; Menu page for the various logging options.
+ */
+function system_logging_overview() {
+  $item = menu_get_item('admin/settings/logging');
+  $content = system_admin_menu_block($item);
+
+  $output = theme('admin_block_content', $content);
+
+  return $output;
+}
+
+/**
+ * Form builder; Configure site performance settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_performance_settings() {
+
+  $description = '<p>'. t("The normal cache mode is suitable for most sites and does not cause any side effects. The aggressive cache mode causes Drupal to skip the loading (boot) and unloading (exit) of enabled modules when serving a cached page. This results in an additional performance boost but can cause unwanted side effects.") .'</p>';
+
+  $problem_modules = array_unique(array_merge(module_implements('boot'), module_implements('exit')));
+  sort($problem_modules);
+
+  if (count($problem_modules) > 0) {
+    $description .= '<p>'. t('<strong class="error">The following enabled modules are incompatible with aggressive mode caching and will not function properly: %modules</strong>', array('%modules' => implode(', ', $problem_modules))) .'.</p>';
+  }
+  else {
+    $description .= '<p>'. t('<strong class="ok">Currently, all enabled modules are compatible with the aggressive caching policy.</strong> Please note, if you use aggressive caching and enable new modules, you will need to check this page again to ensure compatibility.') .'</p>';
+  }
+  $form['page_cache'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Page cache'),
+    '#description' => t('Enabling the page cache will offer a significant performance boost. Drupal can store and send compressed cached pages requested by <em>anonymous</em> users. By caching a web page, Drupal does not have to construct the page each time it is viewed.'),
+  );
+
+  $form['page_cache']['cache'] = array(
+    '#type' => 'radios',
+    '#title' => t('Caching mode'),
+    '#default_value' => variable_get('cache', CACHE_DISABLED),
+    '#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Normal (recommended for production sites, no side effects)'), CACHE_AGGRESSIVE => t('Aggressive (experts only, possible side effects)')),
+    '#description' => $description
+  );
+
+  $period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval');
+  $period[0] = '<'. t('none') .'>';
+  $form['page_cache']['cache_lifetime'] = array(
+    '#type' => 'select',
+    '#title' => t('Minimum cache lifetime'),
+    '#default_value' => variable_get('cache_lifetime', 0),
+    '#options' => $period,
+    '#description' => t('On high-traffic sites, it may be necessary to enforce a minimum cache lifetime. The minimum cache lifetime is the minimum amount of time that will elapse before the cache is emptied and recreated, and is applied to both page and block caches. A larger minimum cache lifetime offers better performance, but users will not see new content for a longer period of time.')
+  );
+  $form['page_cache']['page_compression'] = array(
+    '#type' => 'radios',
+    '#title' => t('Page compression'),
+    '#default_value' => variable_get('page_compression', TRUE),
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#description' => t("By default, Drupal compresses the pages it caches in order to save bandwidth and improve download times. This option should be disabled when using a webserver that performs compression."),
+  );
+
+  $form['block_cache'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Block cache'),
+    '#description' => t('Enabling the block cache can offer a performance increase for all users by preventing blocks from being reconstructed on each page load. If the page cache is also enabled, performance increases from enabling the block cache will mainly benefit authenticated users.'),
+  );
+
+  $form['block_cache']['block_cache'] = array(
+    '#type' => 'radios',
+    '#title' => t('Block cache'),
+    '#default_value' => variable_get('block_cache', CACHE_DISABLED),
+    '#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Enabled (recommended)')),
+    '#disabled' => count(module_implements('node_grants')),
+    '#description' => t('Note that block caching is inactive when modules defining content access restrictions are enabled.'),
+  );
+
+  $form['bandwidth_optimizations'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Bandwidth optimizations'),
+    '#description' => t('<p>Drupal can automatically optimize external resources like CSS and JavaScript, which can reduce both the size and number of requests made to your website. CSS files can be aggregated and compressed into a single file, while JavaScript files are aggregated (but not compressed). These optional optimizations may reduce server load, bandwidth requirements, and page loading times.</p><p>These options are disabled if you have not set up your files directory, or if your download method is set to private.</p>')
+  );
+
+  $directory = file_directory_path();
+  $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
+  $form['bandwidth_optimizations']['preprocess_css'] = array(
+    '#type' => 'radios',
+    '#title' => t('Optimize CSS files'),
+    '#default_value' => intval(variable_get('preprocess_css', 0) && $is_writable),
+    '#disabled' => !$is_writable,
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#description' => t('This option can interfere with theme development and should only be enabled in a production environment.'),
+  );
+  $form['bandwidth_optimizations']['preprocess_js'] = array(
+    '#type' => 'radios',
+    '#title' => t('Optimize JavaScript files'),
+    '#default_value' => intval(variable_get('preprocess_js', 0) && $is_writable),
+    '#disabled' => !$is_writable,
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#description' => t('This option can interfere with module development and should only be enabled in a production environment.'),
+  );
+
+  $form['clear_cache'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Clear cached data'),
+    '#description' => t('Caching data improves performance, but may cause problems while troubleshooting new modules, themes, or translations, if outdated information has been cached. To refresh all cached data on your site, click the button below. <em>Warning: high-traffic sites will experience performance slowdowns while cached data is rebuilt.</em>'),
+  );
+
+  $form['clear_cache']['clear'] = array(
+    '#type' => 'submit',
+    '#value' => t('Clear cached data'),
+    '#submit' => array('system_clear_cache_submit'),
+  );
+
+  $form['#submit'][] = 'drupal_clear_css_cache';
+  $form['#submit'][] = 'drupal_clear_js_cache';
+
+  return system_settings_form($form);
+}
+
+/**
+ * Submit callback; clear system caches.
+ *
+ * @ingroup forms
+ */
+function system_clear_cache_submit(&$form_state, $form) {
+  drupal_flush_all_caches();
+  drupal_set_message('Caches cleared.');
+}
+
+/**
+ * Form builder; Configure the site file handling.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_file_system_settings() {
+
+  $form['file_directory_path'] = array(
+    '#type' => 'textfield',
+    '#title' => t('File system path'),
+    '#default_value' => file_directory_path(),
+    '#maxlength' => 255,
+    '#description' => t('A file system path where the files will be stored. This directory must exist and be writable by Drupal. If the download method is set to public, this directory must be relative to the Drupal installation directory and be accessible over the web. If the download method is set to private, this directory should not be accessible over the web. Changing this location will modify all download paths and may cause unexpected problems on an existing site.'),
+    '#after_build' => array('system_check_directory'),
+  );
+
+  $form['file_directory_temp'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Temporary directory'),
+    '#default_value' => file_directory_temp(),
+    '#maxlength' => 255,
+    '#description' => t('A file system path where uploaded files will be stored during previews.'),
+    '#after_build' => array('system_check_directory'),
+  );
+
+  $form['file_downloads'] = array(
+    '#type' => 'radios',
+    '#title' => t('Download method'),
+    '#default_value' => variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC),
+    '#options' => array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using HTTP directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')),
+    '#description' => t('Choose the <em>Public download</em> method unless you wish to enforce fine-grained access controls over file downloads. Changing the download method will modify all download paths and may cause unexpected problems on an existing site.')
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure site image toolkit usage.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_image_toolkit_settings() {
+  $toolkits_available = image_get_available_toolkits();
+  if (count($toolkits_available) > 1) {
+    $form['image_toolkit'] = array(
+      '#type' => 'radios',
+      '#title' => t('Select an image processing toolkit'),
+      '#default_value' => variable_get('image_toolkit', image_get_toolkit()),
+      '#options' => $toolkits_available
+    );
+  }
+  elseif (count($toolkits_available) == 1) {
+    variable_set('image_toolkit', key($toolkits_available));
+  }
+
+  $form['image_toolkit_settings'] = image_toolkit_invoke('settings');
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure how the site handles RSS feeds.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_rss_feeds_settings() {
+
+  $form['feed_default_items'] = array(
+    '#type' => 'select',
+    '#title' => t('Number of items in each feed'),
+    '#default_value' => variable_get('feed_default_items', 10),
+    '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)),
+    '#description' => t('Default number of items to include in each feed.')
+  );
+  $form['feed_item_length'] = array(
+    '#type' => 'select',
+    '#title' => t('Feed content'),
+    '#default_value' => variable_get('feed_item_length', 'teaser'),
+    '#options' => array('title' => t('Titles only'), 'teaser' => t('Titles plus teaser'), 'fulltext' => t('Full text')),
+    '#description' => t('Global setting for the default display of content items in each feed.')
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure the site date and time settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ * @see system_date_time_settings_submit()
+ */
+function system_date_time_settings() {
+  drupal_add_js(drupal_get_path('module', 'system') .'/system.js', 'module');
+  drupal_add_js(array('dateTime' => array('lookup' => url('admin/settings/date-time/lookup'))), 'setting');
+
+  // Date settings:
+  $zones = _system_zonelist();
+
+  // Date settings: possible date formats
+  $date_short = array('Y-m-d H:i', 'm/d/Y - H:i', 'd/m/Y - H:i', 'Y/m/d - H:i',
+           'd.m.Y - H:i', 'm/d/Y - g:ia', 'd/m/Y - g:ia', 'Y/m/d - g:ia',
+           'M j Y - H:i', 'j M Y - H:i', 'Y M j - H:i',
+           'M j Y - g:ia', 'j M Y - g:ia', 'Y M j - g:ia');
+  $date_medium = array('D, Y-m-d H:i', 'D, m/d/Y - H:i', 'D, d/m/Y - H:i',
+          'D, Y/m/d - H:i', 'F j, Y - H:i', 'j F, Y - H:i', 'Y, F j - H:i',
+          'D, m/d/Y - g:ia', 'D, d/m/Y - g:ia', 'D, Y/m/d - g:ia',
+          'F j, Y - g:ia', 'j F Y - g:ia', 'Y, F j - g:ia', 'j. F Y - G:i');
+  $date_long = array('l, F j, Y - H:i', 'l, j F, Y - H:i', 'l, Y, F j - H:i',
+        'l, F j, Y - g:ia', 'l, j F Y - g:ia', 'l, Y, F j - g:ia', 'l, j. F Y - G:i');
+
+  // Date settings: construct choices for user
+  foreach ($date_short as $f) {
+    $date_short_choices[$f] = format_date(time(), 'custom', $f);
+  }
+  foreach ($date_medium as $f) {
+    $date_medium_choices[$f] = format_date(time(), 'custom', $f);
+  }
+  foreach ($date_long as $f) {
+    $date_long_choices[$f] = format_date(time(), 'custom', $f);
+  }
+
+  $date_long_choices['custom'] = $date_medium_choices['custom'] = $date_short_choices['custom'] = t('Custom format');
+
+  $form['locale'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Locale settings'),
+  );
+
+  $form['locale']['date_default_timezone'] = array(
+    '#type' => 'select',
+    '#title' => t('Default time zone'),
+    '#default_value' => variable_get('date_default_timezone', 0),
+    '#options' => $zones,
+    '#description' => t('Select the default site time zone.')
+  );
+
+  $form['locale']['configurable_timezones'] = array(
+    '#type' => 'radios',
+    '#title' => t('User-configurable time zones'),
+    '#default_value' => variable_get('configurable_timezones', 1),
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#description' => t('When enabled, users can set their own time zone and dates will be displayed accordingly.')
+  );
+
+  $form['locale']['date_first_day'] = array(
+    '#type' => 'select',
+    '#title' => t('First day of week'),
+    '#default_value' => variable_get('date_first_day', 0),
+    '#options' => array(0 => t('Sunday'), 1 => t('Monday'), 2 => t('Tuesday'), 3 => t('Wednesday'), 4 => t('Thursday'), 5 => t('Friday'), 6 => t('Saturday')),
+    '#description' => t('The first day of the week for calendar views.')
+  );
+
+  $form['date_formats'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Formatting'),
+  );
+
+  $date_format_short = variable_get('date_format_short', $date_short[1]);
+  $form['date_formats']['date_format_short'] = array(
+    '#prefix' => '<div class="date-container"><div class="select-container">',
+    '#suffix' => '</div>',
+    '#type' => 'select',
+    '#title' => t('Short date format'),
+    '#attributes' => array('class' => 'date-format'),
+    '#default_value' => (isset($date_short_choices[$date_format_short]) ? $date_format_short : 'custom'),
+    '#options' => $date_short_choices,
+    '#description' => t('The short format of date display.'),
+  );
+
+  $default_short_custom = variable_get('date_format_short_custom', (isset($date_short_choices[$date_format_short]) ? $date_format_short : ''));
+  $form['date_formats']['date_format_short_custom'] = array(
+    '#prefix' => '<div class="custom-container">',
+    '#suffix' => '</div></div>',
+    '#type' => 'textfield',
+    '#title' => t('Custom short date format'),
+    '#attributes' => array('class' => 'custom-format'),
+    '#default_value' => $default_short_custom,
+    '#description' => t('A user-defined short date format. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => format_date(time(), 'custom', $default_short_custom))),
+  );
+
+  $date_format_medium = variable_get('date_format_medium', $date_medium[1]);
+  $form['date_formats']['date_format_medium'] = array(
+    '#prefix' => '<div class="date-container"><div class="select-container">',
+    '#suffix' => '</div>',
+    '#type' => 'select',
+    '#title' => t('Medium date format'),
+    '#attributes' => array('class' => 'date-format'),
+    '#default_value' => (isset($date_medium_choices[$date_format_medium]) ? $date_format_medium : 'custom'),
+    '#options' => $date_medium_choices,
+    '#description' => t('The medium sized date display.'),
+  );
+
+  $default_medium_custom = variable_get('date_format_medium_custom', (isset($date_medium_choices[$date_format_medium]) ? $date_format_medium : ''));
+  $form['date_formats']['date_format_medium_custom'] = array(
+    '#prefix' => '<div class="custom-container">',
+    '#suffix' => '</div></div>',
+    '#type' => 'textfield',
+    '#title' => t('Custom medium date format'),
+    '#attributes' => array('class' => 'custom-format'),
+    '#default_value' => $default_medium_custom,
+    '#description' => t('A user-defined medium date format. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => format_date(time(), 'custom', $default_medium_custom))),
+  );
+
+  $date_format_long = variable_get('date_format_long', $date_long[0]);
+  $form['date_formats']['date_format_long'] = array(
+    '#prefix' => '<div class="date-container"><div class="select-container">',
+    '#suffix' => '</div>',
+    '#type' => 'select',
+    '#title' => t('Long date format'),
+    '#attributes' => array('class' => 'date-format'),
+    '#default_value' => (isset($date_long_choices[$date_format_long]) ? $date_format_long : 'custom'),
+    '#options' => $date_long_choices,
+    '#description' => t('Longer date format used for detailed display.')
+  );
+
+  $default_long_custom = variable_get('date_format_long_custom', (isset($date_long_choices[$date_format_long]) ? $date_format_long : ''));
+  $form['date_formats']['date_format_long_custom'] = array(
+    '#prefix' => '<div class="custom-container">',
+    '#suffix' => '</div></div>',
+    '#type' => 'textfield',
+    '#title' => t('Custom long date format'),
+    '#attributes' => array('class' => 'custom-format'),
+    '#default_value' => $default_long_custom,
+    '#description' => t('A user-defined long date format. See the <a href="@url">PHP manual</a> for available options. This format is currently set to display as <span>%date</span>.', array('@url' => 'http://php.net/manual/function.date.php', '%date' => format_date(time(), 'custom', $default_long_custom))),
+  );
+
+  $form = system_settings_form($form);
+  // We will call system_settings_form_submit() manually, so remove it for now.
+  unset($form['#submit']);
+  return $form;
+}
+
+/**
+ * Process system_date_time_settings form submissions.
+ */
+function system_date_time_settings_submit($form, &$form_state) {
+  if ($form_state['values']['date_format_short'] == 'custom') {
+    $form_state['values']['date_format_short'] = $form_state['values']['date_format_short_custom'];
+  }
+  if ($form_state['values']['date_format_medium'] == 'custom') {
+    $form_state['values']['date_format_medium'] = $form_state['values']['date_format_medium_custom'];
+  }
+  if ($form_state['values']['date_format_long'] == 'custom') {
+    $form_state['values']['date_format_long'] = $form_state['values']['date_format_long_custom'];
+  }
+  return system_settings_form_submit($form, $form_state);
+}
+
+/**
+ * Return the date for a given format string via Ajax.
+ */
+function system_date_time_lookup() {
+  $result = format_date(time(), 'custom', $_GET['format']);
+  echo drupal_to_js($result);
+  exit;
+}
+
+/**
+ * Form builder; Configure the site's maintenance status.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_site_maintenance_settings() {
+
+  $form['site_offline'] = array(
+    '#type' => 'radios',
+    '#title' => t('Site status'),
+    '#default_value' => variable_get('site_offline', 0),
+    '#options' => array(t('Online'), t('Off-line')),
+    '#description' => t('When set to "Online", all visitors will be able to browse your site normally. When set to "Off-line", only users with the "administer site configuration" permission will be able to access your site to perform maintenance; all other visitors will see the site off-line message configured below. Authorized users can log in during "Off-line" mode directly via the <a href="@user-login">user login</a> page.', array('@user-login' => url('user'))),
+  );
+
+  $form['site_offline_message'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Site off-line message'),
+    '#default_value' => variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))),
+    '#description' => t('Message to show visitors when the site is in off-line mode.')
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Form builder; Configure Clean URL settings.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function system_clean_url_settings() {
+  $form['clean_url'] = array(
+    '#type' => 'radios',
+    '#title' => t('Clean URLs'),
+    '#default_value' => variable_get('clean_url', 0),
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#description' => t('This option makes Drupal emit "clean" URLs (i.e. without <code>?q=</code> in the URL).'),
+  );
+
+  if (!variable_get('clean_url', 0)) {
+    if (strpos(request_uri(), '?q=') !== FALSE) {
+      drupal_add_js(drupal_get_path('module', 'system') .'/system.js', 'module');
+
+      $form['clean_url']['#description'] .= ' <span>'. t('Before enabling clean URLs, you must perform a test to determine if your server is properly configured. If you are able to see this page again after clicking the "Run the clean URL test" link, the test has succeeded and the radio buttons above will be available. If instead you are directed to a "Page not found" error, you will need to change the configuration of your server. The <a href="@handbook">handbook page on Clean URLs</a> has additional troubleshooting information.', array('@handbook' => 'http://drupal.org/node/15365')) .'</span>';
+
+      $form['clean_url']['#disabled'] = TRUE;
+      $form['clean_url']['#prefix'] = '<div id="clean-url">';
+      $form['clean_url']['#suffix'] = '<p>'. t('<a href="@clean_url">Run the clean url test</a>.', array('@clean_url' => base_path() .'admin/settings/clean-urls')) .'</p></div>';
+    }
+    else {
+      $form['clean_url']['#description'] .= ' <div class="ok">'. t('Your server has been successfully tested to support this feature.') .'</div>';
+    }
+  }
+
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback: displays the site status report. Can also be used as a pure check.
+ *
+ * @param $check
+ *   If true, only returns a boolean whether there are system status errors.
+ */
+function system_status($check = FALSE) {
+  // Load .install files
+  include_once './includes/install.inc';
+  drupal_load_updates();
+
+  // Check run-time requirements and status information.
+  $requirements = module_invoke_all('requirements', 'runtime');
+  usort($requirements, '_system_sort_requirements');
+
+  if ($check) {
+    return drupal_requirements_severity($requirements) == REQUIREMENT_ERROR;
+  }
+  // MySQL import might have set the uid of the anonymous user to autoincrement
+  // value. Let's try fixing it. See http://drupal.org/node/204411
+  db_query("UPDATE {users} SET uid = uid - uid WHERE name = '' AND pass = '' AND status = 0");
+
+  return theme('status_report', $requirements);
+}
+
+/**
+ * Menu callback: run cron manually.
+ */
+function system_run_cron() {
+  // Run cron manually
+  if (drupal_cron_run()) {
+    drupal_set_message(t('Cron ran successfully.'));
+  }
+  else {
+    drupal_set_message(t('Cron run failed.'), 'error');
+  }
+
+  drupal_goto('admin/reports/status');
+}
+
+/**
+ * Menu callback: return information about PHP.
+ */
+function system_php() {
+  phpinfo(INFO_GENERAL | INFO_CONFIGURATION);
+  exit();
+}
+
+/**
+ * Theme a SQL result table.
+ *
+ * @param $data
+ *   The actual table data.
+ * @param $keys
+ *   Data keys and descriptions.
+ * @return
+ *   The output HTML.
+ */
+function _system_sql($data, $keys) {
+  $rows = array();
+  foreach ($keys as $key => $explanation) {
+    if (isset($data[$key])) {
+      $rows[] = array(check_plain($key), check_plain($data[$key]), $explanation);
+    }
+  }
+
+  return theme('table', array(t('Variable'), t('Value'), t('Description')), $rows);
+}
+
+/**
+ * Menu callback: return information about the database.
+ */
+function system_sql() {
+
+  $result = db_query("SHOW STATUS");
+  while ($entry = db_fetch_object($result)) {
+    // 'SHOW STATUS' returns fields named 'Variable_name' and 'Value',
+    // case is important.
+    $data[$entry->Variable_name] = $entry->Value;
+  }
+
+  $output  = '<h2>'. t('Command counters') .'</h2>';
+  $output .= _system_sql($data, array(
+   'Com_select' => t('The number of <code>SELECT</code>-statements.'),
+   'Com_insert' => t('The number of <code>INSERT</code>-statements.'),
+   'Com_update' => t('The number of <code>UPDATE</code>-statements.'),
+   'Com_delete' => t('The number of <code>DELETE</code>-statements.'),
+   'Com_lock_tables' => t('The number of table locks.'),
+   'Com_unlock_tables' => t('The number of table unlocks.')
+  ));
+
+  $output .= '<h2>'. t('Query performance') .'</h2>';
+  $output .= _system_sql($data, array(
+   'Select_full_join' => t('The number of joins without an index; should be zero.'),
+   'Select_range_check' => t('The number of joins without keys that check for key usage after each row; should be zero.'),
+   'Sort_scan' => t('The number of sorts done without using an index; should be zero.'),
+   'Table_locks_immediate' => t('The number of times a lock could be acquired immediately.'),
+   'Table_locks_waited' => t('The number of times the server had to wait for a lock.')
+  ));
+
+  $output .= '<h2>'. t('Query cache information') .'</h2>';
+  $output .= '<p>'. t('The MySQL query cache can improve performance of your site by storing the result of queries. Then, if an identical query is received later, the MySQL server retrieves the result from the query cache rather than parsing and executing the statement again.') .'</p>';
+  $output .= _system_sql($data, array(
+   'Qcache_queries_in_cache' => t('The number of queries in the query cache.'),
+   'Qcache_hits' => t('The number of times MySQL found previous results in the cache.'),
+   'Qcache_inserts' => t('The number of times MySQL added a query to the cache (misses).'),
+   'Qcache_lowmem_prunes' => t('The number of times MySQL had to remove queries from the cache because it ran out of memory. Ideally should be zero.')
+  ));
+
+  return $output;
+}
+
+/**
+ * Default page callback for batches.
+ */
+function system_batch_page() {
+  require_once './includes/batch.inc';
+  $output = _batch_page();
+  if ($output === FALSE) {
+    drupal_access_denied();
+  }
+  elseif (isset($output)) {
+    // Force a page without blocks or messages to
+    // display a list of collected messages later.
+    print theme('page', $output, FALSE, FALSE);
+  }
+}
+
+/**
+ * This function formats an administrative block for display.
+ *
+ * @param $block
+ *   An array containing information about the block. It should
+ *   include a 'title', a 'description' and a formatted 'content'.
+ * @ingroup themeable
+ */
+function theme_admin_block($block) {
+  // Don't display the block if it has no content to display.
+  if (empty($block['content'])) {
+    return '';
+  }
+
+  $output = <<< EOT
+  <div class="admin-panel">
+    <h3>
+      $block[title]
+    </h3>
+    <div class="body">
+      <p class="description">
+        $block[description]
+      </p>
+      $block[content]
+    </div>
+  </div>
+EOT;
+  return $output;
+}
+
+/**
+ * This function formats the content of an administrative block.
+ *
+ * @param $block
+ *   An array containing information about the block. It should
+ *   include a 'title', a 'description' and a formatted 'content'.
+ * @ingroup themeable
+ */
+function theme_admin_block_content($content) {
+  if (!$content) {
+    return '';
+  }
+
+  if (system_admin_compact_mode()) {
+    $output = '<ul class="menu">';
+    foreach ($content as $item) {
+      $output .= '<li class="leaf">'. l($item['title'], $item['href'], $item['localized_options']) .'</li>';
+    }
+    $output .= '</ul>';
+  }
+  else {
+    $output = '<dl class="admin-list">';
+    foreach ($content as $item) {
+      $output .= '<dt>'. l($item['title'], $item['href'], $item['localized_options']) .'</dt>';
+      $output .= '<dd>'. $item['description'] .'</dd>';
+    }
+    $output .= '</dl>';
+  }
+  return $output;
+}
+
+/**
+ * This function formats an administrative page for viewing.
+ *
+ * @param $blocks
+ *   An array of blocks to display. Each array should include a
+ *   'title', a 'description', a formatted 'content' and a
+ *   'position' which will control which container it will be
+ *   in. This is usually 'left' or 'right'.
+ * @ingroup themeable
+ */
+function theme_admin_page($blocks) {
+  $stripe = 0;
+  $container = array();
+
+  foreach ($blocks as $block) {
+    if ($block_output = theme('admin_block', $block)) {
+      if (empty($block['position'])) {
+        // perform automatic striping.
+        $block['position'] = ++$stripe % 2 ? 'left' : 'right';
+      }
+      if (!isset($container[$block['position']])) {
+        $container[$block['position']] = '';
+      }
+      $container[$block['position']] .= $block_output;
+    }
+  }
+
+  $output = '<div class="admin clear-block">';
+  $output .= '<div class="compact-link">';
+  if (system_admin_compact_mode()) {
+    $output .= l(t('Show descriptions'), 'admin/compact/off', array('title' => t('Expand layout to include descriptions.')));
+  }
+  else {
+    $output .= l(t('Hide descriptions'), 'admin/compact/on', array('title' => t('Compress layout by hiding descriptions.')));
+  }
+  $output .= '</div>';
+
+  foreach ($container as $id => $data) {
+    $output .= '<div class="'. $id .' clear-block">';
+    $output .= $data;
+    $output .= '</div>';
+  }
+  $output .= '</div>';
+  return $output;
+}
+
+/**
+ * Theme output of the dashboard page.
+ *
+ * @param $menu_items
+ *   An array of modules to be displayed.
+ * @ingroup themeable
+ */
+function theme_system_admin_by_module($menu_items) {
+  $stripe = 0;
+  $output = '';
+  $container = array('left' => '', 'right' => '');
+  $flip = array('left' => 'right', 'right' => 'left');
+  $position = 'left';
+
+  // Iterate over all modules
+  foreach ($menu_items as $module => $block) {
+    list($description, $items) = $block;
+
+    // Output links
+    if (count($items)) {
+      $block = array();
+      $block['title'] = $module;
+      $block['content'] = theme('item_list', $items);
+      $block['description'] = t($description);
+
+      if ($block_output = theme('admin_block', $block)) {
+        if (!isset($block['position'])) {
+          // Perform automatic striping.
+          $block['position'] = $position;
+          $position = $flip[$position];
+        }
+        $container[$block['position']] .= $block_output;
+      }
+    }
+  }
+
+  $output = '<div class="admin clear-block">';
+  foreach ($container as $id => $data) {
+    $output .= '<div class="'. $id .' clear-block">';
+    $output .= $data;
+    $output .= '</div>';
+  }
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Theme requirements status report.
+ *
+ * @param $requirements
+ *   An array of requirements.
+ * @ingroup themeable
+ */
+function theme_status_report(&$requirements) {
+  $i = 0;
+  $output = '<table class="system-status-report">';
+  foreach ($requirements as $requirement) {
+    if (empty($requirement['#type'])) {
+      $class = ++$i % 2 == 0 ? 'even' : 'odd';
+
+      $classes = array(
+        REQUIREMENT_INFO => 'info',
+        REQUIREMENT_OK => 'ok',
+        REQUIREMENT_WARNING => 'warning',
+        REQUIREMENT_ERROR => 'error',
+      );
+      $class = $classes[isset($requirement['severity']) ? (int)$requirement['severity'] : 0] .' '. $class;
+
+      // Output table row(s)
+      if (!empty($requirement['description'])) {
+        $output .= '<tr class="'. $class .' merge-down"><th>'. $requirement['title'] .'</th><td>'. $requirement['value'] .'</td></tr>';
+        $output .= '<tr class="'. $class .' merge-up"><td colspan="2">'. $requirement['description'] .'</td></tr>';
+      }
+      else {
+        $output .= '<tr class="'. $class .'"><th>'. $requirement['title'] .'</th><td>'. $requirement['value'] .'</td></tr>';
+      }
+    }
+  }
+
+  $output .= '</table>';
+  return $output;
+}
+
+/**
+ * Theme callback for the modules form.
+ *
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @ingroup themeable
+ */
+function theme_system_modules($form) {
+  if (isset($form['confirm'])) {
+    return drupal_render($form);
+  }
+
+  // Individual table headers.
+  $header = array();
+  $header[] = array('data' => t('Enabled'), 'class' => 'checkbox');
+  if (module_exists('throttle')) {
+    $header[] = array('data' => t('Throttle'), 'class' => 'checkbox');
+  }
+  $header[] = t('Name');
+  $header[] = t('Version');
+  $header[] = t('Description');
+
+  // Pull package information from module list and start grouping modules.
+  $modules = $form['validation_modules']['#value'];
+  foreach ($modules as $module) {
+    if (!isset($module->info['package']) || !$module->info['package']) {
+      $module->info['package'] = t('Other');
+    }
+    $packages[$module->info['package']][$module->name] = $module->info;
+  }
+  ksort($packages);
+
+  // Display packages.
+  $output = '';
+  foreach ($packages as $package => $modules) {
+    $rows = array();
+    foreach ($modules as $key => $module) {
+      $row = array();
+      $description = drupal_render($form['description'][$key]);
+      if (isset($form['status']['#incompatible_modules_core'][$key])) {
+        unset($form['status'][$key]);
+        $status = theme('image', 'misc/watchdog-error.png', t('incompatible'), t('Incompatible with this version of Drupal core'));
+        $description .= '<div class="incompatible">'. t('This version is incompatible with the !core_version version of Drupal core.', array('!core_version' => VERSION)) .'</div>';
+      }
+      elseif (isset($form['status']['#incompatible_modules_php'][$key])) {
+        unset($form['status'][$key]);
+        $status = theme('image', 'misc/watchdog-error.png', t('incompatible'), t('Incompatible with this version of PHP'));
+        $php_required = $form['status']['#incompatible_modules_php'][$key];
+        if (substr_count($php_required, '.') < 2) {
+          $php_required .= '.*';
+        }
+        $description .= '<div class="incompatible">'. t('This module requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $php_required, '!php_version' => phpversion())) .'</div>';
+      }
+      else {
+        $status = drupal_render($form['status'][$key]);
+      }
+      $row[] = array('data' => $status, 'class' => 'checkbox');
+      if (module_exists('throttle')) {
+        $row[] = array('data' => drupal_render($form['throttle'][$key]), 'class' => 'checkbox');
+      }
+
+      // Add labels only when there is also a checkbox.
+      if (isset($form['status'][$key])) {
+        $row[] = '<strong><label for="'. $form['status'][$key]['#id'] .'">'. drupal_render($form['name'][$key]) .'</label></strong>';
+      }
+      else {
+        $row[] = '<strong>'. drupal_render($form['name'][$key]) .'</strong>';
+      }
+
+      $row[] = drupal_render($form['version'][$key]);
+      $row[] = array('data' => $description, 'class' => 'description');
+      $rows[] = $row;
+    }
+    $fieldset = array(
+      '#title' => t($package),
+      '#collapsible' => TRUE,
+      '#collapsed' => ($package == 'Core - required'),
+      '#value' => theme('table', $header, $rows, array('class' => 'package')),
+    );
+    $output .= theme('fieldset', $fieldset);
+  }
+
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Themes a table of currently disabled modules.
+ *
+ * @ingroup themeable
+ * @param $form
+ *   The form array representing the currently disabled modules.
+ * @return
+ *   An HTML string representing the table.
+ */
+function theme_system_modules_uninstall($form) {
+  // No theming for the confirm form.
+  if (isset($form['confirm'])) {
+    return drupal_render($form);
+  }
+
+  // Table headers.
+  $header = array(t('Uninstall'),
+    t('Name'),
+    t('Description'),
+  );
+
+  // Display table.
+  $rows = array();
+  foreach (element_children($form['modules']) as $module) {
+    $rows[] = array(
+      array('data' => drupal_render($form['uninstall'][$module]), 'align' => 'center'),
+      '<strong>'. drupal_render($form['modules'][$module]['name']) .'</strong>',
+      array('data' => drupal_render($form['modules'][$module]['description']), 'class' => 'description'),
+    );
+  }
+
+  // Only display table if there are modules that can be uninstalled.
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No modules are available to uninstall.'), 'colspan' => '3', 'align' => 'center', 'class' => 'message'));
+  }
+
+  $output  = theme('table', $header, $rows);
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Theme the theme select form.
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @ingroup themeable
+ */
+function theme_system_theme_select_form($form) {
+  foreach (element_children($form) as $key) {
+    $row = array();
+    if (isset($form[$key]['description']) && is_array($form[$key]['description'])) {
+      $row[] = drupal_render($form[$key]['screenshot']);
+      $row[] = drupal_render($form[$key]['description']);
+      $row[] = drupal_render($form['theme'][$key]);
+    }
+    $rows[] = $row;
+  }
+
+  $header = array(t('Screenshot'), t('Name'), t('Selected'));
+  $output = theme('table', $header, $rows);
+  return $output;
+}
+
+/**
+ * Theme function for the system themes form.
+ *
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @ingroup themeable
+ */
+function theme_system_themes_form($form) {
+  foreach (element_children($form) as $key) {
+    // Only look for themes
+    if (!isset($form[$key]['info'])) {
+      continue;
+    }
+
+    // Fetch info
+    $info = $form[$key]['info']['#value'];
+    // Localize theme description.
+    $description = t($info['description']);
+    // Make sure it is compatible and render the checkbox if so.
+    if (isset($form['status']['#incompatible_themes_core'][$key])) {
+      unset($form['status'][$key]);
+      $status = theme('image', 'misc/watchdog-error.png', t('incompatible'), t('Incompatible with this version of Drupal core'));
+      $description .= '<div class="incompatible">'. t('This version is incompatible with the !core_version version of Drupal core.', array('!core_version' => VERSION)) .'</div>';
+    }
+    elseif (isset($form['status']['#incompatible_themes_php'][$key])) {
+      unset($form['status'][$key]);
+      $status = theme('image', 'misc/watchdog-error.png', t('incompatible'), t('Incompatible with this version of PHP'));
+      $php_required = $form['status']['#incompatible_themes_php'][$key];
+      if (substr_count($php_required, '.') < 2) {
+        $php_required .= '.*';
+      }
+      $description .= '<div class="incompatible">'. t('This theme requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $php_required, '!php_version' => phpversion())) .'</div>';
+    }
+    else {
+      $status = drupal_render($form['status'][$key]);
+    }
+
+    // Style theme info
+    $theme = '<div class="theme-info"><h2>'. $info['name'] .'</h2><div class="description">'. $description .'</div></div>';
+
+    // Build rows
+    $row = array();
+    $row[] = drupal_render($form[$key]['screenshot']);
+    $row[] = $theme;
+    $row[] = isset($info['version']) ? $info['version'] : '';
+    $row[] = array('data' => $status, 'align' => 'center');
+    if ($form['theme_default']) {
+      $row[] = array('data' => drupal_render($form['theme_default'][$key]), 'align' => 'center');
+      $row[] = array('data' => drupal_render($form[$key]['operations']), 'align' => 'center');
+    }
+    $rows[] = $row;
+  }
+
+  $header = array(t('Screenshot'), t('Name'), t('Version'), t('Enabled'), t('Default'), t('Operations'));
+  $output = theme('table', $header, $rows);
+  $output .= drupal_render($form);
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,544 @@
+/* $Id: system.css,v 1.48 2008/01/09 09:56:39 goba Exp $ */
+
+/*
+** HTML elements
+*/
+body.drag {
+  cursor: move;
+}
+th.active img {
+  display: inline;
+}
+tr.even, tr.odd {
+  background-color: #eee;
+  border-bottom: 1px solid #ccc;
+  padding: 0.1em 0.6em;
+}
+tr.drag {
+  background-color: #fffff0;
+}
+tr.drag-previous {
+  background-color: #ffd;
+}
+td.active {
+  background-color: #ddd;
+}
+td.checkbox, th.checkbox {
+  text-align: center;
+}
+tbody {
+  border-top: 1px solid #ccc;
+}
+tbody th {
+  border-bottom: 1px solid #ccc;
+}
+thead th {
+  text-align: left; /* LTR */
+  padding-right: 1em; /* LTR */
+  border-bottom: 3px solid #ccc;
+}
+
+/*
+** Other common styles
+*/
+.breadcrumb {
+  padding-bottom: .5em
+}
+div.indentation {
+  width: 20px;
+  height: 1.7em;
+  margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */
+  padding: 0.42em 0 0.42em 0.6em; /* LTR */
+  float: left; /* LTR */
+}
+div.tree-child {
+  background: url(../../misc/tree.png) no-repeat 11px center; /* LTR */
+}
+div.tree-child-last {
+  background: url(../../misc/tree-bottom.png) no-repeat 11px center; /* LTR */
+}
+div.tree-child-horizontal {
+  background: url(../../misc/tree.png) no-repeat -11px center;
+}
+.error {
+  color: #e55;
+}
+div.error {
+  border: 1px solid #d77;
+}
+div.error, tr.error {
+  background: #fcc;
+  color: #200;
+  padding: 2px;
+}
+.warning {
+  color: #e09010;
+}
+div.warning {
+  border: 1px solid #f0c020;
+}
+div.warning, tr.warning {
+  background: #ffd;
+  color: #220;
+  padding: 2px;
+}
+.ok {
+  color: #008000;
+}
+div.ok {
+  border: 1px solid #00aa00;
+}
+div.ok, tr.ok {
+  background: #dfd;
+  color: #020;
+  padding: 2px;
+}
+.item-list .icon {
+  color: #555;
+  float: right; /* LTR */
+  padding-left: 0.25em; /* LTR */
+  clear: right; /* LTR */
+}
+.item-list .title {
+  font-weight: bold;
+}
+.item-list ul {
+  margin: 0 0 0.75em 0;
+  padding: 0;
+}
+.item-list ul li {
+  margin: 0 0 0.25em 1.5em; /* LTR */
+  padding: 0;
+  list-style: disc;
+}
+ol.task-list li.active {
+  font-weight: bold;
+}
+.form-item {
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+tr.odd .form-item, tr.even .form-item {
+  margin-top: 0;
+  margin-bottom: 0;
+  white-space: nowrap;
+}
+tr.merge-down, tr.merge-down td, tr.merge-down th {
+  border-bottom-width: 0 !important;
+}
+tr.merge-up, tr.merge-up td, tr.merge-up th {
+  border-top-width: 0 !important;
+}
+.form-item input.error, .form-item textarea.error, .form-item select.error {
+  border: 2px solid red;
+}
+.form-item .description {
+  font-size: 0.85em;
+}
+.form-item label {
+  display: block;
+  font-weight: bold;
+}
+.form-item label.option {
+  display: inline;
+  font-weight: normal;
+}
+.form-checkboxes, .form-radios {
+  margin: 1em 0;
+}
+.form-checkboxes .form-item, .form-radios .form-item {
+  margin-top: 0.4em;
+  margin-bottom: 0.4em;
+}
+.marker, .form-required {
+  color: #f00;
+}
+.more-link {
+  text-align: right; /* LTR */
+}
+.more-help-link {
+  font-size: 0.85em;
+  text-align: right; /* LTR */
+}
+.nowrap {
+  white-space: nowrap;
+}
+.item-list .pager {
+  clear: both;
+  text-align: center;
+}
+.item-list .pager li {
+  background-image:none;
+  display:inline;
+  list-style-type:none;
+  padding: 0.5em;
+}
+.pager-current {
+  font-weight:bold;
+}
+.tips {
+  margin-top: 0;
+  margin-bottom: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  font-size: 0.9em;
+}
+dl.multiselect dd.b, dl.multiselect dd.b .form-item, dl.multiselect dd.b select {
+  font-family: inherit;
+  font-size: inherit;
+  width: 14em;
+}
+dl.multiselect dd.a, dl.multiselect dd.a .form-item {
+  width: 8em;
+}
+dl.multiselect dt, dl.multiselect dd {
+  float: left; /* LTR */
+  line-height: 1.75em;
+  padding: 0;
+  margin: 0 1em 0 0; /* LTR */
+}
+dl.multiselect .form-item {
+  height: 1.75em;
+  margin: 0;
+}
+
+/*
+** Inline items (need to override above)
+*/
+.container-inline div, .container-inline label {
+  display: inline;
+}
+
+/*
+** Tab navigation
+*/
+ul.primary {
+  border-collapse: collapse;
+  padding: 0 0 0 1em; /* LTR */
+  white-space: nowrap;
+  list-style: none;
+  margin: 5px;
+  height: auto;
+  line-height: normal;
+  border-bottom: 1px solid #bbb;
+}
+ul.primary li {
+  display: inline;
+}
+ul.primary li a {
+  background-color: #ddd;
+  border-color: #bbb;
+  border-width: 1px;
+  border-style: solid solid none solid;
+  height: auto;
+  margin-right: 0.5em; /* LTR */
+  padding: 0 1em;
+  text-decoration: none;
+}
+ul.primary li.active a {
+  background-color: #fff;
+  border: 1px solid #bbb;
+  border-bottom: #fff 1px solid;
+}
+ul.primary li a:hover {
+  background-color: #eee;
+  border-color: #ccc;
+  border-bottom-color: #eee;
+}
+ul.secondary {
+  border-bottom: 1px solid #bbb;
+  padding: 0.5em 1em;
+  margin: 5px;
+}
+ul.secondary li {
+  display: inline;
+  padding: 0 1em;
+  border-right: 1px solid #ccc; /* LTR */
+}
+ul.secondary a {
+  padding: 0;
+  text-decoration: none;
+}
+ul.secondary a.active {
+  border-bottom: 4px solid #999;
+}
+
+/*
+** Autocomplete styles
+*/
+/* Suggestion list */
+#autocomplete {
+  position: absolute;
+  border: 1px solid;
+  overflow: hidden;
+  z-index: 100;
+}
+#autocomplete ul {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+#autocomplete li {
+  background: #fff;
+  color: #000;
+  white-space: pre;
+  cursor: default;
+}
+#autocomplete li.selected {
+  background: #0072b9;
+  color: #fff;
+}
+/* Animated throbber */
+html.js input.form-autocomplete {
+  background-image: url(../../misc/throbber.gif);
+  background-repeat: no-repeat;
+  background-position: 100% 2px; /* LTR */
+}
+html.js input.throbbing {
+  background-position: 100% -18px; /* LTR */
+}
+
+/*
+** Collapsing fieldsets
+*/
+html.js fieldset.collapsed {
+  border-bottom-width: 0;
+  border-left-width: 0;
+  border-right-width: 0;
+  margin-bottom: 0;
+  height: 1em;
+}
+html.js fieldset.collapsed * {
+  display: none;
+}
+html.js fieldset.collapsed legend {
+  display: block;
+}
+html.js fieldset.collapsible legend a {
+  padding-left: 15px; /* LTR */
+  background: url(../../misc/menu-expanded.png) 5px 75% no-repeat; /* LTR */
+}
+html.js fieldset.collapsed legend a {
+  background-image: url(../../misc/menu-collapsed.png); /* LTR */
+  background-position: 5px 50%; /* LTR */
+}
+/* Note: IE-only fix due to '* html' (breaks Konqueror otherwise). */
+* html.js fieldset.collapsed legend,
+* html.js fieldset.collapsed legend *,
+* html.js fieldset.collapsed table * {
+  display: inline;
+}
+/* For Safari 2 to prevent collapsible fieldsets containing tables from dissapearing due to tableheader.js. */
+html.js fieldset.collapsible {
+  position: relative;
+}
+html.js fieldset.collapsible legend a {
+  display: block;
+}
+/* Avoid jumping around due to margins collapsing into collapsible fieldset border */
+html.js fieldset.collapsible .fieldset-wrapper {
+  overflow: auto;
+}
+
+/*
+** Resizable text areas
+*/
+.resizable-textarea {
+  width: 95%;
+}
+.resizable-textarea .grippie {
+  height: 9px;
+  overflow: hidden;
+  background: #eee url(../../misc/grippie.png) no-repeat center 2px;
+  border: 1px solid #ddd;
+  border-top-width: 0;
+  cursor: s-resize;
+}
+html.js .resizable-textarea textarea {
+  margin-bottom: 0;
+  width: 100%;
+  display: block;
+}
+
+/*
+** Table drag and drop.
+*/
+.draggable a.tabledrag-handle {
+  cursor: move;
+  float: left; /* LTR */
+  height: 1.7em;
+  margin: -0.4em 0 -0.4em -0.5em; /* LTR */
+  padding: 0.42em 1.5em 0.42em 0.5em; /* LTR */
+  text-decoration: none;
+}
+a.tabledrag-handle:hover {
+  text-decoration: none;
+}
+a.tabledrag-handle .handle {
+  margin-top: 4px;
+  height: 13px;
+  width: 13px;
+  background: url(../../misc/draggable.png) no-repeat 0 0;
+}
+a.tabledrag-handle-hover .handle {
+  background-position: 0 -20px;
+}
+
+/*
+** Teaser splitter
+*/
+.joined + .grippie {
+  height: 5px;
+  background-position: center 1px;
+  margin-bottom: -2px;
+}
+/* Keeps inner content contained in Opera 9. */
+.teaser-checkbox {
+  padding-top: 1px;
+}
+div.teaser-button-wrapper {
+  float: right; /* LTR */
+  padding-right: 5%; /* LTR */
+  margin: 0;
+}
+.teaser-checkbox div.form-item {
+  float: right; /* LTR */
+  margin: 0 5% 0 0; /* LTR */
+  padding: 0;
+}
+textarea.teaser {
+  display: none;
+}
+html.js .no-js {
+  display: none;
+}
+
+/*
+** Progressbar styles
+*/
+.progress {
+  font-weight: bold;
+}
+.progress .bar {
+  background: #fff url(../../misc/progress.gif);
+  border: 1px solid #00375a;
+  height: 1.5em;
+  margin: 0 0.2em;
+}
+.progress .filled {
+  background: #0072b9;
+  height: 1em;
+  border-bottom: 0.5em solid #004a73;
+  width: 0%;
+}
+.progress .percentage {
+  float: right; /* LTR */
+}
+.progress-disabled {
+  float: left; /* LTR */
+}
+.ahah-progress {
+  float: left; /* LTR */
+}
+.ahah-progress .throbber {
+  width: 15px;
+  height: 15px;
+  margin: 2px;
+  background: transparent url(../../misc/throbber.gif) no-repeat 0px -18px;
+  float: left; /* LTR */
+}
+tr .ahah-progress .throbber {
+  margin: 0 2px;
+}
+.ahah-progress-bar {
+  width: 16em;
+}
+
+/*
+** Formatting for welcome page
+*/
+#first-time strong {
+  display: block;
+  padding: 1.5em 0 .5em;
+}
+
+/*
+** To be used with tableselect.js
+*/
+tr.selected td {
+  background: #ffc;
+}
+
+/*
+** Floating header for tableheader.js
+*/
+table.sticky-header {
+  margin-top: 0;
+  background: #fff;
+}
+
+/*
+** Installation clean URLs
+*/
+#clean-url.install {
+  display: none;
+}
+
+/*
+** For anything you want to hide on page load when JS is enabled, so
+** that you can use the JS to control visibility and avoid flicker.
+*/
+html.js .js-hide {
+  display: none;
+}
+
+/*
+** Styles for the system modules page (admin/build/modules)
+*/
+#system-modules div.incompatible {
+  font-weight: bold;
+}
+
+/*
+** Styles for the system themes page (admin/build/themes)
+*/
+#system-themes-form div.incompatible {
+  font-weight: bold;
+}
+
+/*
+** Password strength indicator
+*/
+span.password-strength {
+  visibility: hidden;
+}
+input.password-field {
+  margin-right: 10px; /* LTR */
+}
+div.password-description {
+  padding: 0 2px;
+  margin: 4px 0 0 0;
+  font-size: 0.85em;
+  max-width: 500px;
+}
+div.password-description ul {
+  margin-bottom: 0;
+}
+.password-parent {
+  margin: 0 0 0 0;
+}
+/*
+** Password confirmation checker
+*/
+input.password-confirm {
+  margin-right: 10px; /* LTR */
+}
+.confirm-parent {
+  margin: 5px 0 0 0;
+}
+span.password-confirm {
+  visibility: hidden;
+}
+span.password-confirm span {
+  font-weight: normal;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: system.info,v 1.4 2007/06/08 05:50:56 dries Exp $
+name = System
+description = Handles general site configuration for administrators.
+package = Core - required
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2517 @@
+<?php
+// $Id: system.install,v 1.238.2.1 2008/02/08 17:07:55 goba Exp $
+
+/**
+ * Test and report Drupal installation requirements.
+ *
+ * @param $phase
+ *   The current system installation phase.
+ * @return
+ *   An array of system requirements.
+ */
+function system_requirements($phase) {
+  $requirements = array();
+  // Ensure translations don't break at install time
+  $t = get_t();
+
+  // Report Drupal version
+  if ($phase == 'runtime') {
+    $requirements['drupal'] = array(
+      'title' => $t('Drupal'),
+      'value' => VERSION,
+      'severity' => REQUIREMENT_INFO,
+      'weight' => -10,
+    );
+  }
+
+  // Web server information.
+  $software = $_SERVER['SERVER_SOFTWARE'];
+  $requirements['webserver'] = array(
+    'title' => $t('Web server'),
+    'value' => $software,
+  );
+
+  // Test PHP version
+  $requirements['php'] = array(
+    'title' => $t('PHP'),
+    'value' => ($phase == 'runtime') ? l(phpversion(), 'admin/reports/status/php') : phpversion(),
+  );
+  if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) {
+    $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP));
+    $requirements['php']['severity'] = REQUIREMENT_ERROR;
+  }
+
+  // Test PHP register_globals setting.
+  $requirements['php_register_globals'] = array(
+    'title' => $t('PHP register globals'),
+  );
+  $register_globals = trim(ini_get('register_globals'));
+  // Unfortunately, ini_get() may return many different values, and we can't
+  // be certain which values mean 'on', so we instead check for 'not off'
+  // since we never want to tell the user that their site is secure
+  // (register_globals off), when it is in fact on. We can only guarantee
+  // register_globals is off if the value returned is 'off', '', or 0.
+  if (!empty($register_globals) && strtolower($register_globals) != 'off') {
+    $requirements['php_register_globals']['description'] = $t('<em>register_globals</em> is enabled. Drupal requires this configuration directive to be disabled. Your site may not be secure when <em>register_globals</em> is enabled. The PHP manual has instructions for <a href="http://php.net/configuration.changes">how to change configuration settings</a>.');
+    $requirements['php_register_globals']['severity'] = REQUIREMENT_ERROR;
+    $requirements['php_register_globals']['value'] = $t("Enabled ('@value')", array('@value' => $register_globals));
+  }
+  else {
+    $requirements['php_register_globals']['value'] = $t('Disabled');
+  }
+
+  // Test PHP memory_limit
+  $memory_limit = ini_get('memory_limit');
+  $requirements['php_memory_limit'] = array(
+    'title' => $t('PHP memory limit'),
+    'value' => $memory_limit,
+  );
+
+  if ($memory_limit && parse_size($memory_limit) < parse_size(DRUPAL_MINIMUM_PHP_MEMORY_LIMIT)) {
+    $description = '';
+    if ($phase == 'install') {
+      $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the installation process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+    }
+    elseif ($phase == 'update') {
+      $description = $t('Consider increasing your PHP memory limit to %memory_minimum_limit to help prevent errors in the update process.', array('%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+    }
+    elseif ($phase == 'runtime') {
+      $description = $t('Depending on your configuration, Drupal can run with a %memory_limit PHP memory limit. However, a %memory_minimum_limit PHP memory limit or above is recommended, especially if your site uses additional custom or contributed modules.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => DRUPAL_MINIMUM_PHP_MEMORY_LIMIT));
+    }
+
+    if (!empty($description)) {
+      if ($php_ini_path = get_cfg_var('cfg_file_path')) {
+        $description .= ' '. $t('Increase the memory limit by editing the memory_limit parameter in the file %configuration-file and then restart your web server (or contact your system administrator or hosting provider for assistance).', array('%configuration-file' => $php_ini_path));
+      }
+      else {
+        $description .= ' '. $t('Contact your system administrator or hosting provider for assistance with increasing your PHP memory limit.');
+      }
+
+      $requirements['php_memory_limit']['description'] = $description .' '. $t('See the <a href="@url">Drupal requirements</a> for more information.', array('@url' => 'http://drupal.org/requirements'));
+      $requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
+    }
+  }
+
+  // Test DB version
+  global $db_type;
+  if (function_exists('db_status_report')) {
+    $requirements += db_status_report($phase);
+  }
+
+  // Test settings.php file writability
+  if ($phase == 'runtime') {
+    $conf_dir = drupal_verify_install_file(conf_path(), FILE_NOT_WRITABLE, 'dir');
+    $conf_file = drupal_verify_install_file(conf_path() .'/settings.php', FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE);
+    if (!$conf_dir || !$conf_file) {
+      $requirements['settings.php'] = array(
+        'value' => $t('Not protected'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => '',
+      );
+      if (!$conf_dir) {
+        $requirements['settings.php']['description'] .= $t('The directory %file is not protected from modifications and poses a security risk. You must change the directory\'s permissions to be non-writable. ', array('%file' => conf_path()));
+      }
+      if (!$conf_file) {
+        $requirements['settings.php']['description'] .= $t('The file %file is not protected from modifications and poses a security risk. You must change the file\'s permissions to be non-writable.', array('%file' => conf_path() .'/settings.php'));
+      }
+    }
+    else {
+      $requirements['settings.php'] = array(
+        'value' => $t('Protected'),
+      );
+    }
+    $requirements['settings.php']['title'] = $t('Configuration file');
+  }
+
+  // Report cron status.
+  if ($phase == 'runtime') {
+    // Cron warning threshold defaults to two days.
+    $threshold_warning = variable_get('cron_threshold_warning', 172800);
+    // Cron error threshold defaults to two weeks.
+    $threshold_error = variable_get('cron_threshold_error', 1209600);
+    // Cron configuration help text.
+    $help = $t('For more information, see the online handbook entry for <a href="@cron-handbook">configuring cron jobs</a>.', array('@cron-handbook' => 'http://drupal.org/cron'));
+
+    // Determine when cron last ran. If never, use the install time to
+    // determine the warning or error status.
+    $cron_last = variable_get('cron_last', NULL);
+    $never_run = FALSE;
+    if (!is_numeric($cron_last)) {
+      $never_run = TRUE;
+      $cron_last = variable_get('install_time', 0);
+    }
+
+    // Determine severity based on time since cron last ran.
+    $severity = REQUIREMENT_OK;
+    if (time() - $cron_last > $threshold_error) {
+      $severity = REQUIREMENT_ERROR;
+    }
+    else if ($never_run || (time() - $cron_last > $threshold_warning)) {
+      $severity = REQUIREMENT_WARNING;
+    }
+
+    // If cron hasn't been run, and the user is viewing the main
+    // administration page, instead of an error, we display a helpful reminder
+    // to configure cron jobs.
+    if ($never_run && $severity != REQUIREMENT_ERROR && $_GET['q'] == 'admin' && user_access('administer site configuration')) {
+      drupal_set_message($t('Cron has not run. Please visit the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))));
+    }
+
+    // Set summary and description based on values determined above.
+    if ($never_run) {
+      $summary = $t('Never run');
+      $description = $t('Cron has not run.') .' '. $help;
+    }
+    else {
+      $summary = $t('Last run !time ago', array('!time' => format_interval(time() - $cron_last)));
+      $description = '';
+      if ($severity != REQUIREMENT_OK) {
+        $description = $t('Cron has not run recently.') .' '. $help;
+      }
+    }
+
+    $requirements['cron'] = array(
+      'title' => $t('Cron maintenance tasks'),
+      'severity' => $severity,
+      'value' => $summary,
+      'description' => $description .' '. $t('You can <a href="@cron">run cron manually</a>.', array('@cron' => url('admin/reports/status/run-cron'))),
+    );
+  }
+
+  // Test files directory
+  $directory = file_directory_path();
+  $requirements['file system'] = array(
+    'title' => $t('File system'),
+  );
+
+  // For installer, create the directory if possible.
+  if ($phase == 'install' && !is_dir($directory) && @mkdir($directory)) {
+    @chmod($directory, 0775); // Necessary for non-webserver users.
+  }
+
+  $is_writable = is_writable($directory);
+  $is_directory = is_dir($directory);
+  if (!$is_writable || !$is_directory) {
+    $description = '';
+    $requirements['file system']['value'] = $t('Not writable');
+    if (!$is_directory) {
+      $error = $t('The directory %directory does not exist.', array('%directory' => $directory));
+    }
+    else {
+      $error = $t('The directory %directory is not writable.', array('%directory' => $directory));
+    }
+    // The files directory requirement check is done only during install and runtime.
+    if ($phase == 'runtime') {
+      $description = $error .' '. $t('You may need to set the correct directory at the <a href="@admin-file-system">file system settings page</a> or change the current directory\'s permissions so that it is writable.', array('@admin-file-system' => url('admin/settings/file-system')));
+    }
+    elseif ($phase == 'install') {
+      // For the installer UI, we need different wording. 'value' will
+      // be treated as version, so provide none there.
+      $description = $error .' '. $t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually, or ensure that the installer has the permissions to create it automatically. For more information, please see INSTALL.txt or the <a href="@handbook_url">on-line handbook</a>.', array('@handbook_url' => 'http://drupal.org/server-permissions'));
+      $requirements['file system']['value'] = '';
+    }
+    if (!empty($description)) {
+      $requirements['file system']['description'] = $description;
+      $requirements['file system']['severity'] = REQUIREMENT_ERROR;
+    }
+  }
+  else {
+    if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
+      $requirements['file system']['value'] = $t('Writable (<em>public</em> download method)');
+    }
+    else {
+      $requirements['file system']['value'] = $t('Writable (<em>private</em> download method)');
+    }
+  }
+
+  // See if updates are available in update.php.
+  if ($phase == 'runtime') {
+    $requirements['update'] = array(
+      'title' => $t('Database updates'),
+      'severity' => REQUIREMENT_OK,
+      'value' => $t('Up to date'),
+    );
+
+    // Check installed modules.
+    foreach (module_list() as $module) {
+      $updates = drupal_get_schema_versions($module);
+      if ($updates !== FALSE) {
+        $default = drupal_get_installed_schema_version($module);
+        if (max($updates) > $default) {
+          $requirements['update']['severity'] = REQUIREMENT_ERROR;
+          $requirements['update']['value'] = $t('Out of date');
+          $requirements['update']['description'] = $t('Some modules have database schema updates to install. You should run the <a href="@update">database update script</a> immediately.', array('@update' => base_path() .'update.php'));
+          break;
+        }
+      }
+    }
+  }
+
+  // Verify the update.php access setting
+  if ($phase == 'runtime') {
+    if (!empty($GLOBALS['update_free_access'])) {
+      $requirements['update access'] = array(
+        'value' => $t('Not protected'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => $t('The update.php script is accessible to everyone without authentication check, which is a security risk. You must change the $update_free_access value in your settings.php back to FALSE.'),
+      );
+    }
+    else {
+      $requirements['update access'] = array(
+        'value' => $t('Protected'),
+      );
+    }
+    $requirements['update access']['title'] = $t('Access to update.php');
+  }
+
+  // Test Unicode library
+  include_once './includes/unicode.inc';
+  $requirements = array_merge($requirements, unicode_requirements());
+
+  // Check for update status module.
+  if ($phase == 'runtime') {
+    if (!module_exists('update')) {
+      $requirements['update status'] = array(
+        'value' => $t('Not enabled'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => $t('Update notifications are not enabled. It is <strong>highly recommended</strong> that you enable the update status module from the <a href="@module">module administration page</a> in order to stay up-to-date on new releases. For more information please read the <a href="@update">Update status handbook page</a>.', array('@update' => 'http://drupal.org/handbook/modules/update', '@module' => url('admin/build/modules'))),
+      );
+    }
+    else {
+      $requirements['update status'] = array(
+        'value' => $t('Enabled'),
+      );
+      if (variable_get('drupal_http_request_fails', FALSE)) {
+        $requirements['http requests'] = array(
+          'title' => $t('HTTP request status'),
+          'value' => $t('Fails'),
+          'severity' => REQUIREMENT_ERROR,
+          'description' => $t('Your system or network configuration does not allow Drupal to access web pages, resulting in reduced functionality. This could be due to your webserver configuration or PHP settings, and should be resolved in order to download information about available updates, fetch aggregator feeds, sign in via OpenID, or use other network-dependent services.'),
+        );
+      }
+    }
+    $requirements['update status']['title'] = $t('Update notifications');
+  }
+
+  return $requirements;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function system_install() {
+  if ($GLOBALS['db_type'] == 'pgsql') {
+    // Create unsigned types.
+    db_query("CREATE DOMAIN int_unsigned integer CHECK (VALUE >= 0)");
+    db_query("CREATE DOMAIN smallint_unsigned smallint CHECK (VALUE >= 0)");
+    db_query("CREATE DOMAIN bigint_unsigned bigint CHECK (VALUE >= 0)");
+
+    // Create functions.
+    db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
+      \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
+      LANGUAGE \'sql\''
+    );
+    db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
+      \'SELECT greatest($1, greatest($2, $3));\'
+      LANGUAGE \'sql\''
+    );
+    if (!db_result(db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'"))) {
+      db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
+        \'SELECT random();\'
+        LANGUAGE \'sql\''
+      );
+    }
+
+    if (!db_result(db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'concat'"))) {
+      db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
+        \'SELECT $1 || $2;\'
+        LANGUAGE \'sql\''
+      );
+    }
+    db_query('CREATE OR REPLACE FUNCTION "if"(boolean, text, text) RETURNS text AS
+      \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\'
+      LANGUAGE \'sql\''
+    );
+    db_query('CREATE OR REPLACE FUNCTION "if"(boolean, integer, integer) RETURNS integer AS
+      \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\'
+      LANGUAGE \'sql\''
+    );
+  }
+
+  // Create tables.
+  $modules = array('system', 'filter', 'block', 'user', 'node', 'comment', 'taxonomy');
+  foreach ($modules as $module) {
+    drupal_install_schema($module);
+  }
+
+  // Load system theme data appropriately.
+  system_theme_data();
+
+  // Inserting uid 0 here confuses MySQL -- the next user might be created as
+  // uid 2 which is not what we want. So we insert the first user here, the
+  // anonymous user. uid is 1 here for now, but very soon it will be changed
+  // to 0.
+  db_query("INSERT INTO {users} (name, mail) VALUES('%s', '%s')", '', '');
+  // We need some placeholders here as name and mail are uniques and data is
+  // presumed to be a serialized array. Install will change uid 1 immediately
+  // anyways. So we insert the superuser here, the uid is 2 here for now, but
+  // very soon it will be changed to 1.
+  db_query("INSERT INTO {users} (name, mail, created, data) VALUES('%s', '%s', %d, '%s')", 'placeholder-for-uid-1', 'placeholder-for-uid-1', time(), serialize(array()));
+  // This sets the above two users uid 0 (anonymous). We avoid an explicit 0
+  // otherwise MySQL might insert the next auto_increment value.
+  db_query("UPDATE {users} SET uid = uid - uid WHERE name = '%s'", '');
+  // This sets uid 1 (superuser). We skip uid 2 but that's not a big problem.
+  db_query("UPDATE {users} SET uid = 1 WHERE name = '%s'", 'placeholder-for-uid-1');
+
+  db_query("INSERT INTO {role} (name) VALUES ('%s')", 'anonymous user');
+  db_query("INSERT INTO {role} (name) VALUES ('%s')", 'authenticated user');
+
+  db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (%d, '%s', %d)", 1, 'access content', 0);
+  db_query("INSERT INTO {permission} (rid, perm, tid) VALUES (%d, '%s', %d)", 2, 'access comments, access content, post comments, post comments without approval', 0);
+
+  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'theme_default', 's:7:"garland";');
+  db_query("UPDATE {system} SET status = %d WHERE type = '%s' AND name = '%s'", 1, 'theme', 'garland');
+  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'user', '0', 'garland', 1, 0, 'left', '', -1);
+  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'user', '1', 'garland', 1, 0, 'left', '', -1);
+  db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, pages, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s', %d)", 'system', '0', 'garland', 1, 10, 'footer', '', -1);
+
+  db_query("INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, '%s', %d, %d, %d)", 0, 0, 'all', 1, 0, 0);
+
+  // Add input formats.
+  db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Filtered HTML', ',1,2,', 1);
+  db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Full HTML', '', 1);
+
+  // Enable filters for each input format.
+
+  // Filtered HTML:
+  // URL filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 2, 0);
+  // HTML filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 0, 1);
+  // Line break filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 1, 2);
+  // HTML corrector filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 1, 'filter', 3, 10);
+
+  // Full HTML:
+  // URL filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 2, 0);
+  // Line break filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 1, 1);
+  // HTML corrector filter.
+  db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 3, 10);
+
+  db_query("INSERT INTO {variable} (name, value) VALUES ('%s','%s')", 'filter_html_1', 'i:1;');
+
+  db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'node_options_forum', 'a:1:{i:0;s:6:"status";}');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function system_schema() {
+  // NOTE: {variable} needs to be created before all other tables, as
+  // some database drivers, e.g. Oracle and DB2, will require variable_get()
+  // and variable_set() for overcoming some database specific limitations.
+  $schema['variable'] = array(
+    'description' => t('Named variable/value pairs created by Drupal core or any other module or theme. All variables are cached in memory at the start of every Drupal request so developers should not be careless about what is stored here.'),
+    'fields' => array(
+      'name' => array(
+        'description' => t('The name of the variable.'),
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''),
+      'value' => array(
+        'description' => t('The value of the variable.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big'),
+      ),
+    'primary key' => array('name'),
+    );
+
+  $schema['actions'] = array(
+    'description' => t('Stores action information.'),
+    'fields' => array(
+      'aid' => array(
+        'description' => t('Primary Key: Unique actions ID.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '0'),
+      'type' => array(
+        'description' => t('The object that that action acts on (node, user, comment, system or custom types.)'),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => ''),
+      'callback' => array(
+        'description' => t('The callback function that executes when the action runs.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'parameters' => array(
+        'description' => t('Parameters to be passed to the callback function.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big'),
+      'description' => array(
+        'description' => t('Description of the action.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '0'),
+      ),
+    'primary key' => array('aid'),
+    );
+
+  $schema['actions_aid'] = array(
+    'description' => t('Stores action IDs for non-default actions.'),
+    'fields' => array(
+      'aid' => array(
+        'description' => t('Primary Key: Unique actions ID.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      ),
+    'primary key' => array('aid'),
+    );
+
+  $schema['batch'] = array(
+    'description' => t('Stores details about batches (processes that run in multiple HTTP requests).'),
+    'fields' => array(
+      'bid' => array(
+        'description' => t('Primary Key: Unique batch ID.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'token' => array(
+        'description' => t("A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it."),
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE),
+      'timestamp' => array(
+        'description' => t('A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.'),
+        'type' => 'int',
+        'not null' => TRUE),
+      'batch' => array(
+        'description' => t('A serialized array containing the processing data for the batch.'),
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big')
+      ),
+    'primary key' => array('bid'),
+    'indexes' => array('token' => array('token')),
+    );
+
+  $schema['cache'] = array(
+    'description' => t('Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.'),
+    'fields' => array(
+      'cid' => array(
+        'description' => t('Primary Key: Unique cache ID.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'data' => array(
+        'description' => t('A collection of data to cache.'),
+        'type' => 'blob',
+        'not null' => FALSE,
+        'size' => 'big'),
+      'expire' => array(
+        'description' => t('A Unix timestamp indicating when the cache entry should expire, or 0 for never.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'created' => array(
+        'description' => t('A Unix timestamp indicating when the cache entry was created.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'headers' => array(
+        'description' => t('Any custom HTTP headers to be added to cached data.'),
+        'type' => 'text',
+        'not null' => FALSE),
+      'serialized' => array(
+        'description' => t('A flag to indicate whether content is serialized (1) or not (0).'),
+        'type' => 'int',
+        'size' => 'small',
+        'not null' => TRUE,
+        'default' => 0)
+      ),
+    'indexes' => array('expire' => array('expire')),
+    'primary key' => array('cid'),
+    );
+
+  $schema['cache_form'] = $schema['cache'];
+  $schema['cache_form']['description'] = t('Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.');
+  $schema['cache_page'] = $schema['cache'];
+  $schema['cache_page']['description'] = t('Cache table used to store compressed pages for anonymous users, if page caching is enabled.');
+  $schema['cache_menu'] = $schema['cache'];
+  $schema['cache_menu']['description'] = t('Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.');
+
+  $schema['files'] = array(
+    'description' => t('Stores information for uploaded files.'),
+    'fields' => array(
+      'fid' => array(
+        'description' => t('Primary Key: Unique files ID.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'uid' => array(
+        'description' => t('The {users}.uid of the user who is associated with the file.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'filename' => array(
+        'description' => t('Name of the file.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'filepath' => array(
+        'description' => t('Path of the file relative to Drupal root.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'filemime' => array(
+        'description' => t('The file MIME type.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'filesize' => array(
+        'description' => t('The size of the file in bytes.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'status' => array(
+        'description' => t('A flag indicating whether file is temporary (1) or permanent (0).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'timestamp' => array(
+        'description' => t('UNIX timestamp for when the file was added.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      ),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'status' => array('status'),
+      'timestamp' => array('timestamp'),
+      ),
+    'primary key' => array('fid'),
+    );
+
+  $schema['flood'] = array(
+    'description' => t('Flood controls the threshold of events, such as the number of contact attempts.'),
+    'fields' => array(
+      'fid' => array(
+        'description' => t('Unique flood event ID.'),
+        'type' => 'serial',
+        'not null' => TRUE),
+      'event' => array(
+        'description' => t('Name of event (e.g. contact).'),
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => ''),
+      'hostname' => array(
+        'description' => t('Hostname of the visitor.'),
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''),
+      'timestamp' => array(
+        'description' => t('Timestamp of the event.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0)
+      ),
+    'primary key' => array('fid'),
+    'indexes' => array(
+      'allow' => array('event', 'hostname', 'timestamp'),
+    ),
+    );
+
+  $schema['history'] = array(
+    'description' => t('A record of which {users} have read which {node}s.'),
+    'fields' => array(
+      'uid' => array(
+        'description' => t('The {users}.uid that read the {node} nid.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'nid' => array(
+        'description' => t('The {node}.nid that was read.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'timestamp' => array(
+        'description' => t('The Unix timestamp at which the read occurred.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0)
+      ),
+    'primary key' => array('uid', 'nid'),
+    'indexes' => array(
+      'nid' => array('nid'),
+    ),
+    );
+  $schema['menu_router'] = array(
+    'description' => t('Maps paths to various callbacks (access, page and title)'),
+    'fields' => array(
+      'path' => array(
+        'description' => t('Primary Key: the Drupal path this entry describes'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'load_functions' => array(
+        'description' => t('A serialized array of function names (like node_load) to be called to load an object corresponding to a part of the current path.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'to_arg_functions' => array(
+        'description' => t('A serialized array of function names (like user_current_to_arg) to be called to replace a part of the router path with another string.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'access_callback' => array(
+        'description' => t('The callback which determines the access to this router path. Defaults to user_access.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'access_arguments' => array(
+        'description' => t('A serialized array of arguments for the access callback.'),
+        'type' => 'text',
+        'not null' => FALSE),
+      'page_callback' => array(
+        'description' => t('The name of the function that renders the page.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'page_arguments' => array(
+        'description' => t('A serialized array of arguments for the page callback.'),
+        'type' => 'text',
+        'not null' => FALSE),
+      'fit' => array(
+        'description' => t('A numeric representation of how specific the path is.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'number_parts' => array(
+        'description' => t('Number of parts in this router path.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'tab_parent' => array(
+        'description' => t('Only for local tasks (tabs) - the router path of the parent page (which may also be a local task).'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'tab_root' => array(
+        'description' => t('Router path of the closest non-tab parent page. For pages that are not local tasks, this will be the same as the path.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'title' => array(
+        'description' => t('The title for the current page, or the title for the tab if this is a local task.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'title_callback' => array(
+        'description' => t('A function which will alter the title. Defaults to t()'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'title_arguments' => array(
+        'description' => t('A serialized array of arguments for the title callback. If empty, the title will be used as the sole argument for the title callback.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'type' => array(
+        'description' => t('Numeric representation of the type of the menu item, like MENU_LOCAL_TASK.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'block_callback' => array(
+        'description' => t('Name of a function used to render the block on the system administration page for this item.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'description' => array(
+        'description' => t('A description of this item.'),
+        'type' => 'text',
+        'not null' => TRUE),
+      'position' => array(
+        'description' => t('The position of the block (left or right) on the system administration page for this item.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'weight' => array(
+        'description' => t('Weight of the element. Lighter weights are higher up, heavier weights go down.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'file' => array(
+        'description' => t('The file to include for this element, usually the page callback function lives in this file.'),
+        'type' => 'text',
+        'size' => 'medium')
+      ),
+    'indexes' => array(
+      'fit' => array('fit'),
+      'tab_parent' => array('tab_parent')
+      ),
+    'primary key' => array('path'),
+    );
+
+  $schema['menu_links'] = array(
+    'description' => t('Contains the individual links within a menu.'),
+    'fields' => array(
+     'menu_name' => array(
+        'description' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => ''),
+      'mlid' => array(
+        'description' => t('The menu link ID (mlid) is the integer primary key.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'plid' => array(
+        'description' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'link_path' => array(
+        'description' => t('The Drupal path or external path this link points to.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'router_path' => array(
+        'description' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'link_title' => array(
+      'description' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'options' => array(
+        'description' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
+        'type' => 'text',
+        'not null' => FALSE),
+      'module' => array(
+        'description' => t('The name of the module that generated this link.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => 'system'),
+      'hidden' => array(
+        'description' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'external' => array(
+        'description' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'has_children' => array(
+        'description' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'expanded' => array(
+        'description' => t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'weight' => array(
+        'description' => t('Link weight among links in the same menu at the same depth.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'depth' => array(
+        'description' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'customized' => array(
+        'description' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      'p1' => array(
+        'description' => t('The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p2' => array(
+        'description' => t('The second mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p3' => array(
+        'description' => t('The third mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p4' => array(
+        'description' => t('The fourth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p5' => array(
+        'description' => t('The fifth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p6' => array(
+        'description' => t('The sixth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p7' => array(
+        'description' => t('The seventh mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p8' => array(
+        'description' => t('The eighth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'p9' => array(
+        'description' => t('The ninth mlid in the materialized path. See p1.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'updated' => array(
+        'description' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small'),
+      ),
+    'indexes' => array(
+      'path_menu' => array(array('link_path', 128), 'menu_name'),
+      'menu_plid_expand_child' => array(
+        'menu_name', 'plid', 'expanded', 'has_children'),
+      'menu_parents' => array(
+        'menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+      'router_path' => array(array('router_path', 128)),
+      ),
+    'primary key' => array('mlid'),
+    );
+
+  $schema['sessions'] = array(
+    'description' => t("Drupal's session handlers read and write into the sessions table. Each record represents a user session, either anonymous or authenticated."),
+    'fields' => array(
+      'uid' => array(
+        'description' => t('The {users}.uid corresponding to a session, or 0 for anonymous user.'),
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'sid' => array(
+        'description' => t("Primary key: A session ID. The value is generated by PHP's Session API."),
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => ''),
+      'hostname' => array(
+        'description' => t('The IP address that last used this session ID (sid).'),
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''),
+      'timestamp' => array(
+        'description' => t('The Unix timestamp when this session last requested a page. Old records are purged by PHP automatically.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'cache' => array(
+        'description' => t("The time of this user's last post. This is used when the site has specified a minimum_cache_lifetime. See cache_get()."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'session' => array(
+        'description' => t('The serialized contents of $_SESSION, an array of name/value pairs that persists across page requests by this session ID. Drupal loads $_SESSION from here at the start of each request and saves it at the end.'),
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big')
+      ),
+    'primary key' => array('sid'),
+    'indexes' => array(
+      'timestamp' => array('timestamp'),
+      'uid' => array('uid')
+      ),
+    );
+
+  $schema['system'] = array(
+    'description' => t("A list of all modules, themes, and theme engines that are or have been installed in Drupal's file system."),
+    'fields' => array(
+      'filename' => array(
+        'description' => t('The path of the primary file for this item, relative to the Drupal root; e.g. modules/node/node.module.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'name' => array(
+        'description' => t('The name of the item; e.g. node.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'type' => array(
+        'description' => t('The type of the item, either module, theme, or theme_engine.'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'owner' => array(
+        'description' => t("A theme's 'parent'. Can be either a theme or an engine."),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'status' => array(
+        'description' => t('Boolean indicating whether or not this item is enabled.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'throttle' => array(
+        'description' => t('Boolean indicating whether this item is disabled when the throttle.module disables throttleable items.'),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny'),
+      'bootstrap' => array(
+        'description' => t("Boolean indicating whether this module is loaded during Drupal's early bootstrapping phase (e.g. even before the page cache is consulted)."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'schema_version' => array(
+        'description' => t("The module's database schema version number. -1 if the module is not installed (its tables do not exist); 0 or the largest N of the module's hook_update_N() function that has either been run or existed when the module was first installed."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => -1,
+        'size' => 'small'),
+      'weight' => array(
+        'description' => t("The order in which this module's hooks should be invoked relative to other modules. Equal-weighted modules are ordered by name."),
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0),
+      'info' => array(
+        'description' => t("A serialized array containing information from the module's .info file; keys can include name, description, package, version, core, dependencies, dependents, and php."),
+        'type' => 'text',
+        'not null' => FALSE)
+      ),
+    'primary key' => array('filename'),
+    'indexes' =>
+      array(
+        'modules' => array(array('type', 12), 'status', 'weight', 'filename'),
+        'bootstrap' => array(array('type', 12), 'status', 'bootstrap', 'weight', 'filename'),
+      ),
+    );
+
+  $schema['url_alias'] = array(
+    'description' => t('A list of URL aliases for Drupal paths; a user may visit either the source or destination path.'),
+    'fields' => array(
+      'pid' => array(
+        'description' => t('A unique path alias identifier.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'src' => array(
+        'description' => t('The Drupal path this alias is for; e.g. node/12.'),
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''),
+      'dst' => array(
+        'description' => t('The alias for this path; e.g. title-of-the-story.'),
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''),
+      'language' => array(
+        'description' => t('The language this alias is for; if blank, the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.'),
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '')
+      ),
+    'unique keys' => array('dst_language' => array('dst', 'language')),
+    'primary key' => array('pid'),
+    'indexes' => array('src' => array('src')),
+    );
+
+  return $schema;
+}
+
+// Updates for core.
+
+function system_update_last_removed() {
+  return 1021;
+}
+
+/**
+ * @defgroup updates-5.x-extra Extra system updates for 5.x
+ * @{
+ */
+
+/**
+ * Add index on users created column.
+ */
+function system_update_1022() {
+  $ret = array();
+  db_add_index($ret, 'users', 'created', array('created'));
+  // Also appears as system_update_6004(). Ensure we don't update twice.
+  variable_set('system_update_1022', TRUE);
+  return $ret;
+}
+
+/**
+ * @} End of "defgroup updates-5.x-extra"
+ */
+
+/**
+ * @defgroup updates-5.x-to-6.x System updates from 5.x to 6.x
+ * @{
+ */
+
+/**
+ * Remove auto_increment from {boxes} to allow adding custom blocks with
+ * visibility settings.
+ */
+function system_update_6000() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $max = (int)db_result(db_query('SELECT MAX(bid) FROM {boxes}'));
+      $ret[] = update_sql('ALTER TABLE {boxes} CHANGE COLUMN bid bid int NOT NULL');
+      $ret[] = update_sql("REPLACE INTO {sequences} VALUES ('{boxes}_bid', $max)");
+      break;
+  }
+  return $ret;
+}
+
+/**
+ * Add version id column to {term_node} to allow taxonomy module to use revisions.
+ */
+function system_update_6001() {
+  $ret = array();
+
+  // Add vid to term-node relation.  The schema says it is unsigned.
+  db_add_field($ret, 'term_node', 'vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_drop_primary_key($ret, 'term_node');
+  db_add_primary_key($ret, 'term_node', array('vid', 'tid', 'nid'));
+  db_add_index($ret, 'term_node', 'vid', array('vid'));
+
+  db_query('UPDATE {term_node} t SET vid = (SELECT vid FROM {node} n WHERE t.nid = n.nid)');
+  return $ret;
+}
+
+/**
+ * Increase the maximum length of variable names from 48 to 128.
+ */
+function system_update_6002() {
+  $ret = array();
+  db_drop_primary_key($ret, 'variable');
+  db_change_field($ret, 'variable', 'name', 'name', array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
+  db_add_primary_key($ret, 'variable', array('name'));
+  return $ret;
+}
+
+/**
+ * Add index on comments status column.
+ */
+function system_update_6003() {
+  $ret = array();
+  db_add_index($ret, 'comments', 'status', array('status'));
+  return $ret;
+}
+
+/**
+ * This update used to add an index on users created column (#127941).
+ * However, system_update_1022() does the same thing.  This update
+ * tried to detect if 1022 had already run but failed to do so,
+ * resulting in an "index already exists" error.
+ *
+ * Adding the index here is never necessary.  Sites installed before
+ * 1022 will run 1022, getting the update.  Sites installed on/after 1022
+ * got the index when the table was first created.  Therefore, this
+ * function is now a no-op.
+ */
+function system_update_6004() {
+  return array();
+}
+
+/**
+ * Add language to url_alias table and modify indexes.
+ */
+function system_update_6005() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      db_add_column($ret, 'url_alias', 'language', 'varchar(12)', array('default' => "''", 'not null' => TRUE));
+
+      // As of system.install:1.85 (before the new language
+      // subsystem), new installs got a unique key named
+      // url_alias_dst_key on url_alias.dst.  Unfortunately,
+      // system_update_162 created a unique key inconsistently named
+      // url_alias_dst_idx on url_alias.dst (keys should have the _key
+      // suffix, indexes the _idx suffix).  Therefore, sites installed
+      // before system_update_162 have a unique key with a different
+      // name than sites installed after system_update_162().  Now, we
+      // want to drop the unique key on dst which may have either one
+      // of two names and create a new unique key on (dst, language).
+      // There is no way to know which key name exists so we have to
+      // drop both, causing an SQL error.  Thus, we just hide the
+      // error and only report the update_sql results that work.
+      $err = error_reporting(0);
+      $ret1 = update_sql('DROP INDEX {url_alias}_dst_idx');
+      if ($ret1['success']) {
+  $ret[] = $ret1;
+      }
+      $ret1 = array();
+      db_drop_unique_key($ret, 'url_alias', 'dst');
+      foreach ($ret1 as $r) {
+  if ($r['success']) {
+    $ret[] = $r;
+  }
+      }
+      error_reporting($err);
+
+      $ret[] = update_sql('CREATE UNIQUE INDEX {url_alias}_dst_language_idx ON {url_alias}(dst, language)');
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {url_alias} ADD language varchar(12) NOT NULL default ''");
+      $ret[] = update_sql("ALTER TABLE {url_alias} DROP INDEX dst");
+      $ret[] = update_sql("ALTER TABLE {url_alias} ADD UNIQUE dst_language (dst, language)");
+      break;
+  }
+  return $ret;
+}
+
+/**
+ * Drop useless indices on node_counter table.
+ */
+function system_update_6006() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      $ret[] = update_sql('DROP INDEX {node_counter}_daycount_idx');
+      $ret[] = update_sql('DROP INDEX {node_counter}_totalcount_idx');
+      $ret[] = update_sql('DROP INDEX {node_counter}_timestamp_idx');
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {node_counter} DROP INDEX daycount");
+      $ret[] = update_sql("ALTER TABLE {node_counter} DROP INDEX totalcount");
+      $ret[] = update_sql("ALTER TABLE {node_counter} DROP INDEX timestamp");
+      break;
+  }
+  return $ret;
+}
+
+/**
+ * Change the severity column in the watchdog table to the new values.
+ */
+function system_update_6007() {
+  $ret = array();
+  $ret[] = update_sql("UPDATE {watchdog} SET severity = ". WATCHDOG_NOTICE ." WHERE severity = 0");
+  $ret[] = update_sql("UPDATE {watchdog} SET severity = ". WATCHDOG_WARNING ." WHERE severity = 1");
+  $ret[] = update_sql("UPDATE {watchdog} SET severity = ". WATCHDOG_ERROR ." WHERE severity = 2");
+  return $ret;
+}
+
+/**
+ * Add info files to themes.  The info and owner columns are added by
+ * update_fix_d6_requirements() in update.php to avoid a large number
+ * of error messages from update.php.  All we need to do here is copy
+ * description to owner and then drop description.
+ */
+function system_update_6008() {
+  $ret = array();
+  $ret[] = update_sql('UPDATE {system} SET owner = description');
+  db_drop_field($ret, 'system', 'description');
+
+  // Rebuild system table contents.
+  module_rebuild_cache();
+  system_theme_data();
+
+  return $ret;
+}
+
+/**
+ * The PHP filter is now a separate module.
+ */
+function system_update_6009() {
+  $ret = array();
+
+  // If any input format used the Drupal 5 PHP filter.
+  if (db_result(db_query("SELECT COUNT(format) FROM {filters} WHERE module = 'filter' AND delta = 1"))) {
+    // Enable the PHP filter module.
+    $ret[] = update_sql("UPDATE {system} SET status = 1 WHERE name = 'php' AND type = 'module'");
+    // Update the input filters.
+    $ret[] = update_sql("UPDATE {filters} SET delta = 0, module = 'php' WHERE module = 'filter' AND delta = 1");
+  }
+
+  // With the removal of the PHP evaluator filter, the deltas of the line break
+  // and URL filter have changed.
+  $ret[] = update_sql("UPDATE {filters} SET delta = 1 WHERE module = 'filter' AND delta = 2");
+  $ret[] = update_sql("UPDATE {filters} SET delta = 2 WHERE module = 'filter' AND delta = 3");
+
+  return $ret;
+}
+
+/**
+ * Add variable replacement for watchdog messages.
+ *
+ * The variables field is NOT NULL and does not have a default value.
+ * Existing log messages should not be translated in the new system,
+ * so we insert 'N;' (serialize(NULL)) as the temporary default but
+ * then remove the default value to match the schema.
+ */
+function system_update_6010() {
+  $ret = array();
+  db_add_field($ret, 'watchdog', 'variables', array('type' => 'text', 'size' => 'big', 'not null' => TRUE, 'initial' => 'N;'));
+  return $ret;
+}
+
+/**
+ * Add language support to nodes
+ */
+function system_update_6011() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      db_add_column($ret, 'node', 'language', 'varchar(12)', array('default' => "''", 'not null' => TRUE));
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {node} ADD language varchar(12) NOT NULL default ''");
+      break;
+  }
+  return $ret;
+}
+
+/**
+ * Add serialized field to cache tables.  This is now handled directly
+ * by update.php, so this function is a no-op.
+ */
+function system_update_6012() {
+  return array();
+}
+
+/**
+ * Rebuild cache data for theme system changes
+ */
+function system_update_6013() {
+  // Rebuild system table contents.
+  module_rebuild_cache();
+  system_theme_data();
+
+  return array(array('success' => TRUE, 'query' => 'Cache rebuilt.'));
+}
+
+/**
+ * Record that the installer is done, so it is not
+ * possible to run the installer on upgraded sites.
+ */
+function system_update_6014() {
+  variable_set('install_task', 'done');
+
+  return array(array('success' => TRUE, 'query' => "variable_set('install_task')"));
+}
+
+/**
+ * Add the form cache table.
+ */
+function system_update_6015() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      $ret[] = update_sql("CREATE TABLE {cache_form} (
+        cid varchar(255) NOT NULL default '',
+        data bytea,
+        expire int NOT NULL default '0',
+        created int NOT NULL default '0',
+        headers text,
+        serialized smallint NOT NULL default '0',
+        PRIMARY KEY (cid)
+    )");
+      $ret[] = update_sql("CREATE INDEX {cache_form}_expire_idx ON {cache_form} (expire)");
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("CREATE TABLE {cache_form} (
+        cid varchar(255) NOT NULL default '',
+        data longblob,
+        expire int NOT NULL default '0',
+        created int NOT NULL default '0',
+        headers text,
+        serialized int(1) NOT NULL default '0',
+        PRIMARY KEY (cid),
+        INDEX expire (expire)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+      break;
+  }
+
+  return $ret;
+}
+
+/**
+ * Make {node}'s primary key be nid, change nid,vid to a unique key.
+ * Add primary keys to block, filters, flood, permission, and term_relation.
+ */
+function system_update_6016() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      $ret[] = update_sql("ALTER TABLE {node} ADD CONSTRAINT {node}_nid_vid_key UNIQUE (nid, vid)");
+      db_add_column($ret, 'blocks', 'bid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {blocks} ADD PRIMARY KEY (bid)");
+      db_add_column($ret, 'filters', 'fid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {filters} ADD PRIMARY KEY (fid)");
+      db_add_column($ret, 'flood', 'fid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {flood} ADD PRIMARY KEY (fid)");
+      db_add_column($ret, 'permission', 'pid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {permission} ADD PRIMARY KEY (pid)");
+      db_add_column($ret, 'term_relation', 'trid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {term_relation} ADD PRIMARY KEY (trid)");
+      db_add_column($ret, 'term_synonym', 'tsid', 'serial');
+      $ret[] = update_sql("ALTER TABLE {term_synonym} ADD PRIMARY KEY (tsid)");
+      break;
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql('ALTER TABLE {node} ADD UNIQUE KEY nid_vid (nid, vid)');
+      $ret[] = update_sql("ALTER TABLE {blocks} ADD bid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      $ret[] = update_sql("ALTER TABLE {filters} ADD fid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      $ret[] = update_sql("ALTER TABLE {flood} ADD fid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      $ret[] = update_sql("ALTER TABLE {permission} ADD pid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      $ret[] = update_sql("ALTER TABLE {term_relation} ADD trid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      $ret[] = update_sql("ALTER TABLE {term_synonym} ADD tsid int NOT NULL AUTO_INCREMENT PRIMARY KEY");
+      break;
+  }
+
+  return $ret;
+}
+
+/**
+ * Rename settings related to user.module email notifications.
+ */
+function system_update_6017() {
+  $ret = array();
+  // Maps old names to new ones.
+  $var_names = array(
+    'admin'    => 'register_admin_created',
+    'approval' => 'register_pending_approval',
+    'welcome'  => 'register_no_approval_required',
+    'pass'     => 'password_reset',
+  );
+  foreach ($var_names as $old => $new) {
+    foreach (array('_subject', '_body') as $suffix) {
+      $old_name = 'user_mail_'. $old . $suffix;
+      $new_name = 'user_mail_'. $new . $suffix;
+      if ($old_val = variable_get($old_name, FALSE)) {
+        variable_set($new_name, $old_val);
+        variable_del($old_name);
+        $ret[] = array('success' => TRUE, 'query' => "variable_set($new_name)");
+        $ret[] = array('success' => TRUE, 'query' => "variable_del($old_name)");
+        if ($old_name == 'user_mail_approval_body') {
+          drupal_set_message('Saving an old value of the welcome message body for users that are pending administrator approval. However, you should consider modifying this text, since Drupal can now be configured to automatically notify users and send them their login information when their accounts are approved. See the <a href="'. url('admin/user/settings') .'">User settings</a> page for details.');
+        }
+      }
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Add HTML corrector to HTML formats or replace the old module if it was in use.
+ */
+function system_update_6018() {
+  $ret = array();
+
+  // Disable htmlcorrector.module, if it exists and replace its filter.
+  if (module_exists('htmlcorrector')) {
+    module_disable(array('htmlcorrector'));
+    $ret[] = update_sql("UPDATE {filter_formats} SET module = 'filter', delta = 3 WHERE module = 'htmlcorrector'");
+    $ret[] = array('success' => TRUE, 'query' => 'HTML Corrector module was disabled; this functionality has now been added to core.');
+    return $ret;
+  }
+
+  // Otherwise, find any format with 'HTML' in its name and add the filter at the end.
+  $result = db_query("SELECT format, name FROM {filter_formats} WHERE name LIKE '%HTML%'");
+  while ($format = db_fetch_object($result)) {
+    $weight = db_result(db_query("SELECT MAX(weight) FROM {filters} WHERE format = %d", $format->format));
+    db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", $format->format, 'filter', 3, max(10, $weight + 1));
+    $ret[] = array('success' => TRUE, 'query' => "HTML corrector filter added to the '". $format->name ."' input format.");
+  }
+
+  return $ret;
+}
+
+/**
+ * Reconcile small differences in the previous, manually created mysql
+ * and pgsql schemas so they are the same and can be represented by a
+ * single schema structure.
+ *
+ * Note that the mysql and pgsql cases make different changes.  This
+ * is because each schema needs to be tweaked in different ways to
+ * conform to the new schema structure.  Also, since they operate on
+ * tables defined by many optional core modules which may not ever
+ * have been installed, they must test each table for existence.  If
+ * the modules are first installed after this update exists the tables
+ * will be created from the schema structure and will start out
+ * correct.
+ */
+function system_update_6019() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'pgsql':
+      // Remove default ''.
+      if (db_table_exists('aggregator_feed')) {
+        db_field_set_no_default($ret, 'aggregator_feed', 'description');
+        db_field_set_no_default($ret, 'aggregator_feed', 'image');
+      }
+      db_field_set_no_default($ret, 'blocks', 'pages');
+      if (db_table_exists('contact')) {
+        db_field_set_no_default($ret, 'contact', 'recipients');
+        db_field_set_no_default($ret, 'contact', 'reply');
+      }
+      db_field_set_no_default($ret, 'watchdog', 'location');
+      db_field_set_no_default($ret, 'node_revisions', 'body');
+      db_field_set_no_default($ret, 'node_revisions', 'teaser');
+      db_field_set_no_default($ret, 'node_revisions', 'log');
+
+      // Update from pgsql 'float' (which means 'double precision') to
+      // schema 'float' (which in pgsql means 'real').
+      if (db_table_exists('search_index')) {
+        db_change_field($ret, 'search_index', 'score', 'score', array('type' => 'float'));
+      }
+      if (db_table_exists('search_total')) {
+        db_change_field($ret, 'search_total', 'count', 'count', array('type' => 'float'));
+      }
+
+      // Replace unique index dst_language with a unique constraint.  The
+      // result is the same but the unique key fits our current schema
+      // structure.  Also, the postgres documentation implies that
+      // unique constraints are preferable to unique indexes.  See
+      // http://www.postgresql.org/docs/8.2/interactive/indexes-unique.html.
+      if (db_table_exists('url_alias')) {
+        db_drop_index($ret, 'url_alias', 'dst_language');
+        db_add_unique_key($ret, 'url_alias', 'dst_language',
+          array('dst', 'language'));
+      }
+
+      // Fix term_node pkey: mysql and pgsql code had different orders.
+      if (db_table_exists('term_node')) {
+        db_drop_primary_key($ret, 'term_node');
+        db_add_primary_key($ret, 'term_node', array('vid', 'tid', 'nid'));
+      }
+
+      // Make boxes.bid unsigned.
+      db_drop_primary_key($ret, 'boxes');
+      db_change_field($ret, 'boxes', 'bid', 'bid', array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array('bid')));
+
+      // Fix primary key
+      db_drop_primary_key($ret, 'node');
+      db_add_primary_key($ret, 'node', array('nid'));
+
+      break;
+
+    case 'mysql':
+    case 'mysqli':
+      // Rename key 'link' to 'url'.
+      if (db_table_exists('aggregator_feed')) {
+        db_drop_unique_key($ret, 'aggregator_feed', 'link');
+        db_add_unique_key($ret, 'aggregator_feed', 'url', array('url'));
+      }
+
+      // Change to size => small.
+      if (db_table_exists('boxes')) {
+        db_change_field($ret, 'boxes', 'format', 'format', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+      }
+
+      // Change to size => small.
+      // Rename index 'lid' to 'nid'.
+      if (db_table_exists('comments')) {
+        db_change_field($ret, 'comments', 'format', 'format', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+        db_drop_index($ret, 'comments', 'lid');
+        db_add_index($ret, 'comments', 'nid', array('nid'));
+      }
+
+      // Change to size => small.
+      db_change_field($ret, 'cache', 'serialized', 'serialized', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+      db_change_field($ret, 'cache_filter', 'serialized', 'serialized', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+      db_change_field($ret, 'cache_page', 'serialized', 'serialized', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+      db_change_field($ret, 'cache_form', 'serialized', 'serialized', array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0));
+
+      // Remove default => 0, set auto increment.
+      $new_uid = 1 + db_result(db_query('SELECT MAX(uid) FROM {users}'));
+      $ret[] = update_sql('UPDATE {users} SET uid = '. $new_uid .' WHERE uid = 0');
+      db_drop_primary_key($ret, 'users');
+      db_change_field($ret, 'users', 'uid', 'uid', array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array('uid')));
+      $ret[] = update_sql('UPDATE {users} SET uid = 0 WHERE uid = '. $new_uid);
+
+      // Special field names.
+      $map = array('node_revisions' => 'vid');
+      // Make sure these tables have proper auto_increment fields.
+      foreach (array('boxes', 'files', 'node', 'node_revisions') as $table) {
+        $field = isset($map[$table]) ? $map[$table] : $table[0] .'id';
+        db_drop_primary_key($ret, $table);
+        db_change_field($ret, $table, $field, $field, array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array($field)));
+      }
+
+      break;
+  }
+
+  return $ret;
+}
+
+/**
+ * Create the tables for the new menu system.
+ */
+function system_update_6020() {
+  $ret = array();
+
+  $schema['menu_router'] = array(
+    'fields' => array(
+      'path'             => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'load_functions'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'to_arg_functions' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'access_callback'  => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'access_arguments' => array('type' => 'text', 'not null' => FALSE),
+      'page_callback'    => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'page_arguments'   => array('type' => 'text', 'not null' => FALSE),
+      'fit'              => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'number_parts'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'tab_parent'       => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'tab_root'         => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'title'            => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'title_callback'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'title_arguments'  => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'type'             => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'block_callback'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'description'      => array('type' => 'text', 'not null' => TRUE),
+      'position'         => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'weight'           => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'file'             => array('type' => 'text', 'size' => 'medium')
+    ),
+    'indexes' => array(
+      'fit'        => array('fit'),
+      'tab_parent' => array('tab_parent')
+    ),
+    'primary key' => array('path'),
+  );
+
+  $schema['menu_links'] = array(
+    'fields' => array(
+      'menu_name'    => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+      'mlid'         => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+      'plid'         => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'link_path'    => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'router_path'  => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'link_title'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'options'      => array('type' => 'text', 'not null' => FALSE),
+      'module'       => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'system'),
+      'hidden'       => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'external'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'has_children' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'expanded'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'weight'       => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+      'depth'        => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'customized'   => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+      'p1'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p2'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p3'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p4'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p5'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p6'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p7'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p8'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'p9'           => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      'updated'      => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+    ),
+    'indexes' => array(
+      'path_menu'              => array(array('link_path', 128), 'menu_name'),
+      'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
+      'menu_parents'           => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+      'router_path'            => array(array('router_path', 128)),
+    ),
+    'primary key' => array('mlid'),
+  );
+
+  foreach ($schema as $name => $table) {
+    db_create_table($ret, $name, $table);
+  }
+  return $ret;
+}
+
+/**
+ * Migrate the menu items from the old menu system to the new menu_links table.
+ */
+function system_update_6021() {
+  $ret = array('#finished' => 0);
+  $menus = array(
+    'navigation' => array(
+      'menu_name' => 'navigation',
+      'title' => 'Navigation',
+      'description' => 'The navigation menu is provided by Drupal and is the main interactive menu for any site. It is usually the only menu that contains personalized links for authenticated users, and is often not even visible to anonymous users.',
+    ),
+    'primary-links' => array(
+      'menu_name' => 'primary-links',
+      'title' => 'Primary links',
+      'description' => 'Primary links are often used at the theme layer to show the major sections of a site. A typical representation for primary links would be tabs along the top.',
+    ),
+    'secondary-links' => array(
+      'menu_name' => 'secondary-links',
+      'title' => 'Secondary links',
+      'description' => 'Secondary links are often used for pages like legal notices, contact details, and other secondary navigation items that play a lesser role than primary links',
+    ),
+  );
+  // Multi-part update
+  if (!isset($_SESSION['system_update_6021'])) {
+    db_add_field($ret, 'menu', 'converted', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
+    $_SESSION['system_update_6021_max'] = db_result(db_query('SELECT COUNT(*) FROM {menu}'));
+    $_SESSION['menu_menu_map'] = array(1 => 'navigation');
+    // 0 => FALSE is for new menus, 1 => FALSE is for the navigation.
+    $_SESSION['menu_item_map'] = array(0 => FALSE, 1 => FALSE);
+    $table = array(
+      'fields' => array(
+        'menu_name'   => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+        'title'       => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+        'description' => array('type' => 'text', 'not null' => FALSE),
+      ),
+      'primary key' => array('menu_name'),
+    );
+    db_create_table($ret, 'menu_custom', $table);
+    db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['navigation']);
+    $_SESSION['system_update_6021'] = 0;
+  }
+
+  $limit = 50;
+  while ($limit-- && ($item = db_fetch_array(db_query_range('SELECT * FROM {menu} WHERE converted = 0', 0, 1)))) {
+    // If it's not a menu...
+    if ($item['pid']) {
+      // Let's climb up until we find an item with a converted parent.
+      $item_original = $item;
+      while ($item && !isset($_SESSION['menu_item_map'][$item['pid']])) {
+        $item = db_fetch_array(db_query('SELECT * FROM {menu} WHERE mid = %d', $item['pid']));
+      }
+      // This can only occur if the menu entry is a leftover in the menu table.
+      // These do not appear in Drupal 5 anyways, so we skip them.
+      if (!$item) {
+        db_query('UPDATE {menu} SET converted = %d WHERE mid = %d', 1, $item_original['mid']);
+        $_SESSION['system_update_6021']++;
+        continue;
+      }
+    }
+    // We need to recheck because item might have changed.
+    if ($item['pid']) {
+      // Fill the new fields.
+      $item['link_title'] = $item['title'];
+      $item['link_path'] = drupal_get_normal_path($item['path']);
+      // We know the parent is already set. If it's not FALSE then it's an item.
+      if ($_SESSION['menu_item_map'][$item['pid']]) {
+        // The new menu system parent link id.
+        $item['plid'] = $_SESSION['menu_item_map'][$item['pid']]['mlid'];
+        // The new menu system menu name.
+        $item['menu_name'] = $_SESSION['menu_item_map'][$item['pid']]['menu_name'];
+      }
+      else {
+        // This a top level element.
+        $item['plid'] = 0;
+        // The menu name is stored among the menus.
+        $item['menu_name'] = $_SESSION['menu_menu_map'][$item['pid']];
+      }
+      // Is the element visible in the menu block?
+      $item['hidden'] = !($item['type'] & MENU_VISIBLE_IN_TREE);
+      // Is it a custom(ized) element?
+      if ($item['type'] & (MENU_CREATED_BY_ADMIN | MENU_MODIFIED_BY_ADMIN)) {
+        $item['customized'] = TRUE;
+      }
+      // Items created via the menu module need to be assigned to it.
+      if ($item['type'] & MENU_CREATED_BY_ADMIN) {
+        $item['module'] = 'menu';
+        $item['router_path'] = '';
+        $item['updated'] = TRUE;
+      }
+      else {
+        $item['module'] = 'system';
+        $item['router_path'] = $item['path'];
+        $item['updated'] = FALSE;
+      }
+      // Save the link.
+      menu_link_save($item);
+      $_SESSION['menu_item_map'][$item['mid']] = array('mlid' => $item['mlid'], 'menu_name' => $item['menu_name']);
+    }
+    elseif (!isset($_SESSION['menu_menu_map'][$item['mid']])) {
+      $item['menu_name'] = 'menu-'. preg_replace('/[^a-zA-Z0-9]/', '-', strtolower($item['title']));
+      $item['menu_name'] = substr($item['menu_name'], 0, 20);
+      $original_menu_name = $item['menu_name'];
+      $i = 0;
+      while (db_result(db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = '%s'", $item['menu_name']))) {
+        $item['menu_name'] = $original_menu_name . ($i++);
+      }
+      if ($item['path']) {
+        // Another bunch of bogus entries. Apparently, these are leftovers
+        // from Drupal 4.7 .
+        $_SESSION['menu_bogus_menus'][] = $item['menu_name'];
+      }
+      else {
+        // Add this menu to the list of custom menus.
+        db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '')", $item['menu_name'], $item['title']);
+      }
+      $_SESSION['menu_menu_map'][$item['mid']] = $item['menu_name'];
+      $_SESSION['menu_item_map'][$item['mid']] = FALSE;
+    }
+    db_query('UPDATE {menu} SET converted = %d WHERE mid = %d', 1, $item['mid']);
+    $_SESSION['system_update_6021']++;
+  }
+
+  if ($_SESSION['system_update_6021'] >= $_SESSION['system_update_6021_max']) {
+    if (!empty($_SESSION['menu_bogus_menus'])) {
+      // Remove entries in bogus menus. This is secure because we deleted
+      // every non-alpanumeric character from the menu name.
+      $ret[] = update_sql("DELETE FROM {menu_links} WHERE menu_name IN ('". implode("', '", $_SESSION['menu_bogus_menus']) ."')");
+    }
+
+    $menu_primary_menu = variable_get('menu_primary_menu', 0);
+    // Ensure that we wind up with a system menu named 'primary-links'.
+    if (isset($_SESSION['menu_menu_map'][2])) {
+      // The primary links menu that ships with Drupal 5 has mid = 2.  If this
+      // menu hasn't been deleted by the site admin, we use that.
+      $updated_primary_links_menu = 2;
+    }
+    elseif (isset($_SESSION['menu_menu_map'][$menu_primary_menu]) && $menu_primary_menu > 1) {
+      // Otherwise, we use the menu that is currently assigned to the primary
+      // links region of the theme, as long as it exists and isn't the
+      // Navigation menu.
+      $updated_primary_links_menu = $menu_primary_menu;
+    }
+    else {
+      // As a last resort, create 'primary-links' as a new menu.
+      $updated_primary_links_menu = 0;
+      db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['primary-links']);
+    }
+
+    if ($updated_primary_links_menu) {
+      // Change the existing menu name to 'primary-links'.
+      $replace = array('%new_name' => 'primary-links', '%desc' => $menus['primary-links']['description'], '%old_name' => $_SESSION['menu_menu_map'][$updated_primary_links_menu]);
+      $ret[] = update_sql(strtr("UPDATE {menu_custom} SET menu_name = '%new_name', description = '%desc' WHERE menu_name = '%old_name'", $replace));
+      $ret[] = update_sql("UPDATE {menu_links} SET menu_name = 'primary-links' WHERE menu_name = '". $_SESSION['menu_menu_map'][$updated_primary_links_menu] ."'");
+      $_SESSION['menu_menu_map'][$updated_primary_links_menu] = 'primary-links';
+    }
+
+    $menu_secondary_menu = variable_get('menu_secondary_menu', 0);
+    // Ensure that we wind up with a system menu named 'secondary-links'.
+    if (isset($_SESSION['menu_menu_map'][$menu_secondary_menu]) && $menu_secondary_menu > 1 && $menu_secondary_menu != $updated_primary_links_menu) {
+      // We use the menu that is currently assigned to the secondary links
+      // region of the theme, as long as (a) it exists, (b) it isn't the
+      // Navigation menu, (c) it isn't the same menu we assigned as the
+      // system 'primary-links' menu above, and (d) it isn't the same menu
+      // assigned to the primary links region of the theme.
+      $updated_secondary_links_menu = $menu_secondary_menu;
+    }
+    else {
+      // Otherwise, create 'secondary-links' as a new menu.
+      $updated_secondary_links_menu = 0;
+      db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menus['secondary-links']);
+    }
+
+    if ($updated_secondary_links_menu) {
+      // Change the existing menu name to 'secondary-links'.
+      $replace = array('%new_name' => 'secondary-links', '%desc' => $menus['secondary-links']['description'], '%old_name' => $_SESSION['menu_menu_map'][$updated_secondary_links_menu]);
+      $ret[] = update_sql(strtr("UPDATE {menu_custom} SET menu_name = '%new_name', description = '%desc' WHERE menu_name = '%old_name'", $replace));
+      $ret[] = update_sql("UPDATE {menu_links} SET menu_name = 'secondary-links' WHERE menu_name = '". $_SESSION['menu_menu_map'][$updated_secondary_links_menu] ."'");
+      $_SESSION['menu_menu_map'][$updated_secondary_links_menu] = 'secondary-links';
+    }
+
+    // Update menu OTF preferences.
+    $mid = variable_get('menu_parent_items', 0);
+    $menu_name = ($mid && isset($_SESSION['menu_menu_map'][$mid])) ? $_SESSION['menu_menu_map'][$mid] : 'navigation';
+    variable_set('menu_default_node_menu', $menu_name);
+    variable_del('menu_parent_items');
+
+    // Update the source of the primary and secondary links.
+    $menu_name = ($menu_primary_menu && isset($_SESSION['menu_menu_map'][$menu_primary_menu])) ? $_SESSION['menu_menu_map'][$menu_primary_menu] : '';
+    variable_set('menu_primary_links_source', $menu_name);
+    variable_del('menu_primary_menu');
+
+    $menu_name = ($menu_secondary_menu && isset($_SESSION['menu_menu_map'][$menu_secondary_menu])) ? $_SESSION['menu_menu_map'][$menu_secondary_menu] : '';
+    variable_set('menu_secondary_links_source', $menu_name);
+    variable_del('menu_secondary_menu');
+
+    // Skip the navigation menu - it is handled by the user module.
+    unset($_SESSION['menu_menu_map'][1]);
+    // Update the deltas for all menu module blocks.
+    foreach ($_SESSION['menu_menu_map'] as $mid => $menu_name) {
+      // This is again secure because we deleted every non-alpanumeric
+      // character from the menu name.
+      $ret[] = update_sql("UPDATE {blocks} SET delta = '". $menu_name ."' WHERE module = 'menu' AND delta = '". $mid ."'");
+      $ret[] = update_sql("UPDATE {blocks_roles} SET delta = '". $menu_name ."' WHERE module = 'menu' AND delta = '". $mid ."'");
+    }
+    $ret[] = array('success' => TRUE, 'query' => 'Relocated '. $_SESSION['system_update_6021'] .' existing items to the new menu system.');
+    $ret[] = update_sql("DROP TABLE {menu}");
+    unset($_SESSION['system_update_6021'], $_SESSION['system_update_6021_max'], $_SESSION['menu_menu_map'], $_SESSION['menu_item_map'], $_SESSION['menu_bogus_menus']);
+    // Create the menu overview links - also calls menu_rebuild(). If menu is
+    // disabled, then just call menu_rebuild.
+    if (function_exists('menu_enable')) {
+      menu_enable();
+    }
+    else {
+      menu_rebuild();
+    }
+    $ret['#finished'] = 1;
+  }
+  else {
+    $ret['#finished'] = $_SESSION['system_update_6021'] / $_SESSION['system_update_6021_max'];
+  }
+  return $ret;
+}
+
+/**
+ * Update files tables to associate files to a uid by default instead of a nid.
+ * Rename file_revisions to upload since it should only be used by the upload
+ * module used by upload to link files to nodes.
+ */
+function system_update_6022() {
+  $ret = array();
+
+  // Rename the nid field to vid, add status and timestamp fields, and indexes.
+  db_drop_index($ret, 'files', 'nid');
+  db_change_field($ret, 'files', 'nid', 'uid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_field($ret, 'files', 'status', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
+  db_add_field($ret, 'files', 'timestamp', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_index($ret, 'files', 'uid', array('uid'));
+  db_add_index($ret, 'files', 'status', array('status'));
+  db_add_index($ret, 'files', 'timestamp', array('timestamp'));
+
+  // Rename the file_revisions table to upload then add nid column. Since we're
+  // changing the table name we need to drop and re-add the indexes and
+  // the primary key so both mysql and pgsql end up with the correct index
+  // names.
+  db_drop_primary_key($ret, 'file_revisions');
+  db_drop_index($ret, 'file_revisions', 'vid');
+  db_rename_table($ret, 'file_revisions', 'upload');
+  db_add_field($ret, 'upload', 'nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_index($ret, 'upload', 'nid', array('nid'));
+  db_add_primary_key($ret, 'upload', array('vid', 'fid'));
+  db_add_index($ret, 'upload', 'fid', array('fid'));
+
+  // The nid column was renamed to uid. Use the old nid to find the node's uid.
+  update_sql('UPDATE {files} SET uid = (SELECT n.uid FROM {node} n WHERE {files}.uid = n.nid)');
+  update_sql('UPDATE {upload} SET nid = (SELECT r.nid FROM {node_revisions} r WHERE {upload}.vid = r.vid)');
+
+  // Mark all existing files as FILE_STATUS_PERMANENT.
+  $ret[] = update_sql('UPDATE {files} SET status = 1');
+
+  return $ret;
+}
+
+function system_update_6023() {
+  $ret = array();
+
+  // nid is DEFAULT 0
+  db_drop_index($ret, 'node_revisions', 'nid');
+  db_change_field($ret, 'node_revisions', 'nid', 'nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_index($ret, 'node_revisions', 'nid', array('nid'));
+  return $ret;
+}
+
+/**
+ * Add translation fields to nodes used by translation module.
+ */
+function system_update_6024() {
+  $ret = array();
+  db_add_field($ret, 'node', 'tnid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_field($ret, 'node', 'translate', array('type' => 'int', 'not null' => TRUE, 'default' => 0));
+  db_add_index($ret, 'node', 'tnid', array('tnid'));
+  db_add_index($ret, 'node', 'translate', array('translate'));
+  return $ret;
+}
+
+/**
+ * Increase the maximum length of node titles from 128 to 255.
+ */
+function system_update_6025() {
+  $ret = array();
+  db_drop_index($ret, 'node', 'node_title_type');
+  db_change_field($ret, 'node', 'title', 'title', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+  db_add_index($ret, 'node', 'node_title_type', array('title', array('type', 4)));
+  db_change_field($ret, 'node_revisions', 'title', 'title', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+  return $ret;
+}
+
+/**
+ * Display warning about new Update status module.
+ */
+function system_update_6026() {
+  $ret = array();
+
+  // Notify user that new update module exists.
+  drupal_set_message('Drupal can check periodically for important bug fixes and security releases using the new update status module. This module can be turned on from the <a href="'. url('admin/build/modules') .'">modules administration page</a>. For more information please read the <a href="http://drupal.org/handbook/modules/update">Update status handbook page</a>.');
+
+  return $ret;
+}
+
+/**
+ * Add block cache.
+ */
+function system_update_6027() {
+  $ret = array();
+
+  // Create the blocks.cache column.
+  db_add_field($ret, 'blocks', 'cache', array('type' => 'int', 'not null' => TRUE, 'default' => 1, 'size' => 'tiny'));
+
+  // The cache_block table is created in update_fix_d6_requirements() since
+  // calls to cache_clear_all() would otherwise cause warnings.
+
+  // Fill in the values for the new 'cache' column in the {blocks} table.
+  foreach (module_list() as $module) {
+    if ($module_blocks = module_invoke($module, 'block', 'list')) {
+      foreach ($module_blocks as $delta => $block) {
+        if (isset($block['cache'])) {
+          db_query("UPDATE {blocks} SET cache = %d WHERE module = '%s' AND delta = %d", $block['cache'], $module, $delta);
+        }
+      }
+    }
+  }
+
+  return $ret;
+}
+
+/**
+ * Add the node load cache table.
+ */
+function system_update_6028() {
+  // Removed node_load cache to discuss it more for Drupal 7.
+  return array();
+}
+
+/**
+ * Enable the dblog module on sites that upgrade, since otherwise
+ * watchdog logging will stop unexpectedly.
+ */
+function system_update_6029() {
+  // The watchdog table is now owned by dblog, which is not yet
+  // "installed" according to the system table, but the table already
+  // exists.  We set the module as "installed" here to avoid an error
+  // later.
+  //
+  // Although not the case for the initial D6 release, it is likely
+  // that dblog.install will have its own update functions eventually.
+  // However, dblog did not exist in D5 and this update is part of the
+  // initial D6 release, so we know that dblog is not installed yet.
+  // It is therefore correct to install it as version 0.  If
+  // dblog updates exist, the next run of update.php will get them.
+  drupal_set_installed_schema_version('dblog', 0);
+  module_enable(array('dblog'));
+  menu_rebuild();
+  return array(array('success' => TRUE, 'query' => "'dblog' module enabled."));
+}
+
+/**
+ * Add the tables required by actions.inc.
+ */
+function system_update_6030() {
+  $ret = array();
+
+  // Rename the old contrib actions table if it exists so the contrib version
+  // of the module can do something with the old data.
+  if (db_table_exists('actions')) {
+    db_rename_table($ret, 'actions', 'actions_old_contrib');
+  }
+
+  $schema['actions'] = array(
+    'fields' => array(
+      'aid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0'),
+      'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+      'callback' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+      'parameters' => array('type' => 'text', 'not null' => TRUE, 'size' => 'big'),
+      'description' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0'),
+    ),
+    'primary key' => array('aid'),
+  );
+
+  $schema['actions_aid'] = array(
+    'fields' => array(
+      'aid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+    ),
+    'primary key' => array('aid'),
+  );
+
+  db_create_table($ret, 'actions', $schema['actions']);
+  db_create_table($ret, 'actions_aid', $schema['actions_aid']);
+
+  return $ret;
+}
+
+/**
+ * Ensure that installer cannot be run again after updating from Drupal 5.x to 6.x
+ * Actually, this is already done by system_update_6014(), so this is now a no-op.
+ */
+function system_update_6031() {
+  return array();
+}
+
+/**
+ * profile_fields.name used to be nullable but is part of a unique key
+ * and so shouldn't be.
+ */
+function system_update_6032() {
+  $ret = array();
+  if (db_table_exists('profile_fields')) {
+    db_drop_unique_key($ret, 'profile_fields', 'name');
+    db_change_field($ret, 'profile_fields', 'name', 'name', array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
+    db_add_unique_key($ret, 'profile_fields', 'name', array('name'));
+  }
+  return $ret;
+}
+
+/**
+ * Change node_comment_statistics to be not autoincrement.
+ */
+function system_update_6033() {
+  $ret = array();
+  if (db_table_exists('node_comment_statistics')) {
+    // On pgsql but not mysql, db_change_field() drops all keys
+    // involving the changed field, which in this case is the primary
+    // key.  The normal approach is explicitly drop the pkey, change the
+    // field, and re-create the pkey.
+    //
+    // Unfortunately, in this case that won't work on mysql; we CANNOT
+    // drop the pkey because on mysql auto-increment fields must be
+    // included in at least one key or index.
+    //
+    // Since we cannot drop the pkey before db_change_field(), after
+    // db_change_field() we may or may not still have a pkey.  The
+    // simple way out is to re-create the pkey only when using pgsql.
+    // Realistic requirements trump idealistic purity.
+    db_change_field($ret, 'node_comment_statistics', 'nid', 'nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+    if ($GLOBALS['db_type'] == 'pgsql') {
+      db_add_primary_key($ret, 'node_comment_statistics', array('nid'));
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Rename permission "administer access control" to "administer permissions".
+ */
+function system_update_6034() {
+  $ret = array();
+  $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+  while ($role = db_fetch_object($result)) {
+    $renamed_permission = preg_replace('/administer access control/', 'administer permissions', $role->perm);
+    if ($renamed_permission != $role->perm) {
+      $ret[] = update_sql("UPDATE {permission} SET perm = '$renamed_permission' WHERE rid = $role->rid");
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Change index on system table for better performance.
+ */
+function system_update_6035() {
+  $ret = array();
+  db_drop_index($ret, 'system', 'weight');
+  db_add_index($ret, 'system', 'modules', array(array('type', 12), 'status', 'weight', 'filename'));
+  db_add_index($ret, 'system', 'bootstrap', array(array('type', 12), 'status', 'bootstrap', 'weight', 'filename'));
+  return $ret;
+}
+
+/**
+ * Change the search schema and indexing.
+ *
+ * The table data is preserved where possible in MYSQL and MYSQLi using
+ * ALTER IGNORE. Other databases don't support that, so for them the
+ * tables are dropped and re-created, and will need to be re-indexed
+ * from scratch.
+ */
+function system_update_6036() {
+  $ret = array();
+  if (db_table_exists('search_index')) {
+    // Create the search_dataset.reindex column.
+    db_add_field($ret, 'search_dataset', 'reindex', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+
+    // Drop the search_index.from fields which are no longer used.
+    db_drop_index($ret, 'search_index', 'from_sid_type');
+    db_drop_field($ret, 'search_index', 'fromsid');
+    db_drop_field($ret, 'search_index', 'fromtype');
+
+    // Drop the search_dataset.sid_type index, so that it can be made unique.
+    db_drop_index($ret, 'search_dataset', 'sid_type');
+
+    // Create the search_node_links Table.
+    $search_node_links_schema = array(
+      'fields' => array(
+        'sid'      => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'type'     => array('type' => 'varchar', 'length' => 16, 'not null' => TRUE, 'default' => ''),
+        'nid'      => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+        'caption'    => array('type' => 'text', 'size' => 'big', 'not null' => FALSE),
+      ),
+      'primary key' => array('sid', 'type', 'nid'),
+      'indexes' => array('nid' => array('nid')),
+    );
+    db_create_table($ret, 'search_node_links', $search_node_links_schema);
+
+    // with the change to search_dataset.reindex, the search queue is handled differently,
+    // and this is no longer needed
+    variable_del('node_cron_last');
+
+    // Add a unique index for the search_index.
+    if ($GLOBALS['db_type'] == 'mysql' || $GLOBALS['db_type'] == 'mysqli') {
+      // Since it's possible that some existing sites have duplicates,
+      // create the index using the IGNORE keyword, which ignores duplicate errors.
+      // However, pgsql doesn't support it
+      $ret[] = update_sql("ALTER IGNORE TABLE {search_index} ADD UNIQUE KEY word_sid_type (word, sid, type)");
+      $ret[] = update_sql("ALTER IGNORE TABLE {search_dataset} ADD UNIQUE KEY sid_type (sid, type)");
+
+      // Everything needs to be reindexed.
+      $ret[] = update_sql("UPDATE {search_dataset} SET reindex = 1");
+    }
+    else {
+      // Delete the existing tables if there are duplicate values
+      if (db_result(db_query("SELECT sid FROM {search_dataset} GROUP BY sid, type HAVING COUNT(*) > 1")) || db_result(db_query("SELECT sid FROM {search_index} GROUP BY word, sid, type HAVING COUNT(*) > 1"))) {
+        $ret[] = update_sql('DELETE FROM {search_dataset}');
+        $ret[] = update_sql('DELETE FROM {search_index}');
+        $ret[] = update_sql('DELETE FROM {search_total}');
+      }
+      else {
+        // Everything needs to be reindexed.
+        $ret[] = update_sql("UPDATE {search_dataset} SET reindex = 1");
+      }
+
+      // create the new indexes
+      db_add_unique_key($ret, 'search_index', 'word_sid_type', array('word', 'sid', 'type'));
+      db_add_unique_key($ret, 'search_dataset', 'sid_type', array('sid', 'type'));
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Create consistent empty region for disabled blocks.
+ */
+function system_update_6037() {
+  $ret = array();
+  db_change_field($ret, 'blocks', 'region', 'region', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''));
+  $ret[] = update_sql("UPDATE {blocks} SET region = '' WHERE status = 0");
+  return $ret;
+}
+
+/**
+ * Ensure that "Account" is not used as a Profile category.
+ */
+function system_update_6038() {
+  $ret = array();
+  if (db_table_exists('profile_fields')) {
+    $ret[] = update_sql("UPDATE {profile_fields} SET category = 'Account settings' WHERE LOWER(category) = 'account'");
+    if ($affectedrows = db_affected_rows()) {
+      drupal_set_message('There were '. $affectedrows .' profile fields that used a reserved category name. They have been assigned to the category "Account settings".');
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Rename permissions "edit foo content" to "edit any foo content".
+ * Also update poll module permission "create polls" to "create
+ * poll content".
+ */
+function system_update_6039() {
+  $ret = array();
+  $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+  while ($role = db_fetch_object($result)) {
+    $renamed_permission = preg_replace('/(?<=^|,\ )edit\ ([a-zA-Z0-9_\-]+)\ content(?=,|$)/', 'edit any $1 content', $role->perm);
+    $renamed_permission = preg_replace('/(?<=^|,\ )create\ polls(?=,|$)/', 'create poll content', $renamed_permission);
+    if ($renamed_permission != $role->perm) {
+      $ret[] = update_sql("UPDATE {permission} SET perm = '$renamed_permission' WHERE rid = $role->rid");
+    }
+  }
+  return $ret;
+}
+
+/**
+ * Add a weight column to the upload table.
+ */
+function system_update_6040() {
+  $ret = array();
+  if (db_table_exists('upload')) {
+    db_add_field($ret, 'upload', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
+  }
+  return $ret;
+}
+
+/**
+ * Change forum vocabulary not to be required by default and set the weight of the forum.module 1 higher than the taxonomy.module.
+ */
+function system_update_6041() {
+  $weight = intval((db_result(db_query("SELECT weight FROM {system} WHERE name = 'taxonomy'"))) + 1);
+  $ret = array();
+  $vid = intval(variable_get('forum_nav_vocabulary', ''));
+  if (db_table_exists('vocabulary') && $vid) {
+    $ret[] = update_sql("UPDATE {vocabulary} SET required = 0 WHERE vid = " . $vid);
+    $ret[] = update_sql("UPDATE {system} SET weight = ". $weight ." WHERE name = 'forum'");
+  }
+  return $ret;
+}
+
+/**
+ * Upgrade recolored theme stylesheets to new array structure.
+ */
+function system_update_6042() {
+  foreach (list_themes() as $theme) {
+    $stylesheet = variable_get('color_'. $theme->name .'_stylesheet', NULL);
+    if (!empty($stylesheet)) {
+      variable_set('color_'. $theme->name .'_stylesheets', array($stylesheet));
+      variable_del('color_'. $theme->name .'_stylesheet');
+    }
+  }
+  return array();
+}
+
+/**
+ * Update table indices to make them more rational and useful.
+ */
+function system_update_6043() {
+  $ret = array();
+  // Required modules first.
+  // Add new system module indexes.
+  db_add_index($ret, 'flood', 'allow', array('event', 'hostname', 'timestamp'));
+  db_add_index($ret, 'history', 'nid', array('nid'));
+  // Change length of theme field in {blocks} to be consistent with module, and
+  // to avoid a MySQL error regarding a too-long index.  Also add new indices.
+  db_change_field($ret, 'blocks', 'theme', 'theme', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''),array(
+                  'unique keys' => array('tmd' => array('theme', 'module', 'delta'),),
+                  'indexes' => array('list' => array('theme', 'status', 'region', 'weight', 'module'),),));
+  db_add_index($ret, 'blocks_roles', 'rid', array('rid'));
+  // Improve filter module indices.
+  db_drop_index($ret, 'filters', 'weight');
+  db_add_unique_key($ret, 'filters', 'fmd', array('format', 'module', 'delta'));
+  db_add_index($ret, 'filters', 'list', array('format', 'weight', 'module', 'delta'));
+  // Drop unneeded keys form the node table.
+  db_drop_index($ret, 'node', 'status');
+  db_drop_unique_key($ret, 'node', 'nid_vid');
+  // Improve user module indices.
+  db_add_index($ret, 'users', 'mail', array('mail'));
+  db_add_index($ret, 'users_roles', 'rid', array('rid'));
+
+  // Optional modules - need to check if the tables exist.
+  // Alter aggregator module's tables primary keys to make them more useful.
+  if (db_table_exists('aggregator_category_feed')) {
+    db_drop_primary_key($ret, 'aggregator_category_feed');
+    db_add_primary_key($ret, 'aggregator_category_feed', array('cid', 'fid'));
+    db_add_index($ret, 'aggregator_category_feed', 'fid', array('fid'));
+  }
+  if (db_table_exists('aggregator_category_item')) {
+    db_drop_primary_key($ret, 'aggregator_category_item');
+    db_add_primary_key($ret, 'aggregator_category_item', array('cid', 'iid'));
+    db_add_index($ret, 'aggregator_category_item', 'iid', array('iid'));
+  }
+  // Alter contact module's table to add an index.
+  if (db_table_exists('contact')) {
+    db_add_index($ret, 'contact', 'list', array('weight', 'category'));
+  }
+  // Alter locale table to add a primary key, drop an index.
+  if (db_table_exists('locales_target')) {
+    db_add_primary_key($ret, 'locales_target', array('language', 'lid', 'plural'));
+  }
+  // Alter a poll module table to add a primary key.
+  if (db_table_exists('poll_votes')) {
+    db_drop_index($ret, 'poll_votes', 'nid');
+    db_add_primary_key($ret, 'poll_votes', array('nid', 'uid', 'hostname'));
+  }
+  // Alter a profile module table to add a primary key.
+  if (db_table_exists('profile_values')) {
+    db_drop_index($ret, 'profile_values', 'uid');
+    db_drop_index($ret, 'profile_values', 'fid');
+    db_change_field($ret,'profile_values' ,'fid', 'fid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,), array('indexes' => array('fid' => array('fid'),)));
+    db_change_field($ret,'profile_values' ,'uid', 'uid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,));
+    db_add_primary_key($ret, 'profile_values', array('uid', 'fid'));
+  }
+  // Alter a statistics module table to add an index.
+  if (db_table_exists('accesslog')) {
+    db_add_index($ret, 'accesslog', 'uid', array('uid'));
+  }
+  // Alter taxonomy module's tables.
+  if (db_table_exists('term_data')) {
+    db_drop_index($ret, 'term_data', 'vid');
+    db_add_index($ret, 'term_data', 'vid_name', array('vid', 'name'));
+    db_add_index($ret, 'term_data', 'taxonomy_tree', array('vid', 'weight', 'name'));
+  }
+  if (db_table_exists('term_node')) {
+    db_drop_primary_key($ret, 'term_node');
+    db_drop_index($ret, 'term_node', 'tid');
+    db_add_primary_key($ret, 'term_node', array('tid', 'vid'));
+  }
+  if (db_table_exists('term_relation')) {
+    db_drop_index($ret, 'term_relation', 'tid1');
+    db_add_unique_key($ret, 'term_relation', 'tid1_tid2', array('tid1', 'tid2'));
+  }
+  if (db_table_exists('term_synonym')) {
+    db_drop_index($ret, 'term_synonym', 'name');
+    db_add_index($ret, 'term_synonym', 'name_tid', array('name', 'tid'));
+  }
+  if (db_table_exists('vocabulary')) {
+    db_add_index($ret, 'vocabulary', 'list', array('weight', 'name'));
+  }
+  if (db_table_exists('vocabulary_node_types')) {
+    db_drop_primary_key($ret, 'vocabulary_node_types');
+    db_add_primary_key($ret, 'vocabulary_node_types', array('type', 'vid'));
+    db_add_index($ret, 'vocabulary_node_types', 'vid', array('vid'));
+  }
+  // If we updated in RC1 or before ensure we don't update twice.
+  variable_set('system_update_6043_RC2', TRUE);
+
+  return $ret;
+}
+
+/**
+ * RC1 to RC2 index cleanup.
+ */
+function system_update_6044() {
+  $ret = array();
+
+  // Delete invalid entries in {term_node} after system_update_6001.
+  $ret[] = update_sql("DELETE FROM {term_node} WHERE vid = 0");
+
+  // Only execute the rest of this function if 6043 was run in RC1 or before.
+  if (variable_get('system_update_6043_RC2', FALSE)) {
+    variable_del('system_update_6043_RC2');
+    return $ret;
+  }
+
+  // User module indices.
+  db_drop_unique_key($ret, 'users', 'mail');
+  db_add_index($ret, 'users', 'mail', array('mail'));
+
+  // Optional modules - need to check if the tables exist.
+  // Alter taxonomy module's tables.
+  if (db_table_exists('term_data')) {
+    db_drop_unique_key($ret, 'term_data', 'vid_name');
+    db_add_index($ret, 'term_data', 'vid_name', array('vid', 'name'));
+  }
+  if (db_table_exists('term_synonym')) {
+    db_drop_unique_key($ret, 'term_synonym', 'name_tid', array('name', 'tid'));
+    db_add_index($ret, 'term_synonym', 'name_tid', array('name', 'tid'));
+  }
+
+  return $ret;
+}
+
+/**
+ * Update blog, book and locale module permissions.
+ *
+ * Blog module got "edit own blog" replaced with the more granular "create
+ * blog entries", "edit own blog entries" and "delete own blog entries"
+ * permissions. We grant create and edit to previously privileged users, but
+ * delete is not granted to be in line with other permission changes in Drupal 6.
+ *
+ * Book module's "edit book pages" was upgraded to the bogus "edit book content"
+ * in Drupal 6 RC1 instead of "edit any book content", which would be correct.
+ *
+ * Locale module introduced "administer languages" and "translate interface"
+ * in place of "administer locales".
+ *
+ * Modeled after system_update_6039().
+ */
+function system_update_6045() {
+  $ret = array();
+  $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+  while ($role = db_fetch_object($result)) {
+    $renamed_permission = preg_replace('/(?<=^|,\ )edit\ own\ blog(?=,|$)/', 'create blog entries, edit own blog entries', $role->perm);
+    $renamed_permission = preg_replace('/(?<=^|,\ )edit\ book\ content(?=,|$)/', 'edit any book content', $renamed_permission);
+    $renamed_permission = preg_replace('/(?<=^|,\ )administer\ locales(?=,|$)/', 'administer languages, translate interface', $renamed_permission);
+    if ($renamed_permission != $role->perm) {
+      $ret[] = update_sql("UPDATE {permission} SET perm = '$renamed_permission' WHERE rid = $role->rid");
+    }
+  }
+
+  // Notify user that delete permissions may have been changed. This was in
+  // effect since system_update_6039(), but there was no user notice.
+  drupal_set_message('Drupal now has separate edit and delete permissions. Previously, users who were able to edit content were automatically allowed to delete it. For added security, delete permissions for individual core content types have been <strong>removed</strong> from all roles on your site (only roles with the "administer nodes" permission can now delete these types of content). If you would like to reenable any individual delete permissions, you can do this at the <a href="'. url('admin/user/permissions', array('fragment' => 'module-node')) .'">permissions page</a>.');
+  return $ret;
+}
+
+/**
+ * Ensure that the file_directory_path variable is set (using the old 5.x
+ * default, if necessary), so that the changed 6.x default won't break
+ * existing sites.
+ */
+function system_update_6046() {
+  $ret = array();
+  if (!variable_get('file_directory_path', FALSE)) {
+    variable_set('file_directory_path', 'files');
+    $ret[] = array('success' => TRUE, 'query' => "variable_set('file_directory_path')");
+  }
+  return $ret;
+}
+
+/**
+ * Fix cache mode for blocks inserted in system_install() in fresh installs of previous RC.
+ */
+function system_update_6047() {
+  $ret = array();
+  $ret[] = update_sql("UPDATE {blocks} SET cache = -1 WHERE module = 'user' AND delta IN ('0', '1')");
+  $ret[] = update_sql("UPDATE {blocks} SET cache = -1 WHERE module = 'system' AND delta = '0'");
+  return $ret;
+}
+
+/**
+ * @} End of "defgroup updates-5.x-to-6.x"
+ * The next series of updates should start at 7000.
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,113 @@
+// $Id: system.js,v 1.14.2.1 2008/02/07 18:23:30 goba Exp $
+
+/**
+ * Internal function to check using Ajax if clean URLs can be enabled on the
+ * settings page.
+ *
+ * This function is not used to verify whether or not clean URLs
+ * are currently enabled.
+ */
+Drupal.behaviors.cleanURLsSettingsCheck = function(context) {
+  // This behavior attaches by ID, so is only valid once on a page.
+  // Also skip if we are on an install page, as Drupal.cleanURLsInstallCheck will handle
+  // the processing.
+  if ($("#clean-url.clean-url-processed, #clean-url.install").size()) {
+    return;
+  }
+  var url = Drupal.settings.basePath +"admin/settings/clean-urls/check";
+  $("#clean-url .description span").html('<div id="testing">'+ Drupal.t('Testing clean URLs...') +"</div>");
+  $("#clean-url p").hide();
+  $.ajax({
+    url: location.protocol +"//"+ location.host + url,
+    dataType: 'json',
+    success: function () {
+      // Check was successful.
+      $("#clean-url input.form-radio").attr("disabled", false);
+      $("#clean-url .description span").append('<div class="ok">'+ Drupal.t('Your server has been successfully tested to support this feature.') +"</div>");
+      $("#testing").hide();
+    },
+    error: function() {
+      // Check failed.
+      $("#clean-url .description span").append('<div class="warning">'+ Drupal.t('Your system configuration does not currently support this feature. The <a href="http://drupal.org/node/15365">handbook page on Clean URLs</a> has additional troubleshooting information.') +"</div>");
+      $("#testing").hide();
+    }
+  });
+  $("#clean-url").addClass('clean-url-processed');
+};
+
+/**
+ * Internal function to check using Ajax if clean URLs can be enabled on the
+ * install page.
+ *
+ * This function is not used to verify whether or not clean URLs
+ * are currently enabled.
+ */
+Drupal.cleanURLsInstallCheck = function() {
+  var url = location.protocol +"//"+ location.host + Drupal.settings.basePath +"admin/settings/clean-urls/check";
+  $("#clean-url .description").append('<span><div id="testing">'+ Drupal.settings.cleanURL.testing +"</div></span>");
+  $("#clean-url.install").css("display", "block");
+  $.ajax({
+    url: url,
+    dataType: 'json',
+    success: function () {
+      // Check was successful.
+      $("#clean-url input.form-radio").attr("disabled", false);
+      $("#clean-url input.form-radio").attr("checked", 1);
+      $("#clean-url .description span").append('<div class="ok">'+ Drupal.settings.cleanURL.success +"</div>");
+      $("#testing").hide();
+    },
+    error: function() {
+      // Check failed.
+      $("#clean-url .description span").append('<div class="warning">'+ Drupal.settings.cleanURL.failure +"</div>");
+      $("#testing").hide();
+    }
+  });
+  $("#clean-url").addClass('clean-url-processed');
+};
+
+/**
+ * When a field is filled out, apply its value to other fields that will likely
+ * use the same value. In the installer this is used to populate the
+ * administrator e-mail address with the same value as the site e-mail address.
+ */
+Drupal.behaviors.copyFieldValue = function (context) {
+  for (var sourceId in Drupal.settings.copyFieldValue) {
+    // Get the list of target fields.
+    targetIds = Drupal.settings.copyFieldValue[sourceId];
+    if (!$('#'+ sourceId + '.copy-field-values-processed').size(), context) {
+      // Add the behavior to update target fields on blur of the primary field.
+      sourceField = $('#' + sourceId);
+      sourceField.bind('blur', function() {
+        for (var delta in targetIds) {
+          var targetField = $('#'+ targetIds[delta]);
+          if (targetField.val() == '') {
+            targetField.val(this.value);
+          }
+        }
+      });
+      sourceField.addClass('copy-field-values-processed');
+    }
+  }
+};
+
+/**
+ * Show/hide custom format sections on the date-time settings page.
+ */
+Drupal.behaviors.dateTime = function(context) {
+  // Show/hide custom format depending on the select's value.
+  $('select.date-format:not(.date-time-processed)', context).change(function() {
+    $(this).addClass('date-time-processed').parents("div.date-container").children("div.custom-container")[$(this).val() == "custom" ? "show" : "hide"]();
+  });
+
+  // Attach keyup handler to custom format inputs.
+  $('input.custom-format:not(.date-time-processed)', context).addClass('date-time-processed').keyup(function() {
+    var input = $(this);
+    var url = Drupal.settings.dateTime.lookup +(Drupal.settings.dateTime.lookup.match(/\?q=/) ? "&format=" : "?format=") + Drupal.encodeURIComponent(input.val());
+    $.getJSON(url, function(data) {
+      $("div.description span", input.parent()).html(data);
+    });
+  });
+
+  // Trigger the event handler to show the form input if necessary.
+  $('select.date-format', context).trigger('change');
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/system/system.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1877 @@
+<?php
+// $Id: system.module,v 1.585.2.6 2008/02/13 14:25:42 goba Exp $
+
+/**
+ * @file
+ * Configuration system that lets administrators modify the workings of the site.
+ */
+
+/**
+ * The current system version.
+ */
+define('VERSION', '6.0');
+
+/**
+ * Core API compatibility.
+ */
+define('DRUPAL_CORE_COMPATIBILITY', '6.x');
+
+/**
+ * Minimum supported version of PHP.
+ */
+define('DRUPAL_MINIMUM_PHP',    '4.3.5');
+
+/**
+ * Minimum recommended value of PHP memory_limit.
+ */
+define('DRUPAL_MINIMUM_PHP_MEMORY_LIMIT',    '16M');
+
+/**
+ * Minimum supported version of MySQL, if it is used.
+ */
+define('DRUPAL_MINIMUM_MYSQL',  '4.1.1');
+
+/**
+ * Minimum supported version of PostgreSQL, if it is used.
+ */
+define('DRUPAL_MINIMUM_PGSQL',  '7.4');
+
+/**
+ * Maximum age of temporary files in seconds.
+ */
+define('DRUPAL_MAXIMUM_TEMP_FILE_AGE', 1440);
+
+/**
+ * Implementation of hook_help().
+ */
+function system_help($path, $arg) {
+  global $base_url;
+
+  switch ($path) {
+    case 'admin/help#system':
+      $output = '<p>'. t('The system module is at the foundation of your Drupal website, and provides basic but extensible functionality for use by other modules and themes. Some integral elements of Drupal are contained in and managed by the system module, including caching, enabling or disabling of modules and themes, preparing and displaying the administrative page, and configuring fundamental site settings. A number of key system maintenance operations are also part of the system module.') .'</p>';
+      $output .= '<p>'. t('The system module provides:') .'</p>';
+      $output .= '<ul><li>'. t('support for enabling and disabling <a href="@modules">modules</a>. Drupal comes packaged with a number of core modules; each module provides a discrete set of features and may be enabled depending on the needs of your site. A wide array of additional modules contributed by members of the Drupal community are available for download at the <a href="@drupal-modules">Drupal.org module page</a>.', array('@modules' => url('admin/build/modules'), '@drupal-modules' => 'http://drupal.org/project/modules')) .'</li>';
+      $output .= '<li>'. t('support for enabling and disabling <a href="@themes">themes</a>, which determine the design and presentation of your site. Drupal comes packaged with several core themes and additional contributed themes are available at the <a href="@drupal-themes">Drupal.org theme page</a>.', array('@themes' => url('admin/build/themes'), '@drupal-themes' => 'http://drupal.org/project/themes')) .'</li>';
+      $output .= '<li>'. t('a robust <a href="@cache-settings">caching system</a> that allows the efficient re-use of previously-constructed web pages and web page components. Drupal stores the pages requested by anonymous users in a compressed format; depending on your site configuration and the amount of your web traffic tied to anonymous visitors, Drupal\'s caching system may significantly increase the speed of your site.', array('@cache-settings' => url('admin/settings/performance'))) .'</li>';
+      $output .= '<li>'. t('a set of routine administrative operations that rely on a correctly-configured <a href="@cron">cron maintenance task</a> to run automatically. A number of other modules, including the feed aggregator, ping module and search also rely on <a href="@cron">cron maintenance tasks</a>. For more information, see the online handbook entry for <a href="@handbook">configuring cron jobs</a>.', array('@cron' => url('admin/reports/status'), '@handbook' => 'http://drupal.org/cron')) .'</li>';
+      $output .= '<li>'. t('basic configuration options for your site, including <a href="@date-settings">date and time settings</a>, <a href="@file-system">file system settings</a>, <a href="@clean-url">clean URL support</a>, <a href="@site-info">site name and other information</a>, and a <a href="@site-maintenance">site maintenance</a> function for taking your site temporarily off-line.', array('@date-settings' => url('admin/settings/date-time'), '@file-system' => url('admin/settings/file-system'), '@clean-url' => url('admin/settings/clean-urls'), '@site-info' => url('admin/settings/site-information'), '@site-maintenance' => url('admin/settings/site-maintenance'))) .'</li></ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@system">System module</a>.', array('@system' => 'http://drupal.org/handbook/modules/system/')) .'</p>';
+      return $output;
+    case 'admin':
+      return '<p>'. t('Welcome to the administration section. Here you may control how your site functions.') .'</p>';
+    case 'admin/by-module':
+      return '<p>'. t('This page shows you all available administration tasks for each module.') .'</p>';
+    case 'admin/build/themes':
+      $output = '<p>'. t('Select which themes are available to your users and specify the default theme. To configure site-wide display settings, click the "configure" task above. Alternatively, to override these settings in a specific theme, click the "configure" link for that theme. Note that different themes may have different regions available for displaying content; for consistency in presentation, you may wish to enable only one theme.') .'</p>';
+      $output .= '<p>'. t('To change the appearance of your site, a number of <a href="@themes">contributed themes</a> are available.', array('@themes' => 'http://drupal.org/project/themes')) .'</p>';
+      return $output;
+    case 'admin/build/themes/settings/'. $arg[4]:
+      $reference = explode('.', $arg[4], 2);
+      $theme = array_pop($reference);
+      return '<p>'. t('These options control the display settings for the <code>%template</code> theme. When your site is displayed using this theme, these settings will be used. By clicking "Reset to defaults," you can choose to use the <a href="@global">global settings</a> for this theme.', array('%template' => $theme, '@global' => url('admin/build/themes/settings'))) .'</p>';
+    case 'admin/build/themes/settings':
+      return '<p>'. t('These options control the default display settings for your entire site, across all themes. Unless they have been overridden by a specific theme, these settings will be used.') .'</p>';
+    case 'admin/build/modules':
+      $output = '<p>'. t('Modules are plugins that extend Drupal\'s core functionality. Enable modules by selecting the <em>Enabled</em> checkboxes below and clicking the <em>Save configuration</em> button. Once a module is enabled, new <a href="@permissions">permissions</a> may be available. To reduce server load, modules with their <em>Throttle</em> checkbox selected are temporarily disabled when your site becomes extremely busy. (Note that the <em>Throttle</em> checkbox is only available if the Throttle module is enabled.)', array('@permissions' => url('admin/user/permissions')));
+      if (module_exists('throttle')) {
+        $output .= ' '. t('The auto-throttle functionality must be enabled on the <a href="@throttle">throttle configuration page</a> after having enabled the throttle module.', array('@throttle' => url('admin/settings/throttle')));
+      }
+      $output .= '</p>';
+      $output .= '<p>'. t('It is important that <a href="@update-php">update.php</a> is run every time a module is updated to a newer version.', array('@update-php' => $base_url .'/update.php')) .'</p>';
+      $output .= '<p>'. t('You can find all administration tasks belonging to a particular module on the <a href="@by-module">administration by module page</a>.', array('@by-module' => url('admin/by-module'))) .'</p>';
+      $output .= '<p>'. t('To extend the functionality of your site, a number of <a href="@modules">contributed modules</a> are available.', array('@modules' => 'http://drupal.org/project/modules')) .'</p>';
+      return $output;
+    case 'admin/build/modules/uninstall':
+      return '<p>'. t('The uninstall process removes all data related to a module. To uninstall a module, you must first disable it. Not all modules support this feature.') .'</p>';
+    case 'admin/build/block/configure':
+      if ($arg[4] == 'system' && $arg[5] == 0) {
+        return '<p>'. t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') .'</p>';
+      }
+      break;
+    case 'admin/settings/actions':
+    case 'admin/settings/actions/manage':
+      $output = '<p>'. t('Actions are individual tasks that the system can do, such as unpublishing a piece of content or banning a user. Modules, such as the trigger module, can fire these actions when certain system events happen; for example, when a new post is added or when a user logs in. Modules may also provide additional actions.') .'</p>';
+      $output .= '<p>'. t('There are two types of actions: simple and advanced. Simple actions do not require any additional configuration, and are listed here automatically. Advanced actions can do more than simple actions; for example, send an e-mail to a specified address, or check for certain words within a piece of content. These actions need to be created and configured first before they may be used. To create an advanced action, select the action from the drop-down below and click the <em>Create</em> button.') .'</p>';
+      if (module_exists('trigger')) {
+        $output .= '<p>'. t('You may proceed to the <a href="@url">Triggers</a> page to assign these actions to system events.', array('@url' => url('admin/build/trigger'))) .'</p>';
+      }
+      return $output;
+    case 'admin/settings/actions/configure':
+      return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Description</em> field is recommended, in order to better identify the precise action taking place. This description will be displayed in modules such as the trigger module when assigning actions to system events, so it is best if it is as descriptive as possible (for example, "Send e-mail to Moderation Team" rather than simply "Send e-mail").');
+    case 'admin/reports/status':
+      return '<p>'. t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on drupal.org's support forums and project issue queues.") .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function system_theme() {
+  return array_merge(drupal_common_theme(), array(
+    'system_theme_select_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_themes_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_modules' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_modules_uninstall' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'status_report' => array(
+      'arguments' => array('requirements' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_page' => array(
+      'arguments' => array('blocks' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_block' => array(
+      'arguments' => array('block' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_block_content' => array(
+      'arguments' => array('content' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_admin_by_module' => array(
+      'arguments' => array('menu_items' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_powered_by' => array(
+      'arguments' => array('image_path' => NULL),
+    ),
+  ));
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function system_perm() {
+  return array('administer site configuration', 'access administration pages', 'administer actions', 'access site reports', 'select different theme', 'administer files');
+}
+
+/**
+ * Implementation of hook_elements().
+ */
+function system_elements() {
+  // Top level form
+  $type['form'] = array('#method' => 'post', '#action' => request_uri());
+
+  // Inputs
+  $type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah'));
+  $type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#process' => array('form_expand_ahah'));
+  $type['image_button'] = array('#input' => TRUE, '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL);
+  $type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, '#process' => array('form_expand_ahah'));
+  $type['password'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#process' => array('form_expand_ahah'));
+  $type['password_confirm'] = array('#input' => TRUE, '#process' => array('expand_password_confirm'));
+  $type['textarea'] = array('#input' => TRUE, '#cols' => 60, '#rows' => 5, '#resizable' => TRUE, '#process' => array('form_expand_ahah'));
+  $type['radios'] = array('#input' => TRUE, '#process' => array('expand_radios'));
+  $type['radio'] = array('#input' => TRUE, '#default_value' => NULL, '#process' => array('form_expand_ahah'));
+  $type['checkboxes'] = array('#input' => TRUE, '#process' => array('expand_checkboxes'), '#tree' => TRUE);
+  $type['checkbox'] = array('#input' => TRUE, '#return_value' => 1, '#process' => array('form_expand_ahah'));
+  $type['select'] = array('#input' => TRUE, '#size' => 0, '#multiple' => FALSE, '#process' => array('form_expand_ahah'));
+  $type['weight'] = array('#input' => TRUE, '#delta' => 10, '#default_value' => 0, '#process' => array('process_weight', 'form_expand_ahah'));
+  $type['date'] = array('#input' => TRUE, '#process' => array('expand_date'), '#element_validate' => array('date_validate'));
+  $type['file'] = array('#input' => TRUE, '#size' => 60);
+
+  // Form structure
+  $type['item'] = array('#value' => '');
+  $type['hidden'] = array('#input' => TRUE, '#process' => array('form_expand_ahah'));
+  $type['value'] = array('#input' => TRUE);
+  $type['markup'] = array('#prefix' => '', '#suffix' => '');
+  $type['fieldset'] = array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL, '#process' => array('form_expand_ahah'));
+  $type['token'] = array('#input' => TRUE);
+  return $type;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function system_menu() {
+  $items['system/files'] = array(
+    'title' => 'File download',
+    'page callback' => 'file_download',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin'] = array(
+    'title' => 'Administer',
+    'access arguments' => array('access administration pages'),
+    'page callback' => 'system_main_admin_page',
+    'weight' => 9,
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/compact'] = array(
+    'title' => 'Compact mode',
+    'page callback' => 'system_admin_compact_page',
+    'type' => MENU_CALLBACK,
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/by-task'] = array(
+    'title' => 'By task',
+    'page callback' => 'system_main_admin_page',
+    'file' => 'system.admin.inc',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/by-module'] = array(
+    'title' => 'By module',
+    'page callback' => 'system_admin_by_module',
+    'file' => 'system.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+  );
+  $items['admin/content'] = array(
+    'title' => 'Content management',
+    'description' => "Manage your site's content.",
+    'position' => 'left',
+    'weight' => -10,
+    'page callback' => 'system_admin_menu_block_page',
+    'file' => 'system.admin.inc',
+  );
+
+  // menu items that are basically just menu blocks
+  $items['admin/settings'] = array(
+    'title' => 'Site configuration',
+    'description' => 'Adjust basic site configuration options.',
+    'position' => 'right',
+    'weight' => -5,
+    'page callback' => 'system_settings_overview',
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/build'] = array(
+    'title' => 'Site building',
+    'description' => 'Control how your site looks and feels.',
+    'position' => 'right',
+    'weight' => -10,
+    'page callback' => 'system_admin_menu_block_page',
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/admin'] = array(
+    'title' => 'Administration theme',
+    'description' => 'Settings for how your administrative pages should look.',
+    'position' => 'left',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_admin_theme_settings'),
+    'access arguments' => array('administer site configuration'),
+    'block callback' => 'system_admin_theme_settings',
+    'file' => 'system.admin.inc',
+  );
+  // Themes:
+  $items['admin/build/themes'] = array(
+    'title' => 'Themes',
+    'description' => 'Change which theme your site uses or allows users to set.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_themes_form', NULL),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/build/themes/select'] = array(
+    'title' => 'List',
+    'description' => 'Select the default theme.',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -1,
+  );
+  $items['admin/build/themes/settings'] = array(
+    'title' => 'Configure',
+    'page arguments' => array('system_theme_settings'),
+    'type' => MENU_LOCAL_TASK,
+  );
+  // Theme configuration subtabs
+  $items['admin/build/themes/settings/global'] = array(
+    'title' => 'Global settings',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -1,
+  );
+
+  foreach (list_themes() as $theme) {
+    $items['admin/build/themes/settings/'. $theme->name] = array(
+      'title' => $theme->info['name'],
+      'page arguments' => array('system_theme_settings', $theme->name),
+      'type' => MENU_LOCAL_TASK,
+      'access callback' => '_system_themes_access',
+      'access arguments' => array($theme),
+    );
+  }
+
+  // Modules:
+  $items['admin/build/modules'] = array(
+    'title' => 'Modules',
+    'description' => 'Enable or disable add-on modules for your site.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_modules'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/build/modules/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/build/modules/list/confirm'] = array(
+    'title' => 'List',
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/build/modules/uninstall'] = array(
+    'title' => 'Uninstall',
+    'page arguments' => array('system_modules_uninstall'),
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/build/modules/uninstall/confirm'] = array(
+    'title' => 'Uninstall',
+    'type' => MENU_CALLBACK,
+  );
+
+  // Actions:
+  $items['admin/settings/actions'] = array(
+    'title' => 'Actions',
+    'description' => 'Manage the actions defined for your site.',
+    'access arguments' => array('administer actions'),
+    'page callback' => 'system_actions_manage'
+  );
+  $items['admin/settings/actions/manage'] = array(
+    'title' => 'Manage actions',
+    'description' => 'Manage the actions defined for your site.',
+    'page callback' => 'system_actions_manage',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -2,
+  );
+  $items['admin/settings/actions/configure'] = array(
+    'title' => 'Configure an advanced action',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_actions_configure'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/actions/delete/%actions'] = array(
+    'title' => 'Delete action',
+    'description' => 'Delete an action.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_actions_delete_form', 4),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/actions/orphan'] = array(
+    'title' => 'Remove orphans',
+    'page callback' => 'system_actions_remove_orphans',
+    'type' => MENU_CALLBACK,
+  );
+
+  // Settings:
+  $items['admin/settings/site-information'] = array(
+    'title' => 'Site information',
+    'description' => 'Change basic site information, such as the site name, slogan, e-mail address, mission, front page and more.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_site_information_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/error-reporting'] = array(
+    'title' => 'Error reporting',
+    'description' => 'Control how Drupal deals with errors including 403/404 errors as well as PHP error reporting.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_error_reporting_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/logging'] = array(
+    'title' => 'Logging and alerts',
+    'description' => "Settings for logging and alerts modules. Various modules can route Drupal's system events to different destination, such as syslog, database, email, ...etc.",
+    'page callback' => 'system_logging_overview',
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/performance'] = array(
+    'title' => 'Performance',
+    'description' => 'Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_performance_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/file-system'] = array(
+    'title' => 'File system',
+    'description' => 'Tell Drupal where to store uploaded files and how they are accessed.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_file_system_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/image-toolkit'] = array(
+    'title' => 'Image toolkit',
+    'description' => 'Choose which image toolkit to use if you have installed optional toolkits.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_image_toolkit_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/content/rss-publishing'] = array(
+    'title' => 'RSS publishing',
+    'description' => 'Configure the number of items per feed and whether feeds should be titles/teasers/full-text.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_rss_feeds_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/date-time'] = array(
+    'title' => 'Date and time',
+    'description' => "Settings for how Drupal displays date and time, as well as the system's default timezone.",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_date_time_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/date-time/lookup'] = array(
+    'title' => 'Date and time lookup',
+    'type' => MENU_CALLBACK,
+    'page callback' => 'system_date_time_lookup',
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/site-maintenance'] = array(
+    'title' => 'Site maintenance',
+    'description' => 'Take the site off-line for maintenance or bring it back online.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_site_maintenance_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/clean-urls'] = array(
+    'title' => 'Clean URLs',
+    'description' => 'Enable or disable clean URLs for your site.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_clean_url_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/settings/clean-urls/check'] = array(
+    'title' => 'Clean URL check',
+    'page callback' => 'drupal_json',
+    'page arguments' => array(array('status' => TRUE)),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+  // Menu handler to test that drupal_http_request() works locally.
+  // @see system_check_http_request()
+  $items['admin/reports/request-test'] = array(
+    'title' => 'Request test',
+    'page callback' => 'printf',
+    'page arguments' => array('request test'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+
+  // Reports:
+  $items['admin/reports'] = array(
+    'title' => 'Reports',
+    'description' => 'View reports from system logs and other status information.',
+    'page callback' => 'system_admin_menu_block_page',
+    'access arguments' => array('access site reports'),
+    'weight' => 5,
+    'position' => 'left',
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/reports/status'] = array(
+    'title' => 'Status report',
+    'description' => "Get a status report about your site's operation and any detected problems.",
+    'page callback' => 'system_status',
+    'weight' => 10,
+    'access arguments' => array('administer site configuration'),
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/reports/status/run-cron'] = array(
+    'title' => 'Run cron',
+    'page callback' => 'system_run_cron',
+    'type' => MENU_CALLBACK,
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/reports/status/php'] = array(
+    'title' => 'PHP',
+    'page callback' => 'system_php',
+    'type' => MENU_CALLBACK,
+    'file' => 'system.admin.inc',
+  );
+  $items['admin/reports/status/sql'] = array(
+    'title' => 'SQL',
+    'page callback' => 'system_sql',
+    'type' => MENU_CALLBACK,
+    'file' => 'system.admin.inc',
+  );
+  // Default page for batch operations
+  $items['batch'] = array(
+    'page callback' => 'system_batch_page',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'system.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Menu item access callback - only admin or enabled themes can be accessed.
+ */
+function _system_themes_access($theme) {
+  return user_access('administer site configuration') && ($theme->status || $theme->name == variable_get('admin_theme', '0'));
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function system_init() {
+  // Use the administrative theme if the user is looking at a page in the admin/* path.
+  if (arg(0) == 'admin' || (variable_get('node_admin_theme', '0') && arg(0) == 'node' && (arg(1) == 'add' || arg(2) == 'edit'))) {
+    global $custom_theme;
+    $custom_theme = variable_get('admin_theme', '0');
+    drupal_add_css(drupal_get_path('module', 'system') .'/admin.css', 'module');
+  }
+
+  // Add the CSS for this module.
+  drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
+  drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
+  drupal_add_css(drupal_get_path('module', 'system') .'/system-menus.css', 'module');
+}
+
+/**
+ * Implementation of hook_user().
+ *
+ * Allows users to individually set their theme and time zone.
+ */
+function system_user($type, $edit, &$user, $category = NULL) {
+  if ($type == 'form' && $category == 'account') {
+    $form['theme_select'] = system_theme_select_form(t('Selecting a different theme will change the look and feel of the site.'), isset($edit['theme']) ? $edit['theme'] : NULL, 2);
+
+    if (variable_get('configurable_timezones', 1)) {
+      $zones = _system_zonelist();
+      $form['timezone'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Locale settings'),
+        '#weight' => 6,
+        '#collapsible' => TRUE,
+      );
+      $form['timezone']['timezone'] = array(
+        '#type' => 'select',
+        '#title' => t('Time zone'),
+        '#default_value' => strlen($edit['timezone']) ? $edit['timezone'] : variable_get('date_default_timezone', 0),
+        '#options' => $zones,
+        '#description' => t('Select your current local time. Dates and times throughout this site will be displayed using this time zone.'),
+      );
+    }
+
+    return $form;
+  }
+}
+
+/**
+ * Implementation of hook_block().
+ *
+ * Generate a block with a promotional link to Drupal.org.
+ */
+function system_block($op = 'list', $delta = 0, $edit = NULL) {
+  switch ($op) {
+    case 'list':
+      $blocks[0] = array(
+        'info' => t('Powered by Drupal'),
+        'weight' => '10',
+         // Not worth caching.
+        'cache' => BLOCK_NO_CACHE,
+      );
+      return $blocks;
+    case 'configure':
+      // Compile a list of fields to show
+      $form['wrapper']['color'] = array(
+        '#type' => 'select',
+        '#title' => t('Badge color'),
+        '#default_value' => variable_get('drupal_badge_color', 'powered-blue'),
+        '#options' => array('powered-black' => t('Black'), 'powered-blue' => t('Blue'), 'powered-gray' => t('Gray')),
+      );
+      $form['wrapper']['size'] = array(
+        '#type' => 'select',
+        '#title' => t('Badge size'),
+        '#default_value' => variable_get('drupal_badge_size', '80x15'),
+        '#options' => array('80x15' => t('Small'), '88x31' => t('Medium'), '135x42' => t('Large')),
+      );
+      return $form;
+    case 'save':
+      variable_set('drupal_badge_color', $edit['color']);
+      variable_set('drupal_badge_size', $edit['size']);
+      break;
+    case 'view':
+      $image_path = 'misc/'. variable_get('drupal_badge_color', 'powered-blue') .'-'. variable_get('drupal_badge_size', '80x15') .'.png';
+      $block['subject'] = NULL; // Don't display a title
+      $block['content'] = theme('system_powered_by', $image_path);
+      return $block;
+  }
+}
+
+/**
+ * Provide a single block on the administration overview page.
+ *
+ * @param $item
+ *   The menu item to be displayed.
+ */
+function system_admin_menu_block($item) {
+  $content = array();
+  if (!isset($item['mlid'])) {
+    $item += db_fetch_array(db_query("SELECT mlid, menu_name FROM {menu_links} ml WHERE ml.router_path = '%s' AND module = 'system'", $item['path']));
+  }
+  $result = db_query("
+    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+    FROM {menu_links} ml
+    LEFT JOIN {menu_router} m ON ml.router_path = m.path
+    WHERE ml.plid = %d AND ml.menu_name = '%s' AND hidden = 0", $item['mlid'], $item['menu_name']);
+  while ($item = db_fetch_array($result)) {
+    _menu_link_translate($item);
+    if (!$item['access']) {
+      continue;
+    }
+    // The link 'description' either derived from the hook_menu 'description' or
+    // entered by the user via menu module is saved as the title attribute.
+    if (!empty($item['localized_options']['attributes']['title'])) {
+      $item['description'] = $item['localized_options']['attributes']['title'];
+    }
+    // Prepare for sorting as in function _menu_tree_check_access().
+    // The weight is offset so it is always positive, with a uniform 5-digits.
+    $content[(50000 + $item['weight']) .' '. $item['title'] .' '. $item['mlid']] = $item;
+  }
+  ksort($content);
+  return $content;
+}
+
+/**
+ * Process admin theme form submissions.
+ */
+function system_admin_theme_submit($form, &$form_state) {
+  // If we're changing themes, make sure the theme has its blocks initialized.
+  if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] != variable_get('admin_theme', '0')) {
+    $result = db_result(db_query("SELECT COUNT(*) FROM {blocks} WHERE theme = '%s'", $form_state['values']['admin_theme']));
+    if (!$result) {
+      system_initialize_theme_blocks($form_state['values']['admin_theme']);
+    }
+  }
+}
+
+/**
+ * Returns a fieldset containing the theme select form.
+ *
+ * @param $description
+ *    description of the fieldset
+ * @param $default_value
+ *    default value of theme radios
+ * @param $weight
+ *    weight of the fieldset
+ * @return
+ *    a form array
+ */
+function system_theme_select_form($description = '', $default_value = '', $weight = 0) {
+  if (user_access('select different theme')) {
+    $enabled = array();
+    $themes = list_themes();
+
+    foreach ($themes as $theme) {
+      if ($theme->status) {
+        $enabled[] = $theme;
+      }
+    }
+
+    if (count($enabled) > 1) {
+      ksort($enabled);
+
+      $form['themes'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Theme configuration'),
+        '#description' => $description,
+        '#collapsible' => TRUE,
+        '#theme' => 'system_theme_select_form'
+      );
+
+      foreach ($enabled as $info) {
+        // For the default theme, revert to an empty string so the user's theme updates when the site theme is changed.
+        $info->key = $info->name == variable_get('theme_default', 'garland') ? '' : $info->name;
+
+        $screenshot = NULL;
+        $theme_key = $info->name;
+        while ($theme_key) {
+          if (file_exists($themes[$theme_key]->info['screenshot'])) {
+            $screenshot = $themes[$theme_key]->info['screenshot'];
+            break;
+          }
+          $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL;
+        }
+
+        $screenshot = $screenshot ? theme('image', $screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
+
+        $form['themes'][$info->key]['screenshot'] = array('#value' => $screenshot);
+        $form['themes'][$info->key]['description'] = array('#type' => 'item', '#title' => $info->name, '#value' => dirname($info->filename) . ($info->name == variable_get('theme_default', 'garland') ? '<br /> <em>'. t('(site default theme)') .'</em>' : ''));
+        $options[$info->key] = '';
+      }
+
+      $form['themes']['theme'] = array('#type' => 'radios', '#options' => $options, '#default_value' => $default_value ? $default_value : '');
+      $form['#weight'] = $weight;
+      return $form;
+    }
+  }
+}
+
+/**
+ * Checks the existence of the directory specified in $form_element. This
+ * function is called from the system_settings form to check both the
+ * file_directory_path and file_directory_temp directories. If validation
+ * fails, the form element is flagged with an error from within the
+ * file_check_directory function.
+ *
+ * @param $form_element
+ *   The form element containing the name of the directory to check.
+ */
+function system_check_directory($form_element) {
+  file_check_directory($form_element['#value'], FILE_CREATE_DIRECTORY, $form_element['#parents'][0]);
+  return $form_element;
+}
+
+/**
+ * Retrieves the current status of an array of files in the system table.
+ *
+ * @param $files
+ *   An array of files to check.
+ * @param $type
+ *   The type of the files.
+ */
+function system_get_files_database(&$files, $type) {
+  // Extract current files from database.
+  $result = db_query("SELECT filename, name, type, status, throttle, schema_version FROM {system} WHERE type = '%s'", $type);
+  while ($file = db_fetch_object($result)) {
+    if (isset($files[$file->name]) && is_object($files[$file->name])) {
+      $file->old_filename = $file->filename;
+      foreach ($file as $key => $value) {
+        if (!isset($files[$file->name]) || !isset($files[$file->name]->$key)) {
+          $files[$file->name]->$key = $value;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Prepare defaults for themes.
+ *
+ * @return
+ *   An array of default themes settings.
+ */
+function system_theme_default() {
+  return array(
+    'regions' => array(
+      'left' => 'Left sidebar',
+      'right' => 'Right sidebar',
+      'content' => 'Content',
+      'header' => 'Header',
+      'footer' => 'Footer',
+    ),
+    'description' => '',
+    'features' => array(
+      'comment_user_picture',
+      'favicon',
+      'mission',
+      'logo',
+      'name',
+      'node_user_picture',
+      'search',
+      'slogan',
+      'primary_links',
+      'secondary_links',
+    ),
+    'stylesheets' => array(
+      'all' => array('style.css')
+    ),
+    'scripts' => array('script.js'),
+    'screenshot' => 'screenshot.png',
+    'php' => DRUPAL_MINIMUM_PHP,
+  );
+}
+
+/**
+ * Collect data about all currently available themes.
+ *
+ * @return
+ *   Array of all available themes and their data.
+ */
+function system_theme_data() {
+  // Scan the installation theme .info files and their engines.
+  $themes = _system_theme_data();
+
+  // Extract current files from database.
+  system_get_files_database($themes, 'theme');
+
+  db_query("DELETE FROM {system} WHERE type = 'theme'");
+
+  foreach ($themes as $theme) {
+    if (!isset($theme->owner)) {
+      $theme->owner = '';
+    }
+
+    db_query("INSERT INTO {system} (name, owner, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d)", $theme->name, $theme->owner, serialize($theme->info), 'theme', $theme->filename, isset($theme->status) ? $theme->status : 0, 0, 0);
+  }
+
+  return $themes;
+}
+
+/**
+ * Helper function to scan and collect theme .info data and their engines.
+ *
+ * @return
+ *   An associative array of themes information.
+ */
+function _system_theme_data() {
+  static $themes_info = array();
+
+  if (empty($theme_info)) {
+    // Find themes
+    $themes = drupal_system_listing('\.info$', 'themes');
+    // Find theme engines
+    $engines = drupal_system_listing('\.engine$', 'themes/engines');
+
+    $defaults = system_theme_default();
+
+    $sub_themes = array();
+    // Read info files for each theme
+    foreach ($themes as $key => $theme) {
+      $themes[$key]->info = drupal_parse_info_file($theme->filename) + $defaults;
+
+      // Invoke hook_system_info_alter() to give installed modules a chance to
+      // modify the data in the .info files if necessary.
+      drupal_alter('system_info', $themes[$key]->info, $themes[$key]);
+
+      if (!empty($themes[$key]->info['base theme'])) {
+        $sub_themes[] = $key;
+      }
+      if (empty($themes[$key]->info['engine'])) {
+        $filename = dirname($themes[$key]->filename) .'/'. $themes[$key]->name .'.theme';
+        if (file_exists($filename)) {
+          $themes[$key]->owner = $filename;
+          $themes[$key]->prefix = $key;
+        }
+      }
+      else {
+        $engine = $themes[$key]->info['engine'];
+        if (isset($engines[$engine])) {
+          $themes[$key]->owner = $engines[$engine]->filename;
+          $themes[$key]->prefix = $engines[$engine]->name;
+          $themes[$key]->template = TRUE;
+        }
+      }
+
+      // Give the stylesheets proper path information.
+      $pathed_stylesheets = array();
+      foreach ($themes[$key]->info['stylesheets'] as $media => $stylesheets) {
+        foreach ($stylesheets as $stylesheet) {
+          $pathed_stylesheets[$media][$stylesheet] = dirname($themes[$key]->filename) .'/'. $stylesheet;
+        }
+      }
+      $themes[$key]->info['stylesheets'] = $pathed_stylesheets;
+
+      // Give the scripts proper path information.
+      $scripts = array();
+      foreach ($themes[$key]->info['scripts'] as $script) {
+        $scripts[$script] = dirname($themes[$key]->filename) .'/'. $script;
+      }
+      $themes[$key]->info['scripts'] = $scripts;
+      // Give the screenshot proper path information.
+      if (!empty($themes[$key]->info['screenshot'])) {
+        $themes[$key]->info['screenshot'] = dirname($themes[$key]->filename) .'/'. $themes[$key]->info['screenshot'];
+      }
+    }
+
+    // Now that we've established all our master themes, go back and fill in
+    // data for subthemes.
+    foreach ($sub_themes as $key) {
+      $base_key = system_find_base_theme($themes, $key);
+      if (!$base_key) {
+        continue;
+      }
+      // Copy the 'owner' and 'engine' over if the top level theme uses a
+      // theme engine.
+      if (isset($themes[$base_key]->owner)) {
+        if (isset($themes[$base_key]->info['engine'])) {
+          $themes[$key]->info['engine'] = $themes[$base_key]->info['engine'];
+          $themes[$key]->owner = $themes[$base_key]->owner;
+          $themes[$key]->prefix = $themes[$base_key]->prefix;
+        }
+        else {
+          $themes[$key]->prefix = $key;
+        }
+      }
+    }
+
+    $themes_info = $themes;
+  }
+
+  return $themes_info;
+}
+
+/**
+ * Recursive function to find the top level base theme. Themes can inherit
+ * templates and function implementations from earlier themes.
+ *
+ * @param $themes
+ *   An array of available themes.
+ * @param $key
+ *   The name of the theme whose base we are looking for.
+ * @param $used_keys
+ *   A recursion parameter preventing endless loops.
+ * @return
+ *   Returns the top level parent that has no ancestor or returns NULL if there isn't a valid parent.
+ */
+function system_find_base_theme($themes, $key, $used_keys = array()) {
+  $base_key = $themes[$key]->info['base theme'];
+  // Does the base theme exist?
+  if (!isset($themes[$base_key])) {
+    return NULL;
+  }
+
+  // Is the base theme itself a child of another theme?
+  if (isset($themes[$base_key]->info['base theme'])) {
+    // Prevent loops.
+    if (!empty($used_keys[$base_key])) {
+      return NULL;
+    }
+    $used_keys[$base_key] = TRUE;
+    return system_find_base_theme($themes, $base_key, $used_keys);
+  }
+  // If we get here, then this is our parent theme.
+  return $base_key;
+}
+
+/**
+ * Get a list of available regions from a specified theme.
+ *
+ * @param $theme_key
+ *   The name of a theme.
+ * @return
+ *   An array of regions in the form $region['name'] = 'description'.
+ */
+function system_region_list($theme_key) {
+  static $list = array();
+
+  if (!array_key_exists($theme_key, $list)) {
+    $info = unserialize(db_result(db_query("SELECT info FROM {system} WHERE type = 'theme' AND name = '%s'", $theme_key)));
+    $list[$theme_key] = array_map('t', $info['regions']);
+  }
+
+  return $list[$theme_key];
+}
+
+/**
+ * Get the name of the default region for a given theme.
+ *
+ * @param $theme
+ *   The name of a theme.
+ * @return
+ *   A string that is the region name.
+ */
+function system_default_region($theme) {
+  $regions = array_keys(system_region_list($theme));
+  return isset($regions[0]) ? $regions[0] : '';
+}
+
+/**
+ * Assign an initial, default set of blocks for a theme.
+ *
+ * This function is called the first time a new theme is enabled. The new theme
+ * gets a copy of the default theme's blocks, with the difference that if a
+ * particular region isn't available in the new theme, the block is assigned
+ * to the new theme's default region.
+ *
+ * @param $theme
+ *   The name of a theme.
+ */
+function system_initialize_theme_blocks($theme) {
+  // Initialize theme's blocks if none already registered.
+  if (!(db_result(db_query("SELECT COUNT(*) FROM {blocks} WHERE theme = '%s'", $theme)))) {
+    $default_theme = variable_get('theme_default', 'garland');
+    $regions = system_region_list($theme);
+    $result = db_query("SELECT * FROM {blocks} WHERE theme = '%s'", $default_theme);
+    while ($block = db_fetch_array($result)) {
+      // If the region isn't supported by the theme, assign the block to the theme's default region.
+      if (!array_key_exists($block['region'], $regions)) {
+        $block['region'] = system_default_region($theme);
+      }
+      db_query("INSERT INTO {blocks} (module, delta, theme, status, weight, region, visibility, pages, custom, throttle, cache) VALUES ('%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, %d)",
+          $block['module'], $block['delta'], $theme, $block['status'], $block['weight'], $block['region'], $block['visibility'], $block['pages'], $block['custom'], $block['throttle'], $block['cache']);
+    }
+  }
+}
+
+/**
+ * Add default buttons to a form and set its prefix.
+ *
+ * @ingroup forms
+ * @see system_settings_form_submit()
+ * @param $form
+ *   An associative array containing the structure of the form.
+ * @return
+ *   The form structure.
+ */
+function system_settings_form($form) {
+  $form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
+  $form['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset to defaults') );
+
+  if (!empty($_POST) && form_get_errors()) {
+    drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
+  }
+  $form['#submit'][] = 'system_settings_form_submit';
+  $form['#theme'] = 'system_settings_form';
+  return $form;
+}
+
+/**
+ * Execute the system_settings_form.
+ *
+ * If you want node type configure style handling of your checkboxes,
+ * add an array_filter value to your form.
+ */
+function system_settings_form_submit($form, &$form_state) {
+  $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
+
+  // Exclude unnecessary elements.
+  unset($form_state['values']['submit'], $form_state['values']['reset'], $form_state['values']['form_id'], $form_state['values']['op'], $form_state['values']['form_token'], $form_state['values']['form_build_id']);
+
+  foreach ($form_state['values'] as $key => $value) {
+    if ($op == t('Reset to defaults')) {
+      variable_del($key);
+    }
+    else {
+      if (is_array($value) && isset($form_state['values']['array_filter'])) {
+        $value = array_keys(array_filter($value));
+      }
+      variable_set($key, $value);
+    }
+  }
+  if ($op == t('Reset to defaults')) {
+    drupal_set_message(t('The configuration options have been reset to their default values.'));
+  }
+  else {
+    drupal_set_message(t('The configuration options have been saved.'));
+  }
+
+  cache_clear_all();
+  drupal_rebuild_theme_registry();
+}
+
+/**
+ * Helper function to sort requirements.
+ */
+function _system_sort_requirements($a, $b) {
+  if (!isset($a['weight'])) {
+    if (!isset($b['weight'])) {
+      return strcmp($a['title'], $b['title']);
+    }
+    return -$b['weight'];
+  }
+  return isset($b['weight']) ? $a['weight'] - $b['weight'] : $a['weight'];
+}
+
+/**
+ * Implementation of hook_node_type().
+ *
+ * Updates theme settings after a node type change.
+ */
+function system_node_type($op, $info) {
+  if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
+    $old = 'toggle_node_info_'. $info->old_type;
+    $new = 'toggle_node_info_'. $info->type;
+
+    $theme_settings = variable_get('theme_settings', array());
+    if (isset($theme_settings[$old])) {
+      $theme_settings[$new] = $theme_settings[$old];
+      unset($theme_settings[$old]);
+      variable_set('theme_settings', $theme_settings);
+    }
+  }
+}
+
+/**
+ * Output a confirmation form
+ *
+ * This function returns a complete form for confirming an action. A link is
+ * offered to go back to the item that is being changed in case the user changes
+ * his/her mind.
+ *
+ * If the submit handler for this form is invoked, the user successfully
+ * confirmed the action. You should never directly inspect $_POST to see if an
+ * action was confirmed.
+ *
+ * @ingroup forms
+ * @param $form
+ *   Additional elements to inject into the form, for example hidden elements.
+ * @param $question
+ *   The question to ask the user (e.g. "Are you sure you want to delete the
+ *   block <em>foo</em>?").
+ * @param $path
+ *   The page to go to if the user denies the action.
+ *   Can be either a drupal path, or an array with the keys 'path', 'query', 'fragment'.
+ * @param $description
+ *   Additional text to display (defaults to "This action cannot be undone.").
+ * @param $yes
+ *   A caption for the button which confirms the action (e.g. "Delete",
+ *   "Replace", ...).
+ * @param $no
+ *   A caption for the link which denies the action (e.g. "Cancel").
+ * @param $name
+ *   The internal name used to refer to the confirmation item.
+ * @return
+ *   The form.
+ */
+function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, $no = NULL, $name = 'confirm') {
+  $description = isset($description) ? $description : t('This action cannot be undone.');
+
+  // Prepare cancel link
+  $query = $fragment = NULL;
+  if (is_array($path)) {
+    $query = isset($path['query']) ? $path['query'] : NULL;
+    $fragment = isset($path['fragment']) ? $path['fragment'] : NULL;
+    $path = isset($path['path']) ? $path['path'] : NULL;
+  }
+  $cancel = l($no ? $no : t('Cancel'), $path, array('query' => $query, 'fragment' => $fragment));
+
+  drupal_set_title($question);
+
+  // Confirm form fails duplication check, as the form values rarely change -- so skip it.
+  $form['#skip_duplicate_check'] = TRUE;
+
+  $form['#attributes'] = array('class' => 'confirmation');
+  $form['description'] = array('#value' => $description);
+  $form[$name] = array('#type' => 'hidden', '#value' => 1);
+
+  $form['actions'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => $yes ? $yes : t('Confirm'));
+  $form['actions']['cancel'] = array('#value' => $cancel);
+  $form['#theme'] = 'confirm_form';
+  return $form;
+}
+
+/**
+ * Determine if a user is in compact mode.
+ */
+function system_admin_compact_mode() {
+  global $user;
+  return (isset($user->admin_compact_mode)) ? $user->admin_compact_mode : variable_get('admin_compact_mode', FALSE);
+}
+
+/**
+ * Generate a list of tasks offered by a specified module.
+ *
+ * @param $module
+ *   Module name.
+ * @return
+ *   An array of task links.
+ */
+function system_get_module_admin_tasks($module) {
+  static $items;
+
+  $admin_access = user_access('administer permissions');
+  $admin_tasks = array();
+
+  if (!isset($items)) {
+    $result = db_query("
+       SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
+       FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.link_path LIKE 'admin/%' AND hidden >= 0 AND module = 'system' AND m.number_parts > 2");
+    $items = array();
+    while ($item = db_fetch_array($result)) {
+      _menu_link_translate($item);
+      if ($item['access']) {
+        $items[$item['router_path']] = $item;
+      }
+    }
+  }
+  $admin_tasks = array();
+  $admin_task_count = 0;
+  // Check for permissions.
+  if (module_hook($module, 'perm') && $admin_access) {
+    $admin_tasks[-1] = l(t('Configure permissions'), 'admin/user/permissions', array('fragment' => 'module-'. $module));
+  }
+
+  // Check for menu items that are admin links.
+  if ($menu = module_invoke($module, 'menu')) {
+    foreach (array_keys($menu) as $path) {
+      if (isset($items[$path])) {
+        $admin_tasks[$items[$path]['title'] . $admin_task_count ++] = l($items[$path]['title'], $path);
+      }
+    }
+  }
+
+  return $admin_tasks;
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * Remove older rows from flood and batch table. Remove old temporary files.
+ */
+function system_cron() {
+  // Cleanup the flood.
+  db_query('DELETE FROM {flood} WHERE timestamp < %d', time() - 3600);
+  // Cleanup the batch table.
+  db_query('DELETE FROM {batch} WHERE timestamp < %d', time() - 864000);
+
+  // Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+  $result = db_query('SELECT * FROM {files} WHERE status = %d and timestamp < %d', FILE_STATUS_TEMPORARY, time() - DRUPAL_MAXIMUM_TEMP_FILE_AGE);
+  while ($file = db_fetch_object($result)) {
+    if (file_exists($file->filepath)) {
+      // If files that exist cannot be deleted, continue so the database remains
+      // consistent.
+      if (!file_delete($file->filepath)) {
+        watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath), 'error');
+        continue;
+      }
+    }
+    db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+  }
+}
+
+/**
+ * Implementation of hook_hook_info().
+ */
+function system_hook_info() {
+  return array(
+    'system' => array(
+      'cron' => array(
+        'run' => array(
+          'runs when' => t('When cron runs'),
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_action_info().
+ */
+function system_action_info() {
+  return array(
+    'system_message_action' => array(
+      'type' => 'system',
+      'description' => t('Display a message to the user'),
+      'configurable' => TRUE,
+      'hooks' => array(
+        'nodeapi' => array('view', 'insert', 'update', 'delete'),
+        'comment' => array('view', 'insert', 'update', 'delete'),
+        'user' => array('view', 'insert', 'update', 'delete', 'login'),
+        'taxonomy' => array('insert', 'update', 'delete'),
+      ),
+    ),
+    'system_send_email_action' => array(
+      'description' => t('Send e-mail'),
+      'type' => 'system',
+      'configurable' => TRUE,
+      'hooks' => array(
+        'nodeapi' => array('view', 'insert', 'update', 'delete'),
+        'comment' => array('view', 'insert', 'update', 'delete'),
+        'user' => array('view', 'insert', 'update', 'delete', 'login'),
+        'taxonomy' => array('insert', 'update', 'delete'),
+      )
+    ),
+    'system_goto_action' => array(
+      'description' => t('Redirect to URL'),
+      'type' => 'system',
+      'configurable' => TRUE,
+      'hooks' => array(
+        'nodeapi' => array('view', 'insert', 'update', 'delete'),
+        'comment' => array('view', 'insert', 'update', 'delete'),
+        'user' => array('view', 'insert', 'update', 'delete', 'login'),
+      )
+    )
+  );
+}
+
+/**
+ * Menu callback. Display an overview of available and configured actions.
+ */
+function system_actions_manage() {
+  $output = '';
+  $actions = actions_list();
+  actions_synchronize($actions);
+  $actions_map = actions_actions_map($actions);
+  $options = array(t('Choose an advanced action'));
+  $unconfigurable = array();
+
+  foreach ($actions_map as $key => $array) {
+    if ($array['configurable']) {
+      $options[$key] = $array['description'] .'...';
+    }
+    else {
+      $unconfigurable[] = $array;
+    }
+  }
+
+  $row = array();
+  $instances_present = db_fetch_object(db_query("SELECT aid FROM {actions} WHERE parameters != ''"));
+  $header = array(
+    array('data' => t('Action type'), 'field' => 'type'),
+    array('data' => t('Description'), 'field' => 'description'),
+    array('data' => $instances_present ? t('Operations') : '', 'colspan' => '2')
+  );
+  $sql = 'SELECT * FROM {actions}';
+  $result = pager_query($sql . tablesort_sql($header), 50);
+  while ($action = db_fetch_object($result)) {
+    $row[] = array(
+      array('data' => $action->type),
+      array('data' => $action->description),
+      array('data' => $action->parameters ? l(t('configure'), "admin/settings/actions/configure/$action->aid") : ''),
+      array('data' => $action->parameters ? l(t('delete'), "admin/settings/actions/delete/$action->aid") : '')
+    );
+  }
+
+  if ($row) {
+    $pager = theme('pager', NULL, 50, 0);
+    if (!empty($pager)) {
+      $row[] = array(array('data' => $pager, 'colspan' => '3'));
+    }
+    $output .= '<h3>'. t('Actions available to Drupal:') .'</h3>';
+    $output .= theme('table', $header, $row);
+  }
+
+  if ($actions_map) {
+    $output .= drupal_get_form('system_actions_manage_form', $options);
+  }
+
+  return $output;
+}
+
+/**
+ * Define the form for the actions overview page.
+ *
+ * @see system_actions_manage_form_submit()
+ * @ingroup forms
+ * @param $form_state
+ *   An associative array containing the current state of the form; not used.
+ * @param $options
+ *   An array of configurable actions.
+ * @return
+ *   Form definition.
+ */
+function system_actions_manage_form($form_state, $options = array()) {
+  $form['parent'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Make a new advanced action available'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $form['parent']['action'] = array(
+    '#type' => 'select',
+    '#default_value' => '',
+    '#options' => $options,
+    '#description' => '',
+  );
+  $form['parent']['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Create'),
+  );
+  return $form;
+}
+
+/**
+ * Process system_actions_manage form submissions.
+ */
+function system_actions_manage_form_submit($form, &$form_state) {
+  if ($form_state['values']['action']) {
+    $form_state['redirect'] = 'admin/settings/actions/configure/'. $form_state['values']['action'];
+  }
+}
+
+/**
+ * Menu callback. Create the form for configuration of a single action.
+ *
+ * We provide the "Description" field. The rest of the form
+ * is provided by the action. We then provide the Save button.
+ * Because we are combining unknown form elements with the action
+ * configuration form, we use actions_ prefix on our elements.
+ *
+ * @see system_actions_configure_validate()
+ * @see system_actions_configure_submit()
+ * @param $action
+ *   md5 hash of action ID or an integer. If it's an md5 hash, we
+ *   are creating a new instance. If it's an integer, we're editing
+ *   an existing instance.
+ * @return
+ *   Form definition.
+ */
+function system_actions_configure($form_state, $action = NULL) {
+  if ($action === NULL) {
+    drupal_goto('admin/settings/actions');
+  }
+
+  $actions_map = actions_actions_map(actions_list());
+  $edit = array();
+
+  // Numeric action denotes saved instance of a configurable action;
+  // else we are creating a new action instance.
+  if (is_numeric($action)) {
+    $aid = $action;
+    // Load stored parameter values from database.
+    $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", intval($aid)));
+    $edit['actions_description'] = $data->description;
+    $edit['actions_type'] = $data->type;
+    $function = $data->callback;
+    $action = md5($data->callback);
+    $params = unserialize($data->parameters);
+    if ($params) {
+      foreach ($params as $name => $val) {
+        $edit[$name] = $val;
+      }
+    }
+  }
+  else {
+    $function = $actions_map[$action]['callback'];
+    $edit['actions_description'] = $actions_map[$action]['description'];
+    $edit['actions_type'] = $actions_map[$action]['type'];
+  }
+
+  $form['actions_description'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Description'),
+    '#default_value' => $edit['actions_description'],
+    '#maxlength' => '255',
+    '#description' => t('A unique description for this advanced action. This description will be displayed in the interface of modules that integrate with actions, such as Trigger module.'),
+    '#weight' => -10
+  );
+  $action_form = $function .'_form';
+  $form = array_merge($form, $action_form($edit));
+  $form['actions_type'] = array(
+    '#type' => 'value',
+    '#value' => $edit['actions_type'],
+  );
+  $form['actions_action'] = array(
+    '#type' => 'hidden',
+    '#value' => $action,
+  );
+  // $aid is set when configuring an existing action instance.
+  if (isset($aid)) {
+    $form['actions_aid'] = array(
+      '#type' => 'hidden',
+      '#value' => $aid,
+    );
+  }
+  $form['actions_configured'] = array(
+    '#type' => 'hidden',
+    '#value' => '1',
+  );
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#weight' => 13
+  );
+
+  return $form;
+}
+
+/**
+ * Validate system_actions_configure form submissions.
+ */
+function system_actions_configure_validate($form, $form_state) {
+  $function = actions_function_lookup($form_state['values']['actions_action']) .'_validate';
+  // Hand off validation to the action.
+  if (function_exists($function)) {
+    $function($form, $form_state);
+  }
+}
+
+/**
+ * Process system_actions_configure form submissions.
+ */
+function system_actions_configure_submit($form, &$form_state) {
+  $function = actions_function_lookup($form_state['values']['actions_action']);
+  $submit_function = $function .'_submit';
+
+  // Action will return keyed array of values to store.
+  $params = $submit_function($form, $form_state);
+  $aid = isset($form_state['values']['actions_aid']) ? $form_state['values']['actions_aid'] : NULL;
+
+  actions_save($function, $form_state['values']['actions_type'], $params, $form_state['values']['actions_description'], $aid);
+  drupal_set_message(t('The action has been successfully saved.'));
+
+  $form_state['redirect'] = 'admin/settings/actions/manage';
+}
+
+/**
+ * Create the form for confirmation of deleting an action.
+ *
+ * @ingroup forms
+ * @see system_actions_delete_form_submit()
+ */
+function system_actions_delete_form($form_state, $action) {
+
+  $form['aid'] = array(
+    '#type' => 'hidden',
+    '#value' => $action->aid,
+  );
+  return confirm_form($form,
+    t('Are you sure you want to delete the action %action?', array('%action' => $action->description)),
+    'admin/settings/actions/manage',
+    t('This cannot be undone.'),
+    t('Delete'), t('Cancel')
+  );
+}
+
+/**
+ * Process system_actions_delete form submissions.
+ *
+ * Post-deletion operations for action deletion.
+ */
+function system_actions_delete_form_submit($form, &$form_state) {
+  $aid = $form_state['values']['aid'];
+  $action = actions_load($aid);
+  actions_delete($aid);
+  $description = check_plain($action->description);
+  watchdog('user', 'Deleted action %aid (%action)', array('%aid' => $aid, '%action' => $description));
+  drupal_set_message(t('Action %action was deleted', array('%action' => $description)));
+  $form_state['redirect'] = 'admin/settings/actions/manage';
+}
+
+/**
+ * Post-deletion operations for deleting action orphans.
+ *
+ * @param $orphaned
+ *   An array of orphaned actions.
+ */
+function system_action_delete_orphans_post($orphaned) {
+  foreach ($orphaned as $callback) {
+    drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
+  }
+}
+
+/**
+ * Remove actions that are in the database but not supported by any enabled module.
+ */
+function system_actions_remove_orphans() {
+  actions_synchronize(actions_list(), TRUE);
+  drupal_goto('admin/settings/actions/manage');
+}
+
+/**
+ * Return a form definition so the Send email action can be configured.
+ *
+ * @see system_send_email_action_validate()
+ * @see system_send_email_action_submit()
+ * @param $context
+ *   Default values (if we are editing an existing action instance).
+ * @return
+ *   Form definition.
+ */
+function system_send_email_action_form($context) {
+  // Set default values for form.
+  if (!isset($context['recipient'])) {
+    $context['recipient'] = '';
+  }
+  if (!isset($context['subject'])) {
+    $context['subject'] = '';
+  }
+  if (!isset($context['message'])) {
+    $context['message'] = '';
+  }
+
+  $form['recipient'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Recipient'),
+    '#default_value' => $context['recipient'],
+    '#maxlength' => '254',
+    '#description' => t('The email address to which the message should be sent OR enter %author if you would like to send an e-mail to the author of the original post.', array('%author' => '%author')),
+  );
+  $form['subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => $context['subject'],
+    '#maxlength' => '254',
+    '#description' => t('The subject of the message.'),
+  );
+  $form['message'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Message'),
+    '#default_value' => $context['message'],
+    '#cols' => '80',
+    '#rows' => '20',
+    '#description' => t('The message that should be sent. You may include the following variables: %site_name, %username, %node_url, %node_type, %title, %teaser, %body. Not all variables will be available in all contexts.'),
+  );
+  return $form;
+}
+
+/**
+ * Validate system_send_email_action form submissions.
+ */
+function system_send_email_action_validate($form, $form_state) {
+  $form_values = $form_state['values'];
+  // Validate the configuration form.
+  if (!valid_email_address($form_values['recipient']) && $form_values['recipient'] != '%author') {
+    // We want the literal %author placeholder to be emphasized in the error message.
+    form_set_error('recipient', t('Please enter a valid email address or %author.', array('%author' => '%author')));
+  }
+}
+
+/**
+ * Process system_send_email_action form submissions.
+ */
+function system_send_email_action_submit($form, $form_state) {
+  $form_values = $form_state['values'];
+  // Process the HTML form to store configuration. The keyed array that
+  // we return will be serialized to the database.
+  $params = array(
+    'recipient' => $form_values['recipient'],
+    'subject'   => $form_values['subject'],
+    'message'   => $form_values['message'],
+  );
+  return $params;
+}
+
+/**
+ * Implementation of a configurable Drupal action. Sends an email.
+ */
+function system_send_email_action($object, $context) {
+  global $user;
+
+  switch ($context['hook']) {
+    case 'nodeapi':
+      // Because this is not an action of type 'node' the node
+      // will not be passed as $object, but it will still be available
+      // in $context.
+      $node = $context['node'];
+      break;
+    // The comment hook provides nid, in $context.
+    case 'comment':
+      $comment = $context['comment'];
+      $node = node_load($comment->nid);
+      break;
+    case 'user':
+      // Because this is not an action of type 'user' the user
+      // object is not passed as $object, but it will still be available
+      // in $context.
+      $account = $context['account'];
+      if (isset($context['node'])) {
+        $node = $context['node'];
+      }
+      elseif ($context['recipient'] == '%author') {
+        // If we don't have a node, we don't have a node author.
+        watchdog('error', 'Cannot use %author token in this context.');
+        return;
+      }
+      break;
+    default:
+      // We are being called directly.
+      $node = $object;
+  }
+
+  $recipient = $context['recipient'];
+
+  if (isset($node)) {
+    if (!isset($account)) {
+      $account = user_load(array('uid' => $node->uid));
+    }
+    if ($recipient == '%author') {
+      $recipient = $account->mail;
+    }
+  }
+
+  if (!isset($account)) {
+    $account = $user;
+
+  }
+  $language = user_preferred_language($account);
+  $params = array('account' => $account, 'object' => $object, 'context' => $context);
+  if (isset($node)) {
+    $params['node'] = $node;
+  }
+
+  if (drupal_mail('system', 'action_send_email', $recipient, $language, $params)) {
+    watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient));
+  }
+  else {
+    watchdog('error', 'Unable to send email to %recipient', array('%recipient' => $recipient));
+  }
+}
+
+/**
+ * Implementation of hook_mail().
+ */
+function system_mail($key, &$message, $params) {
+  $account = $params['account'];
+  $context = $params['context'];
+  $variables = array(
+    '%site_name' => variable_get('site_name', 'Drupal'),
+    '%username' => $account->name,
+  );
+  if ($context['hook'] == 'taxonomy') {
+    $object = $params['object'];
+    $vocabulary = taxonomy_vocabulary_load($object->vid);
+    $variables += array(
+      '%term_name' => $object->name,
+      '%term_description' => $object->description,
+      '%term_id' => $object->tid,
+      '%vocabulary_name' => $vocabulary->name,
+      '%vocabulary_description' => $vocabulary->description,
+      '%vocabulary_id' => $vocabulary->vid,
+    );
+  }
+
+  // Node-based variable translation is only available if we have a node.
+  if (isset($params['node'])) {
+    $node = $params['node'];
+    $variables += array(
+      '%uid' => $node->uid,
+      '%node_url' => url('node/'. $node->nid, array('absolute' => TRUE)),
+      '%node_type' => node_get_types('name', $node),
+      '%title' => $node->title,
+      '%teaser' => $node->teaser,
+      '%body' => $node->body,
+    );
+  }
+  $subject = strtr($context['subject'], $variables);
+  $body = strtr($context['message'], $variables);
+  $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
+  $message['body'][] = drupal_html_to_text($body);
+}
+
+function system_message_action_form($context) {
+  $form['message'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Message'),
+    '#default_value' => isset($context['message']) ? $context['message'] : '',
+    '#required' => TRUE,
+    '#rows' => '8',
+    '#description' => t('The message to be displayed to the current user. You may include the following variables: %site_name, %username, %node_url, %node_type, %title, %teaser, %body. Not all variables will be available in all contexts.'),
+  );
+  return $form;
+}
+
+function system_message_action_submit($form, $form_state) {
+  return array('message' => $form_state['values']['message']);
+}
+
+/**
+ * A configurable Drupal action. Sends a message to the current user's screen.
+ */
+function system_message_action(&$object, $context = array()) {
+  global $user;
+  $variables = array(
+    '%site_name' => variable_get('site_name', 'Drupal'),
+    '%username' => $user->name ? $user->name : variable_get('anonymous', t('Anonymous')),
+  );
+
+  // This action can be called in any context, but if placeholders
+  // are used a node object must be present to be the source
+  // of substituted text.
+  switch ($context['hook']) {
+    case 'nodeapi':
+      // Because this is not an action of type 'node' the node
+      // will not be passed as $object, but it will still be available
+      // in $context.
+      $node = $context['node'];
+      break;
+    // The comment hook also provides the node, in context.
+    case 'comment':
+      $comment = $context['comment'];
+      $node = node_load($comment->nid);
+      break;
+    case 'taxonomy':
+      $vocabulary = taxonomy_vocabulary_load($object->vid);
+      $variables = array_merge($variables, array(
+        '%term_name' => $object->name,
+        '%term_description' => $object->description,
+        '%term_id' => $object->tid,
+        '%vocabulary_name' => $vocabulary->name,
+        '%vocabulary_description' => $vocabulary->description,
+        '%vocabulary_id' => $vocabulary->vid,
+        )
+      );
+      break;
+    default:
+      // We are being called directly.
+      $node = $object;
+  }
+
+  if (isset($node) && is_object($node)) {
+    $variables = array_merge($variables, array(
+      '%uid' => $node->uid,
+      '%node_url' => url('node/'. $node->nid, array('absolute' => TRUE)),
+      '%node_type' => check_plain(node_get_types('name', $node)),
+      '%title' => filter_xss($node->title),
+      '%teaser' => filter_xss($node->teaser),
+      '%body' => filter_xss($node->body),
+      )
+    );
+  }
+  $context['message'] = strtr($context['message'], $variables);
+  drupal_set_message($context['message']);
+}
+
+/**
+ * Implementation of a configurable Drupal action. Redirect user to a URL.
+ */
+function system_goto_action_form($context) {
+  $form['url'] = array(
+    '#type' => 'textfield',
+    '#title' => t('URL'),
+    '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'),
+    '#default_value' => isset($context['url']) ? $context['url'] : '',
+    '#required' => TRUE,
+  );
+  return $form;
+}
+
+function system_goto_action_submit($form, $form_state) {
+  return array(
+    'url' => $form_state['values']['url']
+  );
+}
+
+function system_goto_action($object, $context) {
+  drupal_goto($context['url']);
+}
+
+/**
+ * Generate an array of time zones and their local time&date.
+ */
+function _system_zonelist() {
+  $timestamp = time();
+  $zonelist = array(-11, -10, -9.5, -9, -8, -7, -6, -5, -4, -3.5, -3, -2, -1, 0, 1, 2, 3, 3.5, 4, 5, 5.5, 5.75, 6, 6.5, 7, 8, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 14);
+  $zones = array();
+  foreach ($zonelist as $offset) {
+    $zone = $offset * 3600;
+    $zones[$zone] = format_date($timestamp, 'custom', variable_get('date_format_long', 'l, F j, Y - H:i') .' O', $zone);
+  }
+  return $zones;
+}
+
+/**
+ * Checks whether the server is capable of issuing HTTP requests.
+ *
+ * The function sets the drupal_http_request_fail system variable to TRUE if
+ * drupal_http_request() does not work and then the system status report page
+ * will contain an error.
+ *
+ * @return
+ *  Whether the admin/reports/request-test page can be requested via HTTP
+ *  and contains the same output as if called via the menu system.
+ */
+function system_check_http_request() {
+  // Check whether we can do any request at all. First get the results for
+  // a very simple page which has access TRUE set via the menu system. Then,
+  // try to drupal_http_request() the same page and compare.
+  ob_start();
+  $path = 'admin/reports/request-test';
+  menu_execute_active_handler($path);
+  $nothing = ob_get_contents();
+  ob_end_clean();
+  $result = drupal_http_request(url($path, array('absolute' => TRUE)));
+  $works = isset($result->data) && $result->data == $nothing;
+  variable_set('drupal_http_request_fails', !$works);
+  return $works;
+}
+
+/**
+ * Format the Powered by Drupal text.
+ *
+ * @ingroup themeable
+ */
+function theme_system_powered_by($image_path) {
+  $image = theme('image', $image_path, t('Powered by Drupal, an open source content management system'), t('Powered by Drupal, an open source content management system'));
+  return l($image, 'http://drupal.org', array('html' => TRUE, 'absolute' => TRUE, 'external' => TRUE));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,926 @@
+<?php
+// $Id: taxonomy.admin.inc,v 1.22.2.1 2008/02/07 20:46:57 goba Exp $
+
+/**
+ * @file
+ * Administrative page callbacks for the taxonomy module.
+ */
+
+/**
+ * Form builder to list and manage vocabularies.
+ *
+ * @ingroup forms
+ * @see taxonomy_overview_vocabularies_submit()
+ * @see theme_taxonomy_overview_vocabularies()
+ */
+function taxonomy_overview_vocabularies() {
+  $vocabularies = taxonomy_get_vocabularies();
+  $form = array('#tree' => TRUE);
+  foreach ($vocabularies as $vocabulary) {
+    $types = array();
+    foreach ($vocabulary->nodes as $type) {
+      $node_type = node_get_types('name', $type);
+      $types[] = $node_type ? check_plain($node_type) : check_plain($type);
+    }
+    $form[$vocabulary->vid]['#vocabulary'] = (array)$vocabulary;
+    $form[$vocabulary->vid]['name'] = array('#value' => check_plain($vocabulary->name));
+    $form[$vocabulary->vid]['types'] = array('#value' => implode(', ', $types));
+    $form[$vocabulary->vid]['weight'] = array('#type' => 'weight', '#delta' => 10, '#default_value' => $vocabulary->weight);
+    $form[$vocabulary->vid]['edit'] = array('#value' => l(t('edit vocabulary'), "admin/content/taxonomy/edit/vocabulary/$vocabulary->vid"));
+    $form[$vocabulary->vid]['list'] = array('#value' => l(t('list terms'), "admin/content/taxonomy/$vocabulary->vid"));
+    $form[$vocabulary->vid]['add'] = array('#value' => l(t('add terms'), "admin/content/taxonomy/$vocabulary->vid/add/term"));
+  }
+
+  // Only make this form include a submit button and weight if more than one
+  // vocabulary exists.
+  if (count($vocabularies) > 1) {
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+  }
+  elseif (isset($vocabulary)) {
+    unset($form[$vocabulary->vid]['weight']);
+  }
+  return $form;
+}
+
+/**
+ * Submit handler for vocabularies overview. Updates changed vocabulary weights.
+ *
+ * @see taxonomy_overview_vocabularies()
+ */
+function taxonomy_overview_vocabularies_submit($form, &$form_state) {
+  foreach ($form_state['values'] as $vid => $vocabulary) {
+    if (is_numeric($vid) && $form[$vid]['#vocabulary']['weight'] != $form_state['values'][$vid]['weight']) {
+      $form[$vid]['#vocabulary']['weight'] = $form_state['values'][$vid]['weight'];
+      taxonomy_save_vocabulary($form[$vid]['#vocabulary']);
+    }
+  }
+}
+
+/**
+ * Theme the vocabulary overview as a sortable list of vocabularies.
+ *
+ * @ingroup themeable
+ * @see taxonomy_overview_vocabularies()
+ */
+function theme_taxonomy_overview_vocabularies($form) {
+  $rows = array();
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['name'])) {
+      $vocabulary = &$form[$key];
+
+      $row = array();
+      $row[] = drupal_render($vocabulary['name']);
+      $row[] = drupal_render($vocabulary['types']);
+      if (isset($vocabulary['weight'])) {
+        $vocabulary['weight']['#attributes']['class'] = 'vocabulary-weight';
+        $row[] = drupal_render($vocabulary['weight']);
+      }
+      $row[] = drupal_render($vocabulary['edit']);
+      $row[] = drupal_render($vocabulary['list']);
+      $row[] = drupal_render($vocabulary['add']);
+      $rows[] = array('data' => $row, 'class' => 'draggable');
+    }
+  }
+  if (empty($rows)) {
+    $rows[] = array(array('data' => t('No vocabularies available.'), 'colspan' => '5'));
+  }
+
+  $header = array(t('Name'), t('Type'));
+  if (isset($form['submit'])) {
+    $header[] = t('Weight');
+    drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
+  }
+  $header[] = array('data' => t('Operations'), 'colspan' => '3');
+  return theme('table', $header, $rows, array('id' => 'taxonomy')) . drupal_render($form);
+}
+
+/**
+ * Display form for adding and editing vocabularies.
+ *
+ * @ingroup forms
+ * @see taxonomy_form_vocabulary_submit()
+ */
+function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
+  $edit += array(
+    'name' => '',
+    'description' => '',
+    'help' => '',
+    'nodes' => array(),
+    'hierarchy' => 0,
+    'relations' => 0,
+    'tags' => 0,
+    'multiple' => 0,
+    'required' => 0,
+    'weight' => 0,
+  );
+  $form['identification'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Identification'),
+    '#collapsible' => TRUE,
+  );
+  $form['identification']['name'] = array('#type' => 'textfield',
+    '#title' => t('Vocabulary name'),
+    '#default_value' => $edit['name'],
+    '#maxlength' => 255,
+    '#description' => t('The name for this vocabulary, e.g., <em>"Tags"</em>.'),
+    '#required' => TRUE,
+  );
+  $form['identification']['description'] = array('#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $edit['description'],
+    '#description' => t('Description of the vocabulary; can be used by modules.'),
+  );
+  $form['identification']['help'] = array('#type' => 'textfield',
+    '#title' => t('Help text'),
+    '#maxlength' => 255,
+    '#default_value' => $edit['help'],
+    '#description' => t('Instructions to present to the user when selecting terms, e.g., <em>"Enter a comma separated list of words"</em>.'),
+  );
+  $form['content_types'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Content types'),
+    '#collapsible' => TRUE,
+  );
+  $form['content_types']['nodes'] = array('#type' => 'checkboxes',
+    '#title' => t('Content types'),
+    '#default_value' => $edit['nodes'],
+    '#options' => array_map('check_plain', node_get_types('names')),
+    '#description' => t('Select content types to categorize using this vocabulary.'),
+  );
+  $form['settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['settings']['tags'] = array('#type' => 'checkbox',
+    '#title' => t('Tags'),
+    '#default_value' => $edit['tags'],
+    '#description' => t('Terms are created by users when submitting posts by typing a comma separated list.'),
+  );
+  $form['settings']['multiple'] = array('#type' => 'checkbox',
+    '#title' => t('Multiple select'),
+    '#default_value' => $edit['multiple'],
+    '#description' => t('Allows posts to have more than one term from this vocabulary (always true for tags).'),
+  );
+  $form['settings']['required'] = array('#type' => 'checkbox',
+    '#title' => t('Required'),
+    '#default_value' => $edit['required'],
+    '#description' => t('At least one term in this vocabulary must be selected when submitting a post.'),
+  );
+  $form['settings']['weight'] = array('#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $edit['weight'],
+    '#description' => t('Vocabularies are displayed in ascending order by weight.'),
+  );
+  // Set the hierarchy to "multiple parents" by default. This simplifies the
+  // vocabulary form and standardizes the term form.
+  $form['hierarchy'] = array('#type' => 'value',
+    '#value' => '0',
+  );
+  // Enable "related terms" by default.
+  $form['relations'] = array('#type' => 'value',
+    '#value' => '1',
+  );
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+  if (isset($edit['vid'])) {
+    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+    $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
+    $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
+  }
+  return $form;
+}
+
+/**
+ * Accept the form submission for a vocabulary and save the results.
+ */
+function taxonomy_form_vocabulary_submit($form, &$form_state) {
+  // Fix up the nodes array to remove unchecked nodes.
+  $form_state['values']['nodes'] = array_filter($form_state['values']['nodes']);
+  switch (taxonomy_save_vocabulary($form_state['values'])) {
+    case SAVED_NEW:
+      drupal_set_message(t('Created new vocabulary %name.', array('%name' => $form_state['values']['name'])));
+      watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_state['values']['vid']));
+      break;
+    case SAVED_UPDATED:
+      drupal_set_message(t('Updated vocabulary %name.', array('%name' => $form_state['values']['name'])));
+      watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/vocabulary/'. $form_state['values']['vid']));
+      break;
+  }
+
+  $form_state['vid'] = $form_state['values']['vid'];
+  $form_state['redirect'] = 'admin/content/taxonomy';
+  return;
+}
+
+/**
+ * Page to edit a vocabulary.
+ */
+function taxonomy_admin_vocabulary_edit($vocabulary) {
+  if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || isset($_POST['confirm'])) {
+    return drupal_get_form('taxonomy_vocabulary_confirm_delete', $vocabulary->vid);
+  }
+  return drupal_get_form('taxonomy_form_vocabulary', (array)$vocabulary);
+}
+
+/**
+ * Page to edit a vocabulary term.
+ */
+function taxonomy_admin_term_edit($tid) {
+  if ($term = (array)taxonomy_get_term($tid)) {
+    return drupal_get_form('taxonomy_form_term', taxonomy_vocabulary_load($term['vid']), $term);
+  }
+  return drupal_not_found();
+}
+
+/**
+ * Form builder for the taxonomy terms overview.
+ *
+ * Display a tree of all the terms in a vocabulary, with options to edit
+ * each one. The form is made drag and drop by the theme function.
+ *
+ * @ingroup forms
+ * @see taxonomy_overview_terms_submit()
+ * @see theme_taxonomy_overview_terms()
+ */
+function taxonomy_overview_terms(&$form_state, $vocabulary) {
+  global $pager_page_array, $pager_total, $pager_total_items;
+
+  // Check for confirmation forms.
+  if (isset($form_state['confirm_reset_alphabetical'])) {
+    return taxonomy_vocabulary_confirm_reset_alphabetical($form_state, $vocabulary->vid);
+  }
+
+  drupal_set_title(t('Terms in %vocabulary', array('%vocabulary' => $vocabulary->name)));
+  $form = array(
+    '#vocabulary' => (array)$vocabulary,
+    '#tree' => TRUE,
+    '#parent_fields' => FALSE,
+  );
+
+  $page            = isset($_GET['page']) ? $_GET['page'] : 0;
+  $page_increment  = 10;  // Number of terms per page.
+  $page_entries    = 0;   // Elements shown on this page.
+  $before_entries  = 0;   // Elements at the root level before this page.
+  $after_entries   = 0;   // Elements at the root level after this page.
+  $root_entries    = 0;   // Elements at the root level on this page.
+
+  // Terms from previous and next pages are shown if the term tree would have
+  // been cut in the middle. Keep track of how many extra terms we show on each
+  // page of terms.
+  $back_peddle    = NULL;
+  $forward_peddle = 0;
+
+  // An array of the terms to be displayed on this page.
+  $current_page = array();
+
+  // Case for free tagging.
+  if ($vocabulary->tags) {
+    // We are not calling taxonomy_get_tree because that might fail with a big
+    // number of tags in the freetagging vocabulary.
+    $results = pager_query(db_rewrite_sql('SELECT t.*, h.parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $page_increment, 0, NULL, $vocabulary->vid);
+    $total_entries = db_query(db_rewrite_sql('SELECT count(*) FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d'), $page_increment, 0, NULL, $vocabulary->vid);
+    while ($term = db_fetch_object($results)) {
+      $key = 'tid:'. $term->tid .':0';
+      $current_page[$key] = $term;
+      $page_entries++;
+    }
+  }
+  // Case for restricted vocabulary.
+  else {
+    $term_deltas = array();
+    $tree = taxonomy_get_tree($vocabulary->vid);
+    $term = current($tree);
+    do {
+      // In case this tree is completely empty.
+      if (empty($term)) {
+        break;
+      }
+      // Count entries before the current page.
+      if ($page && ($page * $page_increment) > $before_entries && !isset($back_peddle)) {
+        $before_entries++;
+        continue;
+      }
+      // Count entries after the current page.
+      elseif ($page_entries > $page_increment && isset($complete_tree)) {
+        $after_entries++;
+        continue;
+      }
+
+      // Do not let a term start the page that is not at the root.
+      if (isset($term->depth) && ($term->depth > 0) && !isset($back_peddle)) {
+        $back_peddle = 0;
+        while ($pterm = prev($tree)) {
+          $before_entries--;
+          $back_peddle++;
+          if ($pterm->depth == 0) {
+            prev($tree);
+            continue 2; // Jump back to the start of the root level parent.
+          }
+        }
+      }
+      $back_peddle = isset($back_peddle) ? $back_peddle : 0;
+
+      // Continue rendering the tree until we reach the a new root item.
+      if ($page_entries >= $page_increment + $back_peddle + 1 && $term->depth == 0 && $root_entries > 1) {
+        $complete_tree = TRUE;
+        // This new item at the root level is the first item on the next page.
+        $after_entries++;
+        continue;
+      }
+      if ($page_entries >= $page_increment + $back_peddle) {
+        $forward_peddle++;
+      }
+
+      // Finally, if we've gotten down this far, we're rendering a term on this page.
+      $page_entries++;
+      $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
+      $key = 'tid:'. $term->tid .':'. $term_deltas[$term->tid];
+
+      // Keep track of the first term displayed on this page.
+      if ($page_entries == 1) {
+        $form['#first_tid'] = $term->tid;
+      }
+      // Keep a variable to make sure at least 2 root elements are displayed.
+      if ($term->parents[0] == 0) {
+        $root_entries++;
+      }
+      $current_page[$key] = $term;
+    } while ($term = next($tree));
+
+    // Because we didn't use a pager query, set the necessary pager variables.
+    $total_entries = $before_entries + $page_entries + $after_entries;
+    $pager_total_items[0] = $total_entries;
+    $pager_page_array[0] = $page;
+    $pager_total[0] = ceil($total_entries / $page_increment);
+  }
+
+  // If this form was already submitted once, it's probably hit a validation
+  // error. Ensure the form is rebuilt in the same order as the user submitted.
+  if (!empty($form_state['post'])) {
+    $order = array_flip(array_keys($form_state['post'])); // Get the $_POST order.
+    $current_page = array_merge($order, $current_page); // Update our form with the new order.
+    foreach ($current_page as $key => $term) {
+      // Verify this is a term for the current page and set at the current depth.
+      if (is_array($form_state['post'][$key]) && is_numeric($form_state['post'][$key]['tid'])) {
+        $current_page[$key]->depth = $form_state['post'][$key]['depth'];
+      }
+      else {
+        unset($current_page[$key]);
+      }
+    }
+  }
+
+  // Build the actual form.
+  foreach ($current_page as $key => $term) {
+    // Save the term for the current page so we don't have to load it a second time.
+    $form[$key]['#term'] = (array)$term;
+    if (isset($term->parents)) {
+      $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
+      unset($form[$key]['#term']['parents'], $term->parents);
+    }
+
+    $form[$key]['view'] = array('#value' => l($term->name, "taxonomy/term/$term->tid"));
+    if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
+      $form['#parent_fields'] = TRUE;
+      $form[$key]['tid'] = array(
+        '#type' => 'hidden',
+        '#value' => $term->tid
+      );
+      $form[$key]['parent'] = array(
+        '#type' => 'hidden',
+        // Yes, default_value on a hidden. It needs to be changeable by the javascript.
+        '#default_value' => $term->parent,
+      );
+      $form[$key]['depth'] = array(
+        '#type' => 'hidden',
+        // Same as above, the depth is modified by javascript, so it's a default_value.
+        '#default_value' => $term->depth,
+      );
+    }
+    $form[$key]['edit'] = array('#value' => l(t('edit'), "admin/content/taxonomy/edit/term/$term->tid", array('query' => drupal_get_destination())));
+  }
+
+  $form['#total_entries'] = $total_entries;
+  $form['#page_increment'] = $page_increment;
+  $form['#page_entries'] = $page_entries;
+  $form['#back_peddle'] = $back_peddle;
+  $form['#forward_peddle'] = $forward_peddle;
+  $form['#empty_text'] = t('No terms available.');
+
+  if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save')
+    );
+    $form['reset_alphabetical'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset to alphabetical')
+    );
+    $form['destination'] = array(
+      '#type' => 'hidden',
+      '#value' => $_GET['q'] . (isset($_GET['page']) ? '?page='. $_GET['page'] : '')
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Submit handler for terms overview form.
+ *
+ * Rather than using a textfield or weight field, this form depends entirely
+ * upon the order of form elements on the page to determine new weights.
+ *
+ * Because there might be hundreds or thousands of taxonomy terms that need to
+ * be ordered, terms are weighted from 0 to the number of terms in the
+ * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
+ * lowest to highest, but are not necessarily sequential. Numbers may be skipped
+ * when a term has children so that reordering is minimal when a child is
+ * added or removed from a term.
+ *
+ * @see taxonomy_overview_terms()
+ */
+function taxonomy_overview_terms_submit($form, &$form_state) {
+  if ($form_state['clicked_button']['#value'] == t('Reset to alphabetical')) {
+    // Execute the reset action.
+    if ($form_state['values']['reset_alphabetical'] === TRUE) {
+      return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
+    }
+    // Rebuild the form to confirm the reset action.
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_reset_alphabetical'] = TRUE;
+    return;
+  }
+
+  $order = array_flip(array_keys($form['#post'])); // Get the $_POST order.
+  $form_state['values'] = array_merge($order, $form_state['values']); // Update our original form with the new order.
+
+  $vocabulary = $form['#vocabulary'];
+  $hierarchy = 0; // Update the current hierarchy type as we go.
+
+  $changed_terms = array();
+  $tree = taxonomy_get_tree($vocabulary['vid']);
+
+  if (empty($tree)) {
+    return;
+  }
+
+  // Build a list of all terms that need to be updated on previous pages.
+  $weight = 0;
+  $term = (array)$tree[0];
+  while ($term['tid'] != $form['#first_tid']) {
+    if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+      $term['parent'] = $term['parents'][0];
+      $term['weight'] = $weight;
+      $changed_terms[$term['tid']] = $term;
+    }
+    $weight++;
+    $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+    $term = (array)$tree[$weight];
+  }
+
+  // Renumber the current page weights and assign any new parents.
+  $level_weights = array();
+  foreach ($form_state['values'] as $tid => $values) {
+    if (isset($form[$tid]['#term'])) {
+      $term = $form[$tid]['#term'];
+      // Give terms at the root level a weight in sequence with terms on previous pages.
+      if ($values['parent'] == 0 && $term['weight'] != $weight) {
+        $term['weight'] = $weight;
+        $changed_terms[$term['tid']] = $term;
+      }
+      // Terms not at the root level can safely start from 0 because they're all on this page.
+      elseif ($values['parent'] > 0) {
+        $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
+        if ($level_weights[$values['parent']] != $term['weight']) {
+          $term['weight'] = $level_weights[$values['parent']];
+          $changed_terms[$term['tid']] = $term;
+        }
+      }
+      // Update any changed parents.
+      if ($values['parent'] != $term['parent']) {
+        $term['parent'] = $values['parent'];
+        $changed_terms[$term['tid']] = $term;
+      }
+      $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
+      $weight++;
+    }
+  }
+
+  // Build a list of all terms that need to be updated on following pages.
+  for ($weight; $weight < count($tree); $weight++) {
+    $term = (array)$tree[$weight];
+    if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
+      $term['parent'] = $term['parents'][0];
+      $term['weight'] = $weight;
+      $changed_terms[$term['tid']] = $term;
+    }
+    $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
+  }
+
+  // Save all updated terms.
+  foreach ($changed_terms as $term) {
+    taxonomy_save_term($term);
+  }
+
+  // Update the vocabulary hierarchy to flat or single hierarchy.
+  if ($vocabulary['hierarchy'] != $hierarchy) {
+    $vocabulary['hierarchy'] = $hierarchy;
+    taxonomy_save_vocabulary($vocabulary);
+  }
+}
+
+/**
+ * Theme the terms overview as a sortable list of terms.
+ *
+ * @ingroup themeable
+ * @see taxonomy_overview_terms()
+ */
+function theme_taxonomy_overview_terms($form) {
+  $page_increment  = $form['#page_increment'];
+  $page_entries    = $form['#page_entries'];
+  $back_peddle     = $form['#back_peddle'];
+  $forward_peddle  = $form['#forward_peddle'];
+
+  // Add drag and drop if parent fields are present in the form.
+  if ($form['#parent_fields']) {
+    drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
+    drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
+    drupal_add_js(drupal_get_path('module', 'taxonomy') .'/taxonomy.js');
+    drupal_add_js(array('taxonomy' => array('backPeddle' => $back_peddle, 'forwardPeddle' => $forward_peddle)), 'setting');
+    drupal_add_css(drupal_get_path('module', 'taxonomy') .'/taxonomy.css');
+  }
+
+  $errors = form_get_errors() != FALSE ? form_get_errors() : array();
+  $rows = array();
+  foreach (element_children($form) as $key) {
+    if (isset($form[$key]['#term'])) {
+      $term = &$form[$key];
+
+      $row = array();
+      $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', $term['#term']['depth']) : '') . drupal_render($term['view']);
+      if ($form['#parent_fields']) {
+        $term['tid']['#attributes']['class'] = 'term-id';
+        $term['parent']['#attributes']['class'] = 'term-parent';
+        $term['depth']['#attributes']['class'] = 'term-depth';
+        $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
+      }
+      $row[] = drupal_render($term['edit']);
+
+      $row = array('data' => $row);
+      $rows[$key] = $row;
+    }
+  }
+
+  // Add necessary classes to rows.
+  $row_position = 0;
+  foreach ($rows as $key => $row) {
+    $classes = array();
+    if (isset($form['#parent_fields'])) {
+      $classes[] = 'draggable';
+    }
+
+    // Add classes that mark which terms belong to previous and next pages.
+    if ($row_position < $back_peddle || $row_position >= $page_entries - $forward_peddle) {
+      $classes[] = 'taxonomy-term-preview';
+    }
+
+    if ($row_position !== 0 && $row_position !== count($rows) - 1) {
+      if ($row_position == $back_peddle - 1 || $row_position == $page_entries - $forward_peddle - 1) {
+        $classes[] = 'taxonomy-term-divider-top';
+      }
+      elseif ($row_position == $back_peddle || $row_position == $page_entries - $forward_peddle) {
+        $classes[] = 'taxonomy-term-divider-bottom';
+      }
+    }
+
+    // Add an error class if this row contains a form error.
+    foreach ($errors as $error_key => $error) {
+      if (strpos($error_key, $key) === 0) {
+        $classes[] = 'error';
+      }
+    }
+    $rows[$key]['class'] = implode(' ', $classes);
+    $row_position++;
+  }
+
+  if (empty($rows)) {
+    $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '2'));
+  }
+
+  $header = array(t('Name'), t('Operations'));
+  $output = theme('table', $header, $rows, array('id' => 'taxonomy'));
+  $output .= drupal_render($form);
+  $output .= theme('pager', NULL, $page_increment);
+
+  return $output;
+}
+
+/**
+ * Menu callback; return the edit form for a new term after setting the title.
+ */
+function taxonomy_add_term_page($vocabulary) {
+  drupal_set_title(t('Add term to %vocabulary', array('%vocabulary' => $vocabulary->name)));
+  return drupal_get_form('taxonomy_form_term' , $vocabulary);
+}
+
+/**
+ * Form function for the term edit form.
+ *
+ * @ingroup forms
+ * @see taxonomy_form_term_submit()
+ */
+function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
+  $edit += array(
+    'name' => '',
+    'description' => '',
+    'tid' => NULL,
+    'weight' => 0,
+  );
+
+  $parent = array_keys(taxonomy_get_parents($edit['tid']));
+  $form['#term'] = $edit;
+  $form['#term']['parent'] = $parent;
+  $form['#vocabulary'] = (array)$vocabulary;
+  $form['#vocabulary']['nodes'] = drupal_map_assoc($vocabulary->nodes);;
+
+  // Check for confirmation forms.
+  if (isset($form_state['confirm_delete'])) {
+    return array_merge($form, taxonomy_term_confirm_delete($form_state, $edit['tid']));
+  }
+  elseif (isset($form_state['confirm_parents'])) {
+    return array_merge($form, taxonomy_term_confirm_parents($form_state, $vocabulary));
+  }
+
+  $form['identification'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Identification'),
+    '#collapsible' => TRUE,
+  );
+  $form['identification']['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Term name'),
+    '#default_value' => $edit['name'],
+    '#maxlength' => 255,
+    '#description' => t('The name of this term.'),
+    '#required' => TRUE);
+  $form['identification']['description'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $edit['description'],
+    '#description' => t('A description of the term. To be displayed on taxonomy/term pages and RSS feeds.'));
+
+  $form['advanced'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Advanced options'),
+    '#collapsible' => TRUE,
+    '#collapsed' => $vocabulary->hierarchy > 1 ? FALSE : TRUE,
+  );
+
+  // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
+  // items so we check for taxonomy_override_selector before loading the
+  // full vocabulary. Contrib modules can then intercept before
+  // hook_form_alter to provide scalable alternatives.
+  if (!variable_get('taxonomy_override_selector', FALSE)) {
+    $parent = array_keys(taxonomy_get_parents($edit['tid']));
+    $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']);
+
+    // A term can't be the child of itself, nor of its children.
+    foreach ($children as $child) {
+      $exclude[] = $child->tid;
+    }
+    $exclude[] = $edit['tid'];
+
+    $form['advanced']['parent'] = _taxonomy_term_select(t('Parents'), 'parent', $parent, $vocabulary->vid, t('Parent terms') .'.', 1, '<'. t('root') .'>', $exclude);
+    $form['advanced']['relations'] = _taxonomy_term_select(t('Related terms'), 'relations', array_keys(taxonomy_get_related($edit['tid'])), $vocabulary->vid, NULL, 1, '<'. t('none') .'>', array($edit['tid']));
+  }
+  $form['advanced']['synonyms'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Synonyms'),
+    '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])),
+    '#description' => t('Synonyms of this term, one synonym per line.'));
+  $form['advanced']['weight'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Weight'),
+    '#size' => 6,
+    '#default_value' => $edit['weight'],
+    '#description' => t('Terms are displayed in ascending order by weight.'),
+    '#required' => TRUE);
+  $form['vid'] = array(
+    '#type' => 'value',
+    '#value' => $vocabulary->vid);
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'));
+
+  if ($edit['tid']) {
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'));
+    $form['tid'] = array(
+      '#type' => 'value',
+      '#value' => $edit['tid']);
+  }
+  else {
+    $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
+  }
+
+  return $form;
+}
+
+/**
+ * Validation handler for the term edit form. Ensure numeric weight values.
+ *
+ * @see taxonomy_form_term()
+ */
+function taxonomy_form_term_validate($form, &$form_state) {
+  if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
+    form_set_error('weight', t('Weight value must be numeric.'));
+  }
+}
+
+/**
+ * Submit handler to insert or update a term.
+ *
+ * @see taxonomy_form_term()
+ */
+function taxonomy_form_term_submit($form, &$form_state) {
+  if ($form_state['clicked_button']['#value'] == t('Delete')) {
+    // Execute the term deletion.
+    if ($form_state['values']['delete'] === TRUE) {
+      return taxonomy_term_confirm_delete_submit($form, $form_state);
+    }
+    // Rebuild the form to confirm term deletion.
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_delete'] = TRUE;
+    return;
+  }
+  // Rebuild the form to confirm enabling multiple parents.
+  elseif ($form_state['clicked_button']['#value'] == t('Save') && !$form['#vocabulary']['tags'] && count($form_state['values']['parent']) > 1 && $form['#vocabulary']['hierarchy'] < 2) {
+    $form_state['rebuild'] = TRUE;
+    $form_state['confirm_parents'] = TRUE;
+    return;
+  }
+
+  switch (taxonomy_save_term($form_state['values'])) {
+    case SAVED_NEW:
+      drupal_set_message(t('Created new term %term.', array('%term' => $form_state['values']['name'])));
+      watchdog('taxonomy', 'Created new term %term.', array('%term' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_state['values']['tid']));
+      break;
+    case SAVED_UPDATED:
+      drupal_set_message(t('Updated term %term.', array('%term' => $form_state['values']['name'])));
+      watchdog('taxonomy', 'Updated term %term.', array('%term' => $form_state['values']['name']), WATCHDOG_NOTICE, l(t('edit'), 'admin/content/taxonomy/edit/term/'. $form_state['values']['tid']));
+      break;
+  }
+
+  if (!$form['#vocabulary']['tags']) {
+    $current_parent_count = count($form_state['values']['parent']);
+    $previous_parent_count = count($form['#term']['parent']);
+    // Root doesn't count if it's the only parent.
+    if ($current_parent_count == 1 && isset($form_state['values']['parent'][''])) {
+      $current_parent_count = 0;
+      $form_state['values']['parent'] = array();
+    }
+
+    // If the number of parents has been reduced to one or none, do a check on the
+    // parents of every term in the vocabulary value.
+    if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
+      taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+    }
+    // If we've increased the number of parents and this is a single or flat
+    // hierarchy, update the vocabulary immediately.
+    elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']['hierarchy'] < 2) {
+      $form['#vocabulary']['hierarchy'] = $current_parent_count == 1 ? 1 : 2;
+      taxonomy_save_vocabulary($form['#vocabulary']);
+    }
+  }
+
+  $form_state['tid'] = $form_state['values']['tid'];
+  $form_state['redirect'] = 'admin/content/taxonomy';
+  return;
+}
+
+/**
+ * Form builder for the confirmation of multiple term parents.
+ *
+ * @ingroup forms
+ * @see taxonomy_form_term()
+ */
+function taxonomy_term_confirm_parents(&$form_state, $vocabulary) {
+  $form = array();
+  foreach (element_children($form_state['values']) as $key) {
+    $form[$key] = array(
+      '#type' => 'value',
+      '#value' => $form_state['values'][$key],
+    );
+  }
+  $question = t('Set multiple term parents?');
+  $description = '<p>'. t("Adding multiple parents to a term will cause the %vocabulary vocabulary to look for multiple parents on every term. Because multiple parents are not supported when using the drag and drop outline interface, drag and drop will be disabled if you enable this option. If you choose to have multiple parents, you will only be able to set parents by using the term edit form.", array('%vocabulary' => $vocabulary->name)) .'</p>';
+  $description .= '<p>'. t("You may re-enable the drag and drop interface at any time by reducing multiple parents to a single parent for the terms in this vocabulary.") .'</p>';
+  return confirm_form($form, $question, drupal_get_destination(), $description, t('Set multiple parents'));
+}
+
+/**
+ * Form builder for the term delete form.
+ *
+ * @ingroup forms
+ * @see taxonomy_term_confirm_delete_submit()
+ */
+function taxonomy_term_confirm_delete(&$form_state, $tid) {
+  $term = taxonomy_get_term($tid);
+
+  $form['type'] = array('#type' => 'value', '#value' => 'term');
+  $form['name'] = array('#type' => 'value', '#value' => $term->name);
+  $form['tid'] = array('#type' => 'value', '#value' => $tid);
+  $form['delete'] = array('#type' => 'value', '#value' => TRUE);
+  return confirm_form($form,
+                  t('Are you sure you want to delete the term %title?',
+                  array('%title' => $term->name)),
+                  'admin/content/taxonomy',
+                  t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
+                  t('Delete'),
+                  t('Cancel'));
+}
+
+/**
+ * Submit handler to delete a term after confirmation.
+ *
+ * @see taxonomy_term_confirm_delete()
+ */
+function taxonomy_term_confirm_delete_submit($form, &$form_state) {
+  taxonomy_del_term($form_state['values']['tid']);
+  taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+  drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
+  watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/content/taxonomy';
+  return;
+}
+
+/**
+ * Form builder for the vocabulary delete confirmation form.
+ *
+ * @ingroup forms
+ * @see taxonomy_vocabulary_confirm_delete_submit()
+ */
+function taxonomy_vocabulary_confirm_delete(&$form_state, $vid) {
+  $vocabulary = taxonomy_vocabulary_load($vid);
+
+  $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
+  $form['vid'] = array('#type' => 'value', '#value' => $vid);
+  $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
+  return confirm_form($form,
+                  t('Are you sure you want to delete the vocabulary %title?',
+                  array('%title' => $vocabulary->name)),
+                  'admin/content/taxonomy',
+                  t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
+                  t('Delete'),
+                  t('Cancel'));
+}
+
+/**
+ * Submit handler to delete a vocabulary after confirmation.
+ *
+ * @see taxonomy_vocabulary_confirm_delete()
+ */
+function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
+  $status = taxonomy_del_vocabulary($form_state['values']['vid']);
+  drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
+  watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/content/taxonomy';
+  return;
+}
+
+/**
+ * Form builder to confirm reseting a vocabulary to alphabetical order.
+ *
+ * @ingroup forms
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical(&$form_state, $vid) {
+  $vocabulary = taxonomy_vocabulary_load($vid);
+
+  $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
+  $form['vid'] = array('#type' => 'value', '#value' => $vid);
+  $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
+  $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
+  return confirm_form($form,
+                  t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
+                  array('%title' => $vocabulary->name)),
+                  'admin/content/taxonomy/'. $vid,
+                  t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
+                  t('Reset to alphabetical'),
+                  t('Cancel'));
+}
+
+/**
+ * Submit handler to reset a vocabulary to alphabetical order after confirmation.
+ *
+ * @see taxonomy_vocabulary_confirm_reset_alphabetical()
+ */
+function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
+  db_query('UPDATE {term_data} t SET weight = 0 WHERE vid = %d', $form_state['values']['vid']);
+  drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
+  watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
+  $form_state['redirect'] = 'admin/content/taxonomy/'. $form_state['values']['vid'];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,14 @@
+/* $Id: taxonomy.css,v 1.5 2008/01/25 21:20:26 goba Exp $ */
+
+tr.taxonomy-term-preview {
+  background-color: #EEE;
+}
+tr.taxonomy-term-divider-top {
+  border-bottom: none;
+}
+tr.taxonomy-term-divider-bottom {
+  border-top: 1px dotted #CCC;
+}
+.taxonomy-term-description {
+  margin: 5px 0 20px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: taxonomy.info,v 1.4 2007/06/08 05:50:56 dries Exp $
+name = Taxonomy
+description = Enables the categorization of content.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,286 @@
+<?php
+// $Id: taxonomy.install,v 1.7 2008/01/08 07:46:41 goba Exp $
+
+/**
+ * Implementation of hook_schema().
+ */
+function taxonomy_schema() {
+  $schema['term_data'] = array(
+    'description' => t('Stores term information.'),
+    'fields' => array(
+      'tid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique term ID.'),
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {vocabulary}.vid of the vocabulary to which the term is assigned.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The term name.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => t('A description of the term.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The weight of this term in relation to other terms.'),
+      ),
+    ),
+    'primary key' => array('tid'),
+    'indexes' => array(
+      'taxonomy_tree' => array('vid', 'weight', 'name'),
+      'vid_name' => array('vid', 'name'),
+    ),
+  );
+
+  $schema['term_hierarchy'] = array(
+    'description' => t('Stores the hierarchical relationship between terms.'),
+    'fields' => array(
+      'tid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {term_data}.tid of the term.'),
+      ),
+      'parent' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("Primary Key: The {term_data}.tid of the term's parent. 0 indicates no parent."),
+      ),
+    ),
+    'indexes' => array(
+      'parent' => array('parent'),
+    ),
+    'primary key' => array('tid', 'parent'),
+  );
+
+  $schema['term_node'] = array(
+    'description' => t('Stores the relationship of terms to nodes.'),
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {node}.nid of the node.'),
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {node}.vid of the node.'),
+      ),
+      'tid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {term_data}.tid of a term assigned to the node.'),
+      ),
+    ),
+    'indexes' => array(
+      'vid' => array('vid'),
+      'nid' => array('nid'),
+    ),
+    'primary key' => array('tid', 'vid'),
+  );
+
+  $schema['term_relation'] = array(
+    'description' => t('Stores non-hierarchical relationships between terms.'),
+    'fields' => array(
+      'trid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique term relation ID.'),
+      ),
+      'tid1' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {term_data}.tid of the first term in a relationship.'),
+      ),
+      'tid2' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {term_data}.tid of the second term in a relationship.'),
+      ),
+    ),
+    'unique keys' => array(
+      'tid1_tid2' => array('tid1', 'tid2'),
+    ),
+    'indexes' => array(
+      'tid2' => array('tid2'),
+    ),
+    'primary key' => array('trid'),
+  );
+
+  $schema['term_synonym'] = array(
+    'description' => t('Stores term synonyms.'),
+    'fields' => array(
+      'tsid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique term synonym ID.'),
+      ),
+      'tid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {term_data}.tid of the term.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The name of the synonym.'),
+      ),
+    ),
+    'indexes' => array(
+      'tid' => array('tid'),
+      'name_tid' => array('name', 'tid'),
+    ),
+    'primary key' => array('tsid'),
+  );
+
+  $schema['vocabulary'] = array(
+    'description' => t('Stores vocabulary information.'),
+    'fields' => array(
+      'vid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique vocabulary ID.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Name of the vocabulary.'),
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => t('Description of the vocabulary.'),
+      ),
+      'help' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Help text to display for the vocabulary.'),
+      ),
+      'relations' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'),
+      ),
+      'hierarchy' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
+      ),
+      'multiple' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether or not multiple terms from this vocabulary may be assigned to a node. (0 = disabled, 1 = enabled)'),
+      ),
+      'required' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether or not terms are required for nodes using this vocabulary. (0 = disabled, 1 = enabled)'),
+      ),
+      'tags' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether or not free tagging is enabled for the vocabulary. (0 = disabled, 1 = enabled)'),
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The module which created the vocabulary.'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('The weight of the vocabulary in relation to other vocabularies.'),
+      ),
+    ),
+    'primary key' => array('vid'),
+    'indexes' => array(
+      'list' => array('weight', 'name'),
+    ),
+  );
+
+  $schema['vocabulary_node_types'] = array(
+    'description' => t('Stores which node types vocabularies may be used with.'),
+    'fields' => array(
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: the {vocabulary}.vid of the vocabulary.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The {node}.type of the node type for which the vocabulary may be used.'),
+      ),
+    ),
+    'primary key' => array('type', 'vid'),
+    'indexes' => array(
+      'vid' => array('vid'),
+    ),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,36 @@
+// $Id: taxonomy.js,v 1.2 2007/12/16 10:36:53 goba Exp $
+
+/**
+ * Move a block in the blocks table from one region to another via select list.
+ *
+ * This behavior is dependent on the tableDrag behavior, since it uses the
+ * objects initialized in that behavior to update the row.
+ */
+Drupal.behaviors.termDrag = function(context) {
+  var table = $('#taxonomy', context);
+  var tableDrag = Drupal.tableDrag.taxonomy; // Get the blocks tableDrag object.
+  var rows = $('tr', table).size();
+
+  // When a row is swapped, keep previous and next page classes set.
+  tableDrag.row.prototype.onSwap = function(swappedRow) {
+    $('tr.taxonomy-term-preview', table).removeClass('taxonomy-term-preview');
+    $('tr.taxonomy-term-divider-top', table).removeClass('taxonomy-term-divider-top');
+    $('tr.taxonomy-term-divider-bottom', table).removeClass('taxonomy-term-divider-bottom');
+
+    if (Drupal.settings.taxonomy.backPeddle) {
+      for (var n = 0; n < Drupal.settings.taxonomy.backPeddle; n++) {
+        $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+      }
+      $(table[0].tBodies[0].rows[Drupal.settings.taxonomy.backPeddle - 1]).addClass('taxonomy-term-divider-top');
+      $(table[0].tBodies[0].rows[Drupal.settings.taxonomy.backPeddle]).addClass('taxonomy-term-divider-bottom');
+    }
+
+    if (Drupal.settings.taxonomy.forwardPeddle) {
+      for (var n = rows - Drupal.settings.taxonomy.forwardPeddle - 1; n < rows - 1; n++) {
+        $(table[0].tBodies[0].rows[n]).addClass('taxonomy-term-preview');
+      }
+      $(table[0].tBodies[0].rows[rows - Drupal.settings.taxonomy.forwardPeddle - 2]).addClass('taxonomy-term-divider-top');
+      $(table[0].tBodies[0].rows[rows - Drupal.settings.taxonomy.forwardPeddle - 1]).addClass('taxonomy-term-divider-bottom');
+    }
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1313 @@
+<?php
+// $Id: taxonomy.module,v 1.414 2008/01/27 17:55:15 goba Exp $
+
+/**
+ * @file
+ * Enables the organization of content into categories.
+ */
+
+/**
+ * Implementation of hook_perm().
+ */
+function taxonomy_perm() {
+  return array('administer taxonomy');
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function taxonomy_theme() {
+  return array(
+    'taxonomy_term_select' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'taxonomy_term_page' => array(
+      'arguments' => array('tids' => array(), 'result' => NULL),
+    ),
+    'taxonomy_overview_vocabularies' => array(
+      'arguments' => array('form' => array()),
+    ),
+    'taxonomy_overview_terms' => array(
+      'arguments' => array('form' => array()),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_link().
+ *
+ * This hook is extended with $type = 'taxonomy terms' to allow themes to
+ * print lists of terms associated with a node. Themes can print taxonomy
+ * links with:
+ *
+ * if (module_exists('taxonomy')) {
+ *   $terms = taxonomy_link('taxonomy terms', $node);
+ *   print theme('links', $terms);
+ * }
+ */
+function taxonomy_link($type, $node = NULL) {
+  if ($type == 'taxonomy terms' && $node != NULL) {
+    $links = array();
+    // If previewing, the terms must be converted to objects first.
+    if ($node->build_mode == NODE_BUILD_PREVIEW) {
+      $node->taxonomy = taxonomy_preview_terms($node);
+    }
+    if (!empty($node->taxonomy)) {
+      foreach ($node->taxonomy as $term) {
+        // During preview the free tagging terms are in an array unlike the
+        // other terms which are objects. So we have to check if a $term
+        // is an object or not.
+        if (is_object($term)) {
+          $links['taxonomy_term_'. $term->tid] = array(
+            'title' => $term->name,
+            'href' => taxonomy_term_path($term),
+            'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
+          );
+        }
+        // Previewing free tagging terms; we don't link them because the
+        // term-page might not exist yet.
+        else {
+          foreach ($term as $free_typed) {
+            $typed_terms = drupal_explode_tags($free_typed);
+            foreach ($typed_terms as $typed_term) {
+              $links['taxonomy_preview_term_'. $typed_term] = array(
+                'title' => $typed_term,
+              );
+            }
+          }
+        }
+      }
+    }
+
+    // We call this hook again because some modules and themes
+    // call taxonomy_link('taxonomy terms') directly.
+    drupal_alter('link', $links, $node);
+
+    return $links;
+  }
+}
+
+/**
+ * For vocabularies not maintained by taxonomy.module, give the maintaining
+ * module a chance to provide a path for terms in that vocabulary.
+ *
+ * @param $term
+ *   A term object.
+ * @return
+ *   An internal Drupal path.
+ */
+
+function taxonomy_term_path($term) {
+  $vocabulary = taxonomy_vocabulary_load($term->vid);
+  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
+    return $path;
+  }
+  return 'taxonomy/term/'. $term->tid;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function taxonomy_menu() {
+  $items['admin/content/taxonomy'] = array(
+    'title' => 'Taxonomy',
+    'description' => 'Manage tagging, categorization, and classification of your content.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('taxonomy_overview_vocabularies'),
+    'access arguments' => array('administer taxonomy'),
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  $items['admin/content/taxonomy/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+
+  $items['admin/content/taxonomy/add/vocabulary'] = array(
+    'title' => 'Add vocabulary',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('taxonomy_form_vocabulary'),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/taxonomy',
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  $items['admin/content/taxonomy/edit/vocabulary/%taxonomy_vocabulary'] = array(
+    'title' => 'Edit vocabulary',
+    'page callback' => 'taxonomy_admin_vocabulary_edit',
+    'page arguments' => array(5),
+    'type' => MENU_CALLBACK,
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  $items['admin/content/taxonomy/edit/term'] = array(
+    'title' => 'Edit term',
+    'page callback' => 'taxonomy_admin_term_edit',
+    'type' => MENU_CALLBACK,
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  $items['taxonomy/term/%'] = array(
+    'title' => 'Taxonomy term',
+    'page callback' => 'taxonomy_term_page',
+    'page arguments' => array(2),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'taxonomy.pages.inc',
+  );
+
+  $items['taxonomy/autocomplete'] = array(
+    'title' => 'Autocomplete taxonomy',
+    'page callback' => 'taxonomy_autocomplete',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'taxonomy.pages.inc',
+  );
+  $items['admin/content/taxonomy/%taxonomy_vocabulary'] = array(
+    'title' => 'List terms',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('taxonomy_overview_terms', 3),
+    'access arguments' => array('administer taxonomy'),
+    'type' => MENU_CALLBACK,
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  $items['admin/content/taxonomy/%taxonomy_vocabulary/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+
+  $items['admin/content/taxonomy/%taxonomy_vocabulary/add/term'] = array(
+    'title' => 'Add term',
+    'page callback' => 'taxonomy_add_term_page',
+    'page arguments' => array(3),
+    'type' => MENU_LOCAL_TASK,
+    'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
+    'file' => 'taxonomy.admin.inc',
+  );
+
+  return $items;
+}
+
+function taxonomy_save_vocabulary(&$edit) {
+  $edit['nodes'] = empty($edit['nodes']) ? array() : $edit['nodes'];
+
+  if (!isset($edit['module'])) {
+    $edit['module'] = 'taxonomy';
+  }
+
+  if (!empty($edit['vid']) && !empty($edit['name'])) {
+    drupal_write_record('vocabulary', $edit, 'vid');
+    db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']);
+    foreach ($edit['nodes'] as $type => $selected) {
+      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
+    }
+    module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
+    $status = SAVED_UPDATED;
+  }
+  else if (!empty($edit['vid'])) {
+    $status = taxonomy_del_vocabulary($edit['vid']);
+  }
+  else {
+    drupal_write_record('vocabulary', $edit);
+    foreach ($edit['nodes'] as $type => $selected) {
+      db_query("INSERT INTO {vocabulary_node_types} (vid, type) VALUES (%d, '%s')", $edit['vid'], $type);
+    }
+    module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
+    $status = SAVED_NEW;
+  }
+
+  cache_clear_all();
+
+  return $status;
+}
+
+/**
+ * Delete a vocabulary.
+ *
+ * @param $vid
+ *   A vocabulary ID.
+ * @return
+ *   Constant indicating items were deleted.
+ */
+function taxonomy_del_vocabulary($vid) {
+  $vocabulary = (array) taxonomy_vocabulary_load($vid);
+
+  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
+  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
+  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
+  while ($term = db_fetch_object($result)) {
+    taxonomy_del_term($term->tid);
+  }
+
+  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
+
+  cache_clear_all();
+
+  return SAVED_DELETED;
+}
+
+/**
+ * Dynamicly check and update the hierarachy flag of a vocabulary.
+ *
+ * Checks the current parents of all terms in a vocabulary and updates the
+ * vocabularies hierarchy setting to the lowest possible level. A hierarchy with
+ * no parents in any of its terms will be given a hierarchy of 0. If terms
+ * contain at most a single parent, the vocabulary will be given a hierarchy of
+ * 1. If any term contain multiple parents, the vocabulary will be given a
+ * hieararchy of 2.
+ *
+ * @param $vocabulary
+ *   An array of the vocabulary structure.
+ * @param $changed_term
+ *   An array of the term structure that was updated.
+ */
+function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
+  $tree = taxonomy_get_tree($vocabulary['vid']);
+  $hierarchy = 0;
+  foreach ($tree as $term) {
+    // Update the changed term with the new parent value before comparision.
+    if ($term->tid == $changed_term['tid']) {
+      $term = (object)$changed_term;
+      $term->parents = $term->parent;
+    }
+    // Check this term's parent count.
+    if (count($term->parents) > 1) {
+      $hierarchy = 2;
+      break;
+    }
+    elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) {
+      $hierarchy = 1;
+    }
+  }
+  if ($hierarchy != $vocabulary['hierarchy']) {
+    $vocabulary['hierarchy'] = $hierarchy;
+    taxonomy_save_vocabulary($vocabulary);
+  }
+
+  return $hierarchy;
+}
+
+/**
+ * Helper function for taxonomy_form_term_submit().
+ *
+ * @param $form_state['values']
+ * @return
+ *   Status constant indicating if term was inserted or updated.
+ */
+function taxonomy_save_term(&$form_values) {
+  $form_values += array(
+    'description' => '',
+    'weight' => 0
+  );
+
+  if (!empty($form_values['tid']) && $form_values['name']) {
+    drupal_write_record('term_data', $form_values, 'tid');
+    $hook = 'update';
+    $status = SAVED_UPDATED;
+  }
+  else if (!empty($form_values['tid'])) {
+    return taxonomy_del_term($form_values['tid']);
+  }
+  else {
+    drupal_write_record('term_data', $form_values);
+    $hook = 'insert';
+    $status = SAVED_NEW;
+  }
+
+  db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $form_values['tid'], $form_values['tid']);
+  if (!empty($form_values['relations'])) {
+    foreach ($form_values['relations'] as $related_id) {
+      if ($related_id != 0) {
+        db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $form_values['tid'], $related_id);
+      }
+    }
+  }
+
+  db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $form_values['tid']);
+  if (!isset($form_values['parent']) || empty($form_values['parent'])) {
+    $form_values['parent'] = array(0);
+  }
+  if (is_array($form_values['parent'])) {
+    foreach ($form_values['parent'] as $parent) {
+      if (is_array($parent)) {
+        foreach ($parent as $tid) {
+          db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $tid);
+        }
+      }
+      else {
+        db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $parent);
+      }
+    }
+  }
+  else {
+    db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $form_values['parent']);
+  }
+
+  db_query('DELETE FROM {term_synonym} WHERE tid = %d', $form_values['tid']);
+  if (!empty($form_values['synonyms'])) {
+    foreach (explode ("\n", str_replace("\r", '', $form_values['synonyms'])) as $synonym) {
+      if ($synonym) {
+        db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $form_values['tid'], chop($synonym));
+      }
+    }
+  }
+
+  if (isset($hook)) {
+    module_invoke_all('taxonomy', $hook, 'term', $form_values);
+  }
+
+  cache_clear_all();
+
+  return $status;
+}
+
+/**
+ * Delete a term.
+ *
+ * @param $tid
+ *   The term ID.
+ * @return
+ *   Status constant indicating deletion.
+ */
+function taxonomy_del_term($tid) {
+  $tids = array($tid);
+  while ($tids) {
+    $children_tids = $orphans = array();
+    foreach ($tids as $tid) {
+      // See if any of the term's children are about to be become orphans:
+      if ($children = taxonomy_get_children($tid)) {
+        foreach ($children as $child) {
+          // If the term has multiple parents, we don't delete it.
+          $parents = taxonomy_get_parents($child->tid);
+          if (count($parents) == 1) {
+            $orphans[] = $child->tid;
+          }
+        }
+      }
+
+      $term = (array) taxonomy_get_term($tid);
+
+      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
+      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
+      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
+      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
+      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
+
+      module_invoke_all('taxonomy', 'delete', 'term', $term);
+    }
+
+    $tids = $orphans;
+  }
+
+  cache_clear_all();
+
+  return SAVED_DELETED;
+}
+
+/**
+ * Generate a form element for selecting terms from a vocabulary.
+ */
+function taxonomy_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
+  $vocabulary = taxonomy_vocabulary_load($vid);
+  $help = ($help) ? $help : $vocabulary->help;
+
+  if (!$vocabulary->multiple) {
+    $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None selected -');
+  }
+  else {
+    $blank = ($vocabulary->required) ? 0 : t('- None -');
+  }
+
+  return _taxonomy_term_select(check_plain($vocabulary->name), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
+}
+
+/**
+ * Generate a set of options for selecting a term from all vocabularies.
+ */
+function taxonomy_form_all($free_tags = 0) {
+  $vocabularies = taxonomy_get_vocabularies();
+  $options = array();
+  foreach ($vocabularies as $vid => $vocabulary) {
+    if ($vocabulary->tags && !$free_tags) { continue; }
+    $tree = taxonomy_get_tree($vid);
+    if ($tree && (count($tree) > 0)) {
+      $options[$vocabulary->name] = array();
+      foreach ($tree as $term) {
+        $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
+      }
+    }
+  }
+  return $options;
+}
+
+/**
+ * Return an array of all vocabulary objects.
+ *
+ * @param $type
+ *   If set, return only those vocabularies associated with this node type.
+ */
+function taxonomy_get_vocabularies($type = NULL) {
+  if ($type) {
+    $result = db_query(db_rewrite_sql("SELECT v.vid, v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", 'v', 'vid'), $type);
+  }
+  else {
+    $result = db_query(db_rewrite_sql('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid ORDER BY v.weight, v.name', 'v', 'vid'));
+  }
+
+  $vocabularies = array();
+  $node_types = array();
+  while ($voc = db_fetch_object($result)) {
+    // If no node types are associated with a vocabulary, the LEFT JOIN will
+    // return a NULL value for type.
+    if (isset($voc->type)) {
+      $node_types[$voc->vid][$voc->type] = $voc->type;
+      unset($voc->type);
+      $voc->nodes = $node_types[$voc->vid];
+    }
+    elseif (!isset($voc->nodes)) {
+      $voc->nodes = array();
+    }
+    $vocabularies[$voc->vid] = $voc;
+  }
+
+  return $vocabularies;
+}
+
+/**
+ * Implementation of hook_form_alter().
+ * Generate a form for selecting terms to associate with a node.
+ * We check for taxonomy_override_selector before loading the full
+ * vocabulary, so contrib modules can intercept before hook_form_alter
+ *  and provide scalable alternatives.
+ */
+function taxonomy_form_alter(&$form, $form_state, $form_id) {
+  if (isset($form['type']) && isset($form['#node']) && (!variable_get('taxonomy_override_selector', FALSE)) && $form['type']['#value'] .'_node_form' == $form_id) {
+    $node = $form['#node'];
+
+    if (!isset($node->taxonomy)) {
+      $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
+    }
+    else {
+      // After preview the terms must be converted to objects.
+      if (isset($form_state['node_preview'])) {
+        $node->taxonomy = taxonomy_preview_terms($node);
+      }
+      $terms = $node->taxonomy;
+    }
+
+    $c = db_query(db_rewrite_sql("SELECT v.* FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", 'v', 'vid'), $node->type);
+
+    while ($vocabulary = db_fetch_object($c)) {
+      if ($vocabulary->tags) {
+        if (isset($form_state['node_preview'])) {
+          // Typed string can be changed by the user before preview,
+          // so we just insert the tags directly as provided in the form.
+          $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
+        }
+        else {
+          $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
+        }
+        if ($vocabulary->help) {
+          $help = $vocabulary->help;
+        }
+        else {
+          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
+        }
+        $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' => 'textfield',
+          '#title' => $vocabulary->name,
+          '#description' => $help,
+          '#required' => $vocabulary->required,
+          '#default_value' => $typed_string,
+          '#autocomplete_path' => 'taxonomy/autocomplete/'. $vocabulary->vid,
+          '#weight' => $vocabulary->weight,
+          '#maxlength' => 255,
+        );
+      }
+      else {
+        // Extract terms belonging to the vocabulary in question.
+        $default_terms = array();
+        foreach ($terms as $term) {
+          // Free tagging has no default terms and also no vid after preview.
+          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
+            $default_terms[$term->tid] = $term;
+          }
+        }
+        $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), $vocabulary->help);
+        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
+        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
+      }
+    }
+    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
+      if (count($form['taxonomy']) > 1) {
+        // Add fieldset only if form has more than 1 element.
+        $form['taxonomy'] += array(
+          '#type' => 'fieldset',
+          '#title' => t('Vocabularies'),
+          '#collapsible' => TRUE,
+          '#collapsed' => FALSE,
+        );
+      }
+      $form['taxonomy']['#weight'] = -3;
+      $form['taxonomy']['#tree'] = TRUE;
+    }
+  }
+}
+
+/**
+ * Helper function to convert terms after a preview.
+ *
+ * After preview the tags are an array instead of proper objects. This function
+ * converts them back to objects with the exception of 'free tagging' terms,
+ * because new tags can be added by the user before preview and those do not
+ * yet exist in the database. We therefore save those tags as a string so
+ * we can fill the form again after the preview.
+ */
+function taxonomy_preview_terms($node) {
+  $taxonomy = array();
+  if (isset($node->taxonomy)) {
+    foreach ($node->taxonomy as $key => $term) {
+      unset($node->taxonomy[$key]);
+      // A 'Multiple select' and a 'Free tagging' field returns an array.
+      if (is_array($term)) {
+        foreach ($term as $tid) {
+          if ($key == 'tags') {
+            // Free tagging; the values will be saved for later as strings
+            // instead of objects to fill the form again.
+            $taxonomy['tags'] = $term;
+          }
+          else {
+            $taxonomy[$tid] = taxonomy_get_term($tid);
+          }
+        }
+      }
+      // A 'Single select' field returns the term id.
+      elseif ($term) {
+        $taxonomy[$term] = taxonomy_get_term($term);
+      }
+    }
+  }
+  return $taxonomy;
+}
+
+/**
+ * Find all terms associated with the given node, within one vocabulary.
+ */
+function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid') {
+  $result = db_query(db_rewrite_sql('SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_node} r ON r.tid = t.tid WHERE t.vid = %d AND r.vid = %d ORDER BY weight', 't', 'tid'), $vid, $node->vid);
+  $terms = array();
+  while ($term = db_fetch_object($result)) {
+    $terms[$term->$key] = $term;
+  }
+  return $terms;
+}
+
+/**
+ * Find all terms associated with the given node, ordered by vocabulary and term weight.
+ */
+function taxonomy_node_get_terms($node, $key = 'tid') {
+  static $terms;
+
+  if (!isset($terms[$node->vid][$key])) {
+    $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $node->vid);
+    $terms[$node->vid][$key] = array();
+    while ($term = db_fetch_object($result)) {
+      $terms[$node->vid][$key][$term->$key] = $term;
+    }
+  }
+  return $terms[$node->vid][$key];
+}
+
+/**
+ * Make sure incoming vids are free tagging enabled.
+ */
+function taxonomy_node_validate(&$node) {
+  if (!empty($node->taxonomy)) {
+    $terms = $node->taxonomy;
+    if (!empty($terms['tags'])) {
+      foreach ($terms['tags'] as $vid => $vid_value) {
+        $vocabulary = taxonomy_vocabulary_load($vid);
+        if (empty($vocabulary->tags)) {
+          // see form_get_error $key = implode('][', $element['#parents']);
+          // on why this is the key
+          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => $vocabulary->name)));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Save term associations for a given node.
+ */
+function taxonomy_node_save($node, $terms) {
+
+  taxonomy_node_delete_revision($node);
+
+  // Free tagging vocabularies do not send their tids in the form,
+  // so we'll detect them here and process them independently.
+  if (isset($terms['tags'])) {
+    $typed_input = $terms['tags'];
+    unset($terms['tags']);
+
+    foreach ($typed_input as $vid => $vid_value) {
+      $typed_terms = drupal_explode_tags($vid_value);
+
+      $inserted = array();
+      foreach ($typed_terms as $typed_term) {
+        // See if the term exists in the chosen vocabulary
+        // and return the tid; otherwise, add a new record.
+        $possibilities = taxonomy_get_term_by_name($typed_term);
+        $typed_term_tid = NULL; // tid match, if any.
+        foreach ($possibilities as $possibility) {
+          if ($possibility->vid == $vid) {
+            $typed_term_tid = $possibility->tid;
+          }
+        }
+
+        if (!$typed_term_tid) {
+          $edit = array('vid' => $vid, 'name' => $typed_term);
+          $status = taxonomy_save_term($edit);
+          $typed_term_tid = $edit['tid'];
+        }
+
+        // Defend against duplicate, differently cased tags
+        if (!isset($inserted[$typed_term_tid])) {
+          db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $typed_term_tid);
+          $inserted[$typed_term_tid] = TRUE;
+        }
+      }
+    }
+  }
+
+  if (is_array($terms)) {
+    foreach ($terms as $term) {
+      if (is_array($term)) {
+        foreach ($term as $tid) {
+          if ($tid) {
+            db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $tid);
+          }
+        }
+      }
+      else if (is_object($term)) {
+        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term->tid);
+      }
+      else if ($term) {
+        db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term);
+      }
+    }
+  }
+}
+
+/**
+ * Remove associations of a node to its terms.
+ */
+function taxonomy_node_delete($node) {
+  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
+}
+
+/**
+ * Remove associations of a node to its terms.
+ */
+function taxonomy_node_delete_revision($node) {
+  db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid);
+}
+
+/**
+ * Implementation of hook_node_type().
+ */
+function taxonomy_node_type($op, $info) {
+  if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
+    db_query("UPDATE {vocabulary_node_types} SET type = '%s' WHERE type = '%s'", $info->type, $info->old_type);
+  }
+  elseif ($op == 'delete') {
+    db_query("DELETE FROM {vocabulary_node_types} WHERE type = '%s'", $info->type);
+  }
+}
+
+/**
+ * Find all term objects related to a given term ID.
+ */
+function taxonomy_get_related($tid, $key = 'tid') {
+  if ($tid) {
+    $result = db_query('SELECT t.*, tid1, tid2 FROM {term_relation}, {term_data} t WHERE (t.tid = tid1 OR t.tid = tid2) AND (tid1 = %d OR tid2 = %d) AND t.tid != %d ORDER BY weight, name', $tid, $tid, $tid);
+    $related = array();
+    while ($term = db_fetch_object($result)) {
+      $related[$term->$key] = $term;
+    }
+    return $related;
+  }
+  else {
+    return array();
+  }
+}
+
+/**
+ * Find all parents of a given term ID.
+ */
+function taxonomy_get_parents($tid, $key = 'tid') {
+  if ($tid) {
+    $result = db_query(db_rewrite_sql('SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.parent = t.tid WHERE h.tid = %d ORDER BY weight, name', 't', 'tid'), $tid);
+    $parents = array();
+    while ($parent = db_fetch_object($result)) {
+      $parents[$parent->$key] = $parent;
+    }
+    return $parents;
+  }
+  else {
+    return array();
+  }
+}
+
+/**
+ * Find all ancestors of a given term ID.
+ */
+function taxonomy_get_parents_all($tid) {
+  $parents = array();
+  if ($tid) {
+    $parents[] = taxonomy_get_term($tid);
+    $n = 0;
+    while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
+      $parents = array_merge($parents, $parent);
+      $n++;
+    }
+  }
+  return $parents;
+}
+
+/**
+ * Find all children of a term ID.
+ */
+function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
+  if ($vid) {
+    $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.tid = t.tid WHERE t.vid = %d AND h.parent = %d ORDER BY weight, name', 't', 'tid'), $vid, $tid);
+  }
+  else {
+    $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON h.tid = t.tid WHERE parent = %d ORDER BY weight, name', 't', 'tid'), $tid);
+  }
+  $children = array();
+  while ($term = db_fetch_object($result)) {
+    $children[$term->$key] = $term;
+  }
+  return $children;
+}
+
+/**
+ * Create a hierarchical representation of a vocabulary.
+ *
+ * @param $vid
+ *   Which vocabulary to generate the tree for.
+ *
+ * @param $parent
+ *   The term ID under which to generate the tree. If 0, generate the tree
+ *   for the entire vocabulary.
+ *
+ * @param $depth
+ *   Internal use only.
+ *
+ * @param $max_depth
+ *   The number of levels of the tree to return. Leave NULL to return all levels.
+ *
+ * @return
+ *   An array of all term objects in the tree. Each term object is extended
+ *   to have "depth" and "parents" attributes in addition to its normal ones.
+ *   Results are statically cached.
+ */
+function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
+  static $children, $parents, $terms;
+
+  $depth++;
+
+  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
+  // and its children, too.
+  if (!isset($children[$vid])) {
+    $children[$vid] = array();
+
+    $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
+    while ($term = db_fetch_object($result)) {
+      $children[$vid][$term->parent][] = $term->tid;
+      $parents[$vid][$term->tid][] = $term->parent;
+      $terms[$vid][$term->tid] = $term;
+    }
+  }
+
+  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
+  $tree = array();
+  if (!empty($children[$vid][$parent])) {
+    foreach ($children[$vid][$parent] as $child) {
+      if ($max_depth > $depth) {
+        $term = drupal_clone($terms[$vid][$child]);
+        $term->depth = $depth;
+        // The "parent" attribute is not useful, as it would show one parent only.
+        unset($term->parent);
+        $term->parents = $parents[$vid][$child];
+        $tree[] = $term;
+
+        if (!empty($children[$vid][$child])) {
+          $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $depth, $max_depth));
+        }
+      }
+    }
+  }
+
+  return $tree;
+}
+
+/**
+ * Return an array of synonyms of the given term ID.
+ */
+function taxonomy_get_synonyms($tid) {
+  if ($tid) {
+    $synonyms = array();
+    $result = db_query('SELECT name FROM {term_synonym} WHERE tid = %d', $tid);
+    while ($synonym = db_fetch_array($result)) {
+      $synonyms[] = $synonym['name'];
+    }
+    return $synonyms;
+  }
+  else {
+    return array();
+  }
+}
+
+/**
+ * Return the term object that has the given string as a synonym.
+ */
+function taxonomy_get_synonym_root($synonym) {
+  return db_fetch_object(db_query("SELECT * FROM {term_synonym} s, {term_data} t WHERE t.tid = s.tid AND s.name = '%s'", $synonym));
+}
+
+/**
+ * Count the number of published nodes classified by a term.
+ *
+ * @param $tid
+ *   The term's ID
+ *
+ * @param $type
+ *   The $node->type. If given, taxonomy_term_count_nodes only counts
+ *   nodes of $type that are classified with the term $tid.
+ *
+ * @return int
+ *   An integer representing a number of nodes.
+ *   Results are statically cached.
+ */
+function taxonomy_term_count_nodes($tid, $type = 0) {
+  static $count;
+
+  if (!isset($count[$type])) {
+    // $type == 0 always evaluates TRUE if $type is a string
+    if (is_numeric($type)) {
+      $result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 GROUP BY t.tid'));
+    }
+    else {
+      $result = db_query(db_rewrite_sql("SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 AND n.type = '%s' GROUP BY t.tid"), $type);
+    }
+    while ($term = db_fetch_object($result)) {
+      $count[$type][$term->tid] = $term->c;
+    }
+  }
+  $children_count = 0;
+  foreach (_taxonomy_term_children($tid) as $c) {
+    $children_count += taxonomy_term_count_nodes($c, $type);
+  }
+  return $children_count + (isset($count[$type][$tid]) ? $count[$type][$tid] : 0);
+}
+
+/**
+ * Helper for taxonomy_term_count_nodes(). Used to find out
+ * which terms are children of a parent term.
+ *
+ * @param $tid
+ *   The parent term's ID
+ *
+ * @return array
+ *   An array of term IDs representing the children of $tid.
+ *   Results are statically cached.
+ *
+ */
+function _taxonomy_term_children($tid) {
+  static $children;
+
+  if (!isset($children)) {
+    $result = db_query('SELECT tid, parent FROM {term_hierarchy}');
+    while ($term = db_fetch_object($result)) {
+      $children[$term->parent][] = $term->tid;
+    }
+  }
+  return isset($children[$tid]) ? $children[$tid] : array();
+}
+
+/**
+ * Try to map a string to an existing term, as for glossary use.
+ *
+ * Provides a case-insensitive and trimmed mapping, to maximize the
+ * likelihood of a successful match.
+ *
+ * @param name
+ *   Name of the term to search for.
+ *
+ * @return
+ *   An array of matching term objects.
+ */
+function taxonomy_get_term_by_name($name) {
+  $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE LOWER(t.name) LIKE LOWER('%s')", 't', 'tid'), trim($name));
+  $result = array();
+  while ($term = db_fetch_object($db_result)) {
+    $result[] = $term;
+  }
+
+  return $result;
+}
+
+/**
+ * Return the vocabulary object matching a vocabulary ID.
+ *
+ * @param $vid
+ *   The vocabulary's ID
+ *
+ * @return
+ *   The vocabulary object with all of its metadata, if exists, NULL otherwise.
+ *   Results are statically cached.
+ */
+function taxonomy_vocabulary_load($vid) {
+  static $vocabularies = array();
+
+  if (!isset($vocabularies[$vid])) {
+    // Initialize so if this vocabulary does not exist, we have
+    // that cached, and we will not try to load this later.
+    $vocabularies[$vid] = FALSE;
+    // Try to load the data and fill up the object.
+    $result = db_query('SELECT v.*, n.type FROM {vocabulary} v LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE v.vid = %d', $vid);
+    $node_types = array();
+    while ($voc = db_fetch_object($result)) {
+      if (!empty($voc->type)) {
+        $node_types[$voc->type] = $voc->type;
+      }
+      unset($voc->type);
+      $voc->nodes = $node_types;
+      $vocabularies[$vid] = $voc;
+    }
+  }
+
+  // Return NULL if this vocabulary does not exist.
+  return !empty($vocabularies[$vid]) ? $vocabularies[$vid] : NULL;
+}
+
+/**
+ * Return the term object matching a term ID.
+ *
+ * @param $tid
+ *   A term's ID
+ *
+ * @return Object
+ *   A term object. Results are statically cached.
+ */
+function taxonomy_get_term($tid) {
+  static $terms = array();
+
+  if (!isset($terms[$tid])) {
+    $terms[$tid] = db_fetch_object(db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
+  }
+
+  return $terms[$tid];
+}
+
+function _taxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
+  $tree = taxonomy_get_tree($vocabulary_id);
+  $options = array();
+
+  if ($blank) {
+    $options[''] = $blank;
+  }
+  if ($tree) {
+    foreach ($tree as $term) {
+      if (!in_array($term->tid, $exclude)) {
+        $choice = new stdClass();
+        $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
+        $options[] = $choice;
+      }
+    }
+  }
+
+  return array('#type' => 'select',
+    '#title' => $title,
+    '#default_value' => $value,
+    '#options' => $options,
+    '#description' => $description,
+    '#multiple' => $multiple,
+    '#size' => $multiple ? min(9, count($options)) : 0,
+    '#weight' => -15,
+    '#theme' => 'taxonomy_term_select',
+  );
+}
+
+/**
+ * Format the selection field for choosing terms
+ * (by deafult the default selection field is used).
+ *
+ * @ingroup themeable
+ */
+function theme_taxonomy_term_select($element) {
+  return theme('select', $element);
+}
+
+/**
+ * Finds all nodes that match selected taxonomy conditions.
+ *
+ * @param $tids
+ *   An array of term IDs to match.
+ * @param $operator
+ *   How to interpret multiple IDs in the array. Can be "or" or "and".
+ * @param $depth
+ *   How many levels deep to traverse the taxonomy tree. Can be a nonnegative
+ *   integer or "all".
+ * @param $pager
+ *   Whether the nodes are to be used with a pager (the case on most Drupal
+ *   pages) or not (in an XML feed, for example).
+ * @param $order
+ *   The order clause for the query that retrieve the nodes.
+ * @return
+ *   A resource identifier pointing to the query results.
+ */
+function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
+  if (count($tids) > 0) {
+    // For each term ID, generate an array of descendant term IDs to the right depth.
+    $descendant_tids = array();
+    if ($depth === 'all') {
+      $depth = NULL;
+    }
+    foreach ($tids as $index => $tid) {
+      $term = taxonomy_get_term($tid);
+      $tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
+      $descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree));
+    }
+
+    if ($operator == 'or') {
+      $args = call_user_func_array('array_merge', $descendant_tids);
+      $placeholders = db_placeholders($args, 'int');
+      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN ('. $placeholders .') AND n.status = 1 ORDER BY '. $order;
+      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN ('. $placeholders .') AND n.status = 1';
+    }
+    else {
+      $joins = '';
+      $wheres = '';
+      $args = array();
+      foreach ($descendant_tids as $index => $tids) {
+        $joins .= ' INNER JOIN {term_node} tn'. $index .' ON n.vid = tn'. $index .'.vid';
+        $wheres .= ' AND tn'. $index .'.tid IN ('. db_placeholders($tids, 'int') .')';
+        $args = array_merge($args, $tids);
+      }
+      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres .' ORDER BY '. $order;
+      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n '. $joins .' WHERE n.status = 1 '. $wheres;
+    }
+    $sql = db_rewrite_sql($sql);
+    $sql_count = db_rewrite_sql($sql_count);
+    if ($pager) {
+      $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
+    }
+    else {
+      $result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
+    }
+  }
+
+  return $result;
+}
+
+/**
+ * Accepts the result of a pager_query() call, such as that performed by
+ * taxonomy_select_nodes(), and formats each node along with a pager.
+ */
+function taxonomy_render_nodes($result) {
+  $output = '';
+  $has_rows = FALSE;
+  while ($node = db_fetch_object($result)) {
+    $output .= node_view(node_load($node->nid), 1);
+    $has_rows = TRUE;
+  }
+  if ($has_rows) {
+    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
+  }
+  else {
+    $output .= '<p>'. t('There are currently no posts in this category.') .'</p>';
+  }
+  return $output;
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function taxonomy_nodeapi($node, $op, $arg = 0) {
+  switch ($op) {
+    case 'load':
+      $output['taxonomy'] = taxonomy_node_get_terms($node);
+      return $output;
+
+    case 'insert':
+      if (!empty($node->taxonomy)) {
+        taxonomy_node_save($node, $node->taxonomy);
+      }
+      break;
+
+    case 'update':
+      if (!empty($node->taxonomy)) {
+        taxonomy_node_save($node, $node->taxonomy);
+      }
+      break;
+
+    case 'delete':
+      taxonomy_node_delete($node);
+      break;
+
+    case 'delete revision':
+      taxonomy_node_delete_revision($node);
+      break;
+
+    case 'validate':
+      taxonomy_node_validate($node);
+      break;
+
+    case 'rss item':
+      return taxonomy_rss_item($node);
+
+    case 'update index':
+      return taxonomy_node_update_index($node);
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi('update_index').
+ */
+function taxonomy_node_update_index(&$node) {
+  $output = array();
+  foreach ($node->taxonomy as $term) {
+    $output[] = $term->name;
+  }
+  if (count($output)) {
+    return '<strong>('. implode(', ', $output) .')</strong>';
+  }
+}
+
+/**
+ * Parses a comma or plus separated string of term IDs.
+ *
+ * @param $str_tids
+ *   A string of term IDs, separated by plus or comma.
+ *   comma (,) means AND
+ *   plus (+) means OR
+ *
+ * @return an associative array with an operator key (either 'and'
+ *   or 'or') and a tid key containing an array of the term ids.
+ */
+function taxonomy_terms_parse_string($str_tids) {
+  $terms = array('operator' => '', 'tids' => array());
+  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
+    $terms['operator'] = 'or';
+    // The '+' character in a query string may be parsed as ' '.
+    $terms['tids'] = preg_split('/[+ ]/', $str_tids);
+  }
+  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
+    $terms['operator'] = 'and';
+    $terms['tids'] = explode(',', $str_tids);
+  }
+  return $terms;
+}
+
+/**
+ * Provides category information for RSS feeds.
+ */
+function taxonomy_rss_item($node) {
+  $output = array();
+  foreach ($node->taxonomy as $term) {
+    $output[] = array('key'   => 'category',
+                      'value' => check_plain($term->name),
+                      'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, array('absolute' => TRUE))));
+  }
+  return $output;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function taxonomy_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#taxonomy':
+      $output = '<p>'. t('The taxonomy module allows you to categorize content using various systems of classification. Free-tagging vocabularies are created by users on the fly when they submit posts (as commonly found in blogs and social bookmarking applications). Controlled vocabularies allow for administrator-defined short lists of terms as well as complex hierarchies with multiple relationships between different terms. These methods can be applied to different content types and combined together to create a powerful and flexible method of classifying and presenting your content.') .'</p>';
+      $output .= '<p>'. t('For example, when creating a recipe site, you might want to classify posts by both the type of meal and preparation time. A vocabulary for each allows you to categorize using each criteria independently instead of creating a tag for every possible combination.') .'</p>';
+      $output .= '<p>'. t('Type of Meal: <em>Appetizer, Main Course, Salad, Dessert</em>') .'</p>';
+      $output .= '<p>'. t('Preparation Time: <em>0-30mins, 30-60mins, 1-2 hrs, 2hrs+</em>') .'</p>';
+      $output .= '<p>'. t("Each taxonomy term (often called a 'category' or 'tag' in other systems) automatically provides lists of posts and a corresponding RSS feed. These taxonomy/term URLs can be manipulated to generate AND and OR lists of posts classified with terms. In our recipe site example, it then becomes easy to create pages displaying 'Main courses', '30 minute recipes', or '30 minute main courses and appetizers' by using terms on their own or in combination with others. There are a significant number of contributed modules which you to alter and extend the behavior of the core module for both display and organization of terms.") .'</p>';
+      $output .= '<p>'. t("Terms can also be organized in parent/child relationships from the admin interface. An example would be a vocabulary grouping countries under their parent geo-political regions. The taxonomy module also enables advanced implementations of hierarchy, for example placing Turkey in both the 'Middle East' and 'Europe'.") .'</p>';
+      $output .= '<p>'. t('The taxonomy module supports the use of both synonyms and related terms, but does not directly use this functionality. However, optional contributed or custom modules may make full use of these advanced features.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@taxonomy">Taxonomy module</a>.', array('@taxonomy' => 'http://drupal.org/handbook/modules/taxonomy/')) .'</p>';
+      return $output;
+    case 'admin/content/taxonomy':
+      $output = '<p>'. t("The taxonomy module allows you to categorize your content using both tags and administrator defined terms. It is a flexible tool for classifying content with many advanced features. To begin, create a 'Vocabulary' to hold one set of terms or tags. You can create one free-tagging vocabulary for everything, or separate controlled vocabularies to define the various properties of your content, for example 'Countries' or 'Colors'.") .'</p>';
+      $output .= '<p>'. t('Use the list below to configure and review the vocabularies defined on your site, or to list and manage the terms (tags) they contain. A vocabulary may (optionally) be tied to specific content types as shown in the <em>Type</em> column and, if so, will be displayed when creating or editing posts of that type. Multiple vocabularies tied to the same content type will be displayed in the order shown below. To change the order of a vocabulary, grab a drag-and-drop handle under the <em>Name</em> column and drag it to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save</em> button at the bottom of the page.') .'</p>';
+      return $output;
+    case 'admin/content/taxonomy/%':
+      $vocabulary = taxonomy_vocabulary_load($arg[3]);
+      if ($vocabulary->tags) {
+        return '<p>'. t('%capital_name is a free-tagging vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) .'</p>';
+      }
+      switch ($vocabulary->hierarchy) {
+        case 0:
+          return '<p>'. t('%capital_name is a flat vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'</p>';
+        case 1:
+          return '<p>'. t('%capital_name is a single hierarchy vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'</p>';
+        case 2:
+          return '<p>'. t('%capital_name is a multiple hierarchy vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term. Drag and drop of multiple hierarchies is not supported, but you can re-enable drag and drop support by editing each term to include only a single parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) .'</p>';
+      }
+    case 'admin/content/taxonomy/add/vocabulary':
+      return '<p>'. t('Define how your vocabulary will be presented to administrators and users, and which content types to categorize with it. Tags allows users to create terms when submitting posts by typing a comma separated list. Otherwise terms are chosen from a select list and can only be created by users with the "administer taxonomy" permission.') .'</p>';
+  }
+}
+
+/**
+ * Helper function for array_map purposes.
+ */
+function _taxonomy_get_tid_from_term($term) {
+  return $term->tid;
+}
+
+/**
+ * Implode a list of tags of a certain vocabulary into a string.
+ */
+function taxonomy_implode_tags($tags, $vid = NULL) {
+  $typed_tags = array();
+  foreach ($tags as $tag) {
+    // Extract terms belonging to the vocabulary in question.
+    if (is_null($vid) || $tag->vid == $vid) {
+
+      // Commas and quotes in tag names are special cases, so encode 'em.
+      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
+        $tag->name = '"'. str_replace('"', '""', $tag->name) .'"';
+      }
+
+      $typed_tags[] = $tag->name;
+    }
+  }
+  return implode(', ', $typed_tags);
+}
+
+/**
+ * Implementation of hook_hook_info().
+ */
+function taxonomy_hook_info() {
+  return array(
+    'taxonomy' => array(
+      'taxonomy' => array(
+        'insert' => array(
+          'runs when' => t('After saving a new term to the database'),
+        ),
+        'update' => array(
+          'runs when' => t('After saving an updated term to the database'),
+        ),
+        'delete' => array(
+          'runs when' => t('After deleting a term')
+        ),
+      ),
+    ),
+  );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/taxonomy/taxonomy.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,137 @@
+<?php
+// $Id: taxonomy.pages.inc,v 1.9 2008/01/18 16:23:57 goba Exp $
+
+/**
+ * @file
+ * Page callbacks for the taxonomy module.
+ */
+
+/**
+ * Menu callback; displays all nodes associated with a term.
+ */
+function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') {
+  $terms = taxonomy_terms_parse_string($str_tids);
+  if ($terms['operator'] != 'and' && $terms['operator'] != 'or') {
+    drupal_not_found();
+  }
+
+  if ($terms['tids']) {
+    $result = db_query(db_rewrite_sql('SELECT t.tid, t.name FROM {term_data} t WHERE t.tid IN ('. db_placeholders($terms['tids']) .')', 't', 'tid'), $terms['tids']);
+    $tids = array(); // we rebuild the $tids-array so it only contains terms the user has access to.
+    $names = array();
+    while ($term = db_fetch_object($result)) {
+      $tids[] = $term->tid;
+      $names[] = $term->name;
+    }
+
+    if ($names) {
+      $title = check_plain(implode(', ', $names));
+      drupal_set_title($title);
+
+      switch ($op) {
+        case 'page':
+          // Build breadcrumb based on first hierarchy of first term:
+          $current->tid = $tids[0];
+          $breadcrumb = array();
+          while ($parents = taxonomy_get_parents($current->tid)) {
+            $current = array_shift($parents);
+            $breadcrumb[] = l($current->name, 'taxonomy/term/'. $current->tid);
+          }
+          $breadcrumb[] = l(t('Home'), NULL);
+          $breadcrumb = array_reverse($breadcrumb);
+          drupal_set_breadcrumb($breadcrumb);
+
+          $output = theme('taxonomy_term_page', $tids, taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE));
+          drupal_add_feed(url('taxonomy/term/'. $str_tids .'/'. $depth .'/feed'), 'RSS - '. $title);
+          return $output;
+          break;
+
+        case 'feed':
+          $channel['link'] = url('taxonomy/term/'. $str_tids .'/'. $depth, array('absolute' => TRUE));
+          $channel['title'] = variable_get('site_name', 'Drupal') .' - '. $title;
+          // Only display the description if we have a single term, to avoid clutter and confusion.
+          if (count($tids) == 1) {
+            $term = taxonomy_get_term($tids[0]);
+            // HTML will be removed from feed description, so no need to filter here.
+            $channel['description'] = $term->description;
+          }
+
+          $result = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
+          $items = array(); 
+          while ($row = db_fetch_object($result)) {
+            $items[] = $row->nid;
+          }
+
+          node_feed($items, $channel);
+          break;
+
+        default:
+          drupal_not_found();
+      }
+    }
+    else {
+      drupal_not_found();
+    }
+  }
+}
+
+/**
+ * Render a taxonomy term page HTML output.
+ *
+ * @param $tids
+ *   An array of term ids.
+ * @param $result
+ *   A pager_query() result, such as that performed by taxonomy_select_nodes().
+ *
+ * @ingroup themeable
+ */
+function theme_taxonomy_term_page($tids, $result) {
+  drupal_add_css(drupal_get_path('module', 'taxonomy') .'/taxonomy.css');
+
+  $output = '';
+
+  // Only display the description if we have a single term, to avoid clutter and confusion.
+  if (count($tids) == 1) {
+    $term = taxonomy_get_term($tids[0]);
+    $description = $term->description;
+
+    // Check that a description is set.
+    if (!empty($description)) {
+      $output .= '<div class="taxonomy-term-description">';
+      $output .= filter_xss_admin($description);
+      $output .= '</div>';
+    }
+  }
+
+  $output .= taxonomy_render_nodes($result);
+
+  return $output;
+}
+
+/**
+ * Helper function for autocompletion
+ */
+function taxonomy_autocomplete($vid, $string = '') {
+  // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+  $array = drupal_explode_tags($string);
+
+  // Fetch last tag
+  $last_string = trim(array_pop($array));
+  $matches = array();
+  if ($last_string != '') {
+    $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10);
+
+    $prefix = count($array) ? implode(', ', $array) .', ' : '';
+
+    while ($tag = db_fetch_object($result)) {
+      $n = $tag->name;
+      // Commas and quotes in terms are special cases, so encode 'em.
+      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
+        $n = '"'. str_replace('"', '""', $tag->name) .'"';
+      }
+      $matches[$prefix . $n] = check_plain($tag->name);
+    }
+  }
+
+  drupal_json($matches);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/throttle/throttle.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,55 @@
+<?php
+// $Id: throttle.admin.inc,v 1.2 2008/01/08 10:35:43 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the throttle module.
+ */
+
+/**
+ * Form builder; Configure the throttle system.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ * @see throttle_admin_settings_validate()
+ */
+function throttle_admin_settings() {
+  $probabilities = array(0 => '100%', 1 => '50%', 2 => '33.3%', 3 => '25%', 4 => '20%', 5 => '16.6%', 7 => '12.5%', 9 => '10%', 19 => '5%', 99 => '1%', 199 => '.5%', 399 => '.25%', 989 => '.1%');
+
+  $form['throttle_anonymous'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Auto-throttle on anonymous users'),
+    '#default_value' => variable_get('throttle_anonymous', 0),
+    '#size' => 5,
+    '#maxlength' => 6,
+    '#description' => t('The congestion control throttle can be automatically enabled when the number of anonymous users currently visiting your site exceeds the specified threshold. For example, to start the throttle when your site has 250 anonymous users online at once, enter \'250\' in this field. Leave this value blank or set to "0" if you do not wish to auto-throttle on anonymous users. You can inspect the current number of anonymous users using the "Who\'s online" block.')
+  );
+  $form['throttle_user'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Auto-throttle on authenticated users'),
+    '#default_value' => variable_get('throttle_user', 0),
+    '#size' => 5,
+    '#maxlength' => 6,
+    '#description' => t('The congestion control throttle can be automatically enabled when the number of authenticated users currently visiting your site exceeds the specified threshold. For example, to start the throttle when your site has 50 registered users online at once, enter \'50\' in this field. Leave this value blank or set to "0" if you do not wish to auto-throttle on authenticated users. You can inspect the current number of authenticated users using the "Who\'s online" block.')
+  );
+  $form['throttle_probability_limiter'] = array(
+    '#type' => 'select',
+    '#title' => t('Auto-throttle probability limiter'),
+    '#default_value' => variable_get('throttle_probability_limiter', 9),
+    '#options' => $probabilities,
+    '#description' => t('The auto-throttle probability limiter is an efficiency mechanism to statistically reduce the overhead of the auto-throttle. The limiter is expressed as a percentage of page views, so for example if set to the default of 10% we only perform the extra database queries to update the throttle status 1 out of every 10 page views. The busier your site, the lower you should set the limiter value.')
+  );
+
+  $form['#validate'] = array('throttle_admin_settings_validate');
+
+  return system_settings_form($form);
+}
+
+function throttle_admin_settings_validate($form, &$form_state) {
+  if (!is_numeric($form_state['values']['throttle_anonymous']) || $form_state['values']['throttle_anonymous'] < 0) {
+    form_set_error('throttle_anonymous', t("%value is not a valid auto-throttle setting. Please enter a positive numeric value.", array('%value' => $form_state['values']['throttle_anonymous'])));
+  }
+  if (!is_numeric($form_state['values']['throttle_user']) || $form_state['values']['throttle_user'] < 0) {
+    form_set_error('throttle_user', t("%value is not a valid auto-throttle setting. Please enter a positive numeric value.", array('%value' => $form_state['values']['throttle_user'])));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/throttle/throttle.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: throttle.info,v 1.4 2007/06/08 05:50:57 dries Exp $
+name = Throttle
+description = Handles the auto-throttling mechanism, to control site congestion.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/throttle/throttle.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,128 @@
+<?php
+// $Id: throttle.module,v 1.83 2007/12/14 18:08:49 goba Exp $
+
+/**
+ * @file
+ * Allows configuration of congestion control auto-throttle mechanism.
+ */
+
+function throttle_menu() {
+  $items['admin/settings/throttle'] = array(
+    'title' => 'Throttle',
+    'description' => 'Control how your site cuts out content during heavy load.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('throttle_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'throttle.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Determine the current load on the site.
+ *
+ * Call the throttle_status() function from your own modules, themes, blocks,
+ * etc. as follows:
+ *
+ *   $throttle = module_invoke('throttle', 'status');
+ *
+ * to determine the current throttle status. Use module_invoke() so the
+ * call will still work if the throttle module is disabled. For example, in
+ * your theme you might choose to disable pictures when your site is too busy
+ * (reducing bandwidth), or in your modules you might choose to disable
+ * some complicated logic when your site is too busy (reducing CPU utilization).
+ *
+ * @return
+ *   0 or 1. 0 means that the throttle is currently disabled. 1 means that
+ *   the throttle is currently enabled. When the throttle is enabled, CPU
+ *   and bandwidth intensive functionality should be disabled.
+ */
+function throttle_status() {
+  return variable_get('throttle_level', 0);
+}
+
+/**
+ * Implementation of hook_exit().
+ *
+ * Changes the current throttle level based on page hits.
+ */
+function throttle_exit() {
+  // The following logic determines what the current throttle level should
+  //  be, and can be disabled by the admin. If enabled, the mt_rand() function
+  //  returns a number between 0 and N, N being specified by the admin. If
+  //  0 is returned, the throttle logic is run, adding two additional database
+  //  queries. Otherwise, the following logic is skipped. This mechanism is
+  //  referred to in the admin page as the 'probability limiter', roughly
+  //  limiting throttle related database calls to 1 in N.
+  if (!mt_rand(0, variable_get('throttle_probability_limiter', 9))) {
+
+    // Count users with activity in the past n seconds.
+    // This value is defined in the user module Who's Online block.
+    $time_period = variable_get('user_block_seconds_online', 900);
+
+    // When determining throttle status in your own module or theme, use
+    // $throttle = module_invoke('throttle', 'status');
+    // as that will still work when throttle.module is disabled.
+    // Clearly here the module is enabled so we call throttle_status() directly.
+    $throttle = throttle_status();
+
+    if ($max_guests = variable_get('throttle_anonymous', 0)) {
+      $guests = sess_count(time() - $time_period, TRUE);
+    }
+    else {
+      $guests = 0;
+    }
+    if ($max_users = variable_get('throttle_user', 0)) {
+      $users = sess_count(time() - $time_period, FALSE);
+    }
+    else {
+      $users = 0;
+    }
+
+    // update the throttle status
+    $message = '';
+    if ($max_users && $users > $max_users) {
+      if (!$throttle) {
+        variable_set('throttle_level', 1);
+        $message = format_plural($users,
+                                 '1 user accessing site; throttle enabled.',
+                                 '@count users accessing site; throttle enabled.');
+      }
+    }
+    elseif ($max_guests && $guests > $max_guests) {
+      if (!$throttle) {
+        variable_set('throttle_level', 1);
+        $message = format_plural($guests,
+                                 '1 guest accessing site; throttle enabled.',
+                                 '@count guests accessing site; throttle enabled.');
+      }
+    }
+    else {
+      if ($throttle) {
+        variable_set('throttle_level', 0);
+        // Note: unorthodox format_plural() usage due to Gettext plural limitations.
+        $message = format_plural($users, '1 user', '@count users') .', ';
+        $message .= format_plural($guests, '1 guest accessing site; throttle disabled', '@count guests accessing site; throttle disabled');
+      }
+    }
+    if ($message) {
+      cache_clear_all();
+      watchdog('throttle', 'Throttle: %message', array('%message' => $message));
+    }
+  }
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function throttle_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#throttle':
+      $output = '<p>'. t('The throttle module provides a congestion control mechanism that automatically adjusts to a surge in incoming traffic. If your site is referenced by a popular website, or experiences a "Denial of Service" (DoS) attack, your webserver might become overwhelmed. The throttle mechanism is utilized by modules to temporarily disable CPU-intensive functionality, increasing performance. For instance, via the throttle module, modules may choose to disable resource-intensive blocks or the code within the site theme may temporarily disable user pictures in posts.') .'</p>';
+      $output .= '<p>'. t('The congestion control throttle can be automatically enabled when the number of anonymous or authenticated users currently visiting the site exceeds a specified threshold.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@throttle">Throttle module</a>.', array('@throttle' => 'http://drupal.org/handbook/modules/throttle/')) .'</p>';
+      return $output;
+    case 'admin/settings/throttle':
+      return '<p>'. t('The throttle module provides a congestion control mechanism that automatically adjusts to a surge in incoming traffic. If your site is referenced by a popular website, or experiences a "Denial of Service" (DoS) attack, your webserver might become overwhelmed. The throttle mechanism is utilized by modules to temporarily disable CPU-intensive functionality, increasing performance.') .'</p>';
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/tracker/tracker.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+/* $Id: tracker.css,v 1.1 2006/08/14 07:14:50 drumm Exp $ */
+
+#tracker td.replies {
+  text-align: center;
+}
+#tracker table {
+  width: 100%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/tracker/tracker.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+; $Id: tracker.info,v 1.5 2007/07/04 10:54:26 goba Exp $
+name = Tracker
+description = Enables tracking of recent posts for users.
+dependencies[] = comment
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/tracker/tracker.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,57 @@
+<?php
+// $Id: tracker.module,v 1.154 2007/12/14 18:08:49 goba Exp $
+
+/**
+ * @file
+ * Enables tracking of recent posts for users.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function tracker_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#tracker':
+      $output = '<p>'. t('The tracker module displays the most recently added or updated content on your site, and provides user-level tracking to follow the contributions of particular authors.') .'</p>';
+      $output .= '<p>'. t("The <em>Recent posts</em> page is available via a link in the navigation menu block and displays new and recently-updated content (including the content type, the title, the author's name, number of comments, and time of last update) in reverse chronological order. Posts are marked updated when changes occur in the text, or when new comments are added. To use the tracker module to follow a specific user's contributions, select the <em>Track</em> tab from the user's profile page.") .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@tracker">Tracker module</a>.', array('@tracker' => 'http://drupal.org/handbook/modules/tracker/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function tracker_menu() {
+  $items['tracker'] = array(
+    'title' => 'Recent posts',
+    'page callback' => 'tracker_page',
+    'access arguments' => array('access content'),
+    'weight' => 1,
+    'file' => 'tracker.pages.inc',
+  );
+  $items['tracker/all'] = array(
+    'title' => 'All recent posts',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'access callback' => 'user_is_logged_in',
+  );
+  $items['tracker/%user_current'] = array(
+    'title' => 'My recent posts',
+    'access callback' => 'user_is_logged_in',
+    'page arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  $items['user/%user/track'] = array(
+    'title' => 'Track',
+    'page callback' => 'tracker_page',
+    'page arguments' => array(1, TRUE),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'tracker.pages.inc',
+  );
+  $items['user/%user/track/posts'] = array(
+    'title' => 'Track posts',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  return $items;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/tracker/tracker.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,73 @@
+<?php
+// $Id: tracker.pages.inc,v 1.5 2007/11/28 10:29:20 dries Exp $
+
+/**
+ * @file
+ * User page callbacks for the tracker module.
+ */
+
+
+/**
+ * Menu callback. Prints a listing of active nodes on the site.
+ */
+function tracker_page($account = NULL, $set_title = FALSE) {
+  // Add CSS
+  drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css', 'module', 'all', FALSE);
+
+  if ($account) {
+    if ($set_title) {
+      // When viewed from user/%user/track, display the name of the user
+      // as page title -- the tab title remains Track so this needs to be done
+      // here and not in the menu definiton.
+      drupal_set_title(check_plain($account->name));
+    }
+  // TODO: These queries are very expensive, see http://drupal.org/node/105639
+    $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d) ORDER BY last_updated DESC';
+    $sql = db_rewrite_sql($sql);
+    $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d)';
+    $sql_count = db_rewrite_sql($sql_count);
+    $result = pager_query($sql, 25, 0, $sql_count, COMMENT_PUBLISHED, $account->uid, $account->uid);
+  }
+  else {
+    $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 ORDER BY last_updated DESC';
+    $sql = db_rewrite_sql($sql);
+    $sql_count = 'SELECT COUNT(n.nid) FROM {node} n WHERE n.status = 1';
+    $sql_count = db_rewrite_sql($sql_count);
+    $result = pager_query($sql, 25, 0, $sql_count);
+  }
+
+  $rows = array();
+  while ($node = db_fetch_object($result)) {
+    // Determine the number of comments:
+    $comments = 0;
+    if ($node->comment_count) {
+      $comments = $node->comment_count;
+
+      if ($new = comment_num_new($node->nid)) {
+        $comments .= '<br />';
+        $comments .= l(format_plural($new, '1 new', '@count new'), "node/$node->nid", array('query' => comment_new_page_count($node->comment_count, $new, $node), 'fragment' => 'new'));
+      }
+    }
+
+    $rows[] = array(
+      check_plain(node_get_types('name', $node->type)),
+      l($node->title, "node/$node->nid") .' '. theme('mark', node_mark($node->nid, $node->changed)),
+      theme('username', $node),
+      array('class' => 'replies', 'data' => $comments),
+      t('!time ago', array('!time' => format_interval(time() - $node->last_updated)))
+    );
+  }
+
+  if (!$rows) {
+    $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '5'));
+  }
+
+  $header = array(t('Type'), t('Post'), t('Author'), t('Replies'), t('Last updated'));
+
+  $output = '<div id="tracker">';
+  $output .= theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 25, 0);
+  $output .= '</div>';
+
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/translation/translation.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+; $Id: translation.info,v 1.1 2007/06/15 18:40:14 goba Exp $
+name = Content translation
+description = Allows content to be translated into different languages.
+dependencies[] = locale
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/translation/translation.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,340 @@
+<?php
+// $Id: translation.module,v 1.23 2008/01/28 11:38:58 goba Exp $
+
+/**
+ * @file
+ *   Manages content translations.
+ *
+ *   Translations are managed in sets of posts, which represent the same
+ *   information in different languages. Only content types for which the
+ *   administrator explicitly enabled translations could have translations
+ *   associated. Translations are managed in sets with exactly one source
+ *   post per set. The source post is used to translate to different
+ *   languages, so if the source post is significantly updated, the
+ *   editor can decide to mark all translations outdated.
+ *
+ *   The node table stores the values used by this module:
+ *    - 'tnid' is the translation set id, which equals the node id
+ *      of the source post.
+ *    - 'translate' is a flag, either indicating that the translation
+ *      is up to date (0) or needs to be updated (1).
+ */
+
+/**
+ * Identifies a content type which has translation support enabled.
+ */
+define('TRANSLATION_ENABLED', 2);
+
+/**
+ * Implementation of hook_help().
+ */
+function translation_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#translation':
+      $output = '<p>'. t('The content translation module allows content to be translated into different languages. Working with the <a href="@locale">locale module</a> (which manages enabled languages and provides translation for the site interface), the content translation module is key to creating and maintaining translated site content.', array('@locale' => url('admin/help/locale'))) .'</p>';
+      $output .= '<p>'. t('Configuring content translation and translation-enabled content types:') .'</p>';
+      $output .= '<ul><li>'. t('Assign the <em>translate content</em> permission to the appropriate user roles at the <a href="@permissions">Permissions configuration page</a>.', array('@permissions' => url('admin/user/permissions'))) .'</li>';
+      $output .= '<li>'. t('Add and enable desired languages at the <a href="@languages">Languages configuration page</a>.', array('@languages' => url('admin/settings/language'))) .'</li>';
+      $output .= '<li>'. t('Determine which <a href="@content-types">content types</a> should support translation features. To enable translation support for a content type, edit the type and at the <em>Multilingual support</em> drop down, select <em>Enabled, with translation</em>. (<em>Multilingual support</em> is located within <em>Workflow settings</em>.) Be sure to save each content type after enabling multilingual support.', array('@content-types' => url('admin/content/types'))) .'</li></ul>';
+      $output .= '<p>'. t('Working with translation-enabled content types:') .'</p>';
+      $output .= '<ul><li>'. t('Use the <em>Language</em> drop down to select the appropriate language when creating or editing posts.') .'</li>';
+      $output .= '<li>'. t('Provide new or edit current translations for existing posts via the <em>Translation</em> tab. Only visible while viewing a post as a user with the <em>translate content</em> permission, this tab allows translations to be added or edited using a specialized editing form that also displays the content being translated.') .'</li>';
+      $output .= '<li>'. t('Update translations as needed, so that they accurately reflect changes in the content of the original post. The translation status flag provides a simple method for tracking outdated translations. After editing a post, for example, select the <em>Flag translations as outdated</em> check box to mark all of its translations as outdated and in need of revision. Individual translations may be marked for revision by selecting the <em>This translation needs to be updated</em> check box on the translation editing form.') .'</li>';
+      $output .= '<li>'. t('The <a href="@content-node">Content management administration page</a> displays the language of each post, and also allows filtering by language or translation status.', array('@content-node' => url('admin/content/node'))) .'</li></ul>';
+      $output .= '<p>'. t('Use the <a href="@blocks">language switcher block</a> provided by locale module to allow users to select a language. If available, both the site interface and site content are presented in the language selected.', array('@blocks' => url('admin/build/block'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@translation">Translation module</a>.', array('@translation' => 'http://drupal.org/handbook/modules/translation/')) .'</p>';
+      return $output;
+    case 'node/%/translate':
+      $output = '<p>'. t('Translations of a piece of content are managed with translation sets. Each translation set has one source post and any number of translations in any of the <a href="!languages">enabled languages</a>. All translations are tracked to be up to date or outdated based on whether the source post was modified significantly.', array('!languages' => url('admin/settings/language'))) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function translation_menu() {
+  $items = array();
+  $items['node/%node/translate'] = array(
+    'title' => 'Translate',
+    'page callback' => 'translation_node_overview',
+    'page arguments' => array(1),
+    'access callback' => '_translation_tab_access',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+    'file' => 'translation.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * Menu access callback.
+ *
+ * Only display translation tab for node types, which have translation enabled
+ * and where the current node is not language neutral (which should span
+ * all languages).
+ */
+function _translation_tab_access($node) {
+  if (!empty($node->language) && translation_supported_type($node->type)) {
+    return user_access('translate content');
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function translation_perm() {
+  return array('translate content');
+}
+
+/**
+ * Implementation of hook_form_alter().
+ *
+ * - Add translation option to content type form.
+ * - Alters language fields on node forms when a translation
+ *   is about to be created.
+ */
+function translation_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'node_type_form') {
+    // Add translation option to content type form.
+    $form['workflow']['language_content_type']['#options'][TRANSLATION_ENABLED] = t('Enabled, with translation');
+    // Description based on text from locale.module.
+    $form['workflow']['language_content_type']['#description'] = t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. You can also turn on translation for this content type, which lets you have content translated to any of the enabled languages. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/settings/language')));
+  }
+  elseif (isset($form['#id']) && $form['#id'] == 'node-form' && translation_supported_type($form['#node']->type)) {
+    $node = $form['#node'];
+    if (!empty($node->translation_source)) {
+      // We are creating a translation. Add values and lock language field.
+      $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source);
+      $form['language']['#disabled'] = TRUE;
+    }
+    elseif (!empty($node->nid) && !empty($node->tnid)) {
+      // Disable languages for existing translations, so it is not possible to switch this
+      // node to some language which is already in the translation set. Also remove the
+      // language neutral option.
+      unset($form['language']['#options']['']);
+      foreach (translation_node_get_translations($node->tnid) as $translation) {
+        if ($translation->nid != $node->nid) {
+          unset($form['language']['#options'][$translation->language]);
+        }
+      }
+      // Add translation values and workflow options.
+      $form['tnid'] = array('#type' => 'value', '#value' => $node->tnid);
+      $form['translation'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Translation settings'),
+        '#access' => user_access('translate content'),
+        '#collapsible' => TRUE,
+        '#collapsed' => !$node->translate,
+        '#tree' => TRUE,
+        '#weight' => 30,
+      );
+      if ($node->tnid == $node->nid) {
+        // This is the source node of the translation
+        $form['translation']['retranslate'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Flag translations as outdated'),
+          '#default_value' => 0,
+          '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
+        );
+        $form['translation']['status'] = array('#type' => 'value', '#value' => 0);
+      }
+      else {
+        $form['translation']['status'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('This translation needs to be updated'),
+          '#default_value' => $node->translate,
+          '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
+        );
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_link().
+ *
+ * Display translation links with native language names, if this node
+ * is part of a translation set.
+ */
+function translation_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+  if ($type == 'node' && ($node->tnid) && $translations = translation_node_get_translations($node->tnid)) {
+    // Do not show link to the same node.
+    unset($translations[$node->language]);
+    $languages = language_list();
+    foreach ($translations as $language => $translation) {
+      $links["node_translation_$language"] = array(
+        'title' => $languages[$language]->native,
+        'href' => "node/$translation->nid",
+        'language' => $languages[$language],
+        'attributes' => array('title' => $translation->title, 'class' => 'translation-link')
+      );
+    }
+  }
+  return $links;
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * Manages translation information for nodes.
+ */
+function translation_nodeapi(&$node, $op, $teaser, $page) {
+  // Only act if we are dealing with a content type supporting translations.
+  if (!translation_supported_type($node->type)) {
+    return;
+  }
+
+  switch ($op) {
+    case 'prepare':
+      if (empty($node->nid) && isset($_GET['translation']) && isset($_GET['language']) &&
+          ($source_nid = $_GET['translation']) && ($language = $_GET['language']) &&
+          (user_access('translate content'))) {
+        // We are translating a node from a source node, so
+        // load the node to be translated and populate fields.
+        $node->language = $language;
+        $node->translation_source = node_load($source_nid);
+        $node->title = $node->translation_source->title;
+        $node->body = $node->translation_source->body;
+        // Let every module add custom translated fields.
+        node_invoke_nodeapi($node, 'prepare translation');
+      }
+      break;
+
+    case 'insert':
+      if (!empty($node->translation_source)) {
+        if ($node->translation_source->tnid) {
+          // Add node to existing translation set.
+          $tnid = $node->translation_source->tnid;
+        }
+        else {
+          // Create new translation set, using nid from the source node.
+          $tnid = $node->translation_source->nid;
+          db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->translation_source->nid);
+        }
+        db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $tnid, 0, $node->nid);
+      }
+      break;
+
+    case 'update':
+      if (isset($node->translation) && $node->translation && !empty($node->language) && $node->tnid) {
+        // Update translation information.
+        db_query("UPDATE {node} SET tnid = %d, translate = %d WHERE nid = %d", $node->tnid, $node->translation['status'], $node->nid);
+        if (!empty($node->translation['retranslate'])) {
+          // This is the source node, asking to mark all translations outdated.
+          db_query("UPDATE {node} SET translate = 1 WHERE tnid = %d AND nid != %d", $node->tnid, $node->nid);
+        }
+      }
+      break;
+
+    case 'delete':
+      translation_remove_from_set($node);
+      break;
+  }
+}
+
+/**
+ * Remove a node from its translation set (if any)
+ * and update the set accordingly.
+ */
+function translation_remove_from_set($node) {
+  if (isset($node->tnid)) {
+    if (db_result(db_query('SELECT COUNT(*) FROM {node} WHERE tnid = %d', $node->tnid)) <= 2) {
+      // There would only be one node left in the set: remove the set altogether.
+      db_query('UPDATE {node} SET tnid = 0, translate = 0 WHERE tnid = %d', $node->tnid);
+    }
+    else {
+      db_query('UPDATE {node} SET tnid = 0, translate = 0 WHERE nid = %d', $node->nid);
+
+      // If the node being removed was the source of the translation set,
+      // we pick a new source - preferably one that is up to date.
+      if ($node->tnid == $node->nid) {
+        $new_tnid = db_result(db_query('SELECT nid FROM {node} WHERE tnid = %d ORDER BY translate ASC, nid ASC', $node->tnid));
+        db_query('UPDATE {node} SET tnid = %d WHERE tnid = %d', $new_tnid, $node->tnid);
+      }
+    }
+  }
+}
+
+/**
+ * Get all nodes in a translation set, represented by $tnid.
+ *
+ * @param $tnid
+ *   The translation source nid of the translation set, the identifier
+ *   of the node used to derive all translations in the set.
+ * @return
+ *   Array of partial node objects (nid, title, language) representing
+ *   all nodes in the translation set, in effect all translations
+ *   of node $tnid, including node $tnid itself. Because these are
+ *   partial nodes, you need to node_load() the full node, if you
+ *   need more properties. The array is indexed by language code.
+ */
+function translation_node_get_translations($tnid) {
+  static $translations = array();
+
+  if (is_numeric($tnid) && $tnid) {
+    if (!isset($translations[$tnid])) {
+      $translations[$tnid] = array();
+      $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.language FROM {node} n WHERE n.tnid = %d'), $tnid);
+      while ($node = db_fetch_object($result)) {
+        $translations[$tnid][$node->language] = $node;
+      }
+    }
+    return $translations[$tnid];
+  }
+}
+
+/**
+ * Returns whether the given content type has support for translations.
+ *
+ * @return
+ *   Boolean value.
+ */
+function translation_supported_type($type) {
+  return variable_get('language_content_type_'. $type, 0) == TRANSLATION_ENABLED;
+}
+
+/**
+ * Return paths of all translations of a node, based on
+ * its Drupal path.
+ *
+ * @param $path
+ *   A Drupal path, for example node/432.
+ * @return
+ *   An array of paths of translations of the node accessible
+ *   to the current user keyed with language codes.
+ */
+function translation_path_get_translations($path) {
+  $paths = array();
+  // Check for a node related path, and for its translations.
+  if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid)) {
+    foreach (translation_node_get_translations($node->tnid) as $language => $translation_node) {
+      $paths[$language] = 'node/'. $translation_node->nid . $matches[2];
+    }
+  }
+  return $paths;
+}
+
+/**
+ * Implementation of hook_alter_translation_link().
+ *
+ * Replaces links with pointers to translated versions of the content.
+ */
+function translation_translation_link_alter(&$links, $path) {
+  if ($paths = translation_path_get_translations($path)) {
+    foreach ($links as $langcode => $link) {
+      if (isset($paths[$langcode])) {
+        // Translation in a different node.
+        $links[$langcode]['href'] = $paths[$langcode];
+      }
+      else {
+        // No translation in this language, or no permission to view.
+        unset($links[$langcode]);
+      }
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/translation/translation.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,59 @@
+<?php
+// $Id: translation.pages.inc,v 1.2 2008/01/07 13:18:40 goba Exp $
+
+/**
+ * @file
+ * User page callbacks for the translation module.
+ */
+
+/**
+ * Overview page for a node's translations.
+ *
+ * @param $node
+ *   Node object.
+ */
+function translation_node_overview($node) {
+  if ($node->tnid) {
+    // Already part of a set, grab that set.
+    $tnid = $node->tnid;
+    $translations = translation_node_get_translations($node->tnid);
+  }
+  else {
+    // We have no translation source nid, this could be a new set, emulate that.
+    $tnid = $node->nid;
+    $translations = array($node->language => $node);
+  }
+
+  $header = array(t('Language'), t('Title'), t('Status'), t('Operations'));
+
+  foreach (language_list() as $language) {
+    $options = array();
+    $language_name = $language->name;
+    if (isset($translations[$language->language])) {
+      // Existing translation in the translation set: display status.
+      // We load the full node to check whether the user can edit it.
+      $translation_node = node_load($translations[$language->language]->nid);
+      $title = l($translation_node->title, 'node/'. $translation_node->nid);
+      if (node_access('update', $translation_node)) {
+        $options[] = l(t('edit'), "node/$translation_node->nid/edit");
+      }
+      $status = $translation_node->status ? t('Published') : t('Not published');
+      $status .= $translation_node->translate ? ' - <span class="marker">'. t('outdated') .'</span>' : '';
+      if ($translation_node->nid == $tnid) {
+        $language_name = '<strong>'. $language_name .'</strong> (source)';
+      }
+    }
+    else {
+      // No such translation in the set yet: help user to create it.
+      $title = t('n/a');
+      if (node_access('create', $node)) {
+        $options[] = l(t('add translation'), 'node/add/'. str_replace('_', '-', $node->type), array('query' => "translation=$node->nid&language=$language->language"));
+      }
+      $status = t('Not translated');
+    }
+    $rows[] = array($language_name, $title, $status, implode(" | ", $options));
+  }
+
+  drupal_set_title(t('Translations of %title', array('%title' => $node->title)));
+  return theme('table', $header, $rows);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/trigger/trigger.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,285 @@
+<?php
+// $Id: trigger.admin.inc,v 1.5 2008/01/08 10:35:43 goba Exp $
+
+/**
+ * @file
+ * Admin page callbacks for the trigger module.
+ */
+
+/**
+ * Build the form that allows users to assign actions to hooks.
+ *
+ * @param $type
+ *   Name of hook.
+ * @return
+ *   HTML form.
+ */
+function trigger_assign($type = NULL) {
+  // If no type is specified we default to node actions, since they
+  // are the most common.
+  if (!isset($type)) {
+    drupal_goto('admin/build/trigger/node');
+  }
+  if ($type == 'node') {
+    $type = 'nodeapi';
+  }
+
+  $output = '';
+  $hooks = module_invoke_all('hook_info');
+  foreach ($hooks as $module => $hook) {
+    if (isset($hook[$type])) {
+      foreach ($hook[$type] as $op => $description) {
+        $form_id = 'trigger_'. $type .'_'. $op .'_assign_form';
+        $output .= drupal_get_form($form_id, $type, $op, $description['runs when']);
+      }
+    }
+  }
+  return $output;
+}
+
+/**
+ * Confirm removal of an assigned action.
+ *
+ * @param $hook
+ * @param $op
+ * @param $aid
+ *   The action ID.
+ * @ingroup forms
+ * @see trigger_unassign_submit()
+ */
+function trigger_unassign($form_state, $hook = NULL, $op = NULL, $aid = NULL) {
+  if (!($hook && $op && $aid)) {
+    drupal_goto('admin/build/trigger/assign');
+  }
+
+  $form['hook'] = array(
+    '#type' => 'value',
+    '#value' => $hook,
+  );
+  $form['operation'] = array(
+    '#type' => 'value',
+    '#value' => $op,
+  );
+  $form['aid'] = array(
+    '#type' => 'value',
+    '#value' => $aid,
+  );
+
+  $action = actions_function_lookup($aid);
+  $actions = actions_get_all_actions();
+
+  $destination = 'admin/build/trigger/'. ($hook == 'nodeapi' ? 'node' : $hook);
+
+  return confirm_form($form,
+    t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['description'])),
+    $destination,
+    t('You can assign it again later if you wish.'),
+    t('Unassign'), t('Cancel')
+  );
+}
+
+function trigger_unassign_submit($form, &$form_state) {
+  $form_values = $form_state['values'];
+  if ($form_values['confirm'] == 1) {
+    $aid = actions_function_lookup($form_values['aid']);
+    db_query("DELETE FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid);
+    $actions = actions_get_all_actions();
+    watchdog('actions', 'Action %action has been unassigned.',  array('%action' => check_plain($actions[$aid]['description'])));
+    drupal_set_message(t('Action %action has been unassigned.', array('%action' => $actions[$aid]['description'])));
+    $hook = $form_values['hook'] == 'nodeapi' ? 'node' : $form_values['hook'];
+    $form_state['redirect'] = 'admin/build/trigger/'. $hook;
+  }
+  else {
+    drupal_goto('admin/build/trigger');
+  }
+}
+
+/**
+ * Create the form definition for assigning an action to a hook-op combination.
+ *
+ * @param $form_state
+ *   Information about the current form.
+ * @param $hook
+ *   The name of the hook, e.g., 'nodeapi'.
+ * @param $op
+ *   The name of the hook operation, e.g., 'insert'.
+ * @param $description
+ *   A plain English description of what this hook operation does.
+ * @return
+ *
+ * @ingoup forms
+ * @see trigger_assign_form_validate()
+ * @see trigger_assign_form_submit()
+ */
+function trigger_assign_form($form_state, $hook, $op, $description) {
+  $form['hook'] = array(
+    '#type' => 'hidden',
+    '#value' => $hook,
+  );
+  $form['operation'] = array(
+    '#type' => 'hidden',
+    '#value' => $op,
+  );
+  // All of these forms use the same validate and submit functions.
+  $form['#validate'][] = 'trigger_assign_form_validate';
+  $form['#submit'][] = 'trigger_assign_form_submit';
+
+  $options = array();
+  $functions = array();
+  // Restrict the options list to actions that declare support for this hook-op
+  // combination.
+  foreach (actions_list() as $func => $metadata) {
+    if (isset($metadata['hooks']['any']) || (isset($metadata['hooks'][$hook]) && is_array($metadata['hooks'][$hook]) && (in_array($op, $metadata['hooks'][$hook])))) {
+      $functions[] = $func;
+    }
+  }
+  foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
+    if (in_array($action['callback'], $functions)) {
+      $options[$action['type']][$aid] = $action['description'];
+    }
+  }
+
+  $form[$op] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Trigger: ') . $description,
+    '#theme' => 'trigger_display'
+    );
+  // Retrieve actions that are already assigned to this hook-op combination.
+  $actions = _trigger_get_hook_actions($hook, $op);
+  $form[$op]['assigned']['#type'] = 'value';
+  $form[$op]['assigned']['#value'] = array();
+  foreach ($actions as $aid => $description) {
+    $form[$op]['assigned']['#value'][$aid] = array(
+      'description' => $description,
+      'link' => l(t('unassign'), "admin/build/trigger/unassign/$hook/$op/". md5($aid))
+    );
+  }
+
+  $form[$op]['parent'] = array(
+    '#prefix' => "<div class='container-inline'>",
+    '#suffix' => '</div>',
+  );
+  // List possible actions that may be assigned.
+  if (count($options) != 0) {
+    array_unshift($options, t('Choose an action'));
+    $form[$op]['parent']['aid'] = array(
+      '#type' => 'select',
+      '#options' => $options,
+    );
+    $form[$op]['parent']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Assign')
+    );
+  }
+  else {
+    $form[$op]['none'] = array(
+      '#value' => t('No available actions for this trigger.')
+    );
+  }
+  return $form;
+}
+
+/**
+ * Validation function for trigger_assign_form().
+ *
+ * Makes sure that the user is not re-assigning an action to an event.
+ */
+function trigger_assign_form_validate($form, $form_state) {
+  $form_values = $form_state['values'];
+  if (!empty($form_values['aid'])) {
+    $aid = actions_function_lookup($form_values['aid']);
+    if (db_result(db_query("SELECT aid FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid))) {
+      form_set_error($form_values['operation'], t('The action you chose is already assigned to that trigger.'));
+    }
+  }
+}
+
+/**
+ * Submit function for trigger_assign_form().
+ */
+function trigger_assign_form_submit($form, $form_state) {
+  $form_values = $form_state['values'];
+
+  if (!empty($form_values['aid'])) {
+    $aid = actions_function_lookup($form_values['aid']);
+    $weight = db_result(db_query("SELECT MAX(weight) FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s'", $form_values['hook'], $form_values['operation']));
+    db_query("INSERT INTO {trigger_assignments} values ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], $aid, $weight + 1);
+    // If this action changes a node property, we need to save the node
+    // so the change will persist.
+    $actions = actions_list();
+    if (isset($actions[$aid]['behavior']) && in_array('changes_node_property', $actions[$aid]['behavior']) && ($form_values['operation'] != 'presave')) {
+      // Delete previous node_save_action if it exists, and re-add a new one at a higher weight.
+      $save_post_action_assigned = db_result(db_query("SELECT aid FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']));
+      if ($save_post_action_assigned) {
+        db_query("DELETE FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']);
+      }
+      db_query("INSERT INTO {trigger_assignments} VALUES ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], 'node_save_action', $weight + 2);
+      if (!$save_post_action_assigned) {
+        drupal_set_message(t('You have added an action that changes a the property of a post. A Save post action has been added so that the property change will be saved.'));
+      }
+    }
+  }
+}
+
+/**
+ * Display actions assigned to this hook-op combination in a table.
+ *
+ * @param array $element
+ *   The fieldset including all assigned actions.
+ * @return
+ *   The rendered form with the table prepended.
+ *
+ * @ingroup themeable
+ */
+function theme_trigger_display($element) {
+  $header = array();
+  $rows = array();
+  if (count($element['assigned']['#value'])) {
+    $header = array(array('data' => t('Name')), array('data' => t('Operation')));
+    $rows = array();
+    foreach ($element['assigned']['#value'] as $aid => $info) {
+      $rows[] = array(
+        $info['description'],
+        $info['link']
+      );
+    }
+  }
+
+  if (count($rows)) {
+    $output = theme('table', $header, $rows) . drupal_render($element);
+  }
+  else {
+    $output = drupal_render($element);
+  }
+  return $output;
+}
+
+
+/**
+ * Get the actions that have already been defined for this
+ * type-hook-op combination.
+ *
+ * @param $type
+ *   One of 'node', 'user', 'comment'.
+ * @param $hook
+ *   The name of the hook for which actions have been assigned,
+ *   e.g. 'nodeapi'.
+ * @param $op
+ *   The hook operation for which the actions have been assigned,
+ *   e.g., 'view'.
+ * @return
+ *   An array of action descriptions keyed by action IDs.
+ */
+function _trigger_get_hook_actions($hook, $op, $type = NULL) {
+  $actions = array();
+  if ($type) {
+    $result = db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE a.type = '%s' AND h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $type, $hook, $op);
+  }
+  else {
+    $result = db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $hook, $op);
+  }
+  while ($action = db_fetch_object($result)) {
+    $actions[$action->aid] = $action->description;
+  }
+  return $actions;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/trigger/trigger.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: trigger.info,v 1.1 2007/09/11 14:50:05 goba Exp $
+name = Trigger
+description = Enables actions to be fired on certain system events, such as when new content is created.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/trigger/trigger.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,63 @@
+<?php
+// $Id: trigger.install,v 1.5 2007/12/28 12:02:52 dries Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function trigger_install() {
+  // Create tables.
+  drupal_install_schema('trigger');
+
+  // Do initial synchronization of actions in code and the database.
+  actions_synchronize(actions_list());
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function trigger_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('trigger');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function trigger_schema() {
+  $schema['trigger_assignments'] = array(
+    'description' => t('Maps trigger to hook and operation assignments from trigger.module.'),
+    'fields' => array(
+      'hook' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Primary Key: The name of the internal Drupal hook upon which an action is firing; for example, nodeapi.'),
+      ),
+      'op' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Primary Key: The specific operation of the hook upon which an action is firing: for example, presave.'),
+      ),
+      'aid' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("Primary Key: Action's {actions}.aid."),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The weight of the trigger assignment in relation to other triggers.'),
+      ),
+    ),
+    'primary key' => array('hook', 'op', 'aid'),
+  );
+  return $schema;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/trigger/trigger.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,431 @@
+<?php
+// $Id: trigger.module,v 1.13 2008/01/21 20:08:15 goba Exp $
+
+/**
+ * @file
+ * Enables functions to be stored and executed at a later time when
+ * triggered by other modules or by one of Drupal's core API hooks.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function trigger_help($path, $arg) {
+  $explanation = '<p>'. t('Triggers are system events, such as when new content is added or when a user logs in. Trigger module combines these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/settings/actions'))) .'</p>';
+  switch ($path) {
+    case 'admin/build/trigger/comment':
+      return $explanation .'<p>'. t('Below you can assign actions to run when certain comment-related triggers happen. For example, you could promote a post to the front page when a comment is added.') .'</p>';
+    case 'admin/build/trigger/node':
+      return $explanation .'<p>'. t('Below you can assign actions to run when certain content-related triggers happen. For example, you could send an e-mail to an administrator when a post is created or updated.') .'</p>';
+    case 'admin/build/trigger/cron':
+      return $explanation .'<p>'. t('Below you can assign actions to run during each pass of a <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) .'</p>';
+    case 'admin/build/trigger/taxonomy':
+      return $explanation .'<p>'. t('Below you can assign actions to run when certain taxonomy-related triggers happen. For example, you could send an e-mail to an administrator when a term is deleted.') .'</p>';
+    case 'admin/build/trigger/user':
+      return $explanation .'<p>'. t("Below you can assign actions to run when certain user-related triggers happen. For example, you could send an e-mail to an administrator when a user account is deleted.") .'</p>';
+    case 'admin/help#trigger':
+      $output = '<p>'. t('The Trigger module provides the ability to trigger <a href="@actions">actions</a> upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/settings/actions'))) .'</p>';
+      $output .= '<p>'. t('The combination of actions and triggers can perform many useful tasks, such as e-mailing an administrator if a user account is deleted, or automatically unpublishing comments that contain certain words. By default, there are five "contexts" of events (Comments, Content, Cron, Taxonomy, and Users), but more may be added by additional modules.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@trigger">Trigger module</a>.', array('@trigger' => 'http://drupal.org/handbook/modules/trigger/')) .'</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function trigger_menu() {
+  $items['admin/build/trigger'] = array(
+    'title' => 'Triggers',
+    'description' => 'Tell Drupal when to execute actions.',
+    'page callback' => 'trigger_assign',
+    'access callback' => 'trigger_access_check',
+    'access arguments' => array('node'),
+    'file' => 'trigger.admin.inc',
+  );
+  // We don't use a menu wildcard here because these are tabs,
+  // not invisible items.
+  $items['admin/build/trigger/node'] = array(
+    'title' => 'Content',
+    'page callback' => 'trigger_assign',
+    'page arguments' => array('node'),
+    'access arguments' => array('node'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'trigger.admin.inc',
+  );
+  $items['admin/build/trigger/user'] = array(
+    'title' => 'Users',
+    'page callback' => 'trigger_assign',
+    'page arguments' => array('user'),
+    'access arguments' => array('user'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'trigger.admin.inc',
+  );
+  $items['admin/build/trigger/comment'] = array(
+    'title' => 'Comments',
+    'page callback' => 'trigger_assign',
+    'page arguments' => array('comment'),
+    'access callback' => 'trigger_access_check',
+    'access arguments' => array('comment'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'trigger.admin.inc',
+  );
+  $items['admin/build/trigger/taxonomy'] = array(
+    'title' => 'Taxonomy',
+    'page callback' => 'trigger_assign',
+    'page arguments' => array('taxonomy'),
+    'access callback' => 'trigger_access_check',
+    'access arguments' => array('taxonomy'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'trigger.admin.inc',
+  );
+  $items['admin/build/trigger/cron'] = array(
+    'title' => 'Cron',
+    'page callback' => 'trigger_assign',
+    'page arguments' => array('cron'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'trigger.admin.inc',
+  );
+
+  // We want contributed modules to be able to describe
+  // their hooks and have actions assignable to them.
+  $hooks = module_invoke_all('hook_info');
+  foreach ($hooks as $module => $hook) {
+    // We've already done these.
+    if (in_array($module, array('node', 'comment', 'user', 'system', 'taxonomy'))) {
+      continue;
+    }
+    $info = db_result(db_query("SELECT info FROM {system} WHERE name = '%s'", $module));
+    $info = unserialize($info);
+    $nice_name = $info['name'];
+    $items["admin/build/trigger/$module"] = array(
+      'title' => $nice_name,
+      'page callback' => 'trigger_assign',
+      'page arguments' => array($module),
+      'access arguments' => array($module),
+      'type' => MENU_LOCAL_TASK,
+      'file' => 'trigger.admin.inc',
+    );
+  }
+  $items['admin/build/trigger/unassign'] = array(
+    'title' => 'Unassign',
+    'description' => 'Unassign an action from a trigger.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('trigger_unassign'),
+    'type' => MENU_CALLBACK,
+    'file' => 'trigger.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Access callback for menu system.
+ */
+function trigger_access_check($module) {
+  return (module_exists($module) && user_access('administer actions'));
+}
+
+/**
+ * Get the aids of actions to be executed for a hook-op combination.
+ *
+ * @param $hook
+ *   The name of the hook being fired.
+ * @param $op
+ *   The name of the operation being executed. Defaults to an empty string
+ *   because some hooks (e.g., hook_cron()) do not have operations.
+ * @return
+ *   An array of action IDs.
+ */
+function _trigger_get_hook_aids($hook, $op = '') {
+  $aids = array();
+  $result = db_query("SELECT aa.aid, a.type FROM {trigger_assignments} aa LEFT JOIN {actions} a ON aa.aid = a.aid WHERE aa.hook = '%s' AND aa.op = '%s' ORDER BY weight", $hook, $op);
+  while ($action = db_fetch_object($result)) {
+    $aids[$action->aid]['type'] = $action->type;
+  }
+  return $aids;
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function trigger_theme() {
+  return array(
+    'trigger_display' => array(
+      'arguments' => array('element'),
+      'file' => 'trigger.admin.inc',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_forms(). We reuse code by using the
+ * same assignment form definition for each node-op combination.
+ */
+function trigger_forms() {
+  $hooks = module_invoke_all('hook_info');
+  foreach ($hooks as $module => $info) {
+    foreach ($hooks[$module] as $hook => $ops) {
+      foreach ($ops as $op => $description) {
+        $forms['trigger_'. $hook .'_'. $op .'_assign_form'] = array('callback' => 'trigger_assign_form');
+      }
+    }
+  }
+
+  return $forms;
+}
+
+/**
+ * When an action is called in a context that does not match its type,
+ * the object that the action expects must be retrieved. For example, when
+ * an action that works on users is called during the node hook, the
+ * user object is not available since the node hook doesn't pass it.
+ * So here we load the object the action expects.
+ *
+ * @param $type
+ *   The type of action that is about to be called.
+ * @param $node
+ *   The node that was passed via the nodeapi hook.
+ * @return
+ *   The object expected by the action that is about to be called.
+ */
+function _trigger_normalize_node_context($type, $node) {
+  switch ($type) {
+    // If an action that works on comments is being called in a node context,
+    // the action is expecting a comment object. But we do not know which comment
+    // to give it. The first? The most recent? All of them? So comment actions
+    // in a node context are not supported.
+
+    // An action that works on users is being called in a node context.
+    // Load the user object of the node's author.
+    case 'user':
+      return user_load(array('uid' => $node->uid));
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function trigger_nodeapi(&$node, $op, $a3, $a4) {
+  // Keep objects for reuse so that changes actions make to objects can persist.
+  static $objects;
+  // Prevent recursion by tracking which operations have already been called.
+  static $recursion;
+  // Support a subset of operations.
+  if (!in_array($op, array('view', 'update', 'presave', 'insert', 'delete')) || isset($recursion[$op])) {
+    return;
+  }
+  $recursion[$op] = TRUE;
+
+  $aids = _trigger_get_hook_aids('nodeapi', $op);
+  if (!$aids) {
+    return;
+  }
+  $context = array(
+    'hook' => 'nodeapi',
+    'op' => $op,
+  );
+
+  // We need to get the expected object if the action's type is not 'node'.
+  // We keep the object in $objects so we can reuse it if we have multiple actions
+  // that make changes to an object.
+  foreach ($aids as $aid => $action_info) {
+    if ($action_info['type'] != 'node') {
+      if (!isset($objects[$action_info['type']])) {
+        $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
+      }
+      // Since we know about the node, we pass that info along to the action.
+      $context['node'] = $node;
+      $result = actions_do($aid, $objects[$action_info['type']], $context, $a4, $a4);
+    }
+    else {
+      actions_do($aid, $node, $context, $a3, $a4);
+    }
+  }
+}
+
+/**
+ * When an action is called in a context that does not match its type,
+ * the object that the action expects must be retrieved. For example, when
+ * an action that works on nodes is called during the comment hook, the
+ * node object is not available since the comment hook doesn't pass it.
+ * So here we load the object the action expects.
+ *
+ * @param $type
+ *   The type of action that is about to be called.
+ * @param $comment
+ *   The comment that was passed via the comment hook.
+ * @return
+ *   The object expected by the action that is about to be called.
+ */
+function _trigger_normalize_comment_context($type, $comment) {
+  switch ($type) {
+    // An action that works with nodes is being called in a comment context.
+    case 'node':
+      return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
+
+    // An action that works on users is being called in a comment context.
+    case 'user':
+      return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
+  }
+}
+
+/**
+ * Implementation of hook_comment().
+ */
+function trigger_comment($a1, $op) {
+  // Keep objects for reuse so that changes actions make to objects can persist.
+  static $objects;
+  // We support a subset of operations.
+  if (!in_array($op, array('insert', 'update', 'delete', 'view'))) {
+    return;
+  }
+  $aids = _trigger_get_hook_aids('comment', $op);
+  $context = array(
+    'hook' => 'comment',
+    'op' => $op,
+  );
+  // We need to get the expected object if the action's type is not 'comment'.
+  // We keep the object in $objects so we can reuse it if we have multiple actions
+  // that make changes to an object.
+  foreach ($aids as $aid => $action_info) {
+    if ($action_info['type'] != 'comment') {
+      if (!isset($objects[$action_info['type']])) {
+        $objects[$action_info['type']] = _trigger_normalize_comment_context($action_info['type'], $a1);
+      }
+      // Since we know about the comment, we pass it along to the action
+      // in case it wants to peek at it.
+      $context['comment'] = (object) $a1;
+      actions_do($aid, $objects[$action_info['type']], $context);
+    }
+    else {
+      $comment = (object) $a1;
+      actions_do($aid, $comment, $context);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function trigger_cron() {
+  $aids = _trigger_get_hook_aids('cron');
+  $context = array(
+    'hook' => 'cron',
+    'op' => '',
+  );
+  // Cron does not act on any specific object.
+  $object = NULL;
+  actions_do(array_keys($aids), $object, $context);
+}
+
+/**
+ * When an action is called in a context that does not match its type,
+ * the object that the action expects must be retrieved. For example, when
+ * an action that works on nodes is called during the user hook, the
+ * node object is not available since the user hook doesn't pass it.
+ * So here we load the object the action expects.
+ *
+ * @param $type
+ *   The type of action that is about to be called.
+ * @param $account
+ *   The account object that was passed via the user hook.
+ * @return
+ *   The object expected by the action that is about to be called.
+ */
+function _trigger_normalize_user_context($type, $account) {
+  switch ($type) {
+    // If an action that works on comments is being called in a user context,
+    // the action is expecting a comment object. But we have no way of
+    // determining the appropriate comment object to pass. So comment
+    // actions in a user context are not supported.
+
+    // An action that works with nodes is being called in a user context.
+    // If a single node is being viewed, return the node.
+    case 'node':
+      // If we are viewing an individual node, return the node.
+      if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
+        return node_load(array('nid' => arg(1)));
+      }
+  }
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function trigger_user($op, &$edit, &$account, $category = NULL) {
+  // Keep objects for reuse so that changes actions make to objects can persist.
+  static $objects;
+  // We support a subset of operations.
+  if (!in_array($op, array('login', 'logout', 'insert', 'update', 'delete', 'view'))) {
+    return;
+  }
+  $aids = _trigger_get_hook_aids('user', $op);
+  $context = array(
+    'hook' => 'user',
+    'op' => $op,
+    'form_values' => &$edit,
+  );
+  foreach ($aids as $aid => $action_info) {
+    if ($action_info['type'] != 'user') {
+      if (!isset($objects[$action_info['type']])) {
+        $objects[$action_info['type']] = _trigger_normalize_user_context($action_info['type'], $account);
+      }
+      $context['account'] = $account;
+      actions_do($aid, $objects[$action_info['type']], $context);
+    }
+    else {
+      actions_do($aid, $account, $context, $category);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy().
+ */
+function trigger_taxonomy($op, $type, $array) {
+  if ($type != 'term') {
+    return;
+  }
+  $aids = _trigger_get_hook_aids('taxonomy', $op);
+  $context = array(
+    'hook' => 'taxonomy',
+    'op' => $op
+  );
+  foreach ($aids as $aid => $action_info) {
+    $taxonomy_object = (object) $array;
+    actions_do($aid, $taxonomy_object, $context);
+  }
+}
+
+/**
+ * Often we generate a select field of all actions. This function
+ * generates the options for that select.
+ *
+ * @param $type
+ *   One of 'node', 'user', 'comment'.
+ * @return
+ *   Array keyed by action ID.
+ */
+function trigger_options($type = 'all') {
+  $options = array(t('Choose an action'));
+  foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
+    $options[$action['type']][$aid] = $action['description'];
+  }
+
+  if ($type == 'all') {
+    return $options;
+  }
+  else {
+    return $options[$type];
+  }
+}
+
+/**
+ * Implementation of hook_actions_delete().
+ *
+ * Remove all trigger entries for the given action, when deleted.
+ */
+function trigger_actions_delete($aid) {
+  db_query("DELETE FROM {trigger_assignments} WHERE aid = '%s'", $aid);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,32 @@
+/* $Id: update-rtl.css,v 1.3 2007/11/27 16:22:22 goba Exp $ */
+
+.update .project {
+  padding-right: .25em;
+}
+
+.update .version-status {
+  float: left;
+  padding-left: 10px;
+}
+
+.update .version-status .icon {
+  padding-right: .5em;
+}
+
+.update table.version .version-title {
+  padding-left: 1em;
+}
+
+.update table.version .version-details {
+  padding-left: .5em;
+  direction: ltr;
+}
+
+.update table.version .version-links {
+  text-align: left;
+  padding-left: 1em;
+}
+
+.update .check-manually {
+  padding-right: 1em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.compare.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,597 @@
+<?php
+// $Id: update.compare.inc,v 1.8 2008/02/03 19:34:02 goba Exp $
+
+/**
+ * @file
+ * Code required only when comparing available updates to existing data.
+ */
+
+/**
+ * Fetch an array of installed and enabled projects.
+ *
+ * This is only responsible for generating an array of projects (taking into
+ * account projects that include more than one module or theme). Other
+ * information like the specific version and install type (official release,
+ * dev snapshot, etc) is handled later in update_process_project_info() since
+ * that logic is only required when preparing the status report, not for
+ * fetching the available release data.
+ *
+ * @see update_process_project_info()
+ * @see update_calculate_project_data()
+ *
+ */
+function update_get_projects() {
+  static $projects = array();
+  if (empty($projects)) {
+    // Retrieve the projects from cache, if present.
+    $projects = update_project_cache('update_project_projects');
+    if (empty($projects)) {
+      // Still empty, so we have to rebuild the cache.
+      _update_process_info_list($projects, module_rebuild_cache(), 'module');
+      _update_process_info_list($projects, system_theme_data(), 'theme');
+      // Set the projects array into the cache table.
+      cache_set('update_project_projects', $projects, 'cache_update', time() + 3600);
+    }
+  }
+  return $projects;
+}
+
+/**
+ * Populate an array of project data.
+ */
+function _update_process_info_list(&$projects, $list, $project_type) {
+  foreach ($list as $file) {
+    if (empty($file->status)) {
+      // Skip disabled modules or themes.
+      continue;
+    }
+
+    // Skip if the .info file is broken.
+    if (empty($file->info)) {
+      continue;
+    }
+
+    // If the .info doesn't define the 'project', try to figure it out.
+    if (!isset($file->info['project'])) {
+      $file->info['project'] = update_get_project_name($file);
+    }
+
+    // If we still don't know the 'project', give up.
+    if (empty($file->info['project'])) {
+      continue;
+    }
+
+    // If we don't already know it, grab the change time on the .info file
+    // itself. Note: we need to use the ctime, not the mtime (modification
+    // time) since many (all?) tar implementations will go out of their way to
+    // set the mtime on the files it creates to the timestamps recorded in the
+    // tarball. We want to see the last time the file was changed on disk,
+    // which is left alone by tar and correctly set to the time the .info file
+    // was unpacked.
+    if (!isset($file->info['_info_file_ctime'])) {
+      $info_filename = dirname($file->filename) .'/'. $file->name .'.info';
+      $file->info['_info_file_ctime'] = filectime($info_filename);
+    }
+
+    $project_name = $file->info['project'];
+    if (!isset($projects[$project_name])) {
+      // Only process this if we haven't done this project, since a single
+      // project can have multiple modules or themes.
+      $projects[$project_name] = array(
+        'name' => $project_name,
+        'info' => $file->info,
+        'datestamp' => isset($file->info['datestamp']) ? $file->info['datestamp'] : 0,
+        'includes' => array($file->name => $file->info['name']),
+        'project_type' => $project_name == 'drupal' ? 'core' : $project_type,
+      );
+    }
+    else {
+      $projects[$project_name]['includes'][$file->name] = $file->info['name'];
+      $projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
+    }
+  }
+}
+
+/**
+ * Given a $file object (as returned by system_get_files_database()), figure
+ * out what project it belongs to.
+ *
+ * @see system_get_files_database()
+ */
+function update_get_project_name($file) {
+  $project_name = '';
+  if (isset($file->info['project'])) {
+    $project_name = $file->info['project'];
+  }
+  elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core -') !== FALSE)) {
+    $project_name = 'drupal';
+  }
+  elseif (in_array($file->name, array('bluemarine', 'chameleon', 'garland', 'marvin', 'minnelli', 'pushbutton'))) {
+    // Unfortunately, there's no way to tell if a theme is part of core,
+    // so we must hard-code a list here.
+    $project_name = 'drupal';
+  }
+  return $project_name;
+}
+
+/**
+ * Process the list of projects on the system to figure out the currently
+ * installed versions, and other information that is required before we can
+ * compare against the available releases to produce the status report.
+ *
+ * @param $projects
+ *   Array of project information from update_get_projects().
+ */
+function update_process_project_info(&$projects) {
+  foreach ($projects as $key => $project) {
+    // Assume an official release until we see otherwise.
+    $install_type = 'official';
+
+    $info = $project['info'];
+
+    if (isset($info['version'])) {
+      // Check for development snapshots
+      if (preg_match('@(dev|HEAD)@', $info['version'])) {
+        $install_type = 'dev';
+      }
+
+      // Figure out what the currently installed major version is. We need
+      // to handle both contribution (e.g. "5.x-1.3", major = 1) and core
+      // (e.g. "5.1", major = 5) version strings.
+      $matches = array();
+      if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
+        $info['major'] = $matches[2];
+      }
+      elseif (!isset($info['major'])) {
+        // This would only happen for version strings that don't follow the
+        // drupal.org convention. We let contribs define "major" in their
+        // .info in this case, and only if that's missing would we hit this.
+        $info['major'] = -1;
+      }
+    }
+    else {
+      // No version info available at all.
+      $install_type = 'unknown';
+      $info['version'] = t('Unknown');
+      $info['major'] = -1;
+    }
+
+    // Finally, save the results we care about into the $projects array.
+    $projects[$key]['existing_version'] = $info['version'];
+    $projects[$key]['existing_major'] = $info['major'];
+    $projects[$key]['install_type'] = $install_type;
+    unset($projects[$key]['info']);
+  }
+}
+
+/**
+ * Given the installed projects and the available release data retrieved from
+ * remote servers, calculate the current status.
+ *
+ * This function is the heart of the update status feature. It iterates over
+ * every currently installed project. For each one, it first checks if the
+ * project has been flagged with a special status like "unsupported" or
+ * "insecure", or if the project node itself has been unpublished. In any of
+ * those cases, the project is marked with an error and the next project is
+ * considered.
+ *
+ * If the project itself is valid, the function decides what major release
+ * series to consider. The project defines what the currently supported major
+ * versions are for each version of core, so the first step is to make sure
+ * the current version is still supported. If so, that's the target version.
+ * If the current version is unsupported, the project maintainer's recommended
+ * major version is used. There's also a check to make sure that this function
+ * never recommends an earlier release than the currently installed major
+ * version.
+ *
+ * Given a target major version, it scans the available releases looking for
+ * the specific release to recommend (avoiding beta releases and development
+ * snapshots if possible). This is complicated to describe, but an example
+ * will help clarify. For the target major version, find the highest patch
+ * level. If there is a release at that patch level with no extra ("beta",
+ * etc), then we recommend the release at that patch level with the most
+ * recent release date. If every release at that patch level has extra (only
+ * betas), then recommend the latest release from the previous patch
+ * level. For example:
+ *
+ * 1.6-bugfix <-- recommended version because 1.6 already exists.
+ * 1.6
+ *
+ * or
+ *
+ * 1.6-beta
+ * 1.5 <-- recommended version because no 1.6 exists.
+ * 1.4
+ *
+ * It also looks for the latest release from the same major version, even a
+ * beta release, to display to the user as the "Latest version" option.
+ * Additionally, it finds the latest official release from any higher major
+ * versions that have been released to provide a set of "Also available"
+ * options.
+ *
+ * Finally, and most importantly, it keeps scanning the release history until
+ * it gets to the currently installed release, searching for anything marked
+ * as a security update. If any security updates have been found between the
+ * recommended release and the installed version, all of the releases that
+ * included a security fix are recorded so that the site administrator can be
+ * warned their site is insecure, and links pointing to the release notes for
+ * each security update can be included (which, in turn, will link to the
+ * official security announcements for each vulnerability).
+ *
+ * This function relies on the fact that the .xml release history data comes
+ * sorted based on major version and patch level, then finally by release date
+ * if there are multiple releases such as betas from the same major.patch
+ * version (e.g. 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
+ * snapshots for a given major version are always listed last.
+ *
+ * @param $available
+ *  Array of data about available project releases.
+ *
+ * @see update_get_available()
+ * @see update_get_projects()
+ * @see update_process_project_info()
+ */
+function update_calculate_project_data($available) {
+  // Retrieve the projects from cache, if present.
+  $projects = update_project_cache('update_project_data');
+  // If $projects is empty, then the cache must be rebuilt.
+  // Otherwise, return the cached data and skip the rest of the function.
+  if (!empty($projects)) {
+    return $projects;
+  }
+  $projects = update_get_projects();
+  update_process_project_info($projects);
+  foreach ($projects as $project => $project_info) {
+    if (isset($available[$project])) {
+
+      // If the project status is marked as something bad, there's nothing
+      // else to consider.
+      if (isset($available[$project]['project_status'])) {
+        switch ($available[$project]['project_status']) {
+          case 'insecure':
+            $projects[$project]['status'] = UPDATE_NOT_SECURE;
+            if (empty($projects[$project]['extra'])) {
+              $projects[$project]['extra'] = array();
+            }
+            $projects[$project]['extra'][] = array(
+              'class' => 'project-not-secure',
+              'label' => t('Project not secure'),
+              'data' => t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'),
+            );
+            break;
+          case 'unpublished':
+          case 'revoked':
+            $projects[$project]['status'] = UPDATE_REVOKED;
+            if (empty($projects[$project]['extra'])) {
+              $projects[$project]['extra'] = array();
+            }
+            $projects[$project]['extra'][] = array(
+              'class' => 'project-revoked',
+              'label' => t('Project revoked'),
+              'data' => t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
+            );
+            break;
+          case 'unsupported':
+            $projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
+            if (empty($projects[$project]['extra'])) {
+              $projects[$project]['extra'] = array();
+            }
+            $projects[$project]['extra'][] = array(
+              'class' => 'project-not-supported',
+              'label' => t('Project not supported'),
+              'data' => t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
+            );
+            break;
+          default:
+            // Assume anything else (e.g. 'published') is valid and we should
+            // perform the rest of the logic in this function.
+            break;
+        }
+      }
+
+      if (!empty($projects[$project]['status'])) {
+        // We already know the status for this project, so there's nothing
+        // else to compute. Just record everything else we fetched from the
+        // XML file into our projects array and move to the next project.
+        $projects[$project] += $available[$project];
+        continue;
+      }
+
+      // Figure out the target major version.
+      $existing_major = $project_info['existing_major'];
+      $supported_majors = array();
+      if (isset($available[$project]['supported_majors'])) {
+        $supported_majors = explode(',', $available[$project]['supported_majors']);
+      }
+      elseif (isset($available[$project]['default_major'])) {
+        // Older release history XML file without supported or recommended.
+        $supported_majors[] = $available[$project]['default_major'];
+      }
+
+      if (in_array($existing_major, $supported_majors)) {
+        // Still supported, stay at the current major version.
+        $target_major = $existing_major;
+      }
+      elseif (isset($available[$project]['recommended_major'])) {
+        // Since 'recommended_major' is defined, we know this is the new XML
+        // format. Therefore, we know the current release is unsupported since
+        // its major version was not in the 'supported_majors' list. We should
+        // find the best release from the recommended major version.
+        $target_major = $available[$project]['recommended_major'];
+        $projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
+      }
+      elseif (isset($available[$project]['default_major'])) {
+        // Older release history XML file without recommended, so recommend
+        // the currently defined "default_major" version.
+        $target_major = $available[$project]['default_major'];
+      }
+      else {
+        // Malformed XML file? Stick with the current version.
+        $target_major = $existing_major;
+      }
+
+      // Make sure we never tell the admin to downgrade. If we recommended an
+      // earlier version than the one they're running, they'd face an
+      // impossible data migration problem, since Drupal never supports a DB
+      // downgrade path. In the unfortunate case that what they're running is
+      // unsupported, and there's nothing newer for them to upgrade to, we
+      // can't print out a "Recommended version", but just have to tell them
+      // what they have is unsupported and let them figure it out.
+      $target_major = max($existing_major, $target_major);
+
+      $version_patch_changed = '';
+      $patch = '';
+
+      // Defend ourselves from XML history files that contain no releases.
+      if (empty($available[$project]['releases'])) {
+        $projects[$project]['status'] = UPDATE_UNKNOWN;
+        $projects[$project]['reason'] = t('No available releases found');
+        continue;
+      }
+      foreach ($available[$project]['releases'] as $version => $release) {
+        // First, if this is the existing release, check a few conditions.
+        if ($projects[$project]['existing_version'] == $version) {
+          if (isset($release['terms']['Release type']) &&
+              in_array('Insecure', $release['terms']['Release type'])) {
+            $projects[$project]['status'] = UPDATE_NOT_SECURE;
+          }
+          elseif ($release['status'] == 'unpublished') {
+            $projects[$project]['status'] = UPDATE_REVOKED;
+            if (empty($projects[$project]['extra'])) {
+              $projects[$project]['extra'] = array();
+            }
+            $projects[$project]['extra'][] = array(
+              'class' => 'release-revoked',
+              'label' => t('Release revoked'),
+              'data' => t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
+            );
+          }
+          elseif (isset($release['terms']['Release type']) &&
+                  in_array('Unsupported', $release['terms']['Release type'])) {
+            $projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
+            if (empty($projects[$project]['extra'])) {
+              $projects[$project]['extra'] = array();
+            }
+            $projects[$project]['extra'][] = array(
+              'class' => 'release-not-supported',
+              'label' => t('Release not supported'),
+              'data' => t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
+            );
+          }
+        }
+
+        // Otherwise, ignore unpublished, insecure, or unsupported releases.
+        if ($release['status'] == 'unpublished' ||
+            (isset($release['terms']['Release type']) &&
+             (in_array('Insecure', $release['terms']['Release type']) ||
+              in_array('Unsupported', $release['terms']['Release type'])))) {
+          continue;
+        }
+
+        // See if this is a higher major version than our target and yet still
+        // supported. If so, record it as an "Also available" release.
+        if ($release['version_major'] > $target_major) {
+          if (in_array($release['version_major'], $supported_majors)) {
+            if (!isset($available[$project]['also'])) {
+              $available[$project]['also'] = array();
+            }
+            if (!isset($available[$project]['also'][$release['version_major']])) {
+              $available[$project]['also'][$release['version_major']] = $version;
+            }
+          }
+          // Otherwise, this release can't matter to us, since it's neither
+          // from the release series we're currently using nor the recommended
+          // release. We don't even care about security updates for this
+          // branch, since if a project maintainer puts out a security release
+          // at a higher major version and not at the lower major version,
+          // they must remove the lower version from the supported major
+          // versions at the same time, in which case we won't hit this code.
+          continue;
+        }
+
+        // Look for the 'latest version' if we haven't found it yet. Latest is
+        // defined as the most recent version for the target major version.
+        if (!isset($available[$project]['latest_version'])
+            && $release['version_major'] == $target_major) {
+          $available[$project]['latest_version'] = $version;
+        }
+
+        // Look for the development snapshot release for this branch.
+        if (!isset($available[$project]['dev_version'])
+            && $release['version_major'] == $target_major
+            && isset($release['version_extra'])
+            && $release['version_extra'] == 'dev') {
+          $available[$project]['dev_version'] = $version;
+        }
+
+        // Look for the 'recommended' version if we haven't found it yet (see
+        // phpdoc at the top of this function for the definition).
+        if (!isset($available[$project]['recommended'])
+            && $release['version_major'] == $target_major
+            && isset($release['version_patch'])) {
+          if ($patch != $release['version_patch']) {
+            $patch = $release['version_patch'];
+            $version_patch_changed = $release['version'];
+          }
+          if (empty($release['version_extra']) && $patch == $release['version_patch']) {
+            $available[$project]['recommended'] = $version_patch_changed;
+          }
+        }
+
+        // Stop searching once we hit the currently installed version.
+        if ($projects[$project]['existing_version'] == $version) {
+          break;
+        }
+
+        // If we're running a dev snapshot and have a timestamp, stop
+        // searching for security updates once we hit an official release
+        // older than what we've got.  Allow 100 seconds of leeway to handle
+        // differences between the datestamp in the .info file and the
+        // timestamp of the tarball itself (which are usually off by 1 or 2
+        // seconds) so that we don't flag that as a new release.
+        if ($projects[$project]['install_type'] == 'dev') {
+          if (empty($projects[$project]['datestamp'])) {
+            // We don't have current timestamp info, so we can't know.
+            continue;
+          }
+          elseif (isset($release['date']) && ($projects[$project]['datestamp'] + 100 > $release['date'])) {
+            // We're newer than this, so we can skip it.
+            continue;
+          }
+        }
+
+        // See if this release is a security update.
+        if (isset($release['terms']['Release type'])
+            && in_array('Security update', $release['terms']['Release type'])) {
+          $projects[$project]['security updates'][] = $release;
+        }
+      }
+
+      // If we were unable to find a recommended version, then make the latest
+      // version the recommended version if possible.
+      if (!isset($available[$project]['recommended']) && isset($available[$project]['latest_version'])) {
+        $available[$project]['recommended'] = $available[$project]['latest_version'];
+      }
+
+      // Stash the info about available releases into our $projects array.
+      $projects[$project] += $available[$project];
+
+      //
+      // Check to see if we need an update or not.
+      //
+
+      if (!empty($projects[$project]['security updates'])) {
+        // If we found security updates, that always trumps any other status.
+        $projects[$project]['status'] = UPDATE_NOT_SECURE;
+      }
+
+      if (isset($projects[$project]['status'])) {
+        // If we already know the status, we're done.
+        continue;
+      }
+
+      // If we don't know what to recommend, there's nothing we can report.
+      // Bail out early.
+      if (!isset($projects[$project]['recommended'])) {
+        $projects[$project]['status'] = UPDATE_UNKNOWN;
+        $projects[$project]['reason'] = t('No available releases found');
+        continue;
+      }
+
+      // If we're running a dev snapshot, compare the date of the dev snapshot
+      // with the latest official version, and record the absolute latest in
+      // 'latest_dev' so we can correctly decide if there's a newer release
+      // than our current snapshot.
+      if ($projects[$project]['install_type'] == 'dev') {
+        if (isset($available[$project]['dev_version']) && $available[$project]['releases'][$available[$project]['dev_version']]['date'] > $available[$project]['releases'][$available[$project]['latest_version']]['date']) {
+          $projects[$project]['latest_dev'] = $available[$project]['dev_version'];
+        }
+        else {
+          $projects[$project]['latest_dev'] = $available[$project]['latest_version'];
+        }
+      }
+
+      // Figure out the status, based on what we've seen and the install type.
+      switch ($projects[$project]['install_type']) {
+        case 'official':
+          if ($projects[$project]['existing_version'] == $projects[$project]['recommended'] || $projects[$project]['existing_version'] == $projects[$project]['latest_version']) {
+            $projects[$project]['status'] = UPDATE_CURRENT;
+          }
+          else {
+            $projects[$project]['status'] = UPDATE_NOT_CURRENT;
+          }
+          break;
+
+        case 'dev':
+          $latest = $available[$project]['releases'][$projects[$project]['latest_dev']];
+          if (empty($projects[$project]['datestamp'])) {
+            $projects[$project]['status'] = UPDATE_NOT_CHECKED;
+            $projects[$project]['reason'] = t('Unknown release date');
+          }
+          elseif (($projects[$project]['datestamp'] + 100 > $latest['date'])) {
+            $projects[$project]['status'] = UPDATE_CURRENT;
+          }
+          else {
+            $projects[$project]['status'] = UPDATE_NOT_CURRENT;
+          }
+          break;
+
+        default:
+          $projects[$project]['status'] = UPDATE_UNKNOWN;
+          $projects[$project]['reason'] = t('Invalid info');
+      }
+    }
+    else {
+      $projects[$project]['status'] = UPDATE_UNKNOWN;
+      $projects[$project]['reason'] = t('No available releases found');
+    }
+  }
+  // Give other modules a chance to alter the status (for example, to allow a
+  // contrib module to provide fine-grained settings to ignore specific
+  // projects or releases).
+  drupal_alter('update_status', $projects);
+
+  // Set the projects array into the cache table.
+  cache_set('update_project_data', $projects, 'cache_update', time() + 3600);
+  return $projects;
+}
+
+/**
+ * Retrieve data from {cache_update} or empty the cache when necessary.
+ *
+ * Two very expensive arrays computed by this module are the list of all
+ * installed modules and themes (and .info data, project associations, etc),
+ * and the current status of the site relative to the currently available
+ * releases. These two arrays are cached in the {cache_update} table and used
+ * whenever possible. The cache is cleared whenever the administrator visits
+ * the status report, available updates report, or the module or theme
+ * administration pages, since we should always recompute the most current
+ * values on any of those pages.
+ *
+ * @param $cid
+ *   The cache id of data to return from the cache. Valid options are
+ *   'update_project_data' and 'update_project_projects'.
+ *
+ * @return
+ *   The cached value of the $projects array generated by
+ *   update_calculate_project_data() or update_get_projects(), or an empty
+ *   array when the cache is cleared.
+ */
+function update_project_cache($cid) {
+  $projects = array();
+
+  // In some cases, we must clear the cache.  Rather than do so on a time
+  // basis, we check for specific paths.
+  $q = $_GET['q'];
+  $paths = array('admin/build/modules', 'admin/build/themes', 'admin/reports', 'admin/reports/updates', 'admin/reports/status', 'admin/reports/updates/check');
+  if (in_array($q, $paths)) {
+    cache_clear_all($cid, 'cache_update');
+  }
+  else {
+    $cache = cache_get($cid, 'cache_update');
+    if (!empty($cache->data) && $cache->expire > time()) {
+      $projects = $cache->data;
+    }
+  }
+  return $projects;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,106 @@
+/* $Id: update.css,v 1.3.2.1 2008/02/05 09:59:21 goba Exp $ */
+
+.update .project {
+  font-weight: bold;
+  font-size: 110%;
+  padding-left: .25em; /* LTR */
+  height: 22px;
+}
+
+.update .version-status {
+  float: right; /* LTR */
+  padding-right: 10px; /* LTR */
+  font-size: 110%;
+  height: 20px;
+}
+
+.update .version-status .icon {
+  padding-left: .5em; /* LTR */
+}
+
+.update .version-date {
+  white-space: nowrap;
+}
+
+.update .info {
+  margin: 0;
+  padding: 1em 1em .25em 1em;
+}
+
+.update tr td {
+  border-top: 1px solid #ccc;
+  border-bottom: 1px solid #ccc;
+}
+
+.update tr.error {
+  background: #fcc;
+}
+
+.update tr.error .version-recommended {
+  background: #fdd;
+}
+
+.update tr.ok {
+  background: #dfd;
+}
+
+.update tr.warning {
+  background: #ffd;
+}
+
+.update tr.warning .version-recommended {
+  background: #ffe;
+}
+
+.current-version, .new-version {
+  direction: ltr; /* Note: version numbers should always be LTR. */
+}
+
+table.update,
+.update table.version {
+  width: 100%;
+  margin-top: .5em;
+}
+
+.update table.version tbody {
+  border: none;
+}
+
+.update table.version tr,
+.update table.version td {
+  line-height: .9em;
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+
+.update table.version .version-title {
+  padding-left: 1em; /* LTR */
+  width: 14em;
+}
+
+.update table.version .version-details {
+  padding-right: .5em; /* LTR */
+}
+
+.update table.version .version-links {
+  text-align: right; /* LTR */
+  padding-right: 1em; /* LTR */
+}
+
+.update table.version-security .version-title {
+  color: #970F00;
+}
+
+.update table.version-recommended-strong .version-title {
+  font-weight: bold;
+}
+
+.update .security-error {
+  font-weight: bold;
+  color: #970F00;
+}
+
+.update .check-manually {
+  padding-left: 1em; /* LTR */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.fetch.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,231 @@
+<?php
+// $Id: update.fetch.inc,v 1.7 2008/01/30 10:14:42 goba Exp $
+
+/**
+ * @file
+ * Code required only when fetching information about available updates.
+ */
+
+/**
+ * Callback to manually check the update status without cron.
+ */
+function update_manual_status() {
+  if (_update_refresh()) {
+    drupal_set_message(t('Fetched information about all available new releases and updates.'));
+  }
+  else {
+    drupal_set_message(t('Unable to fetch any information about available new releases and updates.'), 'error');
+  }
+  drupal_goto('admin/reports/updates');
+}
+
+/**
+ * Fetch project info via XML from a central server.
+ */
+function _update_refresh() {
+  global $base_url;
+  module_load_include('inc', 'update', 'update.compare');
+
+  // Since we're fetching new available update data, we want to clear
+  // everything in our cache, to ensure we recompute the status. Note that
+  // this does not cause update_get_projects() to be recomputed twice in the
+  // same page load (e.g. when manually checking) since that function stashes
+  // its answer in a static array.
+  update_invalidate_cache();
+
+  $available = array();
+  $data = array();
+  $site_key = md5($base_url . drupal_get_private_key());
+  $projects = update_get_projects();
+
+  foreach ($projects as $key => $project) {
+    $url = _update_build_fetch_url($project, $site_key);
+    $xml = drupal_http_request($url);
+    if (isset($xml->data)) {
+      $data[] = $xml->data;
+    }
+  }
+
+  if ($data) {
+    $parser = new update_xml_parser;
+    $available = $parser->parse($data);
+  }
+  if (!empty($available) && is_array($available)) {
+    $frequency = variable_get('update_check_frequency', 1);
+    cache_set('update_info', $available, 'cache_update', time() + (60 * 60 * 24 * $frequency));
+    variable_set('update_last_check', time());
+    watchdog('update', 'Fetched information about all available new releases and updates.', array(), WATCHDOG_NOTICE, l('view', 'admin/reports/updates'));
+  }
+  else {
+    module_invoke('system', 'check_http_request');
+    watchdog('update', 'Unable to fetch any information about available new releases and updates.', array(), WATCHDOG_ERROR, l('view', 'admin/reports/updates'));
+  }
+  return $available;
+}
+
+/**
+ * Generates the URL to fetch information about project updates.
+ *
+ * This figures out the right URL to use, based on the project's .info file
+ * and the global defaults. Appends optional query arguments when the site is
+ * configured to report usage stats.
+ *
+ * @param $project
+ *   The array of project information from update_get_projects().
+ * @param $site_key
+ *   The anonymous site key hash (optional).
+ *
+ * @see update_refresh()
+ * @see update_get_projects()
+ */
+function _update_build_fetch_url($project, $site_key = '') {
+  $default_url = variable_get('update_fetch_url', UPDATE_DEFAULT_URL);
+  if (!isset($project['info']['project status url'])) {
+    $project['info']['project status url'] = $default_url;
+  }
+  $name = $project['name'];
+  $url = $project['info']['project status url'];
+  $url .= '/'. $name .'/'. DRUPAL_CORE_COMPATIBILITY;
+  if (!empty($site_key)) {
+    $url .= (strpos($url, '?') === TRUE) ? '&' : '?';
+    $url .= 'site_key=';
+    $url .= drupal_urlencode($site_key);
+    if (!empty($project['info']['version'])) {
+      $url .= '&version=';
+      $url .= drupal_urlencode($project['info']['version']);
+    }
+  }
+  return $url;
+}
+
+/**
+ * Perform any notifications that should be done once cron fetches new data.
+ *
+ * This method checks the status of the site using the new data and depending
+ * on the configuration of the site, notifys administrators via email if there
+ * are new releases or missing security updates.
+ *
+ * @see update_requirements()
+ */
+function _update_cron_notify() {
+  include_once './includes/install.inc';
+  $status = update_requirements('runtime');
+  $params = array();
+  foreach (array('core', 'contrib') as $report_type) {
+    $type = 'update_'. $report_type;
+    if (isset($status[$type]['severity'])
+        && $status[$type]['severity'] == REQUIREMENT_ERROR) {
+      $params[$report_type] = $status[$type]['reason'];
+    }
+  }
+  if (!empty($params)) {
+    $notify_list = variable_get('update_notify_emails', '');
+    if (!empty($notify_list)) {
+      $default_language = language_default();
+      foreach ($notify_list as $target) {
+        if ($target_user = user_load(array('mail' => $target))) {
+          $target_language = user_preferred_language($target_user);
+        }
+        else {
+          $target_language = $default_language;
+        }
+        drupal_mail('update', 'status_notify', $target, $target_language, $params);
+      }
+    }
+  }
+}
+
+/**
+ * XML Parser object to read Drupal's release history info files.
+ * This uses PHP4's lame XML parsing, but it works.
+ */
+class update_xml_parser {
+  var $projects = array();
+  var $current_project;
+  var $current_release;
+  var $current_term;
+  var $current_tag;
+  var $current_object;
+
+  /**
+   * Parse an array of XML data files.
+   */
+  function parse($data) {
+    foreach ($data as $datum) {
+      $parser = xml_parser_create();
+      xml_set_object($parser, $this);
+      xml_set_element_handler($parser, 'start', 'end');
+      xml_set_character_data_handler($parser, "data");
+      xml_parse($parser, $datum);
+      xml_parser_free($parser);
+    }
+    return $this->projects;
+  }
+
+  function start($parser, $name, $attr) {
+    $this->current_tag = $name;
+    switch ($name) {
+      case 'PROJECT':
+        unset($this->current_object);
+        $this->current_project = array();
+        $this->current_object = &$this->current_project;
+        break;
+      case 'RELEASE':
+        unset($this->current_object);
+        $this->current_release = array();
+        $this->current_object = &$this->current_release;
+        break;
+      case 'TERM':
+        unset($this->current_object);
+        $this->current_term = array();
+        $this->current_object = &$this->current_term;
+        break;
+    }
+  }
+
+  function end($parser, $name) {
+    switch ($name) {
+      case 'PROJECT':
+        unset($this->current_object);
+        $this->projects[$this->current_project['short_name']] = $this->current_project;
+        $this->current_project = array();
+        break;
+      case 'RELEASE':
+        unset($this->current_object);
+        $this->current_project['releases'][$this->current_release['version']] = $this->current_release;
+        break;
+      case 'RELEASES':
+        $this->current_object = &$this->current_project;
+        break;
+      case 'TERM':
+        unset($this->current_object);
+        $term_name = $this->current_term['name'];
+        if (!isset($this->current_release['terms'])) {
+          $this->current_release['terms'] = array();
+        }
+        if (!isset($this->current_release['terms'][$term_name])) {
+          $this->current_release['terms'][$term_name] = array();
+        }
+        $this->current_release['terms'][$term_name][] = $this->current_term['value'];
+        break;
+      case 'TERMS':
+        $this->current_object = &$this->current_release;
+        break;
+      default:
+        $this->current_object[strtolower($this->current_tag)] = trim($this->current_object[strtolower($this->current_tag)]);
+        $this->current_tag = '';
+    }
+  }
+
+  function data($parser, $data) {
+    if ($this->current_tag && !in_array($this->current_tag, array('PROJECT', 'RELEASE', 'RELEASES', 'TERM', 'TERMS'))) {
+      $tag = strtolower($this->current_tag);
+      if (isset($this->current_object[$tag])) {
+        $this->current_object[$tag] .= $data;
+      }
+      else {
+        $this->current_object[$tag] = $data;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: update.info,v 1.1 2007/07/11 15:15:40 dries Exp $
+name = Update status
+description = Checks the status of available updates for Drupal and your installed modules and themes.
+version = VERSION
+package = Core - optional
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,64 @@
+<?php
+// $Id: update.install,v 1.4 2008/02/03 18:38:14 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function update_install() {
+  // Create cache table.
+  drupal_install_schema('update');
+  // Remove stale variables from update_status 5.x contrib, if any.
+  _update_remove_update_status_variables();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function update_uninstall() {
+  // Remove cache table.
+  drupal_uninstall_schema('update');
+  // Clear any variables that might be in use
+  $variables = array(
+    'update_check_frequency',
+    'update_fetch_url',
+    'update_last_check',
+    'update_notification_threshold',
+    'update_notify_emails',
+  );
+  foreach ($variables as $variable) {
+    variable_del($variable);
+  }
+  menu_rebuild();
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function update_schema() {
+  $schema['cache_update'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_update']['description'] = t('Cache table for the Update module to store information about available releases, fetched from central server.');
+  return $schema;
+}
+
+/**
+ * Private helper to clear out stale variables from update_status 5.x contrib. 
+ *
+ * @see update_install()
+ * @see update_update_6000()
+ */
+function _update_remove_update_status_variables() {
+  variable_del('update_status_settings');
+  variable_del('update_status_notify_emails');
+  variable_del('update_status_check_frequency');
+  variable_del('update_status_notification_threshold');
+  variable_del('update_status_last');
+  variable_del('update_status_fetch_url');
+}
+
+/**
+ * Clear out stale variables from update_status.
+ */
+function update_update_6000() {
+  _update_remove_update_status_variables();
+  return array();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,514 @@
+<?php
+// $Id: update.module,v 1.17 2008/01/30 10:14:42 goba Exp $
+
+/**
+ * @file
+ * The "Update status" module checks for available updates of Drupal core and
+ * any installed contributed modules and themes. It warns site administrators
+ * if newer releases are available via the system status report
+ * (admin/reports/status), the module and theme pages, and optionally via email.
+ */
+
+/**
+ * URL to check for updates, if a given project doesn't define its own.
+ */
+define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
+
+// These are internally used constants for this code, do not modify.
+
+/**
+ * Project is missing security update(s).
+ */
+define('UPDATE_NOT_SECURE', 1);
+
+/**
+ * Current release has been unpublished and is no longer available.
+ */
+define('UPDATE_REVOKED', 2);
+
+/**
+ * Current release is no longer supported by the project maintainer.
+ */
+define('UPDATE_NOT_SUPPORTED', 3);
+
+/**
+ * Project has a new release available, but it is not a security release.
+ */
+define('UPDATE_NOT_CURRENT', 4);
+
+/**
+ * Project is up to date.
+ */
+define('UPDATE_CURRENT', 5);
+
+/**
+ * Project's status cannot be checked.
+ */
+define('UPDATE_NOT_CHECKED', -1);
+
+/**
+ * No available update data was found for project.
+ */
+define('UPDATE_UNKNOWN', -2);
+
+
+/**
+ * Implementation of hook_help().
+ */
+function update_help($path, $arg) {
+  switch ($path) {
+    case 'admin/reports/updates':
+      $output = '<p>'. t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') .'</p>';
+      $output .= '<p>'. t('To extend the functionality or to change the look of your site, a number of contributed <a href="@modules">modules</a> and <a href="@themes">themes</a> are available.', array('@modules' => 'http://drupal.org/project/modules', '@themes' => 'http://drupal.org/project/themes')) .'</p>';
+      return $output;
+    case 'admin/build/themes':
+    case 'admin/build/modules':
+      include_once './includes/install.inc';
+      $status = update_requirements('runtime');
+      foreach (array('core', 'contrib') as $report_type) {
+        $type = 'update_'. $report_type;
+        if (isset($status[$type]['severity'])) {
+          if ($status[$type]['severity'] == REQUIREMENT_ERROR) {
+            drupal_set_message($status[$type]['description'], 'error');
+          }
+          elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) {
+            drupal_set_message($status[$type]['description'], 'warning');
+          }
+        }
+      }
+      return '<p>'. t('See the <a href="@available_updates">available updates</a> page for information on installed modules and themes with new versions released.', array('@available_updates' => url('admin/reports/updates'))) .'</p>';
+
+    case 'admin/reports/updates/settings':
+    case 'admin/reports/status':
+      // These two pages don't need additional nagging.
+      break;
+
+    case 'admin/help#update':
+      $output = '<p>'. t("The Update status module periodically checks for new versions of your site's software (including contributed modules and themes), and alerts you to available updates.") .'</p>';
+      $output .= '<p>'. t('The <a href="@update-report">report of available updates</a> will alert you when new releases are available for download. You may configure options for update checking frequency and notifications at the <a href="@update-settings">Update status module settings page</a>.', array('@update-report' => url('admin/reports/updates'), '@update-settings' => url('admin/reports/updates/settings'))) .'</p>';
+      $output .= '<p>'. t('Please note that in order to provide this information, anonymous usage statistics are sent to drupal.org. If desired, you may disable the Update status module from the <a href="@modules">module administration page</a>.', array('@modules' => url('admin/build/modules'))) .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@update">Update status module</a>.', array('@update' => 'http://drupal.org/handbook/modules/update')) .'</p>';
+      return $output;
+
+    default:
+      // Otherwise, if we're on *any* admin page and there's a security
+      // update missing, print an error message about it.
+      if (arg(0) == 'admin' && strpos($path, '#') === FALSE
+          && user_access('administer site configuration')) {
+        include_once './includes/install.inc';
+        $status = update_requirements('runtime');
+        foreach (array('core', 'contrib') as $report_type) {
+          $type = 'update_'. $report_type;
+          if (isset($status[$type])
+              && isset($status[$type]['reason'])
+              && $status[$type]['reason'] === UPDATE_NOT_SECURE) {
+            drupal_set_message($status[$type]['description'], 'error');
+          }
+        }
+      }
+
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function update_menu() {
+  $items = array();
+
+  $items['admin/reports/updates'] = array(
+    'title' => 'Available updates',
+    'description' => 'Get a status report about available updates for your installed modules and themes.',
+    'page callback' => 'update_status',
+    'access arguments' => array('administer site configuration'),
+    'file' => 'update.report.inc',
+    'weight' => 10,
+  );
+  $items['admin/reports/updates/list'] = array(
+    'title' => 'List',
+    'page callback' => 'update_status',
+    'access arguments' => array('administer site configuration'),
+    'file' => 'update.report.inc',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/reports/updates/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('update_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'update.settings.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/reports/updates/check'] = array(
+    'title' => 'Manual update check',
+    'page callback' => 'update_manual_status',
+    'access arguments' => array('administer site configuration'),
+    'file' => 'update.fetch.inc',
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of the hook_theme() registry.
+ */
+function update_theme() {
+  return array(
+    'update_settings' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'update_report' => array(
+      'arguments' => array('data' => NULL),
+    ),
+    'update_version' => array(
+      'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_requirements.
+ *
+ * @return
+ *   An array describing the status of the site regarding available updates.
+ *   If there is no update data, only one record will be returned, indicating
+ *   that the status of core can't be determined. If data is available, there
+ *   will be two records: one for core, and another for all of contrib
+ *   (assuming there are any contributed modules or themes enabled on the
+ *   site). In addition to the fields expected by hook_requirements ('value',
+ *   'severity', and optionally 'description'), this array will contain a
+ *   'reason' attribute, which is an integer constant to indicate why the
+ *   given status is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or
+ *   UPDATE_UNKNOWN). This is used for generating the appropriate e-mail
+ *   notification messages during update_cron(), and might be useful for other
+ *   modules that invoke update_requirements() to find out if the site is up
+ *   to date or not.
+ *
+ * @see _update_message_text()
+ * @see _update_cron_notify()
+ */
+function update_requirements($phase) {
+  if ($phase == 'runtime') {
+    if ($available = update_get_available(FALSE)) {
+      module_load_include('inc', 'update', 'update.compare');
+      $data = update_calculate_project_data($available);
+      // First, populate the requirements for core:
+      $requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
+      // We don't want to check drupal a second time.
+      unset($data['drupal']);
+      if (!empty($data)) {
+        // Now, sort our $data array based on each project's status. The
+        // status constants are numbered in the right order of precedence, so
+        // we just need to make sure the projects are sorted in ascending
+        // order of status, and we can look at the first project we find.
+        uasort($data, '_update_project_status_sort');
+        $first_project = reset($data);
+        $requirements['update_contrib'] = _update_requirement_check($first_project, 'contrib');
+      }
+    }
+    else {
+      $requirements['update_core']['title'] = t('Drupal core update status');
+      $requirements['update_core']['value'] = t('No update data available');
+      $requirements['update_core']['severity'] = REQUIREMENT_WARNING;
+      $requirements['update_core']['reason'] = UPDATE_UNKNOWN;
+      $requirements['update_core']['description'] = _update_no_data();
+    }
+    return $requirements;
+  }
+}
+
+/**
+ * Private helper method to fill in the requirements array.
+ *
+ * This is shared for both core and contrib to generate the right elements in
+ * the array for hook_requirements().
+ *
+ * @param $project
+ *  Array of information about the project we're testing as returned by
+ *  update_calculate_project_data().
+ * @param $type
+ *  What kind of project is this ('core' or 'contrib').
+ *
+ * @return
+ *  An array to be included in the nested $requirements array.
+ *
+ * @see hook_requirements()
+ * @see update_requirements()
+ * @see update_calculate_project_data()
+ */
+function _update_requirement_check($project, $type) {
+  $requirement = array();
+  if ($type == 'core') {
+    $requirement['title'] = t('Drupal core update status');
+  }
+  else {
+    $requirement['title'] = t('Module and theme update status');
+  }
+  $status = $project['status'];
+  if ($status != UPDATE_CURRENT) {
+    $requirement['reason'] = $status;
+    $requirement['description'] = _update_message_text($type, $status, TRUE);
+    $requirement['severity'] = REQUIREMENT_ERROR;
+  }
+  switch ($status) {
+    case UPDATE_NOT_SECURE:
+      $requirement_label = t('Not secure!');
+      break;
+    case UPDATE_REVOKED:
+      $requirement_label = t('Revoked!');
+      break;
+    case UPDATE_NOT_SUPPORTED:
+      $requirement_label = t('Unsupported release');
+      break;
+    case UPDATE_NOT_CURRENT:
+      $requirement_label = t('Out of date');
+      $requirement['severity'] = variable_get('update_notification_threshold', 'all') == 'all' ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
+      break;
+    case UPDATE_UNKNOWN:
+    case UPDATE_NOT_CHECKED:
+      $requirement_label = isset($project['reason']) ? $project['reason'] : t('Can not determine status');
+      $requirement['severity'] = REQUIREMENT_WARNING;
+      break;
+    default:
+      $requirement_label = t('Up to date');
+  }
+  if ($status != UPDATE_CURRENT && $type == 'core' && isset($project['recommended'])) {
+    $requirement_label .= ' '. t('(version @version available)', array('@version' => $project['recommended']));
+  }
+  $requirement['value'] = l($requirement_label, 'admin/reports/updates');
+  return $requirement;
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function update_cron() {
+  $frequency = variable_get('update_check_frequency', 1);
+  $interval = 60 * 60 * 24 * $frequency;
+  if (time() - variable_get('update_last_check', 0) > $interval) {
+    update_refresh();
+    _update_cron_notify();
+  }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ *
+ * Adds a submit handler to the system modules and themes forms, so that if a
+ * site admin saves either form, we invalidate the cache of available updates.
+ *
+ * @see update_invalidate_cache()
+ */
+function update_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'system_modules' || $form_id == 'system_themes' ) {
+    $form['#submit'][] = 'update_invalidate_cache';
+  }
+}
+
+/**
+ * Prints a warning message when there is no data about available updates.
+ */
+function _update_no_data() {
+  $destination = drupal_get_destination();
+  return t('No information is available about potential new releases for currently installed modules and themes. To check for updates, you may need to <a href="@run_cron">run cron</a> or you can <a href="@check_manually">check manually</a>. Please note that checking for available updates can take a long time, so please be patient.', array(
+    '@run_cron' => url('admin/reports/status/run-cron', array('query' => $destination)),
+    '@check_manually' => url('admin/reports/updates/check', array('query' => $destination)),
+  ));
+}
+
+/**
+ * Internal helper to try to get the update information from the cache
+ * if possible, and to refresh the cache when necessary.
+ *
+ * In addition to checking the cache lifetime, this function also ensures that
+ * there are no .info files for enabled modules or themes that have a newer
+ * modification timestamp than the last time we checked for available update
+ * data. If any .info file was modified, it almost certainly means a new
+ * version of something was installed. Without fresh available update data,
+ * the logic in update_calculate_project_data() will be wrong and produce
+ * confusing, bogus results.
+ *
+ * @param $refresh
+ *   Boolean to indicate if this method should refresh the cache automatically
+ *   if there's no data.
+ *
+ * @see update_refresh()
+ * @see update_get_projects()
+ */
+function update_get_available($refresh = FALSE) {
+  module_load_include('inc', 'update', 'update.compare');
+  $available = array();
+
+  // First, make sure that none of the .info files have a change time
+  // newer than the last time we checked for available updates.
+  $needs_refresh = FALSE;
+  $last_check = variable_get('update_last_check', 0);
+  $projects = update_get_projects();
+  foreach ($projects as $key => $project) {
+    if ($project['info']['_info_file_ctime'] > $last_check) {
+      $needs_refresh = TRUE;
+      break;
+    }
+  }
+  if (!$needs_refresh && ($cache = cache_get('update_info', 'cache_update'))
+       && $cache->expire > time()) {
+    $available = $cache->data;
+  }
+  elseif ($needs_refresh || $refresh) {
+    // If we need to refresh due to a newer .info file, ignore the argument
+    // and force the refresh (e.g., even for update_requirements()) to prevent
+    // bogus results.
+    $available = update_refresh();
+  }
+  return $available;
+}
+
+/**
+ * Implementation of hook_flush_caches().
+ *
+ * The function update.php (among others) calls this hook to flush the caches.
+ * Since we're running update.php, we are likely to install a new version of
+ * something, in which case, we want to check for available update data again.
+ */
+function update_flush_caches() {
+  return array('cache_update');
+}
+
+/**
+ * Invalidates any cached data relating to update status.
+ */
+function update_invalidate_cache() {
+  cache_clear_all('*', 'cache_update', TRUE);
+}
+
+/**
+ * Wrapper to load the include file and then refresh the release data.
+ */
+function update_refresh() {
+  module_load_include('inc', 'update', 'update.fetch');
+  return _update_refresh();
+}
+
+/**
+ * Implementation of hook_mail().
+ *
+ * Constructs the email notification message when the site is out of date.
+ *
+ * @param $key
+ *   Unique key to indicate what message to build, always 'status_notify'.
+ * @param $message
+ *   Reference to the message array being built.
+ * @param $params
+ *   Array of parameters to indicate what kind of text to include in the
+ *   message body. This is a keyed array of message type ('core' or 'contrib')
+ *   as the keys, and the status reason constant (UPDATE_NOT_SECURE, etc) for
+ *   the values.
+ *
+ * @see drupal_mail()
+ * @see _update_cron_notify()
+ * @see _update_message_text()
+ */
+function update_mail($key, &$message, $params) {
+  $language = $message['language'];
+  $langcode = $language->language;
+  $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), $langcode);
+  foreach ($params as $msg_type => $msg_reason) {
+    $message['body'][] = _update_message_text($msg_type, $msg_reason, FALSE, $language);
+  }
+  $message['body'][] = t('See the available updates page for more information:', array(), $langcode) ."\n". url('admin/reports/updates', array('absolute' => TRUE, 'language' => $language));
+}
+
+/**
+ * Helper function to return the appropriate message text when the site is out
+ * of date or missing a security update.
+ *
+ * These error messages are shared by both update_requirements() for the
+ * site-wide status report at admin/reports/status and in the body of the
+ * notification emails generated by update_cron().
+ *
+ * @param $msg_type
+ *   String to indicate what kind of message to generate. Can be either
+ *   'core' or 'contrib'.
+ * @param $msg_reason
+ *   Integer constant specifying why message is generated.
+ * @param $report_link
+ *   Boolean that controls if a link to the updates report should be added.
+ * @param $language
+ *   An optional language object to use.
+ * @return
+ *   The properly translated error message for the given key.
+ */
+function _update_message_text($msg_type, $msg_reason, $report_link = FALSE, $language = NULL) {
+  $langcode = isset($language) ? $language->language : NULL;
+  $text = '';
+  switch ($msg_reason) {
+    case UPDATE_NOT_SECURE:
+      if ($msg_type == 'core') {
+        $text = t('There is a security update available for your version of Drupal. To ensure the security of your server, you should update immediately!', array(), $langcode);
+      }
+      else {
+        $text = t('There are security updates available for one or more of your modules or themes. To ensure the security of your server, you should update immediately!', array(), $langcode);
+      }
+      break;
+
+    case UPDATE_REVOKED:
+      if ($msg_type == 'core') {
+        $text = t('Your version of Drupal has been revoked and is no longer available for download. Upgrading is strongly recommended!', array(), $langcode);
+      }
+      else {
+        $text = t('The installed version of at least one of your modules or themes has been revoked and is no longer available for download. Upgrading or disabling is strongly recommended!', array(), $langcode);
+      }
+      break;
+
+    case UPDATE_NOT_SUPPORTED:
+      if ($msg_type == 'core') {
+        $text = t('Your version of Drupal is no longer supported. Upgrading is strongly recommended!', array(), $langcode);
+      }
+      else {
+        $text = t('The installed version of at least one of your modules or themes is no longer supported. Upgrading or disabling is strongly recommended! Please see the project homepage for more details.', array(), $langcode);
+      }
+      break;
+
+    case UPDATE_NOT_CURRENT:
+      if ($msg_type == 'core') {
+        $text = t('There are updates available for your version of Drupal. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
+      }
+      else {
+        $text = t('There are updates available for one or more of your modules or themes. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
+      }
+      break;
+
+    case UPDATE_UNKNOWN:
+    case UPDATE_NOT_CHECKED:
+      if ($msg_type == 'core') {
+        $text = t('There was a problem determining the status of available updates for your version of Drupal.', array(), $langcode);
+      }
+      else {
+        $text = t('There was a problem determining the status of available updates for one or more of your modules or themes.', array(), $langcode);
+      }
+      break;
+  }
+
+  if ($report_link) {
+    $text .= ' '. t('See the <a href="@available_updates">available updates</a> page for more information.', array('@available_updates' => url('admin/reports/updates', array('language' => $language))), $langcode);
+  }
+
+  return $text;
+}
+
+/**
+ * Private sort function to order projects based on their status.
+ *
+ * @see update_requirements()
+ * @see uasort()
+ */
+function _update_project_status_sort($a, $b) {
+  // The status constants are numerically in the right order, so we can
+  // usually subtract the two to compare in the order we want. However,
+  // negative status values should be treated as if they are huge, since we
+  // always want them at the bottom of the list.
+  $a_status = $a['status'] > 0 ? $a['status'] : (-10 * $a['status']);
+  $b_status = $b['status'] > 0 ? $b['status'] : (-10 * $b['status']);
+  return $a_status - $b_status;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.report.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,241 @@
+<?php
+// $Id: update.report.inc,v 1.10.2.1 2008/02/05 09:59:21 goba Exp $
+
+/**
+ * @file
+ * Code required only when rendering the available updates report.
+ */
+
+/**
+ * Menu callback. Generate a page about the update status of projects.
+ */
+function update_status() {
+  if ($available = update_get_available(TRUE)) {
+    module_load_include('inc', 'update', 'update.compare');
+    $data = update_calculate_project_data($available);
+    return theme('update_report', $data);
+  }
+  else {
+    return theme('update_report', _update_no_data());
+  }
+}
+
+/**
+ * Theme project status report.
+ *
+ * @ingroup themeable
+ */
+function theme_update_report($data) {
+  $last = variable_get('update_last_check', 0);
+  $output = '<div class="update checked">'. ($last ? t('Last checked: @time ago', array('@time' => format_interval(time() - $last))) : t('Last checked: never'));
+  $output .= ' <span class="check-manually">('. l(t('Check manually'), 'admin/reports/updates/check') .')</span>';
+  $output .= "</div>\n";
+
+  if (!is_array($data)) {
+    $output .= '<p>'. $data .'</p>';
+    return $output;
+  }
+
+  $header = array();
+  $rows = array();
+
+  $notification_level = variable_get('update_notification_threshold', 'all');
+
+  foreach ($data as $project) {
+    switch ($project['status']) {
+      case UPDATE_CURRENT:
+        $class = 'ok';
+        $icon = theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok'));
+        break;
+      case UPDATE_NOT_SECURE:
+      case UPDATE_REVOKED:
+      case UPDATE_NOT_SUPPORTED:
+      case UPDATE_NOT_CURRENT:
+        if ($notification_level == 'all'
+            || $project['status'] != UPDATE_NOT_CURRENT) {
+          $class = 'error';
+          $icon = theme('image', 'misc/watchdog-error.png', t('error'), t('error'));
+          break;
+        }
+        // Otherwise, deliberate no break and use the warning class/icon.
+      default:
+        $class = 'warning';
+        $icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'));
+        break;
+    }
+
+    $row = '<div class="version-status">';
+    switch ($project['status']) {
+      case UPDATE_NOT_SECURE:
+        $row .= '<span class="security-error">'. t('Security update required!') .'</span>';
+        break;
+      case UPDATE_REVOKED:
+        $row .= '<span class="revoked">'. t('Revoked!') .'</span>';
+        break;
+      case UPDATE_NOT_SUPPORTED:
+        $row .= '<span class="not-supported">'. t('Not supported!') .'</span>';
+        break;
+      case UPDATE_NOT_CURRENT:
+        $row .= '<span class="not-current">'. t('Update available') .'</span>';
+        break;
+      case UPDATE_CURRENT:
+        $row .= '<span class="current">'. t('Up to date') .'</span>';
+        break;
+      default:
+        $row .= check_plain($project['reason']);
+        break;
+    }
+    $row .= '<span class="icon">'. $icon .'</span>';
+    $row .= "</div>\n";
+
+    $row .= '<div class="project">';
+    if (isset($project['title'])) {
+      if (isset($project['link'])) {
+        $row .= l($project['title'], $project['link']);
+      }
+      else {
+        $row .= check_plain($project['title']);
+      }
+    }
+    else {
+      $row .= check_plain($project['name']);
+    }
+    $row .= ' '. check_plain($project['existing_version']);
+    if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) {
+      $row .= ' <span class="version-date">('. format_date($project['datestamp'], 'custom', 'Y-M-d') .')</span>';
+    }
+    $row .= "</div>\n";
+
+    $row .= "<div class=\"versions\">\n";
+
+    if (isset($project['recommended'])) {
+      if ($project['status'] != UPDATE_CURRENT || $project['existing_version'] != $project['recommended']) {
+
+        // First, figure out what to recommend.
+        // If there's only 1 security update and it has the same version we're
+        // recommending, give it the same CSS class as if it was recommended,
+        // but don't print out a separate "Recommended" line for this project.
+        if (!empty($project['security updates']) && count($project['security updates']) == 1 && $project['security updates'][0]['version'] == $project['recommended']) {
+          $security_class = ' version-recommended version-recommended-strong';
+        }
+        else {
+          $security_class = '';
+          $version_class = 'version-recommended';
+          // Apply an extra class if we're displaying both a recommended
+          // version and anything else for an extra visual hint.
+          if ($project['recommended'] != $project['latest_version']
+              || !empty($project['also'])
+              || ($project['install_type'] == 'dev'
+                 && isset($project['dev_version'])
+                 && $project['latest_version'] != $project['dev_version']
+                 && $project['recommended'] != $project['dev_version'])
+              || (isset($project['security updates'][0])
+                 && $project['recommended'] != $project['security updates'][0])
+              ) {
+            $version_class .= ' version-recommended-strong';
+          }
+          $row .= theme('update_version', $project['releases'][$project['recommended']], t('Recommended version:'), $version_class);
+        }
+
+        // Now, print any security updates.
+        if (!empty($project['security updates'])) {
+          foreach ($project['security updates'] as $security_update) {
+            $row .= theme('update_version', $security_update, t('Security update:'), 'version-security'. $security_class);
+          }
+        }
+      }
+
+      if ($project['recommended'] != $project['latest_version']) {
+        $row .= theme('update_version', $project['releases'][$project['latest_version']], t('Latest version:'), 'version-latest');
+      }
+      if ($project['install_type'] == 'dev'
+          && $project['status'] != UPDATE_CURRENT
+          && isset($project['dev_version'])
+          && $project['recommended'] != $project['dev_version']) {
+        $row .= theme('update_version', $project['releases'][$project['dev_version']], t('Development version:'), 'version-latest');
+      }
+    }
+
+    if (isset($project['also'])) {
+      foreach ($project['also'] as $also) {
+        $row .= theme('update_version', $project['releases'][$also], t('Also available:'), 'version-also-available');
+      }
+    }
+
+    $row .= "</div>\n"; // versions div.
+
+    $row .= "<div class=\"info\">\n";
+    if (!empty($project['extra'])) {
+      $row .= '<div class="extra">'."\n";
+      foreach ($project['extra'] as $key => $value) {
+        $row .= '<div class="'. $value['class'] .'">';
+        $row .= check_plain($value['label']) .': ';
+        $row .= theme('placeholder', $value['data']);
+        $row .= "</div>\n";
+      }
+      $row .= "</div>\n";  // extra div.
+    }
+
+    $row .= '<div class="includes">';
+    sort($project['includes']);
+    $row .= t('Includes: %includes', array('%includes' => implode(', ', $project['includes'])));
+    $row .= "</div>\n";
+
+    $row .= "</div>\n"; // info div.
+
+    if (!isset($rows[$project['project_type']])) {
+      $rows[$project['project_type']] = array();
+    }
+    $rows[$project['project_type']][] = array(
+      'class' => $class,
+      'data' => array($row),
+    );
+  }
+
+  $project_types = array(
+    'core' => t('Drupal core'),
+    'module' => t('Modules'),
+    'theme' => t('Themes'),
+    'disabled-module' => t('Disabled modules'),
+    'disabled-theme' => t('Disabled themes'),
+  );
+  foreach ($project_types as $type_name => $type_label) {
+    if (!empty($rows[$type_name])) {
+      $output .= "\n<h3>". $type_label ."</h3>\n";
+      $output .= theme('table', $header, $rows[$type_name], array('class' => 'update'));
+    }
+  }
+  drupal_add_css(drupal_get_path('module', 'update') .'/update.css');
+  return $output;
+}
+
+/**
+ * Theme the version display of a project.
+ *
+ * @ingroup themeable
+ */
+function theme_update_version($version, $tag, $class) {
+  $output = '';
+  $output .= '<table class="version '. $class .'">';
+  $output .= '<tr>';
+  $output .= '<td class="version-title">'. $tag ."</td>\n";
+  $output .= '<td class="version-details">';
+  $output .= l($version['version'], $version['release_link']);
+  $output .= ' <span class="version-date">('. format_date($version['date'], 'custom', 'Y-M-d') .')</span>';
+  $output .= "</td>\n";
+  $output .= '<td class="version-links">';
+  $links = array();
+  $links['update-download'] = array(
+    'title' => t('Download'),
+    'href' => $version['download_link'],
+  );
+  $links['update-release-notes'] = array(
+    'title' => t('Release notes'),
+    'href' => $version['release_link'],
+  );
+  $output .= theme('links', $links);
+  $output .= '</td>';
+  $output .= '</tr>';
+  $output .= "</table>\n";
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/update/update.settings.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,108 @@
+<?php
+// $Id: update.settings.inc,v 1.3 2007/10/20 21:57:50 goba Exp $
+
+/**
+ * @file
+ * Code required only for the update status settings form.
+ */
+
+/**
+ * Form builder for the update settings tab.
+ */
+function update_settings() {
+  $form = array();
+
+  $notify_emails = variable_get('update_notify_emails', array());
+  $form['update_notify_emails'] = array(
+    '#type' => 'textarea',
+    '#title' => t('E-mail addresses to notify when updates are available'),
+    '#rows' => 4,
+    '#default_value' => implode("\n", $notify_emails),
+    '#description' => t('Whenever your site checks for available updates and finds new releases, it can notify a list of users via e-mail. Put each address on a separate line. If blank, no e-mails will be sent.'),
+  );
+
+  $form['update_check_frequency'] = array(
+    '#type' => 'radios',
+    '#title' => t('Check for updates'),
+    '#default_value' => variable_get('update_check_frequency', 1),
+    '#options' => array(
+      '1' => t('Daily'),
+      '7' => t('Weekly'),
+    ),
+    '#description' => t('Select how frequently you want to automatically check for new releases of your currently installed modules and themes.'),
+  );
+
+  $form['update_notification_threshold'] = array(
+    '#type' => 'radios',
+    '#title' => t('Notification threshold'),
+    '#default_value' => variable_get('update_notification_threshold', 'all'),
+    '#options' => array(
+      'all' => t('All newer versions'),
+      'security' => t('Only security updates'),
+    ),
+    '#description' => t('If there are updates available of Drupal core or any of your installed modules and themes, your site will print an error message on the <a href="@status_report">status report</a>, the <a href="@modules_page">modules page</a>, and the <a href="@themes_page">themes page</a>. You can choose to only see these error messages if a security update is available, or to be notified about any newer versions.', array('@status_report' => url('admin/reports/status'), '@modules_page' => url('admin/build/modules'), '@themes_page' => url('admin/build/themes')))
+  );
+
+  $form = system_settings_form($form);
+  // Custom valiation callback for the email notification setting.
+  $form['#validate'][] = 'update_settings_validate';
+  // We need to call our own submit callback first, not the one from
+  // system_settings_form(), so that we can process and save the emails.
+  unset($form['#submit']);
+
+  return $form;
+}
+
+/**
+ * Validation callback for the settings form.
+ *
+ * Validates the email addresses and ensures the field is formatted correctly.
+ */
+function update_settings_validate($form, &$form_state) {
+  if (!empty($form_state['values']['update_notify_emails'])) {
+    $valid = array();
+    $invalid = array();
+    foreach (explode("\n", trim($form_state['values']['update_notify_emails'])) as $email) {
+      $email = trim($email);
+      if (!empty($email)) {
+        if (valid_email_address($email)) {
+          $valid[] = $email;
+        }
+        else {
+          $invalid[] = $email;
+        }
+      }
+    }
+    if (empty($invalid)) {
+      $form_state['notify_emails'] = $valid;
+    }
+    elseif (count($invalid) == 1) {
+      form_set_error('update_notify_emails', t('%email is not a valid e-mail address.', array('%email' => reset($invalid))));
+    }
+    else {
+      form_set_error('update_notify_emails', t('%emails are not valid e-mail addresses.', array('%emails' => implode(', ', $invalid))));
+    }
+  }
+}
+
+/**
+ * Submit handler for the settings tab.
+ */
+function update_settings_submit($form, $form_state) {
+  $op = $form_state['values']['op'];
+
+  if ($op == t('Reset to defaults')) {
+    unset($form_state['notify_emails']);
+  }
+  else {
+    if (empty($form_state['notify_emails'])) {
+      variable_del('update_notify_emails');
+    }
+    else {
+      variable_set('update_notify_emails', $form_state['notify_emails']);
+    }
+    unset($form_state['notify_emails']);
+    unset($form_state['values']['update_notify_emails']);
+  }
+  system_settings_form_submit($form, $form_state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/upload/upload.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,152 @@
+<?php
+// $Id: upload.admin.inc,v 1.7 2008/01/10 20:22:57 goba Exp $
+
+/**
+ * Form API callback to validate the upload settings form.
+ */
+function upload_admin_settings_validate($form, &$form_state) {
+  if (($form_state['values']['upload_max_resolution'] != '0')) {
+    if (!preg_match('/^[0-9]+x[0-9]+$/', $form_state['values']['upload_max_resolution'])) {
+      form_set_error('upload_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
+    }
+  }
+
+  $default_uploadsize = $form_state['values']['upload_uploadsize_default'];
+  $default_usersize = $form_state['values']['upload_usersize_default'];
+
+  $exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))) .'<br/>';
+  $more_info = t("Depending on your server environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory.");
+
+  if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) {
+    form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
+  }
+  if (!is_numeric($default_usersize) || ($default_usersize <= 0)) {
+    form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
+  }
+  if ($default_uploadsize * 1024 * 1024 > file_upload_max_size()) {
+    form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info);
+    $more_info = '';
+  }
+  if ($default_uploadsize > $default_usersize) {
+    form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default'))));
+  }
+
+  foreach ($form_state['values']['roles'] as $rid => $role) {
+    $uploadsize = $form_state['values']['upload_uploadsize_'. $rid];
+    $usersize = $form_state['values']['upload_usersize_'. $rid];
+
+    if (!is_numeric($uploadsize) || ($uploadsize <= 0)) {
+      form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
+    }
+    if (!is_numeric($usersize) || ($usersize <= 0)) {
+      form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
+    }
+    if ($uploadsize * 1024 * 1024 > file_upload_max_size()) {
+      form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info);
+      $more_info = '';
+    }
+    if ($uploadsize > $usersize) {
+      form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role)));
+    }
+  }
+}
+
+/**
+ * Menu callback for the upload settings form.
+ */
+function upload_admin_settings() {
+  $upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp');
+  $upload_uploadsize_default = variable_get('upload_uploadsize_default', 1);
+  $upload_usersize_default = variable_get('upload_usersize_default', 1);
+
+  $form['settings_general'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('General settings'),
+    '#collapsible' => TRUE,
+  );
+  $form['settings_general']['upload_max_resolution'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Maximum resolution for uploaded images'),
+    '#default_value' => variable_get('upload_max_resolution', 0),
+    '#size' => 15,
+    '#maxlength' => 10,
+    '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. If an <a href="!image-toolkit-link">image toolkit</a> is installed, files exceeding this value will be scaled down to fit.', array('!image-toolkit-link' => url('admin/settings/image-toolkit'))),
+    '#field_suffix' => '<kbd>'. t('WIDTHxHEIGHT') .'</kbd>'
+  );
+  $form['settings_general']['upload_list_default'] = array(
+    '#type' => 'select',
+    '#title' => t('List files by default'),
+    '#default_value' => variable_get('upload_list_default', 1),
+    '#options' => array(0 => t('No'), 1 => t('Yes')),
+    '#description' => t('Display attached files when viewing a post.'),
+  );
+
+  $form['settings_general']['upload_extensions_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default permitted file extensions'),
+    '#default_value' => $upload_extensions_default,
+    '#maxlength' => 255,
+    '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
+  );
+  $form['settings_general']['upload_uploadsize_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default maximum file size per upload'),
+    '#default_value' => $upload_uploadsize_default,
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#description' => t('The default maximum file size a user can upload. If an image is uploaded and a maximum resolution is set, the size will be checked after the file has been resized.'),
+    '#field_suffix' => t('MB'),
+  );
+  $form['settings_general']['upload_usersize_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default total file size per user'),
+    '#default_value' => $upload_usersize_default,
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#description' => t('The default maximum size of all files a user can have on the site.'),
+    '#field_suffix' => t('MB'),
+  );
+
+  $form['settings_general']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))) .'</p>');
+
+  $roles = user_roles(FALSE, 'upload files');
+  $form['roles'] = array('#type' => 'value', '#value' => $roles);
+
+  foreach ($roles as $rid => $role) {
+    $form['settings_role_'. $rid] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Settings for @role', array('@role' => $role)),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['settings_role_'. $rid]['upload_extensions_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Permitted file extensions'),
+      '#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default),
+      '#maxlength' => 255,
+      '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
+    );
+    $form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Maximum file size per upload'),
+      '#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default),
+      '#size' => 5,
+      '#maxlength' => 5,
+      '#description' => t('The maximum size of a file a user can upload. If an image is uploaded and a maximum resolution is set, the size will be checked after the file has been resized.'),
+      '#field_suffix' => t('MB'),
+    );
+    $form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
+      '#type' => 'textfield',
+      '#title' => t('Total file size per user'),
+      '#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default),
+      '#size' => 5,
+      '#maxlength' => 5,
+      '#description' => t('The maximum size of all files a user can have on the site.'),
+      '#field_suffix' => t('MB'),
+    );
+  }
+
+  $form['#validate'] = array('upload_admin_settings_validate');
+
+  return system_settings_form($form);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/upload/upload.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: upload.info,v 1.4 2007/06/08 05:50:57 dries Exp $
+name = Upload
+description = Allows users to upload and attach files to content.
+package = Core - optional
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/upload/upload.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,85 @@
+<?php
+// $Id: upload.install,v 1.6.2.1 2008/02/08 18:01:14 goba Exp $
+
+/**
+ * Implementation of hook_install().
+ */
+function upload_install() {
+  // Create table. The upload table might have been created in the Drupal 5
+  // to Drupal 6 upgrade, and was migrated from the file_revisions table. So
+  // in this case, there is no need to create the table, it is already there.
+  if (!db_table_exists('upload')) {
+    drupal_install_schema('upload');
+  }
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function upload_uninstall() {
+  // Remove tables.
+  drupal_uninstall_schema('upload');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function upload_schema() {
+  $schema['upload'] = array(
+    'description' => t('Stores uploaded file information and table associations.'),
+    'fields' => array(
+      'fid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {files}.fid.'),
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {node}.nid associated with the uploaded file.'),
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: The {node}.vid associated with the uploaded file.'),
+      ),
+      'description' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Description of the uploaded file.'),
+      ),
+      'list' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether the file should be visibly listed on the node: yes(1) or no(0).'),
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Weight of this upload in relation to other uploads in this node.'),
+      ),
+    ),
+    'primary key' => array('vid', 'fid'),
+    'indexes' => array(
+      'fid' => array('fid'),
+      'nid' => array('nid'),
+    ),
+  );
+
+  return $schema;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/upload/upload.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,635 @@
+<?php
+// $Id: upload.module,v 1.197.2.1 2008/02/11 15:08:09 goba Exp $
+
+/**
+ * @file
+ * File-handling and attaching files to nodes.
+ *
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function upload_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#upload':
+      $output = '<p>'. t('The upload module allows users to upload files to the site. The ability to upload files is important for members of a community who want to share work. It is also useful to administrators who want to keep uploaded files connected to posts.') .'</p>';
+      $output .= '<p>'. t('Users with the upload files permission can upload attachments to posts. Uploads may be enabled for specific content types on the content types settings page. Each user role can be customized to limit or control the file size of uploads, or the maximum dimension of image files.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@upload">Upload module</a>.', array('@upload' => 'http://drupal.org/handbook/modules/upload/')) .'</p>';
+      return $output;
+    case 'admin/settings/upload':
+      return '<p>'. t('Users with the <a href="@permissions">upload files permission</a> can upload attachments. Users with the <a href="@permissions">view uploaded files permission</a> can view uploaded attachments. You can choose which post types can take attachments on the <a href="@types">content types settings</a> page.', array('@permissions' => url('admin/user/permissions'), '@types' => url('admin/settings/types'))) .'</p>';
+  }
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function upload_theme() {
+  return array(
+    'upload_attachments' => array(
+      'arguments' => array('files' => NULL),
+    ),
+    'upload_form_current' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'upload_form_new' => array(
+      'arguments' => array('form' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function upload_perm() {
+  return array('upload files', 'view uploaded files');
+}
+
+/**
+ * Implementation of hook_link().
+ */
+function upload_link($type, $node = NULL, $teaser = FALSE) {
+  $links = array();
+
+  // Display a link with the number of attachments
+  if ($teaser && $type == 'node' && isset($node->files) && user_access('view uploaded files')) {
+    $num_files = 0;
+    foreach ($node->files as $file) {
+      if ($file->list) {
+        $num_files++;
+      }
+    }
+    if ($num_files) {
+      $links['upload_attachments'] = array(
+        'title' => format_plural($num_files, '1 attachment', '@count attachments'),
+        'href' => "node/$node->nid",
+        'attributes' => array('title' => t('Read full article to view attachments.')),
+        'fragment' => 'attachments'
+      );
+    }
+  }
+
+  return $links;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function upload_menu() {
+  $items['upload/js'] = array(
+    'page callback' => 'upload_js',
+    'access arguments' => array('upload files'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/uploads'] = array(
+    'title' => 'File uploads',
+    'description' => 'Control how files may be attached to content.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('upload_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'upload.admin.inc',
+  );
+  return $items;
+}
+
+function upload_menu_alter(&$items) {
+  $items['system/files']['access arguments'] = array('view uploaded files');
+}
+
+/**
+ * Determine the limitations on files that a given user may upload. The user
+ * may be in multiple roles so we select the most permissive limitations from
+ * all of their roles.
+ *
+ * @param $user
+ *   A Drupal user object.
+ * @return
+ *   An associative array with the following keys:
+ *     'extensions'
+ *       A white space separated string containing all the file extensions this
+ *       user may upload.
+ *     'file_size'
+ *       The maximum size of a file upload in bytes.
+ *     'user_size'
+ *       The total number of bytes for all for a user's files.
+ *     'resolution'
+ *       A string specifying the maximum resolution of images.
+ */
+function _upload_file_limits($user) {
+  $file_limit = variable_get('upload_uploadsize_default', 1);
+  $user_limit = variable_get('upload_usersize_default', 1);
+  $all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
+  foreach ($user->roles as $rid => $name) {
+    $extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
+    $all_extensions = array_merge($all_extensions, explode(' ', $extensions));
+
+    // A zero value indicates no limit, take the least restrictive limit.
+    $file_size = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
+    $file_limit = ($file_limit && $file_size) ? max($file_limit, $file_size) : 0;
+
+    $user_size = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
+    $user_limit = ($user_limit && $user_size) ? max($user_limit, $user_size) : 0;
+  }
+  $all_extensions = implode(' ', array_unique($all_extensions));
+  return array(
+    'extensions' => $all_extensions,
+    'file_size' => $file_limit,
+    'user_size' => $user_limit,
+    'resolution' => variable_get('upload_max_resolution', 0),
+  );
+}
+
+/**
+ * Implementation of hook_file_download().
+ */
+function upload_file_download($file) {
+  if (!user_access('view uploaded files')) {
+    return -1;
+  }
+  $file = file_create_path($file);
+  $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $file);
+  if ($file = db_fetch_object($result)) {
+    return array(
+      'Content-Type: '. $file->filemime,
+      'Content-Length: '. $file->filesize,
+    );
+  }
+}
+
+/**
+ * Save new uploads and store them in the session to be associated to the node
+ * on upload_save.
+ *
+ * @param $node
+ *   A node object to associate with uploaded files.
+ */
+function upload_node_form_submit($form, &$form_state) {
+  global $user;
+
+  $limits = _upload_file_limits($user);
+  $validators = array(
+    'file_validate_extensions' => array($limits['extensions']),
+    'file_validate_image_resolution' => array($limits['resolution']),
+    'file_validate_size' => array($limits['file_size'], $limits['user_size']),
+  );
+
+  // Save new file uploads.
+  if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
+    $file->list = variable_get('upload_list_default', 1);
+    $file->description = $file->filename;
+    $file->weight = 0;
+    $_SESSION['upload_files'][$file->fid] = $file;
+  }
+
+  // Attach session files to node.
+  if (!empty($_SESSION['upload_files'])) {
+    foreach ($_SESSION['upload_files'] as $fid => $file) {
+      if (!isset($form_state['values']['files'][$fid]['filepath'])) {
+        $form_state['values']['files'][$fid] = (array)$file;
+      }
+    }
+  }
+
+  // Order the form according to the set file weight values.
+  if (!empty($form_state['values']['files'])) {
+    $microweight = 0.001;
+    foreach ($form_state['values']['files'] as $fid => $file) {
+      if (is_numeric($fid)) {
+        $form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
+        $microweight += 0.001;
+      }
+    }
+    uasort($form_state['values']['files'], 'element_sort');
+  }
+}
+
+function upload_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
+    $form['workflow']['upload'] = array(
+      '#type' => 'radios',
+      '#title' => t('Attachments'),
+      '#default_value' => variable_get('upload_'. $form['#node_type']->type, 1),
+      '#options' => array(t('Disabled'), t('Enabled')),
+    );
+  }
+
+  if (isset($form['type']) && isset($form['#node'])) {
+    $node = $form['#node'];
+    if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
+      // Attachments fieldset
+      $form['attachments'] = array(
+        '#type' => 'fieldset',
+        '#access' => user_access('upload files'),
+        '#title' => t('File attachments'),
+        '#collapsible' => TRUE,
+        '#collapsed' => empty($node->files),
+        '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'),
+        '#prefix' => '<div class="attachments">',
+        '#suffix' => '</div>',
+        '#weight' => 30,
+      );
+
+      // Wrapper for fieldset contents (used by ahah.js).
+      $form['attachments']['wrapper'] = array(
+        '#prefix' => '<div id="attach-wrapper">',
+        '#suffix' => '</div>',
+      );
+
+      // Make sure necessary directories for upload.module exist and are
+      // writable before displaying the attachment form.
+      $path = file_directory_path();
+      $temp = file_directory_temp();
+      // Note: pass by reference
+      if (!file_check_directory($path, FILE_CREATE_DIRECTORY) || !file_check_directory($temp, FILE_CREATE_DIRECTORY)) {
+        $form['attachments']['#description'] =  t('File attachments are disabled. The file directories have not been properly configured.');
+        if (user_access('administer site configuration')) {
+          $form['attachments']['#description'] .= ' '. t('Please visit the <a href="@admin-file-system">file system configuration page</a>.', array('@admin-file-system' => url('admin/settings/file-system')));
+        }
+        else {
+          $form['attachments']['#description'] .= ' '. t('Please contact the site administrator.');
+        }
+      }
+      else {
+        $form['attachments']['wrapper'] += _upload_form($node);
+        $form['#attributes']['enctype'] = 'multipart/form-data';
+      }
+    }
+    $form['#submit'][] = 'upload_node_form_submit';
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function upload_nodeapi(&$node, $op, $teaser) {
+  switch ($op) {
+
+    case 'load':
+      $output = '';
+      if (variable_get("upload_$node->type", 1) == 1) {
+        $output['files'] = upload_load($node);
+        return $output;
+      }
+      break;
+
+    case 'view':
+      if (isset($node->files) && user_access('view uploaded files')) {
+        // Add the attachments list to node body with a heavy
+        // weight to ensure they're below other elements
+        if (count($node->files)) {
+          if (!$teaser && user_access('view uploaded files')) {
+            $node->content['files'] = array(
+              '#value' => theme('upload_attachments', $node->files),
+              '#weight' => 50,
+            );
+          }
+        }
+      }
+      break;
+
+    case 'prepare':
+      // Initialize $_SESSION['upload_files'] if no post occurred.
+      // This clears the variable from old forms and makes sure it
+      // is an array to prevent notices and errors in other parts
+      // of upload.module.
+      if (!$_POST) {
+        $_SESSION['upload_files'] = array();
+      }
+      break;
+
+    case 'insert':
+    case 'update':
+      if (user_access('upload files')) {
+        upload_save($node);
+      }
+      break;
+
+    case 'delete':
+      upload_delete($node);
+      break;
+
+    case 'delete revision':
+      upload_delete_revision($node);
+      break;
+
+    case 'search result':
+      return isset($node->files) && is_array($node->files) ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
+
+    case 'rss item':
+      if (is_array($node->files)) {
+        $files = array();
+        foreach ($node->files as $file) {
+          if ($file->list) {
+            $files[] = $file;
+          }
+        }
+        if (count($files) > 0) {
+          // RSS only allows one enclosure per item
+          $file = array_shift($files);
+          return array(
+            array(
+              'key' => 'enclosure',
+              'attributes' => array(
+                'url' => file_create_url($file->filepath),
+                'length' => $file->filesize,
+                'type' => $file->filemime
+              )
+            )
+          );
+        }
+      }
+      return array();
+  }
+}
+
+/**
+ * Displays file attachments in table
+ *
+ * @ingroup themeable
+ */
+function theme_upload_attachments($files) {
+  $header = array(t('Attachment'), t('Size'));
+  $rows = array();
+  foreach ($files as $file) {
+    $file = (object)$file;
+    if ($file->list && empty($file->remove)) {
+      $href = file_create_url($file->filepath);
+      $text = $file->description ? $file->description : $file->filename;
+      $rows[] = array(l($text, $href), format_size($file->filesize));
+    }
+  }
+  if (count($rows)) {
+    return theme('table', $header, $rows, array('id' => 'attachments'));
+  }
+}
+
+/**
+ * Determine how much disk space is occupied by a user's uploaded files.
+ *
+ * @param $uid
+ *   The integer user id of a user.
+ * @return
+ *   The amount of disk space used by the user in bytes.
+ */
+function upload_space_used($uid) {
+  return file_space_used($uid);
+}
+
+/**
+ * Determine how much disk space is occupied by uploaded files.
+ *
+ * @return
+ *   The amount of disk space used by uploaded files in bytes.
+ */
+function upload_total_space_used() {
+  return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid'));
+}
+
+function upload_save(&$node) {
+  if (empty($node->files) || !is_array($node->files)) {
+    return;
+  }
+
+  foreach ($node->files as $fid => $file) {
+    // Convert file to object for compatibility
+    $file = (object)$file;
+
+    // Remove file. Process removals first since no further processing
+    // will be required.
+    if (!empty($file->remove)) {
+      db_query('DELETE FROM {upload} WHERE fid = %d AND vid = %d', $fid, $node->vid);
+
+      // If the file isn't used by any other revisions delete it.
+      $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $fid));
+      if ($count < 1) {
+        file_delete($file->filepath);
+        db_query('DELETE FROM {files} WHERE fid = %d', $fid);
+      }
+
+      // Remove it from the session in the case of new uploads,
+      // that you want to disassociate before node submission.
+      unset($_SESSION['upload_files'][$fid]);
+      // Move on, so the removed file won't be added to new revisions.
+      continue;
+    }
+
+    // Create a new revision, or associate a new file needed.
+    if (!empty($node->old_vid) || isset($_SESSION['upload_files'][$fid])) {
+      db_query("INSERT INTO {upload} (fid, nid, vid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $file->fid, $node->nid, $node->vid, $file->list, $file->description, $file->weight);
+      file_set_status($file, FILE_STATUS_PERMANENT);
+    }
+    // Update existing revision.
+    else {
+      db_query("UPDATE {upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->weight, $file->fid, $node->vid);
+      file_set_status($file, FILE_STATUS_PERMANENT);
+    }
+  }
+  // Empty the session storage after save. We use this variable to track files
+  // that haven't been related to the node yet.
+  unset($_SESSION['upload_files']);
+}
+
+function upload_delete($node) {
+  $files = array();
+  $result = db_query('SELECT DISTINCT f.* FROM {upload} u INNER JOIN {files} f ON u.fid = f.fid WHERE u.nid = %d', $node->nid);
+  while ($file = db_fetch_object($result)) {
+    $files[$file->fid] = $file;
+  }
+
+  foreach ($files as $fid => $file) {
+    // Delete all files associated with the node
+    db_query('DELETE FROM {files} WHERE fid = %d', $fid);
+    file_delete($file->filepath);
+  }
+
+  // Delete all file revision information associated with the node
+  db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid);
+}
+
+function upload_delete_revision($node) {
+  if (is_array($node->files)) {
+    foreach ($node->files as $file) {
+      // Check if the file will be used after this revision is deleted
+      $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid));
+
+      // if the file won't be used, delete it
+      if ($count < 2) {
+        db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
+        file_delete($file->filepath);
+      }
+    }
+  }
+
+  // delete the revision
+  db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid);
+}
+
+function _upload_form($node) {
+  global $user;
+
+  $form = array(
+    '#theme' => 'upload_form_new',
+    '#cache' => TRUE,
+  );
+
+  if (!empty($node->files) && is_array($node->files)) {
+    $form['files']['#theme'] = 'upload_form_current';
+    $form['files']['#tree'] = TRUE;
+    foreach ($node->files as $key => $file) {
+      $file = (object)$file;
+      $description = file_create_url($file->filepath);
+      $description = "<small>". check_plain($description) ."</small>";
+      $form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
+      $form['files'][$key]['size'] = array('#value' => format_size($file->filesize));
+      $form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
+      $form['files'][$key]['list'] = array('#type' => 'checkbox',  '#default_value' => $file->list);
+      $form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
+      $form['files'][$key]['filename'] = array('#type' => 'value',  '#value' => $file->filename);
+      $form['files'][$key]['filepath'] = array('#type' => 'value',  '#value' => $file->filepath);
+      $form['files'][$key]['filemime'] = array('#type' => 'value',  '#value' => $file->filemime);
+      $form['files'][$key]['filesize'] = array('#type' => 'value',  '#value' => $file->filesize);
+      $form['files'][$key]['fid'] = array('#type' => 'value',  '#value' => $file->fid);
+    }
+  }
+
+  if (user_access('upload files')) {
+    $limits = _upload_file_limits($user);
+    $form['new']['#weight'] = 10;
+    $form['new']['upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Attach new file'),
+      '#size' => 40,
+      '#description' => ($limits['resolution'] ? t('Images are larger than %resolution will be resized. ', array('%resolution' => $limits['resolution'])) : '') . t('The maximum upload size is %filesize. Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions'], '%filesize' => format_size($limits['file_size']))),
+    );
+    $form['new']['attach'] = array(
+      '#type' => 'submit',
+      '#value' => t('Attach'),
+      '#name' => 'attach',
+      '#ahah' => array(
+        'path' => 'upload/js',
+        'wrapper' => 'attach-wrapper',
+        'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
+      ),
+      '#submit' => array('node_form_submit_build_node'),
+    );
+  }
+
+  // This value is used in upload_js().
+  $form['current']['vid'] = array('#type' => 'hidden', '#value' => isset($node->vid) ? $node->vid : 0);
+  return $form;
+}
+
+/**
+ * Theme the attachments list.
+ *
+ * @ingroup themeable
+ */
+function theme_upload_form_current(&$form) {
+  $header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
+  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
+
+  foreach (element_children($form) as $key) {
+    // Add class to group weight fields for drag and drop.
+    $form[$key]['weight']['#attributes']['class'] = 'upload-weight';
+
+    $row = array('');
+    $row[] = drupal_render($form[$key]['remove']);
+    $row[] = drupal_render($form[$key]['list']);
+    $row[] = drupal_render($form[$key]['description']);
+    $row[] = drupal_render($form[$key]['weight']);
+    $row[] = drupal_render($form[$key]['size']);
+    $rows[] = array('data' => $row, 'class' => 'draggable');
+  }
+  $output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Theme the attachment form.
+ * Note: required to output prefix/suffix.
+ *
+ * @ingroup themeable
+ */
+function theme_upload_form_new($form) {
+  drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
+  $output = drupal_render($form);
+  return $output;
+}
+
+function upload_load($node) {
+  $files = array();
+
+  if ($node->vid) {
+    $result = db_query('SELECT * FROM {files} f INNER JOIN {upload} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY r.weight, f.fid', $node->vid);
+    while ($file = db_fetch_object($result)) {
+      $files[$file->fid] = $file;
+    }
+  }
+
+  return $files;
+}
+
+/**
+ * Menu-callback for JavaScript-based uploads.
+ */
+function upload_js() {
+  // Load the form from the Form API cache.
+  $cache = cache_get('form_'. $_POST['form_build_id'], 'cache_form');
+
+  // We only do the upload.module part of the node validation process.
+  $node = (object)$_POST;
+  unset($node->files['upload']);
+  $form = $cache->data;
+  $form_state = array('values' => $_POST);
+
+  // Handle new uploads, and merge tmp files into node-files.
+  upload_node_form_submit($form, $form_state);
+  $node_files = upload_load($node);
+  if (!empty($form_state['values']['files'])) {
+    foreach ($form_state['values']['files'] as $fid => $file) {
+      if (is_numeric($fid)) {
+        $node->files[$fid] = $file;
+        if (!isset($file['filepath'])) {
+          $node->files[$fid] = $node_files[$fid];
+        }
+      }
+    }
+  }
+  $form = _upload_form($node);
+
+  // Update the default values changed in the $_POST array.
+  $files = isset($_POST['files']) ? $_POST['files'] : array();
+  foreach ($files as $fid => $file) {
+    if (is_numeric($fid)) {
+      $form['files'][$fid]['description']['#default_value'] = $file['description'];
+      $form['files'][$fid]['list']['#default_value'] = isset($file['list']) ? 1 : 0;
+      $form['files'][$fid]['remove']['#default_value'] = isset($file['remove']) ? 1 : 0;
+      $form['files'][$fid]['weight']['#default_value'] = $file['weight'];
+    }
+  }
+
+  // Add the new element to the stored form state and resave.
+  $cache->data['attachments']['wrapper'] = array_merge($cache->data['attachments']['wrapper'], $form);
+  cache_set('form_'. $_POST['form_build_id'], $cache->data, 'cache_form', $cache->expire);
+
+  // Render the form for output.
+  $form += array(
+    '#post' => $_POST,
+    '#programmed' => FALSE,
+    '#tree' => FALSE,
+    '#parents' => array(),
+  );
+  drupal_alter('form', $form, array(), 'upload_js');
+  $form_state = array('submitted' => FALSE);
+  $form = form_builder('upload_js', $form, $form_state);
+  $output = theme('status_messages') . drupal_render($form);
+
+  // We send the updated file attachments form.
+  // Don't call drupal_json(). ahah.js uses an iframe and
+  // the header output by drupal_json() causes problems in some browsers.
+  print drupal_to_js(array('status' => TRUE, 'data' => $output));
+  exit;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user-picture.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,20 @@
+<?php
+// $Id: user-picture.tpl.php,v 1.2 2007/08/07 08:39:36 goba Exp $
+
+/**
+ * @file user-picture.tpl.php
+ * Default theme implementation to present an picture configured for the
+ * user's account.
+ *
+ * Available variables:
+ * - $picture: Image set by the user or the site's default. Will be linked
+ *   depending on the viewer's permission to view the users profile page.
+ * - $account: Array of account information. Potentially unsafe. Be sure to
+ *   check_plain() before use.
+ *
+ * @see template_preprocess_user_picture()
+ */
+?>
+<div class="picture">
+  <?php print $picture; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user-profile-category.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,34 @@
+<?php
+// $Id: user-profile-category.tpl.php,v 1.2 2007/08/07 08:39:36 goba Exp $
+
+/**
+ * @file user-profile-category.tpl.php
+ * Default theme implementation to present profile categories (groups of
+ * profile items).
+ *
+ * Categories are defined when configuring user profile fields for the site.
+ * It can also be defined by modules. All profile items for a category will be
+ * output through the $profile_items variable.
+ *
+ * @see user-profile-item.tpl.php
+ *      where each profile item is rendered. It is implemented as a definition
+ *      list by default.
+ * @see user-profile.tpl.php
+ *      where all items and categories are collected and printed out.
+ *
+ * Available variables:
+ * - $title: Category title for the group of items.
+ * - $profile_items: All the items for the group rendered through
+ *   user-profile-item.tpl.php.
+ * - $attributes: HTML attributes. Usually renders classes.
+ *
+ * @see template_preprocess_user_profile_category()
+ */
+?>
+<?php if ($title) : ?>
+  <h3><?php print $title; ?></h3>
+<?php endif; ?>
+
+<dl<?php print $attributes; ?>>
+  <?php print $profile_items; ?>
+</dl>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user-profile-item.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,27 @@
+<?php
+// $Id: user-profile-item.tpl.php,v 1.2 2007/08/07 08:39:36 goba Exp $
+
+/**
+ * @file user-profile-item.tpl.php
+ * Default theme implementation to present profile items (values from user
+ * account profile fields or modules).
+ *
+ * This template is used to loop through and render each field configured
+ * for the user's account. It can also be the data from modules. The output is
+ * grouped by categories.
+ *
+ * @see user-profile-category.tpl.php
+ *      for the parent markup. Implemented as a definition list by default.
+ * @see user-profile.tpl.php
+ *      where all items and categories are collected and printed out.
+ *
+ * Available variables:
+ * - $title: Field title for the profile item.
+ * - $value: User defined value for the profile item or data from a module.
+ * - $attributes: HTML attributes. Usually renders classes.
+ *
+ * @see template_preprocess_user_profile_item()
+ */
+?>
+<dt<?php print $attributes; ?>><?php print $title; ?></dt>
+<dd<?php print $attributes; ?>><?php print $value; ?></dd>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user-profile.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,46 @@
+<?php
+// $Id: user-profile.tpl.php,v 1.2 2007/08/07 08:39:36 goba Exp $
+
+/**
+ * @file user-profile.tpl.php
+ * Default theme implementation to present all user profile data.
+ *
+ * This template is used when viewing a registered member's profile page,
+ * e.g., example.com/user/123. 123 being the users ID.
+ *
+ * By default, all user profile data is printed out with the $user_profile
+ * variable. If there is a need to break it up you can use $profile instead.
+ * It is keyed to the name of each category or other data attached to the
+ * account. If it is a category it will contain all the profile items. By
+ * default $profile['summary'] is provided which contains data on the user's
+ * history. Other data can be included by modules. $profile['picture'] is
+ * available by default showing the account picture.
+ *
+ * Also keep in mind that profile items and their categories can be defined by
+ * site administrators. They are also available within $profile. For example,
+ * if a site is configured with a category of "contact" with
+ * fields for of addresses, phone numbers and other related info, then doing a
+ * straight print of $profile['contact'] will output everything in the
+ * category. This is useful for altering source order and adding custom
+ * markup for the group.
+ *
+ * To check for all available data within $profile, use the code below.
+ *
+ *   <?php print '<pre>'. check_plain(print_r($profile, 1)) .'</pre>'; ?>
+ *
+ * @see user-profile-category.tpl.php
+ *      where the html is handled for the group.
+ * @see user-profile-field.tpl.php
+ *      where the html is handled for each item in the group.
+ *
+ * Available variables:
+ * - $user_profile: All user profile data. Ready for print.
+ * - $profile: Keyed array of profile categories and their items or other data
+ *   provided by modules.
+ *
+ * @see template_preprocess_user_profile()
+ */
+?>
+<div class="profile">
+  <?php print $user_profile; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,22 @@
+/* $Id: user-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */
+
+#permissions td.permission {
+  padding-left: 0;
+  padding-right: 1.5em;
+}
+#access-rules .access-type, #access-rules .rule-type {
+  margin-right: 0;
+  margin-left: 1em;
+  float: right;
+}
+#user-admin-buttons {
+  float: right;
+  margin-left: 0;
+  margin-right: 0.5em;
+  clear: left;
+}
+
+.profile .picture {
+  float: left;
+  margin: 0 0 1em 1em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.admin.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1012 @@
+<?php
+// $Id: user.admin.inc,v 1.18 2008/01/16 22:54:41 goba Exp $
+
+/**
+ * @file
+ * Admin page callback file for the user module.
+ */
+
+function user_admin($callback_arg = '') {
+  $op = isset($_POST['op']) ? $_POST['op'] : $callback_arg;
+
+  switch ($op) {
+    case t('Create new account'):
+    case 'create':
+      $output = drupal_get_form('user_register');
+      break;
+    default:
+      if (!empty($_POST['accounts']) && isset($_POST['operation']) && ($_POST['operation'] == 'delete')) {
+        $output = drupal_get_form('user_multiple_delete_confirm');
+      }
+      else {
+        $output = drupal_get_form('user_filter_form');
+        $output .= drupal_get_form('user_admin_account');
+      }
+  }
+  return $output;
+}
+
+/**
+ * Form builder; Return form for user administration filters.
+ *
+ * @ingroup forms
+ * @see user_filter_form_submit()
+ */
+function user_filter_form() {
+  $session = &$_SESSION['user_overview_filter'];
+  $session = is_array($session) ? $session : array();
+  $filters = user_filters();
+
+  $i = 0;
+  $form['filters'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Show only users where'),
+    '#theme' => 'user_filters',
+  );
+  foreach ($session as $filter) {
+    list($type, $value) = $filter;
+    // Merge an array of arrays into one if necessary.
+    $options = $type == 'permission' ? call_user_func_array('array_merge', $filters[$type]['options']) : $filters[$type]['options'];
+    $params = array('%property' => $filters[$type]['title'] , '%value' => $options[$value]);
+    if ($i++ > 0) {
+      $form['filters']['current'][] = array('#value' => t('<em>and</em> where <strong>%property</strong> is <strong>%value</strong>', $params));
+    }
+    else {
+      $form['filters']['current'][] = array('#value' => t('<strong>%property</strong> is <strong>%value</strong>', $params));
+    }
+  }
+
+  foreach ($filters as $key => $filter) {
+    $names[$key] = $filter['title'];
+    $form['filters']['status'][$key] = array(
+      '#type' => 'select',
+      '#options' => $filter['options'],
+    );
+  }
+
+  $form['filters']['filter'] = array(
+    '#type' => 'radios',
+    '#options' => $names,
+  );
+  $form['filters']['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => (count($session) ? t('Refine') : t('Filter')),
+  );
+  if (count($session)) {
+    $form['filters']['buttons']['undo'] = array(
+      '#type' => 'submit',
+      '#value' => t('Undo'),
+    );
+    $form['filters']['buttons']['reset'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset'),
+    );
+  }
+
+  drupal_add_js('misc/form.js', 'core');
+
+  return $form;
+}
+
+/**
+ * Process result from user administration filter form.
+ */
+function user_filter_form_submit($form, &$form_state) {
+  $op = $form_state['values']['op'];
+  $filters = user_filters();
+  switch ($op) {
+    case t('Filter'): case t('Refine'):
+      if (isset($form_state['values']['filter'])) {
+        $filter = $form_state['values']['filter'];
+        // Merge an array of arrays into one if necessary.
+        $options = $filter == 'permission' ? call_user_func_array('array_merge', $filters[$filter]['options']) : $filters[$filter]['options'];
+        if (isset($options[$form_state['values'][$filter]])) {
+          $_SESSION['user_overview_filter'][] = array($filter, $form_state['values'][$filter]);
+        }
+      }
+      break;
+    case t('Undo'):
+      array_pop($_SESSION['user_overview_filter']);
+      break;
+    case t('Reset'):
+      $_SESSION['user_overview_filter'] = array();
+      break;
+    case t('Update'):
+      return;
+  }
+
+  $form_state['redirect'] = 'admin/user/user';
+  return;
+}
+
+/**
+ * Form builder; User administration page.
+ *
+ * @ingroup forms
+ * @see user_admin_account_validate()
+ * @see user_admin_account_submit()
+ */
+function user_admin_account() {
+  $filter = user_build_filter_query();
+
+  $header = array(
+    array(),
+    array('data' => t('Username'), 'field' => 'u.name'),
+    array('data' => t('Status'), 'field' => 'u.status'),
+    t('Roles'),
+    array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
+    array('data' => t('Last access'), 'field' => 'u.access'),
+    t('Operations')
+  );
+
+  $sql = 'SELECT DISTINCT u.uid, u.name, u.status, u.created, u.access FROM {users} u LEFT JOIN {users_roles} ur ON u.uid = ur.uid '. $filter['join'] .' WHERE u.uid != 0 '. $filter['where'];
+  $sql .= tablesort_sql($header);
+  $query_count = 'SELECT COUNT(DISTINCT u.uid) FROM {users} u LEFT JOIN {users_roles} ur ON u.uid = ur.uid '. $filter['join'] .' WHERE u.uid != 0 '. $filter['where'];
+  $result = pager_query($sql, 50, 0, $query_count, $filter['args']);
+
+  $form['options'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $options = array();
+  foreach (module_invoke_all('user_operations') as $operation => $array) {
+    $options[$operation] = $array['label'];
+  }
+  $form['options']['operation'] = array(
+    '#type' => 'select',
+    '#options' => $options,
+    '#default_value' => 'unblock',
+  );
+  $form['options']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update'),
+  );
+
+  $destination = drupal_get_destination();
+
+  $status = array(t('blocked'), t('active'));
+  $roles = user_roles(TRUE);
+  $accounts = array();
+  while ($account = db_fetch_object($result)) {
+    $accounts[$account->uid] = '';
+    $form['name'][$account->uid] = array('#value' => theme('username', $account));
+    $form['status'][$account->uid] =  array('#value' => $status[$account->status]);
+    $users_roles = array();
+    $roles_result = db_query('SELECT rid FROM {users_roles} WHERE uid = %d', $account->uid);
+    while ($user_role = db_fetch_object($roles_result)) {
+      $users_roles[] = $roles[$user_role->rid];
+    }
+    asort($users_roles);
+    $form['roles'][$account->uid][0] = array('#value' => theme('item_list', $users_roles));
+    $form['member_for'][$account->uid] = array('#value' => format_interval(time() - $account->created));
+    $form['last_access'][$account->uid] =  array('#value' => $account->access ? t('@time ago', array('@time' => format_interval(time() - $account->access))) : t('never'));
+    $form['operations'][$account->uid] = array('#value' => l(t('edit'), "user/$account->uid/edit", array('query' => $destination)));
+  }
+  $form['accounts'] = array(
+    '#type' => 'checkboxes',
+    '#options' => $accounts
+  );
+  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+
+  return $form;
+}
+
+/**
+ * Submit the user administration update form.
+ */
+function user_admin_account_submit($form, &$form_state) {
+  $operations = module_invoke_all('user_operations', $form_state);
+  $operation = $operations[$form_state['values']['operation']];
+  // Filter out unchecked accounts.
+  $accounts = array_filter($form_state['values']['accounts']);
+  if ($function = $operation['callback']) {
+    // Add in callback arguments if present.
+    if (isset($operation['callback arguments'])) {
+      $args = array_merge(array($accounts), $operation['callback arguments']);
+    }
+    else {
+      $args = array($accounts);
+    }
+    call_user_func_array($function, $args);
+
+    drupal_set_message(t('The update has been performed.'));
+  }
+}
+
+function user_admin_account_validate($form, &$form_state) {
+  $form_state['values']['accounts'] = array_filter($form_state['values']['accounts']);
+  if (count($form_state['values']['accounts']) == 0) {
+    form_set_error('', t('No users selected.'));
+  }
+}
+
+/**
+ * Form builder; Configure user settings for this site.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function user_admin_settings() {
+  // User registration settings.
+  $form['registration'] = array('#type' => 'fieldset', '#title' => t('User registration settings'));
+  $form['registration']['user_register'] = array('#type' => 'radios', '#title' => t('Public registrations'), '#default_value' => variable_get('user_register', 1), '#options' => array(t('Only site administrators can create new user accounts.'), t('Visitors can create accounts and no administrator approval is required.'), t('Visitors can create accounts but administrator approval is required.')));
+  $form['registration']['user_email_verification'] = array('#type' => 'checkbox', '#title' => t('Require e-mail verification when a visitor creates an account'), '#default_value' => variable_get('user_email_verification', TRUE), '#description' => t('If this box is checked, new users will be required to validate their e-mail address prior to logging into the site, and will be assigned a system-generated password. With it unchecked, users will be logged in immediately upon registering, and may select their own passwords during registration.'));
+  $form['registration']['user_registration_help'] = array('#type' => 'textarea', '#title' => t('User registration guidelines'), '#default_value' => variable_get('user_registration_help', ''), '#description' => t('This text is displayed at the top of the user registration form and is useful for helping or instructing your users.'));
+
+  // User e-mail settings.
+  $form['email'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('User e-mail settings'),
+    '#description' => t('Drupal sends emails whenever new users register on your site, and optionally, may also notify users after other account actions. Using a simple set of content templates, notification e-mails can be customized to fit the specific needs of your site.'),
+  );
+  // These email tokens are shared for all settings, so just define
+  // the list once to help ensure they stay in sync.
+  $email_token_help = t('Available variables are:') .' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.';
+
+  $form['email']['admin_created'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Welcome, new user created by administrator'),
+    '#collapsible' => TRUE,
+    '#collapsed' => (variable_get('user_register', 1) != 0),
+    '#description' => t('Customize welcome e-mail messages sent to new member accounts created by an administrator.') .' '. $email_token_help,
+  );
+  $form['email']['admin_created']['user_mail_register_admin_created_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('register_admin_created_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['admin_created']['user_mail_register_admin_created_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('register_admin_created_body'),
+    '#rows' => 15,
+  );
+
+  $form['email']['no_approval_required'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Welcome, no approval required'),
+    '#collapsible' => TRUE,
+    '#collapsed' => (variable_get('user_register', 1) != 1),
+    '#description' => t('Customize welcome e-mail messages sent to new members upon registering, when no administrator approval is required.') .' '. $email_token_help
+  );
+  $form['email']['no_approval_required']['user_mail_register_no_approval_required_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('register_no_approval_required_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['no_approval_required']['user_mail_register_no_approval_required_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('register_no_approval_required_body'),
+    '#rows' => 15,
+  );
+
+  $form['email']['pending_approval'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Welcome, awaiting administrator approval'),
+    '#collapsible' => TRUE,
+    '#collapsed' => (variable_get('user_register', 1) != 2),
+    '#description' => t('Customize welcome e-mail messages sent to new members upon registering, when administrative approval is required.') .' '. $email_token_help,
+  );
+  $form['email']['pending_approval']['user_mail_register_pending_approval_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('register_pending_approval_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['pending_approval']['user_mail_register_pending_approval_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('register_pending_approval_body'),
+    '#rows' => 8,
+  );
+
+  $form['email']['password_reset'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Password recovery email'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Customize e-mail messages sent to users who request a new password.') .' '. $email_token_help,
+  );
+  $form['email']['password_reset']['user_mail_password_reset_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('password_reset_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['password_reset']['user_mail_password_reset_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('password_reset_body'),
+    '#rows' => 12,
+  );
+
+  $form['email']['activated'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Account activation email'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Enable and customize e-mail messages sent to users upon account activation (when an administrator activates an account of a user who has already registered, on a site where administrative approval is required).') .' '. $email_token_help,
+  );
+  $form['email']['activated']['user_mail_status_activated_notify'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Notify user when account is activated.'),
+    '#default_value' => variable_get('user_mail_status_activated_notify', TRUE),
+  );
+  $form['email']['activated']['user_mail_status_activated_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('status_activated_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['activated']['user_mail_status_activated_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('status_activated_body'),
+    '#rows' => 15,
+  );
+
+  $form['email']['blocked'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Account blocked email'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Enable and customize e-mail messages sent to users when their accounts are blocked.') .' '. $email_token_help,
+  );
+  $form['email']['blocked']['user_mail_status_blocked_notify'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Notify user when account is blocked.'),
+    '#default_value' => variable_get('user_mail_status_blocked_notify', FALSE),
+  );
+  $form['email']['blocked']['user_mail_status_blocked_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('status_blocked_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['blocked']['user_mail_status_blocked_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('status_blocked_body'),
+    '#rows' => 3,
+  );
+
+  $form['email']['deleted'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Account deleted email'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Enable and customize e-mail messages sent to users when their accounts are deleted.') .' '. $email_token_help,
+  );
+  $form['email']['deleted']['user_mail_status_deleted_notify'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Notify user when account is deleted.'),
+    '#default_value' => variable_get('user_mail_status_deleted_notify', FALSE),
+  );
+  $form['email']['deleted']['user_mail_status_deleted_subject'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Subject'),
+    '#default_value' => _user_mail_text('status_deleted_subject'),
+    '#maxlength' => 180,
+  );
+  $form['email']['deleted']['user_mail_status_deleted_body'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Body'),
+    '#default_value' => _user_mail_text('status_deleted_body'),
+    '#rows' => 3,
+  );
+
+  // User signatures.
+  $form['signatures'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Signatures'),
+  );
+  $form['signatures']['user_signatures'] = array(
+    '#type' => 'radios',
+    '#title' => t('Signature support'),
+    '#default_value' => variable_get('user_signatures', 0),
+    '#options' => array(t('Disabled'), t('Enabled')),
+  );
+
+  // If picture support is enabled, check whether the picture directory exists:
+  if (variable_get('user_pictures', 0)) {
+    $picture_path = file_create_path(variable_get('user_picture_path', 'pictures'));
+    file_check_directory($picture_path, 1, 'user_picture_path');
+  }
+
+  $form['pictures'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Pictures'),
+  );
+  $picture_support = variable_get('user_pictures', 0);
+  $form['pictures']['user_pictures'] = array(
+    '#type' => 'radios',
+    '#title' => t('Picture support'),
+    '#default_value' => $picture_support,
+    '#options' => array(t('Disabled'), t('Enabled')),
+    '#prefix' => '<div class="user-admin-picture-radios">',
+    '#suffix' => '</div>',
+  );
+  drupal_add_js(drupal_get_path('module', 'user') .'/user.js');
+  // If JS is enabled, and the radio is defaulting to off, hide all
+  // the settings on page load via .css using the js-hide class so
+  // that there's no flicker.
+  $css_class = 'user-admin-picture-settings';
+  if (!$picture_support) {
+    $css_class .= ' js-hide';
+  }
+  $form['pictures']['settings'] = array(
+    '#prefix' => '<div class="'. $css_class .'">',
+    '#suffix' => '</div>',
+  );
+  $form['pictures']['settings']['user_picture_path'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Picture image path'),
+    '#default_value' => variable_get('user_picture_path', 'pictures'),
+    '#size' => 30,
+    '#maxlength' => 255,
+    '#description' => t('Subdirectory in the directory %dir where pictures will be stored.', array('%dir' => file_directory_path() .'/')),
+  );
+  $form['pictures']['settings']['user_picture_default'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default picture'),
+    '#default_value' => variable_get('user_picture_default', ''),
+    '#size' => 30,
+    '#maxlength' => 255,
+    '#description' => t('URL of picture to display for users with no custom picture selected. Leave blank for none.'),
+  );
+  $form['pictures']['settings']['user_picture_dimensions'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Picture maximum dimensions'),
+    '#default_value' => variable_get('user_picture_dimensions', '85x85'),
+    '#size' => 15,
+    '#maxlength' => 10,
+    '#description' => t('Maximum dimensions for pictures, in pixels.'),
+  );
+  $form['pictures']['settings']['user_picture_file_size'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Picture maximum file size'),
+    '#default_value' => variable_get('user_picture_file_size', '30'),
+    '#size' => 15,
+    '#maxlength' => 10,
+    '#description' => t('Maximum file size for pictures, in kB.'),
+  );
+  $form['pictures']['settings']['user_picture_guidelines'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Picture guidelines'),
+    '#default_value' => variable_get('user_picture_guidelines', ''),
+    '#description' => t("This text is displayed at the picture upload form in addition to the default guidelines. It's useful for helping or instructing your users."),
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Menu callback: administer permissions.
+ *
+ * @ingroup forms
+ * @see user_admin_perm_submit()
+ * @see theme_user_admin_perm()
+ */
+function user_admin_perm($form_state, $rid = NULL) {
+  if (is_numeric($rid)) {
+    $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid WHERE r.rid = %d', $rid);
+  }
+  else {
+    $result = db_query('SELECT r.rid, p.perm FROM {role} r LEFT JOIN {permission} p ON r.rid = p.rid ORDER BY name');
+  }
+
+  // Compile role array:
+  // Add a comma at the end so when searching for a permission, we can
+  // always search for "$perm," to make sure we do not confuse
+  // permissions that are substrings of each other.
+  while ($role = db_fetch_object($result)) {
+    $role_permissions[$role->rid] = $role->perm .',';
+  }
+
+  // Retrieve role names for columns.
+  $role_names = user_roles();
+  if (is_numeric($rid)) {
+    $role_names = array($rid => $role_names[$rid]);
+  }
+
+  // Render role/permission overview:
+  $options = array();
+  foreach (module_list(FALSE, FALSE, TRUE) as $module) {
+    if ($permissions = module_invoke($module, 'perm')) {
+      $form['permission'][] = array(
+        '#value' => $module,
+      );
+      asort($permissions);
+      foreach ($permissions as $perm) {
+        $options[$perm] = '';
+        $form['permission'][$perm] = array('#value' => t($perm));
+        foreach ($role_names as $rid => $name) {
+          // Builds arrays for checked boxes for each role
+          if (strpos($role_permissions[$rid], $perm .',') !== FALSE) {
+            $status[$rid][] = $perm;
+          }
+        }
+      }
+    }
+  }
+
+  // Have to build checkboxes here after checkbox arrays are built
+  foreach ($role_names as $rid => $name) {
+    $form['checkboxes'][$rid] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => isset($status[$rid]) ? $status[$rid] : array());
+    $form['role_names'][$rid] = array('#value' => $name, '#tree' => TRUE);
+  }
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save permissions'));
+
+  return $form;
+}
+
+function user_admin_perm_submit($form, &$form_state) {
+  // Save permissions:
+  $result = db_query('SELECT * FROM {role}');
+  while ($role = db_fetch_object($result)) {
+    if (isset($form_state['values'][$role->rid])) {
+      // Delete, so if we clear every checkbox we reset that role;
+      // otherwise permissions are active and denied everywhere.
+      db_query('DELETE FROM {permission} WHERE rid = %d', $role->rid);
+      $form_state['values'][$role->rid] = array_filter($form_state['values'][$role->rid]);
+      if (count($form_state['values'][$role->rid])) {
+        db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, implode(', ', array_keys($form_state['values'][$role->rid])));
+      }
+    }
+  }
+
+  drupal_set_message(t('The changes have been saved.'));
+
+  // Clear the cached pages
+  cache_clear_all();
+}
+
+/**
+ * Theme the administer permissions page.
+ *
+ * @ingroup themeable
+ */
+function theme_user_admin_perm($form) {
+  $roles = user_roles();
+  foreach (element_children($form['permission']) as $key) {
+    // Don't take form control structures
+    if (is_array($form['permission'][$key])) {
+      $row = array();
+      // Module name
+      if (is_numeric($key)) {
+        $row[] = array('data' => t('@module module', array('@module' => drupal_render($form['permission'][$key]))), 'class' => 'module', 'id' => 'module-'. $form['permission'][$key]['#value'], 'colspan' => count($form['role_names']) + 1);
+      }
+      else {
+        $row[] = array('data' => drupal_render($form['permission'][$key]), 'class' => 'permission');
+        foreach (element_children($form['checkboxes']) as $rid) {
+          if (is_array($form['checkboxes'][$rid])) {
+            $row[] = array('data' => drupal_render($form['checkboxes'][$rid][$key]), 'class' => 'checkbox', 'title' => $roles[$rid] .' : '. t($key));
+          }
+        }
+      }
+      $rows[] = $row;
+    }
+  }
+  $header[] = (t('Permission'));
+  foreach (element_children($form['role_names']) as $rid) {
+    if (is_array($form['role_names'][$rid])) {
+      $header[] = array('data' => drupal_render($form['role_names'][$rid]), 'class' => 'checkbox');
+    }
+  }
+  $output = theme('table', $header, $rows, array('id' => 'permissions'));
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Menu callback: administer roles.
+ *
+ * @ingroup forms
+ * @see user_admin_role_validate()
+ * @see user_admin_role_submit()
+ * @see theme_user_admin_new_role()
+ */
+function user_admin_role() {
+  $rid = arg(4);
+  if ($rid) {
+    if ($rid == DRUPAL_ANONYMOUS_RID || $rid == DRUPAL_AUTHENTICATED_RID) {
+      drupal_goto('admin/user/roles');
+    }
+    // Display the edit role form.
+    $role = db_fetch_object(db_query('SELECT * FROM {role} WHERE rid = %d', $rid));
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Role name'),
+      '#default_value' => $role->name,
+      '#size' => 30,
+      '#required' => TRUE,
+      '#maxlength' => 64,
+      '#description' => t('The name for this role. Example: "moderator", "editorial board", "site architect".'),
+    );
+    $form['rid'] = array(
+      '#type' => 'value',
+      '#value' => $rid,
+    );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save role'),
+    );
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete role'),
+    );
+  }
+  else {
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#size' => 32,
+      '#maxlength' => 64,
+    );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Add role'),
+    );
+    $form['#submit'][] = 'user_admin_role_submit';
+    $form['#validate'][] = 'user_admin_role_validate';
+  }
+  return $form;
+}
+
+function user_admin_role_validate($form, &$form_state) {
+  if ($form_state['values']['name']) {
+    if ($form_state['values']['op'] == t('Save role')) {
+      if (db_result(db_query("SELECT COUNT(*) FROM {role} WHERE name = '%s' AND rid != %d", $form_state['values']['name'], $form_state['values']['rid']))) {
+        form_set_error('name', t('The role name %name already exists. Please choose another role name.', array('%name' => $form_state['values']['name'])));
+      }
+    }
+    else if ($form_state['values']['op'] == t('Add role')) {
+      if (db_result(db_query("SELECT COUNT(*) FROM {role} WHERE name = '%s'", $form_state['values']['name']))) {
+        form_set_error('name', t('The role name %name already exists. Please choose another role name.', array('%name' => $form_state['values']['name'])));
+      }
+    }
+  }
+  else {
+    form_set_error('name', t('You must specify a valid role name.'));
+  }
+}
+
+function user_admin_role_submit($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Save role')) {
+    db_query("UPDATE {role} SET name = '%s' WHERE rid = %d", $form_state['values']['name'], $form_state['values']['rid']);
+    drupal_set_message(t('The role has been renamed.'));
+  }
+  else if ($form_state['values']['op'] == t('Delete role')) {
+    db_query('DELETE FROM {role} WHERE rid = %d', $form_state['values']['rid']);
+    db_query('DELETE FROM {permission} WHERE rid = %d', $form_state['values']['rid']);
+    // Update the users who have this role set:
+    db_query('DELETE FROM {users_roles} WHERE rid = %d', $form_state['values']['rid']);
+
+    drupal_set_message(t('The role has been deleted.'));
+  }
+  else if ($form_state['values']['op'] == t('Add role')) {
+    db_query("INSERT INTO {role} (name) VALUES ('%s')", $form_state['values']['name']);
+    drupal_set_message(t('The role has been added.'));
+  }
+  $form_state['redirect'] = 'admin/user/roles';
+  return;
+}
+
+/**
+ * Menu callback: list all access rules
+ */
+function user_admin_access_check() {
+  $output = drupal_get_form('user_admin_check_user');
+  $output .= drupal_get_form('user_admin_check_mail');
+  $output .= drupal_get_form('user_admin_check_host');
+  return $output;
+}
+
+/**
+ * Menu callback: add an access rule
+ */
+function user_admin_access_add($mask = NULL, $type = NULL) {
+  if ($edit = $_POST) {
+    if (!$edit['mask']) {
+      form_set_error('mask', t('You must enter a mask.'));
+    }
+    else {
+      db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $edit['mask'], $edit['type'], $edit['status']);
+      $aid = db_last_insert_id('access', 'aid');
+      drupal_set_message(t('The access rule has been added.'));
+      drupal_goto('admin/user/rules');
+    }
+  }
+  else {
+    $edit['mask'] = $mask;
+    $edit['type'] = $type;
+  }
+  return drupal_get_form('user_admin_access_add_form', $edit, t('Add rule'));
+}
+
+/**
+ * Menu callback: edit an access rule
+ */
+function user_admin_access_edit($aid = 0) {
+  if ($edit = $_POST) {
+    if (!$edit['mask']) {
+      form_set_error('mask', t('You must enter a mask.'));
+    }
+    else {
+      db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $edit['mask'], $edit['type'], $edit['status'], $aid);
+      drupal_set_message(t('The access rule has been saved.'));
+      drupal_goto('admin/user/rules');
+    }
+  }
+  else {
+    $edit = db_fetch_array(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
+  }
+  return drupal_get_form('user_admin_access_edit_form', $edit, t('Save rule'));
+}
+
+/**
+ * Form builder; Configure access rules.
+ *
+ * @ingroup forms
+ */
+function user_admin_access_form(&$form_state, $edit, $submit) {
+  $form['status'] = array(
+    '#type' => 'radios',
+    '#title' => t('Access type'),
+    '#default_value' => isset($edit['status']) ? $edit['status'] : 0,
+    '#options' => array('1' => t('Allow'), '0' => t('Deny')),
+  );
+  $type_options = array('user' => t('Username'), 'mail' => t('E-mail'), 'host' => t('Host'));
+  $form['type'] = array(
+    '#type' => 'radios',
+    '#title' => t('Rule type'),
+    '#default_value' => (isset($type_options[$edit['type']]) ? $edit['type'] : 'user'),
+    '#options' => $type_options,
+  );
+  $form['mask'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Mask'),
+    '#size' => 30,
+    '#maxlength' => 64,
+    '#default_value' => $edit['mask'],
+    '#description' => '%: '. t('Matches any number of characters, even zero characters') .'.<br />_: '. t('Matches exactly one character.'),
+    '#required' => TRUE,
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => $submit);
+
+  return $form;
+}
+
+function user_admin_access_check_validate($form, &$form_state) {
+  if (empty($form_state['values']['test'])) {
+    form_set_error($form_state['values']['type'], t('No value entered. Please enter a test string and try again.'));
+  }
+}
+
+function user_admin_check_user() {
+  $form['user'] = array('#type' => 'fieldset', '#title' => t('Username'));
+  $form['user']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter a username to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => USERNAME_MAX_LENGTH);
+  $form['user']['type'] = array('#type' => 'hidden', '#value' => 'user');
+  $form['user']['submit'] = array('#type' => 'submit', '#value' => t('Check username'));
+  $form['#submit'][] = 'user_admin_access_check_submit';
+  $form['#validate'][] = 'user_admin_access_check_validate';
+  $form['#theme'] = 'user_admin_access_check';
+  return $form;
+}
+
+function user_admin_check_mail() {
+  $form['mail'] = array('#type' => 'fieldset', '#title' => t('E-mail'));
+  $form['mail']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter an e-mail address to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => EMAIL_MAX_LENGTH);
+  $form['mail']['type'] = array('#type' => 'hidden', '#value' => 'mail');
+  $form['mail']['submit'] = array('#type' => 'submit', '#value' => t('Check e-mail'));
+  $form['#submit'][] = 'user_admin_access_check_submit';
+  $form['#validate'][] = 'user_admin_access_check_validate';
+  $form['#theme'] = 'user_admin_access_check';
+  return $form;
+}
+
+function user_admin_check_host() {
+  $form['host'] = array('#type' => 'fieldset', '#title' => t('Hostname'));
+  $form['host']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter a hostname or IP address to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => 64);
+  $form['host']['type'] = array('#type' => 'hidden', '#value' => 'host');
+  $form['host']['submit'] = array('#type' => 'submit', '#value' => t('Check hostname'));
+  $form['#submit'][] = 'user_admin_access_check_submit';
+  $form['#validate'][] = 'user_admin_access_check_validate';
+  $form['#theme'] = 'user_admin_access_check';
+  return $form;
+}
+
+function user_admin_access_check_submit($form, &$form_state) {
+  switch ($form_state['values']['type']) {
+    case 'user':
+      if (drupal_is_denied('user', $form_state['values']['test'])) {
+        drupal_set_message(t('The username %name is not allowed.', array('%name' => $form_state['values']['test'])));
+      }
+      else {
+        drupal_set_message(t('The username %name is allowed.', array('%name' => $form_state['values']['test'])));
+      }
+      break;
+    case 'mail':
+      if (drupal_is_denied('mail', $form_state['values']['test'])) {
+        drupal_set_message(t('The e-mail address %mail is not allowed.', array('%mail' => $form_state['values']['test'])));
+      }
+      else {
+        drupal_set_message(t('The e-mail address %mail is allowed.', array('%mail' => $form_state['values']['test'])));
+      }
+      break;
+    case 'host':
+      if (drupal_is_denied('host', $form_state['values']['test'])) {
+        drupal_set_message(t('The hostname %host is not allowed.', array('%host' => $form_state['values']['test'])));
+      }
+      else {
+        drupal_set_message(t('The hostname %host is allowed.', array('%host' => $form_state['values']['test'])));
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+/**
+ * Menu callback: delete an access rule
+ *
+ * @ingroup forms
+ * @see user_admin_access_delete_confirm_submit()
+ */
+function user_admin_access_delete_confirm($form_state, $aid = 0) {
+  $access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
+  $edit = db_fetch_object(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
+
+  $form = array();
+  $form['aid'] = array('#type' => 'hidden', '#value' => $aid);
+  $output = confirm_form($form,
+                  t('Are you sure you want to delete the @type rule for %rule?', array('@type' => $access_types[$edit->type], '%rule' => $edit->mask)),
+                  'admin/user/rules',
+                  t('This action cannot be undone.'),
+                  t('Delete'),
+                  t('Cancel'));
+  return $output;
+}
+
+function user_admin_access_delete_confirm_submit($form, &$form_state) {
+  db_query('DELETE FROM {access} WHERE aid = %d', $form_state['values']['aid']);
+  drupal_set_message(t('The access rule has been deleted.'));
+  $form_state['redirect'] = 'admin/user/rules';
+  return;
+}
+
+/**
+ * Menu callback: list all access rules
+ */
+function user_admin_access() {
+  $header = array(array('data' => t('Access type'), 'field' => 'status'), array('data' => t('Rule type'), 'field' => 'type'), array('data' => t('Mask'), 'field' => 'mask'), array('data' => t('Operations'), 'colspan' => 2));
+  $result = db_query("SELECT aid, type, status, mask FROM {access}". tablesort_sql($header));
+  $access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
+  $rows = array();
+  while ($rule = db_fetch_object($result)) {
+    $rows[] = array($rule->status ? t('allow') : t('deny'), $access_types[$rule->type], $rule->mask, l(t('edit'), 'admin/user/rules/edit/'. $rule->aid), l(t('delete'), 'admin/user/rules/delete/'. $rule->aid));
+  }
+  if (empty($rows)) {
+    $rows[] = array(array('data' => '<em>'. t('There are currently no access rules.') .'</em>', 'colspan' => 5));
+  }
+  return theme('table', $header, $rows);
+}
+
+/**
+ * Theme user administration overview.
+ *
+ * @ingroup themeable
+ */
+function theme_user_admin_account($form) {
+  // Overview table:
+  $header = array(
+    theme('table_select_header_cell'),
+    array('data' => t('Username'), 'field' => 'u.name'),
+    array('data' => t('Status'), 'field' => 'u.status'),
+    t('Roles'),
+    array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
+    array('data' => t('Last access'), 'field' => 'u.access'),
+    t('Operations')
+  );
+
+  $output = drupal_render($form['options']);
+  if (isset($form['name']) && is_array($form['name'])) {
+    foreach (element_children($form['name']) as $key) {
+      $rows[] = array(
+        drupal_render($form['accounts'][$key]),
+        drupal_render($form['name'][$key]),
+        drupal_render($form['status'][$key]),
+        drupal_render($form['roles'][$key]),
+        drupal_render($form['member_for'][$key]),
+        drupal_render($form['last_access'][$key]),
+        drupal_render($form['operations'][$key]),
+      );
+    }
+  }
+  else {
+    $rows[] = array(array('data' => t('No users available.'), 'colspan' => '7'));
+  }
+
+  $output .= theme('table', $header, $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Theme the new-role form.
+ *
+ * @ingroup themeable
+ */
+function theme_user_admin_new_role($form) {
+  $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => 2));
+  foreach (user_roles() as $rid => $name) {
+    $edit_permissions = l(t('edit permissions'), 'admin/user/permissions/'. $rid);
+    if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
+      $rows[] = array($name, l(t('edit role'), 'admin/user/roles/edit/'. $rid), $edit_permissions);
+    }
+    else {
+      $rows[] = array($name, t('locked'), $edit_permissions);
+    }
+  }
+  $rows[] = array(drupal_render($form['name']), array('data' => drupal_render($form['submit']), 'colspan' => 2));
+
+  $output = drupal_render($form);
+  $output .= theme('table', $header, $rows);
+
+  return $output;
+}
+
+/**
+ * Theme user administration filter form.
+ *
+ * @ingroup themeable
+ */
+function theme_user_filter_form($form) {
+  $output = '<div id="user-admin-filter">';
+  $output .= drupal_render($form['filters']);
+  $output .= '</div>';
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Theme user administration filter selector.
+ *
+ * @ingroup themeable
+ */
+function theme_user_filters($form) {
+  $output = '<ul class="clear-block">';
+  if (!empty($form['current'])) {
+    foreach (element_children($form['current']) as $key) {
+      $output .= '<li>'. drupal_render($form['current'][$key]) .'</li>';
+    }
+  }
+
+  $output .= '<li><dl class="multiselect">'. (!empty($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') .'<dd class="a">';
+  foreach (element_children($form['filter']) as $key) {
+    $output .= drupal_render($form['filter'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '<dt>'. t('is') .'</dt><dd class="b">';
+
+  foreach (element_children($form['status']) as $key) {
+    $output .= drupal_render($form['status'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '</dl>';
+  $output .= '<div class="container-inline" id="user-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
+  $output .= '</li></ul>';
+
+  return $output;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,59 @@
+/* $Id: user.css,v 1.7 2007/06/21 04:38:41 unconed Exp $ */
+
+#permissions td.module {
+  font-weight: bold;
+}
+#permissions td.permission {
+  padding-left: 1.5em; /* LTR */
+}
+#access-rules .access-type, #access-rules .rule-type {
+  margin-right: 1em; /* LTR */
+  float: left; /* LTR */
+}
+#access-rules .access-type .form-item, #access-rules .rule-type .form-item {
+  margin-top: 0;
+}
+#access-rules .mask {
+  clear: both;
+}
+#user-login-form {
+  text-align: center;
+}
+#user-admin-filter ul {
+  list-style-type: none;
+  padding: 0;
+  margin: 0;
+  width: 100%;
+}
+#user-admin-buttons {
+  float: left; /* LTR */
+  margin-left: 0.5em; /* LTR */
+  clear: right; /* LTR */
+}
+#user-admin-settings fieldset .description {
+  font-size: 0.85em;
+  padding-bottom: .5em;
+}
+
+/* Generated by user.module but used by profile.module: */
+.profile {
+  clear: both;
+  margin: 1em 0;
+}
+.profile .picture {
+  float: right; /* LTR */
+  margin: 0 1em 1em 0; /* LTR */
+}
+.profile h3 {
+  border-bottom: 1px solid #ccc;
+}
+.profile dl {
+  margin: 0 0 1.5em 0;
+}
+.profile dt {
+  margin: 0 0 0.2em 0;
+  font-weight: bold;
+}
+.profile dd {
+  margin: 0 0 1em 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: user.info,v 1.4 2007/06/08 05:50:57 dries Exp $
+name = User
+description = Manages the user registration and login system.
+package = Core - required
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.install	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,290 @@
+<?php
+// $Id: user.install,v 1.5 2008/01/08 07:46:41 goba Exp $
+
+/**
+ * Implementation of hook_schema().
+ */
+function user_schema() {
+  $schema['access'] = array(
+    'description' => t('Stores site access rules.'),
+    'fields' => array(
+      'aid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique access ID.'),
+      ),
+      'mask' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Text mask used for filtering access.'),
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Type of access rule: name, mail or host.'),
+      ),
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether rule is to allow(1) or deny(0) access.'),
+      ),
+    ),
+    'primary key' => array('aid'),
+  );
+
+  $schema['authmap'] = array(
+    'description' => t('Stores distributed authentication mapping.'),
+    'fields' => array(
+      'aid' => array(
+        'description' => t('Primary Key: Unique authmap ID.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("User's {users}.uid."),
+      ),
+      'authname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Unique authentication name.'),
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Module which is controlling the authentication.'),
+      ),
+    ),
+    'unique keys' => array('authname' => array('authname')),
+    'primary key' => array('aid'),
+  );
+
+  $schema['permission'] = array(
+    'description' => t('Stores permissions for users.'),
+    'fields' => array(
+      'pid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique permission ID.'),
+      ),
+      'rid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('The {role}.rid to which the permissions are assigned.'),
+      ),
+      'perm' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => t('List of permissions being assigned.'),
+      ),
+      'tid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Originally intended for taxonomy-based permissions, but never used.'),
+      ),
+    ),
+    'primary key' => array('pid'),
+    'indexes' => array('rid' => array('rid')),
+  );
+
+  $schema['role'] = array(
+    'description' => t('Stores user roles.'),
+    'fields' => array(
+      'rid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique role id.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Unique role name.'),
+      ),
+    ),
+    'unique keys' => array('name' => array('name')),
+    'primary key' => array('rid'),
+  );
+
+  $schema['users'] = array(
+    'description' => t('Stores user data.'),
+    'fields' => array(
+      'uid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique user ID.'),
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 60,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Unique user name.'),
+      ),
+      'pass' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("User's password (md5 hash)."),
+      ),
+      'mail' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => FALSE,
+        'default' => '',
+        'description' => t("User's email address."),
+      ),
+      'mode' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Per-user comment display mode (threaded vs. flat), used by the {comment} module.'),
+      ),
+      'sort' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Per-user comment sort order (newest vs. oldest first), used by the {comment} module.'),
+      ),
+      'threshold' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Previously used by the {comment} module for per-user preferences; no longer used.'),
+      ),
+      'theme' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("User's default theme."),
+      ),
+      'signature' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("User's signature."),
+      ),
+      'created' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Timestamp for when user was created.'),
+      ),
+      'access' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Timestamp for previous time user accessed the site.'),
+      ),
+      'login' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t("Timestamp for user's last login."),
+      ),
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => t('Whether the user is active(1) or blocked(0).'),
+      ),
+      'timezone' => array(
+        'type' => 'varchar',
+        'length' => 8,
+        'not null' => FALSE,
+        'description' => t("User's timezone."),
+      ),
+      'language' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("User's default language."),
+      ),
+      'picture' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t("Path to the user's uploaded picture."),
+      ),
+      'init' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => FALSE,
+        'default' => '',
+        'description' => t('Email address used for initial account creation.'),
+      ),
+      'data' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => t('A serialized array of name value pairs that are related to the user. Any form values posted during user edit are stored and are loaded into the $user object during user_load(). Use of this field is discouraged and it will likely disappear in a future version of Drupal.'),
+      ),
+    ),
+    'indexes' => array(
+      'access' => array('access'),
+      'created' => array('created'),
+      'mail' => array('mail'),
+    ),
+    'unique keys' => array(
+      'name' => array('name'),
+    ),
+    'primary key' => array('uid'),
+  );
+
+  $schema['users_roles'] = array(
+    'description' => t('Maps users to roles.'),
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: {users}.uid for user.'),
+      ),
+      'rid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Primary Key: {role}.rid for role.'),
+      ),
+    ),
+    'primary key' => array('uid', 'rid'),
+    'indexes' => array(
+      'rid' => array('rid'),
+    ),
+  );
+
+  return $schema;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.js	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,188 @@
+// $Id: user.js,v 1.6 2007/09/12 18:29:32 goba Exp $
+
+/**
+ * Attach handlers to evaluate the strength of any password fields and to check
+ * that its confirmation is correct.
+ */
+Drupal.behaviors.password = function(context) {
+  var translate = Drupal.settings.password;
+  $("input.password-field:not(.password-processed)", context).each(function() {
+    var passwordInput = $(this).addClass('password-processed');
+    var parent = $(this).parent();
+    // Wait this number of milliseconds before checking password.
+    var monitorDelay = 700;
+
+    // Add the password strength layers.
+    $(this).after('<span class="password-strength"><span class="password-title">'+ translate.strengthTitle +'</span> <span class="password-result"></span></span>').parent();
+    var passwordStrength = $("span.password-strength", parent);
+    var passwordResult = $("span.password-result", passwordStrength);
+    parent.addClass("password-parent");
+
+    // Add the password confirmation layer.
+    var outerItem  = $(this).parent().parent();
+    $("input.password-confirm", outerItem).after('<span class="password-confirm">'+ translate["confirmTitle"] +' <span></span></span>').parent().addClass("confirm-parent");
+    var confirmInput = $("input.password-confirm", outerItem);
+    var confirmResult = $("span.password-confirm", outerItem);
+    var confirmChild = $("span", confirmResult);
+
+    // Add the description box at the end.
+    $(confirmInput).parent().after('<div class="password-description"></div>');
+    var passwordDescription = $("div.password-description", $(this).parent().parent()).hide();
+
+    // Check the password fields.
+    var passwordCheck = function () {
+      // Remove timers for a delayed check if they exist.
+      if (this.timer) {
+        clearTimeout(this.timer);
+      }
+
+      // Verify that there is a password to check.
+      if (!passwordInput.val()) {
+        passwordStrength.css({ visibility: "hidden" });
+        passwordDescription.hide();
+        return;
+      }
+
+      // Evaluate password strength.
+
+      var result = Drupal.evaluatePasswordStrength(passwordInput.val());
+      passwordResult.html(result.strength == "" ? "" : translate[result.strength +"Strength"]);
+
+      // Map the password strength to the relevant drupal CSS class.
+      var classMap = { low: "error", medium: "warning", high: "ok" };
+      var newClass = classMap[result.strength] || "";
+
+      // Remove the previous styling if any exists; add the new class.
+      if (this.passwordClass) {
+        passwordResult.removeClass(this.passwordClass);
+        passwordDescription.removeClass(this.passwordClass);
+      }
+      passwordDescription.html(result.message);
+      passwordResult.addClass(newClass);
+      if (result.strength == "high") {
+        passwordDescription.hide();
+      }
+      else {
+        passwordDescription.addClass(newClass);
+      }
+      this.passwordClass = newClass;
+
+      // Check that password and confirmation match.
+
+      // Hide the result layer if confirmation is empty, otherwise show the layer.
+      confirmResult.css({ visibility: (confirmInput.val() == "" ? "hidden" : "visible") });
+
+      var success = passwordInput.val() == confirmInput.val();
+
+      // Remove the previous styling if any exists.
+      if (this.confirmClass) {
+        confirmChild.removeClass(this.confirmClass);
+      }
+
+      // Fill in the correct message and set the class accordingly.
+      var confirmClass = success ? "ok" : "error";
+      confirmChild.html(translate["confirm"+ (success ? "Success" : "Failure")]).addClass(confirmClass);
+      this.confirmClass = confirmClass;
+
+      // Show the indicator and tips.
+      passwordStrength.css({ visibility: "visible" });
+      passwordDescription.show();
+    };
+
+    // Do a delayed check on the password fields.
+    var passwordDelayedCheck = function() {
+      // Postpone the check since the user is most likely still typing.
+      if (this.timer) {
+        clearTimeout(this.timer);
+      }
+
+      // When the user clears the field, hide the tips immediately.
+      if (!passwordInput.val()) {
+        passwordStrength.css({ visibility: "hidden" });
+        passwordDescription.hide();
+        return;
+      }
+
+      // Schedule the actual check.
+      this.timer = setTimeout(passwordCheck, monitorDelay);
+    };
+    // Monitor keyup and blur events.
+    // Blur must be used because a mouse paste does not trigger keyup.
+    passwordInput.keyup(passwordDelayedCheck).blur(passwordCheck);
+    confirmInput.keyup(passwordDelayedCheck).blur(passwordCheck);
+  });
+};
+
+/**
+ * Evaluate the strength of a user's password.
+ *
+ * Returns the estimated strength and the relevant output message.
+ */
+Drupal.evaluatePasswordStrength = function(value) {
+  var strength = "", msg = "", translate = Drupal.settings.password;
+
+  var hasLetters = value.match(/[a-zA-Z]+/);
+  var hasNumbers = value.match(/[0-9]+/);
+  var hasPunctuation = value.match(/[^a-zA-Z0-9]+/);
+  var hasCasing = value.match(/[a-z]+.*[A-Z]+|[A-Z]+.*[a-z]+/);
+
+  // Check if the password is blank.
+  if (!value.length) {
+    strength = "";
+    msg = "";
+  }
+  // Check if length is less than 6 characters.
+  else if (value.length < 6) {
+    strength = "low";
+    msg = translate.tooShort;
+  }
+  // Check if password is the same as the username (convert both to lowercase).
+  else if (value.toLowerCase() == translate.username.toLowerCase()) {
+    strength  = "low";
+    msg = translate.sameAsUsername;
+  }
+  // Check if it contains letters, numbers, punctuation, and upper/lower case.
+  else if (hasLetters && hasNumbers && hasPunctuation && hasCasing) {
+    strength = "high";
+  }
+  // Password is not secure enough so construct the medium-strength message.
+  else {
+    // Extremely bad passwords still count as low.
+    var count = (hasLetters ? 1 : 0) + (hasNumbers ? 1 : 0) + (hasPunctuation ? 1 : 0) + (hasCasing ? 1 : 0);
+    strength = count > 1 ? "medium" : "low";
+
+    msg = [];
+    if (!hasLetters || !hasCasing) {
+      msg.push(translate.addLetters);
+    }
+    if (!hasNumbers) {
+      msg.push(translate.addNumbers);
+    }
+    if (!hasPunctuation) {
+      msg.push(translate.addPunctuation);
+    }
+    msg = translate.needsMoreVariation +"<ul><li>"+ msg.join("</li><li>") +"</li></ul>";
+  }
+
+  return { strength: strength, message: msg };
+};
+
+/**
+ * Set the client's system timezone as default values of form fields.
+ */
+Drupal.setDefaultTimezone = function() {
+  var offset = new Date().getTimezoneOffset() * -60;
+  $("#edit-date-default-timezone, #edit-user-register-timezone").val(offset);
+};
+
+/**
+ * On the admin/user/settings page, conditionally show all of the
+ * picture-related form elements depending on the current value of the
+ * "Picture support" radio buttons.
+ */
+Drupal.behaviors.userSettings = function (context) {
+  $('div.user-admin-picture-radios input[type=radio]:not(.userSettings-processed)', context).addClass('userSettings-processed').click(function () {
+    $('div.user-admin-picture-settings', context)[['hide', 'show'][this.value]]();
+  });
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.module	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,2448 @@
+<?php
+// $Id: user.module,v 1.892 2008/02/03 19:23:01 goba Exp $
+
+/**
+ * @file
+ * Enables the user registration and login system.
+ */
+
+define('USERNAME_MAX_LENGTH', 60);
+define('EMAIL_MAX_LENGTH', 64);
+
+/**
+ * Invokes hook_user() in every module.
+ *
+ * We cannot use module_invoke() for this, because the arguments need to
+ * be passed by reference.
+ */
+function user_module_invoke($type, &$array, &$user, $category = NULL) {
+  foreach (module_list() as $module) {
+    $function = $module .'_user';
+    if (function_exists($function)) {
+      $function($type, $array, $user, $category);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function user_theme() {
+  return array(
+    'user_picture' => array(
+      'arguments' => array('account' => NULL),
+      'template' => 'user-picture',
+    ),
+    'user_profile' => array(
+      'arguments' => array('account' => NULL),
+      'template' => 'user-profile',
+      'file' => 'user.pages.inc',
+    ),
+    'user_profile_category' => array(
+      'arguments' => array('element' => NULL),
+      'template' => 'user-profile-category',
+      'file' => 'user.pages.inc',
+    ),
+    'user_profile_item' => array(
+      'arguments' => array('element' => NULL),
+      'template' => 'user-profile-item',
+      'file' => 'user.pages.inc',
+    ),
+    'user_list' => array(
+      'arguments' => array('users' => NULL, 'title' => NULL),
+    ),
+    'user_admin_perm' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'user.admin.inc',
+    ),
+    'user_admin_new_role' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'user.admin.inc',
+    ),
+    'user_admin_account' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'user.admin.inc',
+    ),
+    'user_filter_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'user.admin.inc',
+    ),
+    'user_filters' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'user.admin.inc',
+    ),
+    'user_signature' => array(
+      'arguments' => array('signature' => NULL),
+    ),
+  );
+}
+
+function user_external_load($authname) {
+  $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname);
+
+  if ($user = db_fetch_array($result)) {
+    return user_load($user);
+  }
+  else {
+    return 0;
+  }
+}
+
+/**
+ * Perform standard Drupal login operations for a user object.
+ *
+ * The user object must already be authenticated. This function verifies
+ * that the user account is not blocked/denied and then performs the login,
+ * updates the login timestamp in the database, invokes hook_user('login'),
+ * and regenerates the session.
+ *
+ * @param $account
+ *    An authenticated user object to be set as the currently logged
+ *    in user.
+ * @param $edit
+ *    The array of form values submitted by the user, if any.
+ *    This array is passed to hook_user op login.
+ * @return boolean
+ *    TRUE if the login succeeds, FALSE otherwise.
+ */
+function user_external_login($account, $edit = array()) {
+  $form = drupal_get_form('user_login');
+
+  $state['values'] = $edit;
+  if (empty($state['values']['name'])) {
+    $state['values']['name'] = $account->name;
+  }
+
+  // Check if user is blocked or denied by access rules.
+  user_login_name_validate($form, $state, (array)$account);
+  if (form_get_errors()) {
+    // Invalid login.
+    return FALSE;
+  }
+
+  // Valid login.
+  global $user;
+  $user = $account;
+  user_authenticate_finalize($state['values']);
+  return TRUE;
+}
+
+/**
+ * Fetch a user object.
+ *
+ * @param $array
+ *   An associative array of attributes to search for in selecting the
+ *   user, such as user name or e-mail address.
+ *
+ * @return
+ *   A fully-loaded $user object upon successful user load or FALSE if user
+ *   cannot be loaded.
+ */
+function user_load($array = array()) {
+  // Dynamically compose a SQL query:
+  $query = array();
+  $params = array();
+
+  if (is_numeric($array)) {
+    $array = array('uid' => $array);
+  }
+  elseif (!is_array($array)) {
+    return FALSE;
+  }
+
+  foreach ($array as $key => $value) {
+    if ($key == 'uid' || $key == 'status') {
+      $query[] = "$key = %d";
+      $params[] = $value;
+    }
+    else if ($key == 'pass') {
+      $query[] = "pass = '%s'";
+      $params[] = md5($value);
+    }
+    else {
+      $query[]= "LOWER($key) = LOWER('%s')";
+      $params[] = $value;
+    }
+  }
+  $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
+
+  if ($user = db_fetch_object($result)) {
+    $user = drupal_unpack($user);
+
+    $user->roles = array();
+    if ($user->uid) {
+      $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
+    }
+    else {
+      $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
+    }
+    $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
+    while ($role = db_fetch_object($result)) {
+      $user->roles[$role->rid] = $role->name;
+    }
+    user_module_invoke('load', $array, $user);
+  }
+  else {
+    $user = FALSE;
+  }
+
+  return $user;
+}
+
+/**
+ * Save changes to a user account or add a new user.
+ *
+ * @param $account
+ *   The $user object for the user to modify or add. If $user->uid is
+ *   omitted, a new user will be added.
+ *
+ * @param $array
+ *   (optional) An array of fields and values to save. For example,
+ *   array('name' => 'My name'); Setting a field to NULL deletes it from
+ *   the data column.
+ *
+ * @param $category
+ *   (optional) The category for storing profile information in.
+ *
+ * @return
+ *   A fully-loaded $user object upon successful save or FALSE if the save failed.
+ */
+function user_save($account, $array = array(), $category = 'account') {
+  // Dynamically compose a SQL query:
+  $user_fields = user_fields();
+  if (is_object($account) && $account->uid) {
+    user_module_invoke('update', $array, $account, $category);
+    $query = '';
+    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
+    // Consider users edited by an administrator as logged in, if they haven't
+    // already, so anonymous users can view the profile (if allowed).
+    if (empty($array['access']) && empty($account->access) && user_access('administer users')) {
+      $array['access'] = time();
+    }
+    foreach ($array as $key => $value) {
+      if ($key == 'pass' && !empty($value)) {
+        $query .= "$key = '%s', ";
+        $v[] = md5($value);
+      }
+      else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
+        if (in_array($key, $user_fields)) {
+          // Save standard fields.
+          $query .= "$key = '%s', ";
+          $v[] = $value;
+        }
+        else if ($key != 'roles') {
+          // Roles is a special case: it used below.
+          if ($value === NULL) {
+            unset($data[$key]);
+          }
+          else {
+            $data[$key] = $value;
+          }
+        }
+      }
+    }
+    $query .= "data = '%s' ";
+    $v[] = serialize($data);
+
+    $success = db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
+    if (!$success) {
+      // The query failed - better to abort the save than risk further data loss.
+      return FALSE;
+    }
+
+    // Reload user roles if provided.
+    if (isset($array['roles']) && is_array($array['roles'])) {
+      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
+
+      foreach (array_keys($array['roles']) as $rid) {
+        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
+          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
+        }
+      }
+    }
+
+    // Delete a blocked user's sessions to kick them if they are online.
+    if (isset($array['status']) && $array['status'] == 0) {
+      sess_destroy_uid($account->uid);
+    }
+
+    // If the password changed, delete all open sessions and recreate
+    // the current one.
+    if (!empty($array['pass'])) {
+      sess_destroy_uid($account->uid);
+      sess_regenerate();
+    }
+
+    // Refresh user object.
+    $user = user_load(array('uid' => $account->uid));
+
+    // Send emails after we have the new user object.
+    if (isset($array['status']) && $array['status'] != $account->status) {
+      // The user's status is changing; conditionally send notification email.
+      $op = $array['status'] == 1 ? 'status_activated' : 'status_blocked';
+      _user_mail_notify($op, $user);
+    }
+
+    user_module_invoke('after_update', $array, $user, $category);
+  }
+  else {
+    // Allow 'created' to be set by the caller.
+    if (!isset($array['created'])) {
+      $array['created'] = time();
+    }
+    // Consider users created by an administrator as already logged in, so
+    // anonymous users can view the profile (if allowed).
+    if (empty($array['access']) && user_access('administer users')) {
+      $array['access'] = time();
+    }
+
+    // Note: we wait to save the data column to prevent module-handled
+    // fields from being saved there. We cannot invoke hook_user('insert') here
+    // because we don't have a fully initialized user object yet.
+    foreach ($array as $key => $value) {
+      switch ($key) {
+        case 'pass':
+          $fields[] = $key;
+          $values[] = md5($value);
+          $s[] = "'%s'";
+          break;
+        case 'mode':       case 'sort':     case 'timezone':
+        case 'threshold':  case 'created':  case 'access':
+        case 'login':      case 'status':
+          $fields[] = $key;
+          $values[] = $value;
+          $s[] = "%d";
+          break;
+        default:
+          if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
+            $fields[] = $key;
+            $values[] = $value;
+            $s[] = "'%s'";
+          }
+          break;
+      }
+    }
+    $success = db_query('INSERT INTO {users} ('. implode(', ', $fields) .') VALUES ('. implode(', ', $s) .')', $values);
+    if (!$success) {
+      // On a failed INSERT some other existing user's uid may be returned.
+      // We must abort to avoid overwriting their account.
+      return FALSE;
+    }
+
+    // Build the initial user object.
+    $array['uid'] = db_last_insert_id('users', 'uid');
+    $user = user_load(array('uid' => $array['uid']));
+
+    user_module_invoke('insert', $array, $user, $category);
+
+    // Build and save the serialized data field now.
+    $data = array();
+    foreach ($array as $key => $value) {
+      if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
+        $data[$key] = $value;
+      }
+    }
+    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
+
+    // Save user roles (delete just to be safe).
+    if (isset($array['roles']) && is_array($array['roles'])) {
+      db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
+      foreach (array_keys($array['roles']) as $rid) {
+        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
+          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
+        }
+      }
+    }
+
+    // Build the finished user object.
+    $user = user_load(array('uid' => $array['uid']));
+  }
+
+  // Save distributed authentication mappings.
+  $authmaps = array();
+  foreach ($array as $key => $value) {
+    if (substr($key, 0, 4) == 'auth') {
+      $authmaps[$key] = $value;
+    }
+  }
+  if (sizeof($authmaps) > 0) {
+    user_set_authmaps($user, $authmaps);
+  }
+
+  return $user;
+}
+
+/**
+ * Verify the syntax of the given name.
+ */
+function user_validate_name($name) {
+  if (!strlen($name)) return t('You must enter a username.');
+  if (substr($name, 0, 1) == ' ') return t('The username cannot begin with a space.');
+  if (substr($name, -1) == ' ') return t('The username cannot end with a space.');
+  if (strpos($name, '  ') !== FALSE) return t('The username cannot contain multiple spaces in a row.');
+  if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $name)) return t('The username contains an illegal character.');
+  if (preg_match('/[\x{80}-\x{A0}'.          // Non-printable ISO-8859-1 + NBSP
+                   '\x{AD}'.                 // Soft-hyphen
+                   '\x{2000}-\x{200F}'.      // Various space characters
+                   '\x{2028}-\x{202F}'.      // Bidirectional text overrides
+                   '\x{205F}-\x{206F}'.      // Various text hinting characters
+                   '\x{FEFF}'.               // Byte order mark
+                   '\x{FF01}-\x{FF60}'.      // Full-width latin
+                   '\x{FFF9}-\x{FFFD}'.      // Replacement characters
+                   '\x{0}]/u',               // NULL byte
+                   $name)) {
+    return t('The username contains an illegal character.');
+  }
+  if (strpos($name, '@') !== FALSE && !eregi('@([0-9a-z](-?[0-9a-z])*.)+[a-z]{2}([zmuvtg]|fo|me)?$', $name)) return t('The username is not a valid authentication ID.');
+  if (strlen($name) > USERNAME_MAX_LENGTH) return t('The username %name is too long: it must be %max characters or less.', array('%name' => $name, '%max' => USERNAME_MAX_LENGTH));
+}
+
+function user_validate_mail($mail) {
+  if (!$mail) return t('You must enter an e-mail address.');
+  if (!valid_email_address($mail)) {
+    return t('The e-mail address %mail is not valid.', array('%mail' => $mail));
+  }
+}
+
+function user_validate_picture(&$form, &$form_state) {
+  // If required, validate the uploaded picture.
+  $validators = array(
+    'file_validate_is_image' => array(),
+    'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
+    'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
+  );
+  if ($file = file_save_upload('picture_upload', $validators)) {
+    // Remove the old picture.
+    if (isset($form_state['values']['_account']->picture) && file_exists($form_state['values']['_account']->picture)) {
+      file_delete($form_state['values']['_account']->picture);
+    }
+
+    // The image was saved using file_save_upload() and was added to the
+    // files table as a temporary file. We'll make a copy and let the garbage
+    // collector delete the original upload.
+    $info = image_get_info($file->filepath);
+    $destination = variable_get('user_picture_path', 'pictures') .'/picture-'. $form['#uid'] .'.'. $info['extension'];
+    if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
+      $form_state['values']['picture'] = $file->filepath;
+    }
+    else {
+      form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
+    }
+  }
+}
+
+/**
+ * Generate a random alphanumeric password.
+ */
+function user_password($length = 10) {
+  // This variable contains the list of allowable characters for the
+  // password. Note that the number 0 and the letter 'O' have been
+  // removed to avoid confusion between the two. The same is true
+  // of 'I', 1, and 'l'.
+  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
+
+  // Zero-based count of characters in the allowable list:
+  $len = strlen($allowable_characters) - 1;
+
+  // Declare the password as a blank string.
+  $pass = '';
+
+  // Loop the number of times specified by $length.
+  for ($i = 0; $i < $length; $i++) {
+
+    // Each iteration, pick a random character from the
+    // allowable string and append it to the password:
+    $pass .= $allowable_characters[mt_rand(0, $len)];
+  }
+
+  return $pass;
+}
+
+/**
+ * Determine whether the user has a given privilege.
+ *
+ * @param $string
+ *   The permission, such as "administer nodes", being checked for.
+ * @param $account
+ *   (optional) The account to check, if not given use currently logged in user.
+ * @param $reset
+ *   (optional) Resets the user's permissions cache, which will result in a
+ *   recalculation of the user's permissions. This is necessary to support
+ *   dynamically added user roles.
+ *
+ * @return
+ *   Boolean TRUE if the current user has the requested permission.
+ *
+ * All permission checks in Drupal should go through this function. This
+ * way, we guarantee consistent behavior, and ensure that the superuser
+ * can perform all actions.
+ */
+function user_access($string, $account = NULL, $reset = FALSE) {
+  global $user;
+  static $perm = array();
+
+  if ($reset) {
+    unset($perm);
+  }
+
+  if (is_null($account)) {
+    $account = $user;
+  }
+
+  // User #1 has all privileges:
+  if ($account->uid == 1) {
+    return TRUE;
+  }
+
+  // To reduce the number of SQL queries, we cache the user's permissions
+  // in a static variable.
+  if (!isset($perm[$account->uid])) {
+    $result = db_query("SELECT p.perm FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN (". db_placeholders($account->roles) .")", array_keys($account->roles));
+
+    $perms = array();
+    while ($row = db_fetch_object($result)) {
+      $perms += array_flip(explode(', ', $row->perm));
+    }
+    $perm[$account->uid] = $perms;
+  }
+
+  return isset($perm[$account->uid][$string]);
+}
+
+/**
+ * Checks for usernames blocked by user administration.
+ *
+ * @return boolean TRUE for blocked users, FALSE for active.
+ */
+function user_is_blocked($name) {
+  $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name));
+
+  return $deny;
+}
+
+function user_fields() {
+  static $fields;
+
+  if (!$fields) {
+    $result = db_query('SELECT * FROM {users} WHERE uid = 1');
+    if ($field = db_fetch_array($result)) {
+      $fields = array_keys($field);
+    }
+    else {
+      // Make sure we return the default fields at least.
+      $fields = array('uid', 'name', 'pass', 'mail', 'picture', 'mode', 'sort', 'threshold', 'theme', 'signature', 'created', 'access', 'login', 'status', 'timezone', 'language', 'init', 'data');
+    }
+  }
+
+  return $fields;
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function user_perm() {
+  return array('administer permissions', 'administer users', 'access user profiles', 'change own username');
+}
+
+/**
+ * Implementation of hook_file_download().
+ *
+ * Ensure that user pictures (avatars) are always downloadable.
+ */
+function user_file_download($file) {
+  if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
+    $info = image_get_info(file_create_path($file));
+    return array('Content-type: '. $info['mime_type']);
+  }
+}
+
+/**
+ * Implementation of hook_search().
+ */
+function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
+  switch ($op) {
+    case 'name':
+      if ($skip_access_check || user_access('access user profiles')) {
+        return t('Users');
+      }
+    case 'search':
+      if (user_access('access user profiles')) {
+        $find = array();
+        // Replace wildcards with MySQL/PostgreSQL wildcards.
+        $keys = preg_replace('!\*+!', '%', $keys);
+        if (user_access('administer users')) {
+          // Administrators can also search in the otherwise private email field.
+          $result = pager_query("SELECT name, uid, mail FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys);
+          while ($account = db_fetch_object($result)) {
+            $find[] = array('title' => $account->name .' ('. $account->mail .')', 'link' => url('user/'. $account->uid, array('absolute' => TRUE)));
+          }
+        }
+        else {
+          $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
+          while ($account = db_fetch_object($result)) {
+            $find[] = array('title' => $account->name, 'link' => url('user/'. $account->uid, array('absolute' => TRUE)));
+          }
+        }
+        return $find;
+      }
+  }
+}
+
+/**
+ * Implementation of hook_elements().
+ */
+function user_elements() {
+  return array(
+    'user_profile_category' => array(),
+    'user_profile_item' => array(),
+  );
+}
+
+/**
+ * Implementation of hook_user().
+ */
+function user_user($type, &$edit, &$account, $category = NULL) {
+  if ($type == 'view') {
+    $account->content['user_picture'] = array(
+      '#value' => theme('user_picture', $account),
+      '#weight' => -10,
+    );
+    if (!isset($account->content['summary'])) {
+      $account->content['summary'] = array();
+    }
+    $account->content['summary'] += array(
+      '#type' => 'user_profile_category',
+      '#attributes' => array('class' => 'user-member'),
+      '#weight' => 5,
+      '#title' => t('History'),
+    );
+    $account->content['summary']['member_for'] =  array(
+      '#type' => 'user_profile_item',
+      '#title' => t('Member for'),
+      '#value' => format_interval(time() - $account->created),
+    );
+  }
+  if ($type == 'form' && $category == 'account') {
+    $form_state = array();
+    return user_edit_form($form_state, arg(1), $edit);
+  }
+
+  if ($type == 'validate' && $category == 'account') {
+    return _user_edit_validate(arg(1), $edit);
+  }
+
+  if ($type == 'submit' && $category == 'account') {
+    return _user_edit_submit(arg(1), $edit);
+  }
+
+  if ($type == 'categories') {
+    return array(array('name' => 'account', 'title' => t('Account settings'), 'weight' => 1));
+  }
+}
+
+function user_login_block() {
+  $form = array(
+    '#action' => url($_GET['q'], array('query' => drupal_get_destination())),
+    '#id' => 'user-login-form',
+    '#validate' => user_login_default_validators(),
+    '#submit' => array('user_login_submit'),
+  );
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Username'),
+    '#maxlength' => USERNAME_MAX_LENGTH,
+    '#size' => 15,
+    '#required' => TRUE,
+  );
+  $form['pass'] = array('#type' => 'password',
+    '#title' => t('Password'),
+    '#maxlength' => 60,
+    '#size' => 15,
+    '#required' => TRUE,
+  );
+  $form['submit'] = array('#type' => 'submit',
+    '#value' => t('Log in'),
+  );
+  $items = array();
+  if (variable_get('user_register', 1)) {
+    $items[] = l(t('Create new account'), 'user/register', array('title' => t('Create a new user account.')));
+  }
+  $items[] = l(t('Request new password'), 'user/password', array('title' => t('Request new password via e-mail.')));
+  $form['links'] = array('#value' => theme('item_list', $items));
+  return $form;
+}
+
+/**
+ * Implementation of hook_block().
+ */
+function user_block($op = 'list', $delta = 0, $edit = array()) {
+  global $user;
+
+  if ($op == 'list') {
+    $blocks[0]['info'] = t('User login');
+    // Not worth caching.
+    $blocks[0]['cache'] = BLOCK_NO_CACHE;
+
+    $blocks[1]['info'] = t('Navigation');
+    // Menu blocks can't be cached because each menu item can have
+    // a custom access callback. menu.inc manages its own caching.
+    $blocks[1]['cache'] = BLOCK_NO_CACHE;
+
+    $blocks[2]['info'] = t('Who\'s new');
+
+    // Too dynamic to cache.
+    $blocks[3]['info'] = t('Who\'s online');
+    $blocks[3]['cache'] = BLOCK_NO_CACHE;
+    return $blocks;
+  }
+  else if ($op == 'configure' && $delta == 2) {
+    $form['user_block_whois_new_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of users to display'),
+      '#default_value' => variable_get('user_block_whois_new_count', 5),
+      '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
+    );
+    return $form;
+  }
+  else if ($op == 'configure' && $delta == 3) {
+    $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
+    $form['user_block_seconds_online'] = array('#type' => 'select', '#title' => t('User activity'), '#default_value' => variable_get('user_block_seconds_online', 900), '#options' => $period, '#description' => t('A user is considered online for this long after they have last viewed a page.'));
+    $form['user_block_max_list_count'] = array('#type' => 'select', '#title' => t('User list length'), '#default_value' => variable_get('user_block_max_list_count', 10), '#options' => drupal_map_assoc(array(0, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100)), '#description' => t('Maximum number of currently online users to display.'));
+
+    return $form;
+  }
+  else if ($op == 'save' && $delta == 2) {
+    variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
+  }
+  else if ($op == 'save' && $delta == 3) {
+    variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
+    variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
+  }
+  else if ($op == 'view') {
+    $block = array();
+
+    switch ($delta) {
+      case 0:
+        // For usability's sake, avoid showing two login forms on one page.
+        if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
+
+          $block['subject'] = t('User login');
+          $block['content'] = drupal_get_form('user_login_block');
+        }
+        return $block;
+
+      case 1:
+        if ($menu = menu_tree()) {
+          $block['subject'] = $user->uid ? check_plain($user->name) : t('Navigation');
+          $block['content'] = $menu;
+        }
+        return $block;
+
+      case 2:
+        if (user_access('access content')) {
+          // Retrieve a list of new users who have subsequently accessed the site successfully.
+          $result = db_query_range('SELECT uid, name FROM {users} WHERE status != 0 AND access != 0 ORDER BY created DESC', 0, variable_get('user_block_whois_new_count', 5));
+          while ($account = db_fetch_object($result)) {
+            $items[] = $account;
+          }
+          $output = theme('user_list', $items);
+
+          $block['subject'] = t('Who\'s new');
+          $block['content'] = $output;
+        }
+        return $block;
+
+      case 3:
+        if (user_access('access content')) {
+          // Count users active within the defined period.
+          $interval = time() - variable_get('user_block_seconds_online', 900);
+
+          // Perform database queries to gather online user lists.  We use s.timestamp
+          // rather than u.access because it is much faster.
+          $anonymous_count = sess_count($interval);
+          $authenticated_users = db_query('SELECT DISTINCT u.uid, u.name, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= %d AND s.uid > 0 ORDER BY s.timestamp DESC', $interval);
+          $authenticated_count = 0;
+          $max_users = variable_get('user_block_max_list_count', 10);
+          $items = array();
+          while ($account = db_fetch_object($authenticated_users)) {
+            if ($max_users > 0) {
+              $items[] = $account;
+              $max_users--;
+            }
+            $authenticated_count++;
+          }
+
+          // Format the output with proper grammar.
+          if ($anonymous_count == 1 && $authenticated_count == 1) {
+            $output = t('There is currently %members and %visitors online.', array('%members' => format_plural($authenticated_count, '1 user', '@count users'), '%visitors' => format_plural($anonymous_count, '1 guest', '@count guests')));
+          }
+          else {
+            $output = t('There are currently %members and %visitors online.', array('%members' => format_plural($authenticated_count, '1 user', '@count users'), '%visitors' => format_plural($anonymous_count, '1 guest', '@count guests')));
+          }
+
+          // Display a list of currently online users.
+          $max_users = variable_get('user_block_max_list_count', 10);
+          if ($authenticated_count && $max_users) {
+            $output .= theme('user_list', $items, t('Online users'));
+          }
+
+          $block['subject'] = t('Who\'s online');
+          $block['content'] = $output;
+        }
+        return $block;
+    }
+  }
+}
+
+/**
+ * Process variables for user-picture.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $account
+ *
+ * @see user-picture.tpl.php
+ */
+function template_preprocess_user_picture(&$variables) {
+  $variables['picture'] = '';
+  if (variable_get('user_pictures', 0)) {
+    $account = $variables['account'];
+    if (!empty($account->picture) && file_exists($account->picture)) {
+      $picture = file_create_url($account->picture);
+    }
+    else if (variable_get('user_picture_default', '')) {
+      $picture = variable_get('user_picture_default', '');
+    }
+
+    if (isset($picture)) {
+      $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous'))));
+      $variables['picture'] = theme('image', $picture, $alt, $alt, '', FALSE);
+      if (!empty($account->uid) && user_access('access user profiles')) {
+        $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
+        $variables['picture'] = l($variables['picture'], "user/$account->uid", $attributes);
+      }
+    }
+  }
+}
+
+/**
+ * Make a list of users.
+ *
+ * @param $users
+ *   An array with user objects. Should contain at least the name and uid.
+ * @param $title
+ *  (optional) Title to pass on to theme_item_list().
+ *
+ * @ingroup themeable
+ */
+function theme_user_list($users, $title = NULL) {
+  if (!empty($users)) {
+    foreach ($users as $user) {
+      $items[] = theme('username', $user);
+    }
+  }
+  return theme('item_list', $items, $title);
+}
+
+function user_is_anonymous() {
+  // Menu administrators can see items for anonymous when administering.
+  return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
+}
+
+function user_is_logged_in() {
+  return (bool)$GLOBALS['user']->uid;
+}
+
+function user_register_access() {
+  return user_is_anonymous() && variable_get('user_register', 1);
+}
+
+function user_view_access($account) {
+  return $account && $account->uid &&
+    (
+      // Always let users view their own profile.
+      ($GLOBALS['user']->uid == $account->uid) ||
+      // Administrators can view all accounts.
+      user_access('administer users') ||
+      // The user is not blocked and logged in at least once.
+      ($account->access && $account->status && user_access('access user profiles'))
+    );
+}
+
+function user_edit_access($account) {
+  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
+}
+
+function user_load_self($arg) {
+  $arg[1] = user_load($GLOBALS['user']->uid);
+  return $arg;
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function user_menu() {
+  $items['user/autocomplete'] = array(
+    'title' => 'User autocomplete',
+    'page callback' => 'user_autocomplete',
+    'access callback' => 'user_access',
+    'access arguments' => array('access user profiles'),
+    'type' => MENU_CALLBACK,
+    'file' => 'user.pages.inc',
+  );
+
+  // Registration and login pages.
+  $items['user'] = array(
+    'title' => 'User account',
+    'page callback' => 'user_page',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/login'] = array(
+    'title' => 'Log in',
+    'access callback' => 'user_is_anonymous',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+
+  $items['user/register'] = array(
+    'title' => 'Create new account',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_register'),
+    'access callback' => 'user_register_access',
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/password'] = array(
+    'title' => 'Request new password',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_pass'),
+    'access callback' => 'user_is_anonymous',
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.pages.inc',
+  );
+  $items['user/reset/%/%/%'] = array(
+    'title' => 'Reset password',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_pass_reset', 2, 3, 4),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'user.pages.inc',
+  );
+
+  // Admin user pages.
+  $items['admin/user'] = array(
+    'title' => 'User management',
+    'description' => "Manage your site's users, groups and access to site features.",
+    'position' => 'left',
+    'page callback' => 'system_admin_menu_block_page',
+    'access arguments' => array('access administration pages'),
+    'file' => 'system.admin.inc',
+    'file path' => drupal_get_path('module', 'system'),
+  );
+  $items['admin/user/user'] = array(
+    'title' => 'Users',
+    'description' => 'List, add, and edit users.',
+    'page callback' => 'user_admin',
+    'page arguments' => array('list'),
+    'access arguments' => array('administer users'),
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/user/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/user/user/create'] = array(
+    'title' => 'Add user',
+    'page arguments' => array('create'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/settings'] = array(
+    'title' => 'User settings',
+    'description' => 'Configure default behavior of users, including registration requirements, e-mails, and user pictures.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_admin_settings'),
+    'access arguments' => array('administer users'),
+    'file' => 'user.admin.inc',
+  );
+
+  // Admin access pages.
+  $items['admin/user/permissions'] = array(
+    'title' => 'Permissions',
+    'description' => 'Determine access to features by selecting permissions for roles.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_admin_perm'),
+    'access arguments' => array('administer permissions'),
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/roles'] = array(
+    'title' => 'Roles',
+    'description' => 'List, edit, or add user roles.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_admin_new_role'),
+    'access arguments' => array('administer permissions'),
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/roles/edit'] = array(
+    'title' => 'Edit role',
+    'page arguments' => array('user_admin_role'),
+    'type' => MENU_CALLBACK,
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/rules'] = array(
+    'title' => 'Access rules',
+    'description' => 'List and create rules to disallow usernames, e-mail addresses, and IP addresses.',
+    'page callback' => 'user_admin_access',
+    'access arguments' => array('administer permissions'),
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/rules/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/user/rules/add'] = array(
+    'title' => 'Add rule',
+    'page callback' => 'user_admin_access_add',
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/rules/check'] = array(
+    'title' => 'Check rules',
+    'page callback' => 'user_admin_access_check',
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/rules/edit'] = array(
+    'title' => 'Edit rule',
+    'page callback' => 'user_admin_access_edit',
+    'type' => MENU_CALLBACK,
+    'file' => 'user.admin.inc',
+  );
+  $items['admin/user/rules/delete'] = array(
+    'title' => 'Delete rule',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_admin_access_delete_confirm'),
+    'type' => MENU_CALLBACK,
+    'file' => 'user.admin.inc',
+  );
+
+  $items['logout'] = array(
+    'title' => 'Log out',
+    'access callback' => 'user_is_logged_in',
+    'page callback' => 'user_logout',
+    'weight' => 10,
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/%user_current'] = array(
+    'title' => 'My account',
+    'title callback' => 'user_page_title',
+    'title arguments' => array(1),
+    'page callback' => 'user_view',
+    'page arguments' => array(1),
+    'access callback' => 'user_view_access',
+    'access arguments' => array(1),
+    'parent' => '',
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/%user/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+
+  $items['user/%user/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('user_confirm_delete', 1),
+    'access callback' => 'user_access',
+    'access arguments' => array('administer users'),
+    'type' => MENU_CALLBACK,
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/%user_category/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'user_edit',
+    'page arguments' => array(1),
+    'access callback' => 'user_edit_access',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'load arguments' => array('%map', '%index'),
+    'file' => 'user.pages.inc',
+  );
+
+  $items['user/%user_category/edit/account'] = array(
+    'title' => 'Account',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'load arguments' => array('%map', '%index'),
+  );
+
+  $empty_account = new stdClass();
+  if (($categories = _user_categories($empty_account)) && (count($categories) > 1)) {
+    foreach ($categories as $key => $category) {
+      // 'account' is already handled by the MENU_DEFAULT_LOCAL_TASK.
+      if ($category['name'] != 'account') {
+        $items['user/%user_category/edit/'. $category['name']] = array(
+          'title callback' => 'check_plain',
+          'title arguments' => array($category['title']),
+          'page callback' => 'user_edit',
+          'page arguments' => array(1, 3),
+          'access callback' => isset($category['access callback']) ? $category['access callback'] : TRUE,
+          'access arguments' => isset($category['access arguments']) ? $category['access arguments'] : array(),
+          'type' => MENU_LOCAL_TASK,
+          'weight' => $category['weight'],
+          'load arguments' => array('%map', '%index'),
+          'tab_parent' => 'user/%/edit',
+          'file' => 'user.pages.inc',
+        );
+      }
+    }
+  }
+  return $items;
+}
+
+function user_init() {
+  drupal_add_css(drupal_get_path('module', 'user') .'/user.css', 'module');
+}
+
+function user_current_load($arg) {
+  return user_load($arg ? $arg : $GLOBALS['user']->uid);
+}
+
+/**
+ * Return a user object after checking if any profile category in the path exists.
+ */
+function user_category_load($uid, &$map, $index) {
+  static $user_categories, $accounts;
+
+  // Cache $account - this load function will get called for each profile tab.
+  if (!isset($accounts[$uid])) {
+    $accounts[$uid] = user_load($uid);
+  }
+  $valid = TRUE;
+  if ($account = $accounts[$uid]) {
+    // Since the path is like user/%/edit/category_name, the category name will
+    // be at a position 2 beyond the index corresponding to the % wildcard.
+    $category_index = $index + 2;
+    // Valid categories may contain slashes, and hence need to be imploded.
+    $category_path = implode('/', array_slice($map, $category_index));
+    if ($category_path) {
+      // Check that the requested category exists.
+      $valid = FALSE;
+      if (!isset($user_categories)) {
+        $empty_account = new stdClass();
+        $user_categories = _user_categories($empty_account);
+      }
+      foreach ($user_categories as $category) {
+        if ($category['name'] == $category_path) {
+          $valid = TRUE;
+          // Truncate the map array in case the category name had slashes.
+          $map = array_slice($map, 0, $category_index);
+          // Assign the imploded category name to the last map element.
+          $map[$category_index] = $category_path;
+          break;
+        }
+      }
+    }
+  }
+  return $valid ? $account : FALSE;
+}
+
+/**
+ * Returns the user id of the currently logged in user.
+ */
+function user_current_to_arg($arg) {
+  // Give back the current user uid when called from eg. tracker, aka.
+  // with an empty arg. Also use the current user uid when called from
+  // the menu with a % for the current account link.
+  return empty($arg) || $arg == '%' ? $GLOBALS['user']->uid : $arg;
+}
+
+/**
+ * Menu item title callback - use the user name if it's not the current user.
+ */
+function user_page_title($account) {
+  if ($account->uid == $GLOBALS['user']->uid) {
+    return t('My account');
+  }
+  return $account->name;
+}
+
+/**
+ * Discover which external authentication module(s) authenticated a username.
+ *
+ * @param $authname
+ *   A username used by an external authentication module.
+ * @return
+ *   An associative array with module as key and username as value.
+ */
+function user_get_authmaps($authname = NULL) {
+  $result = db_query("SELECT authname, module FROM {authmap} WHERE authname = '%s'", $authname);
+  $authmaps = array();
+  $has_rows = FALSE;
+  while ($authmap = db_fetch_object($result)) {
+    $authmaps[$authmap->module] = $authmap->authname;
+    $has_rows = TRUE;
+  }
+  return $has_rows ? $authmaps : 0;
+}
+
+/**
+ * Save mappings of which external authentication module(s) authenticated
+ * a user. Maps external usernames to user ids in the users table.
+ *
+ * @param $account
+ *   A user object.
+ * @param $authmaps
+ *   An associative array with a compound key and the username as the value.
+ *   The key is made up of 'authname_' plus the name of the external authentication
+ *   module.
+ * @see user_external_login_register()
+ */
+function user_set_authmaps($account, $authmaps) {
+  foreach ($authmaps as $key => $value) {
+    $module = explode('_', $key, 2);
+    if ($value) {
+      db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module[1]);
+      if (!db_affected_rows()) {
+        db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]);
+      }
+    }
+    else {
+      db_query("DELETE FROM {authmap} WHERE uid = %d AND module = '%s'", $account->uid, $module[1]);
+    }
+  }
+}
+
+/**
+ * Form builder; the main user login form.
+ *
+ * @ingroup forms
+ */
+function user_login(&$form_state, $msg = '') {
+  global $user;
+
+  // If we are already logged on, go to the user page instead.
+  if ($user->uid) {
+    drupal_goto('user/'. $user->uid);
+  }
+
+  // Display login form:
+  if ($msg) {
+    $form['message'] = array('#value' => '<p>'. check_plain($msg) .'</p>');
+  }
+  $form['name'] = array('#type' => 'textfield',
+    '#title' => t('Username'),
+    '#size' => 60,
+    '#maxlength' => USERNAME_MAX_LENGTH,
+    '#required' => TRUE,
+    '#attributes' => array('tabindex' => '1'),
+  );
+
+  $form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
+  $form['pass'] = array('#type' => 'password',
+    '#title' => t('Password'),
+    '#description' => t('Enter the password that accompanies your username.'),
+    '#required' => TRUE,
+    '#attributes' => array('tabindex' => '2'),
+  );
+  $form['#validate'] = user_login_default_validators();
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Log in'), '#weight' => 2, '#attributes' => array('tabindex' => '3'));
+
+  return $form;
+}
+
+/**
+ * Set up a series for validators which check for blocked/denied users,
+ * then authenticate against local database, then return an error if
+ * authentication fails. Distributed authentication modules are welcome 
+ * to use hook_form_alter() to change this series in order to 
+ * authenticate against their user database instead of the local users 
+ * table.
+ *
+ * We use three validators instead of one since external authentication
+ * modules usually only need to alter the second validator.
+ *
+ * @see user_login_name_validate()
+ * @see user_login_authenticate_validate()
+ * @see user_login_final_validate()
+ * @return array
+ *   A simple list of validate functions.
+ */
+function user_login_default_validators() {
+  return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
+}
+
+/**
+ * A FAPI validate handler. Sets an error if supplied username has been blocked
+ * or denied access.
+ */
+function user_login_name_validate($form, &$form_state) {
+  if (isset($form_state['values']['name'])) {
+    if (user_is_blocked($form_state['values']['name'])) {
+      // blocked in user administration
+      form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
+    }
+    else if (drupal_is_denied('user', $form_state['values']['name'])) {
+      // denied by access controls
+      form_set_error('name', t('The name %name is a reserved username.', array('%name' => $form_state['values']['name'])));
+    }
+  }
+}
+
+/**
+ * A validate handler on the login form. Check supplied username/password
+ * against local users table. If successful, sets the global $user object.
+ */
+function user_login_authenticate_validate($form, &$form_state) {
+  user_authenticate($form_state['values']);
+}
+
+/**
+ * A validate handler on the login form. Should be the last validator. Sets an
+ * error if user has not been authenticated yet.
+ */
+function user_login_final_validate($form, &$form_state) {
+  global $user;
+  if (!$user->uid) {
+    form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password'))));
+    watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
+  }
+}
+
+/**
+ * Try to log in the user locally.
+ *
+ * @param $form_values
+ *   Form values with at least 'name' and 'pass' keys, as well as anything else
+ *   which should be passed along to hook_user op 'login'.
+ *
+ * @return
+ *  A $user object, if successful.
+ */
+function user_authenticate($form_values = array()) {
+  global $user;
+
+  // Name and pass keys are required.
+  if (!empty($form_values['name']) && !empty($form_values['pass']) &&
+      $account = user_load(array('name' => $form_values['name'], 'pass' => trim($form_values['pass']), 'status' => 1))) {
+    $user = $account;
+    user_authenticate_finalize($form_values);
+    return $user;
+  }
+}
+
+/**
+ * Finalize the login process. Must be called when logging in a user.
+ *
+ * The function records a watchdog message about the new session, saves the
+ * login timestamp, calls hook_user op 'login' and generates a new session.
+ *
+ * $param $edit
+ *   This array is passed to hook_user op login.
+ */
+function user_authenticate_finalize(&$edit) {
+  global $user;
+  watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
+  // Update the user table timestamp noting user has logged in.
+  // This is also used to invalidate one-time login links.
+  $user->login = time();
+  db_query("UPDATE {users} SET login = %d WHERE uid = %d", $user->login, $user->uid);
+  user_module_invoke('login', $edit, $user);
+  sess_regenerate();
+}
+
+/**
+ * Submit handler for the login form. Redirects the user to a page.
+ *
+ * The user is redirected to the My Account page. Setting the destination in
+ * the query string (as done by the user login block) overrides the redirect.
+ */
+function user_login_submit($form, &$form_state) {
+  global $user;
+  if ($user->uid) {
+    $form_state['redirect'] = 'user/'. $user->uid;
+    return;
+  }
+}
+
+/**
+ * Helper function for authentication modules. Either login in or registers
+ * the current user, based on username. Either way, the global $user object is
+ * populated based on $name.
+ */
+function user_external_login_register($name, $module) {
+  global $user;
+
+  $user = user_load(array('name' => $name));
+  if (!isset($user->uid)) {
+    // Register this new user.
+    $userinfo = array(
+      'name' => $name,
+      'pass' => user_password(),
+      'init' => $name,
+      'status' => 1,
+      "authname_$module" => $name,
+      'access' => time()
+    );
+    $account = user_save('', $userinfo);
+    // Terminate if an error occured during user_save().
+    if (!$account) {
+      drupal_set_message(t("Error saving user account."), 'error');
+      return;
+    }
+    $user = $account;
+    watchdog('user', 'New external user: %name using module %module.', array('%name' => $name, '%module' => $module), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
+  }
+}
+
+function user_pass_reset_url($account) {
+  $timestamp = time();
+  return url("user/reset/$account->uid/$timestamp/". user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
+}
+
+function user_pass_rehash($password, $timestamp, $login) {
+  return md5($timestamp . $password . $login);
+}
+
+function user_edit_form(&$form_state, $uid, $edit, $register = FALSE) {
+  _user_password_dynamic_validation();
+  $admin = user_access('administer users');
+
+  // Account information:
+  $form['account'] = array('#type' => 'fieldset',
+    '#title' => t('Account information'),
+    '#weight' => -10,
+  );
+  if (user_access('change own username') || $admin || $register) {
+    $form['account']['name'] = array('#type' => 'textfield',
+      '#title' => t('Username'),
+      '#default_value' => $edit['name'],
+      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'),
+      '#required' => TRUE,
+    );
+  }
+  $form['account']['mail'] = array('#type' => 'textfield',
+    '#title' => t('E-mail address'),
+    '#default_value' => $edit['mail'],
+    '#maxlength' => EMAIL_MAX_LENGTH,
+    '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
+    '#required' => TRUE,
+  );
+  if (!$register) {
+    $form['account']['pass'] = array('#type' => 'password_confirm',
+      '#description' => t('To change the current user password, enter the new password in both fields.'),
+      '#size' => 25,
+    );
+  }
+  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
+    $form['account']['pass'] = array(
+      '#type' => 'password_confirm',
+      '#description' => t('Provide a password for the new account in both fields.'),
+      '#required' => TRUE,
+      '#size' => 25,
+    );
+  }
+  if ($admin) {
+    $form['account']['status'] = array(
+      '#type' => 'radios',
+      '#title' => t('Status'),
+      '#default_value' => isset($edit['status']) ? $edit['status'] : 1,
+      '#options' => array(t('Blocked'), t('Active'))
+    );
+  }
+  if (user_access('administer permissions')) {
+    $roles = user_roles(TRUE);
+
+    // The disabled checkbox subelement for the 'authenticated user' role
+    // must be generated separately and added to the checkboxes element,
+    // because of a limitation in D6 FormAPI not supporting a single disabled
+    // checkbox within a set of checkboxes.
+    // TODO: This should be solved more elegantly. See issue #119038.
+    $checkbox_authenticated = array(
+      '#type' => 'checkbox',
+      '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
+      '#default_value' => TRUE,
+      '#disabled' => TRUE,
+    );
+
+    unset($roles[DRUPAL_AUTHENTICATED_RID]);
+    if ($roles) {
+      $default = empty($edit['roles']) ? array() : array_keys($edit['roles']);
+      $form['account']['roles'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Roles'),
+        '#default_value' => $default,
+        '#options' => $roles,
+        DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
+      );
+    }
+  }
+
+  // Signature:
+  if (variable_get('user_signatures', 0) && module_exists('comment') && !$register) {
+    $form['signature_settings'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Signature settings'),
+      '#weight' => 1,
+    );
+    $form['signature_settings']['signature'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Signature'),
+      '#default_value' => $edit['signature'],
+      '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
+    );
+  }
+
+  // Picture/avatar:
+  if (variable_get('user_pictures', 0) && !$register) {
+    $form['picture'] = array('#type' => 'fieldset', '#title' => t('Picture'), '#weight' => 1);
+    $picture = theme('user_picture', (object)$edit);
+    if ($edit['picture']) {
+      $form['picture']['current_picture'] = array('#value' => $picture);
+      $form['picture']['picture_delete'] = array('#type' => 'checkbox', '#title' => t('Delete picture'), '#description' => t('Check this box to delete your current picture.'));
+    }
+    else {
+      $form['picture']['picture_delete'] = array('#type' => 'hidden');
+    }
+    $form['picture']['picture_upload'] = array('#type' => 'file', '#title' => t('Upload picture'), '#size' => 48, '#description' => t('Your virtual face or picture. Maximum dimensions are %dimensions and the maximum size is %size kB.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'), '%size' => variable_get('user_picture_file_size', '30'))) .' '. variable_get('user_picture_guidelines', ''));
+    $form['#validate'][] = 'user_validate_picture';
+  }
+  $form['#uid'] = $uid;
+
+  return $form;
+}
+
+function _user_edit_validate($uid, &$edit) {
+  $user = user_load(array('uid' => $uid));
+  // Validate the username:
+  if (user_access('change own username') || user_access('administer users') || !$user->uid) {
+    if ($error = user_validate_name($edit['name'])) {
+      form_set_error('name', $error);
+    }
+    else if (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
+      form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name'])));
+    }
+    else if (drupal_is_denied('user', $edit['name'])) {
+      form_set_error('name', t('The name %name has been denied access.', array('%name' => $edit['name'])));
+    }
+  }
+
+  // Validate the e-mail address:
+  if ($error = user_validate_mail($edit['mail'])) {
+    form_set_error('mail', $error);
+  }
+  else if (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
+    form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $edit['mail'], '@password' => url('user/password'))));
+  }
+  else if (drupal_is_denied('mail', $edit['mail'])) {
+    form_set_error('mail', t('The e-mail address %email has been denied access.', array('%email' => $edit['mail'])));
+  }
+}
+
+function _user_edit_submit($uid, &$edit) {
+  $user = user_load(array('uid' => $uid));
+  // Delete picture if requested, and if no replacement picture was given.
+  if (!empty($edit['picture_delete'])) {
+    if ($user->picture && file_exists($user->picture)) {
+      file_delete($user->picture);
+    }
+    $edit['picture'] = '';
+  }
+  if (isset($edit['roles'])) {
+    $edit['roles'] = array_filter($edit['roles']);
+  }
+}
+
+/**
+ * Delete a user.
+ *
+ * @param $edit An array of submitted form values.
+ * @param $uid The user ID of the user to delete.
+ */
+function user_delete($edit, $uid) {
+  $account = user_load(array('uid' => $uid));
+  sess_destroy_uid($uid);
+  _user_mail_notify('status_deleted', $account);
+  db_query('DELETE FROM {users} WHERE uid = %d', $uid);
+  db_query('DELETE FROM {users_roles} WHERE uid = %d', $uid);
+  db_query('DELETE FROM {authmap} WHERE uid = %d', $uid);
+  $variables = array('%name' => $account->name, '%email' => '<'. $account->mail .'>');
+  watchdog('user', 'Deleted user: %name %email.', $variables, WATCHDOG_NOTICE);
+  module_invoke_all('user', 'delete', $edit, $account);
+}
+
+/**
+ * Builds a structured array representing the profile content.
+ *
+ * @param $account
+ *   A user object.
+ *
+ * @return
+ *   A structured array containing the individual elements of the profile.
+ */
+function user_build_content(&$account) {
+  $edit = NULL;
+  user_module_invoke('view', $edit, $account);
+  // Allow modules to modify the fully-built profile.
+  drupal_alter('profile', $account);
+
+  return $account->content;
+}
+
+/**
+ * Implementation of hook_mail().
+ */
+function user_mail($key, &$message, $params) {
+  $language = $message['language'];
+  $variables = user_mail_tokens($params['account'], $language);
+  $message['subject'] .= _user_mail_text($key .'_subject', $language, $variables);
+  $message['body'][] = _user_mail_text($key .'_body', $language, $variables);
+}
+
+/**
+ * Returns a mail string for a variable name.
+ *
+ * Used by user_mail() and the settings forms to retrieve strings.
+ */
+function _user_mail_text($key, $language = NULL, $variables = array()) {
+  $langcode = isset($language) ? $language->language : NULL;
+
+  if ($admin_setting = variable_get('user_mail_'. $key, FALSE)) {
+    // An admin setting overrides the default string.
+    return strtr($admin_setting, $variables);
+  }
+  else {
+    // No override, return default string.
+    switch ($key) {
+      case 'register_no_approval_required_subject':
+        return t('Account details for !username at !site', $variables, $langcode);
+      case 'register_no_approval_required_body':
+        return t("!username,\n\nThank you for registering at !site. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n--  !site team", $variables, $langcode);
+      case 'register_admin_created_subject':
+        return t('An administrator created an account for you at !site', $variables, $langcode);
+      case 'register_admin_created_body':
+        return t("!username,\n\nA site administrator at !site has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n--  !site team", $variables, $langcode);
+      case 'register_pending_approval_subject':
+      case 'pending_approval_admin_subject':
+        return t('Account details for !username at !site (pending admin approval)', $variables, $langcode);
+      case 'register_pending_approval_body':
+        return t("!username,\n\nThank you for registering at !site. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\n\n\n--  !site team", $variables, $langcode);
+      case 'register_pending_approval_admin_body':
+        return t("!username has applied for an account.\n\n!edit_uri", $variables, $langcode);
+      case 'password_reset_subject':
+        return t('Replacement login information for !username at !site', $variables, $langcode);
+      case 'password_reset_body':
+        return t("!username,\n\nA request to reset the password for your account has been made at !site.\n\nYou may now log in to !uri_brief by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.", $variables, $langcode);
+      case 'status_activated_subject':
+        return t('Account details for !username at !site (approved)', $variables, $langcode);
+      case 'status_activated_body':
+        return t("!username,\n\nYour account at !site has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using:\n\nusername: !username\n", $variables, $langcode);
+      case 'status_blocked_subject':
+        return t('Account details for !username at !site (blocked)', $variables, $langcode);
+      case 'status_blocked_body':
+        return t("!username,\n\nYour account on !site has been blocked.", $variables, $langcode);
+      case 'status_deleted_subject':
+        return t('Account details for !username at !site (deleted)', $variables, $langcode);
+      case 'status_deleted_body':
+        return t("!username,\n\nYour account on !site has been deleted.", $variables, $langcode);
+    }
+  }
+}
+
+/*** Administrative features ***********************************************/
+
+/**
+ * Retrieve an array of roles matching specified conditions.
+ *
+ * @param $membersonly
+ *   Set this to TRUE to exclude the 'anonymous' role.
+ * @param $permission
+ *   A string containing a permission. If set, only roles containing that
+ *   permission are returned.
+ *
+ * @return
+ *   An associative array with the role id as the key and the role name as
+ *   value.
+ */
+function user_roles($membersonly = FALSE, $permission = NULL) {
+  // System roles take the first two positions.
+  $roles = array(
+    DRUPAL_ANONYMOUS_RID => NULL,
+    DRUPAL_AUTHENTICATED_RID => NULL,
+  );
+
+  if (!empty($permission)) {
+    $result = db_query("SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name", $permission);
+  }
+  else {
+    $result = db_query('SELECT * FROM {role} ORDER BY name');
+  }
+
+  while ($role = db_fetch_object($result)) {
+    switch ($role->rid) {
+      // We only translate the built in role names
+      case DRUPAL_ANONYMOUS_RID:
+        if (!$membersonly) {
+          $roles[$role->rid] = t($role->name);
+        }
+        break;
+      case DRUPAL_AUTHENTICATED_RID:
+        $roles[$role->rid] = t($role->name);
+        break;
+      default:
+        $roles[$role->rid] = $role->name;
+    }
+  }
+
+  // Filter to remove unmatched system roles.
+  return array_filter($roles);
+}
+
+/**
+ * Implementation of hook_user_operations().
+ */
+function user_user_operations($form_state = array()) {
+  $operations = array(
+    'unblock' => array(
+      'label' => t('Unblock the selected users'),
+      'callback' => 'user_user_operations_unblock',
+    ),
+    'block' => array(
+      'label' => t('Block the selected users'),
+      'callback' => 'user_user_operations_block',
+    ),
+    'delete' => array(
+      'label' => t('Delete the selected users'),
+    ),
+  );
+
+  if (user_access('administer permissions')) {
+    $roles = user_roles(TRUE);
+    unset($roles[DRUPAL_AUTHENTICATED_RID]);  // Can't edit authenticated role.
+
+    $add_roles = array();
+    foreach ($roles as $key => $value) {
+      $add_roles['add_role-'. $key] = $value;
+    }
+
+    $remove_roles = array();
+    foreach ($roles as $key => $value) {
+      $remove_roles['remove_role-'. $key] = $value;
+    }
+
+    if (count($roles)) {
+      $role_operations = array(
+        t('Add a role to the selected users') => array(
+          'label' => $add_roles,
+        ),
+        t('Remove a role from the selected users') => array(
+          'label' => $remove_roles,
+        ),
+      );
+
+      $operations += $role_operations;
+    }
+  }
+
+  // If the form has been posted, we need to insert the proper data for
+  // role editing if necessary.
+  if (!empty($form_state['submitted'])) {
+    $operation_rid = explode('-', $form_state['values']['operation']);
+    $operation = $operation_rid[0];
+    if ($operation == 'add_role' || $operation == 'remove_role') {
+      $rid = $operation_rid[1];
+      if (user_access('administer permissions')) {
+        $operations[$form_state['values']['operation']] = array(
+          'callback' => 'user_multiple_role_edit',
+          'callback arguments' => array($operation, $rid),
+        );
+      }
+      else {
+        watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
+        return;
+      }
+    }
+  }
+
+  return $operations;
+}
+
+/**
+ * Callback function for admin mass unblocking users.
+ */
+function user_user_operations_unblock($accounts) {
+  foreach ($accounts as $uid) {
+    $account = user_load(array('uid' => (int)$uid));
+    // Skip unblocking user if they are already unblocked.
+    if ($account !== FALSE && $account->status == 0) {
+      user_save($account, array('status' => 1));
+    }
+  }
+}
+
+/**
+ * Callback function for admin mass blocking users.
+ */
+function user_user_operations_block($accounts) {
+  foreach ($accounts as $uid) {
+    $account = user_load(array('uid' => (int)$uid));
+    // Skip blocking user if they are already blocked.
+    if ($account !== FALSE && $account->status == 1) {
+      user_save($account, array('status' => 0));
+    }
+  }
+}
+
+/**
+ * Callback function for admin mass adding/deleting a user role.
+ */
+function user_multiple_role_edit($accounts, $operation, $rid) {
+  // The role name is not necessary as user_save() will reload the user
+  // object, but some modules' hook_user() may look at this first.
+  $role_name = db_result(db_query('SELECT name FROM {role} WHERE rid = %d', $rid));
+
+  switch ($operation) {
+    case 'add_role':
+      foreach ($accounts as $uid) {
+        $account = user_load(array('uid' => (int)$uid));
+        // Skip adding the role to the user if they already have it.
+        if ($account !== FALSE && !isset($account->roles[$rid])) {
+          $roles = $account->roles + array($rid => $role_name);
+          user_save($account, array('roles' => $roles));
+        }
+      }
+      break;
+    case 'remove_role':
+      foreach ($accounts as $uid) {
+        $account = user_load(array('uid' => (int)$uid));
+        // Skip removing the role from the user if they already don't have it.
+        if ($account !== FALSE && isset($account->roles[$rid])) {
+          $roles = array_diff($account->roles, array($rid => $role_name));
+          user_save($account, array('roles' => $roles));
+        }
+      }
+      break;
+  }
+}
+
+function user_multiple_delete_confirm(&$form_state) {
+  $edit = $form_state['post'];
+
+  $form['accounts'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
+  // array_filter() returns only elements with TRUE values.
+  foreach (array_filter($edit['accounts']) as $uid => $value) {
+    $user = db_result(db_query('SELECT name FROM {users} WHERE uid = %d', $uid));
+    $form['accounts'][$uid] = array('#type' => 'hidden', '#value' => $uid, '#prefix' => '<li>', '#suffix' => check_plain($user) ."</li>\n");
+  }
+  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+
+  return confirm_form($form,
+                      t('Are you sure you want to delete these users?'),
+                      'admin/user/user', t('This action cannot be undone.'),
+                      t('Delete all'), t('Cancel'));
+}
+
+function user_multiple_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    foreach ($form_state['values']['accounts'] as $uid => $value) {
+      user_delete($form_state['values'], $uid);
+    }
+    drupal_set_message(t('The users have been deleted.'));
+  }
+  $form_state['redirect'] = 'admin/user/user';
+  return;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function user_help($path, $arg) {
+  global $user;
+
+  switch ($path) {
+    case 'admin/help#user':
+      $output = '<p>'. t('The user module allows users to register, login, and log out. Users benefit from being able to sign on because it associates content they create with their account and allows various permissions to be set for their roles. The user module supports user roles which establish fine grained permissions allowing each role to do only what the administrator wants them to. Each user is assigned to one or more roles. By default there are two roles <em>anonymous</em> - a user who has not logged in, and <em>authenticated</em> a user who has signed up and who has been authorized.') .'</p>';
+      $output .= '<p>'. t("Users can use their own name or handle and can specify personal configuration settings through their individual <em>My account</em> page. Users must authenticate by supplying a local username and password or through their OpenID, an optional and secure method for logging into many websites with a single username and password. In some configurations, users may authenticate using a username and password from another Drupal site, or through some other site-specific mechanism.") .'</p>';
+      $output .= '<p>'. t('A visitor accessing your website is assigned a unique ID, or session ID, which is stored in a cookie. The cookie does not contain personal information, but acts as a key to retrieve information from your site. Users should have cookies enabled in their web browser when using your site.') .'</p>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@user">User module</a>.', array('@user' => 'http://drupal.org/handbook/modules/user/')) .'</p>';
+      return $output;
+    case 'admin/user/user':
+      return '<p>'. t('Drupal allows users to register, login, log out, maintain user profiles, etc. Users of the site may not use their own names to post content until they have signed up for a user account.') .'</p>';
+    case 'admin/user/user/create':
+    case 'admin/user/user/account/create':
+      return '<p>'. t("This web page allows administrators to register new users. Users' e-mail addresses and usernames must be unique.") .'</p>';
+    case 'admin/user/rules':
+      return '<p>'. t('Set up username and e-mail address access rules for new <em>and</em> existing accounts (currently logged in accounts will not be logged out). If a username or e-mail address for an account matches any deny rule, but not an allow rule, then the account will not be allowed to be created or to log in. A host rule is effective for every page view, not just registrations.') .'</p>';
+    case 'admin/user/permissions':
+      return '<p>'. t('Permissions let you control what users can do on your site. Each user role (defined on the <a href="@role">user roles page</a>) has its own set of permissions. For example, you could give users classified as "Administrators" permission to "administer nodes" but deny this power to ordinary, "authenticated" users. You can use permissions to reveal new features to privileged users (those with subscriptions, for example). Permissions also allow trusted users to share the administrative burden of running a busy site.', array('@role' => url('admin/user/roles'))) .'</p>';
+    case 'admin/user/roles':
+      return t('<p>Roles allow you to fine tune the security and administration of Drupal. A role defines a group of users that have certain privileges as defined in <a href="@permissions">user permissions</a>. Examples of roles include: anonymous user, authenticated user, moderator, administrator and so on. In this area you will define the <em>role names</em> of the various roles. To delete a role choose "edit".</p><p>By default, Drupal comes with two user roles:</p>
+      <ul>
+      <li>Anonymous user: this role is used for users that don\'t have a user account or that are not authenticated.</li>
+      <li>Authenticated user: this role is automatically granted to all logged in users.</li>
+      </ul>', array('@permissions' => url('admin/user/permissions')));
+    case 'admin/user/search':
+      return '<p>'. t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username or e-mail address. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda@example.com".') .'</p>';
+  }
+}
+
+/**
+ * Retrieve a list of all user setting/information categories and sort them by weight.
+ */
+function _user_categories($account) {
+  $categories = array();
+
+  foreach (module_list() as $module) {
+    if ($data = module_invoke($module, 'user', 'categories', NULL, $account, '')) {
+      $categories = array_merge($data, $categories);
+    }
+  }
+
+  usort($categories, '_user_sort');
+
+  return $categories;
+}
+
+function _user_sort($a, $b) {
+  $a = (array)$a + array('weight' => 0, 'title' => '');
+  $b = (array)$b + array('weight' => 0, 'title' => '');
+  return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : ($a['title'] < $b['title'] ? -1 : 1));
+}
+
+/**
+ * List user administration filters that can be applied.
+ */
+function user_filters() {
+  // Regular filters
+  $filters = array();
+  $roles = user_roles(TRUE);
+  unset($roles[DRUPAL_AUTHENTICATED_RID]); // Don't list authorized role.
+  if (count($roles)) {
+    $filters['role'] = array(
+      'title' => t('role'),
+      'where' => "ur.rid = %d",
+      'options' => $roles,
+      'join' => '',
+    );
+  }
+
+  $options = array();
+  foreach (module_list() as $module) {
+    if ($permissions = module_invoke($module, 'perm')) {
+      asort($permissions);
+      foreach ($permissions as $permission) {
+        $options[t('@module module', array('@module' => $module))][$permission] = t($permission);
+      }
+    }
+  }
+  ksort($options);
+  $filters['permission'] = array(
+    'title' => t('permission'),
+    'join' => 'LEFT JOIN {permission} p ON ur.rid = p.rid',
+    'where' => " ((p.perm IS NOT NULL AND p.perm LIKE '%%%s%%') OR u.uid = 1) ",
+    'options' => $options,
+  );
+
+  $filters['status'] = array(
+    'title' => t('status'),
+    'where' => 'u.status = %d',
+    'join' => '',
+    'options' => array(1 => t('active'), 0 => t('blocked')),
+  );
+  return $filters;
+}
+
+/**
+ * Build query for user administration filters based on session.
+ */
+function user_build_filter_query() {
+  $filters = user_filters();
+
+  // Build query
+  $where = $args = $join = array();
+  foreach ($_SESSION['user_overview_filter'] as $filter) {
+    list($key, $value) = $filter;
+    // This checks to see if this permission filter is an enabled permission for
+    // the authenticated role. If so, then all users would be listed, and we can
+    // skip adding it to the filter query.
+    if ($key == 'permission') {
+      $account = new stdClass();
+      $account->uid = 'user_filter';
+      $account->roles = array(DRUPAL_AUTHENTICATED_RID => 1);
+      if (user_access($value, $account)) {
+        continue;
+      }
+    }
+    $where[] = $filters[$key]['where'];
+    $args[] = $value;
+    $join[] = $filters[$key]['join'];
+  }
+  $where = !empty($where) ? 'AND '. implode(' AND ', $where) : '';
+  $join = !empty($join) ? ' '. implode(' ', array_unique($join)) : '';
+
+  return array('where' => $where,
+           'join' => $join,
+           'args' => $args,
+         );
+}
+
+/**
+ * Implementation of hook_forms().
+ */
+function user_forms() {
+  $forms['user_admin_access_add_form']['callback'] = 'user_admin_access_form';
+  $forms['user_admin_access_edit_form']['callback'] = 'user_admin_access_form';
+  $forms['user_admin_new_role']['callback'] = 'user_admin_role';
+  return $forms;
+}
+
+/**
+ * Implementation of hook_comment().
+ */
+function user_comment(&$comment, $op) {
+  // Validate signature.
+  if ($op == 'view') {
+    if (variable_get('user_signatures', 0) && !empty($comment->signature)) {
+      $comment->signature = check_markup($comment->signature, $comment->format);
+    }
+    else {
+      $comment->signature = '';
+    }
+  }
+}
+
+/**
+ * Theme output of user signature.
+ *
+ * @ingroup themeable
+ */
+function theme_user_signature($signature) {
+  $output = '';
+  if ($signature) {
+    $output .= '<div class="clear">';
+    $output .= '<div>—</div>';
+    $output .= $signature;
+    $output .= '</div>';
+  }
+
+  return $output;
+}
+
+/**
+ * Return an array of token to value mappings for user e-mail messages.
+ *
+ * @param $account
+ *  The user object of the account being notified.  Must contain at
+ *  least the fields 'uid', 'name', and 'mail'.
+ * @param $language
+ *  Language object to generate the tokens with.
+ * @return
+ *  Array of mappings from token names to values (for use with strtr()).
+ */
+function user_mail_tokens($account, $language) {
+  global $base_url;
+  $tokens = array(
+    '!username' => $account->name,
+    '!site' => variable_get('site_name', 'Drupal'),
+    '!login_url' => user_pass_reset_url($account),
+    '!uri' => $base_url,
+    '!uri_brief' => substr($base_url, strlen('http://')),
+    '!mailto' => $account->mail,
+    '!date' => format_date(time(), 'medium', '', NULL, $language->language),
+    '!login_uri' => url('user', array('absolute' => TRUE, 'language' => $language)),
+    '!edit_uri' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE, 'language' => $language)),
+  );
+  if (!empty($account->password)) {
+    $tokens['!password'] = $account->password;
+  }
+  return $tokens;
+}
+
+/**
+ * Get the language object preferred by the user. This user preference can
+ * be set on the user account editing page, and is only available if there
+ * are more than one languages enabled on the site. If the user did not
+ * choose a preferred language, or is the anonymous user, the $default
+ * value, or if it is not set, the site default language will be returned.
+ *
+ * @param $account
+ *   User account to look up language for.
+ * @param $default
+ *   Optional default language object to return if the account
+ *   has no valid language.
+ */
+function user_preferred_language($account, $default = NULL) {
+  $language_list = language_list();
+  if ($account->language && isset($language_list[$account->language])) {
+    return $language_list[$account->language];
+  }
+  else {
+    return $default ? $default : language_default();
+  }
+}
+
+/**
+ * Conditionally create and send a notification email when a certain
+ * operation happens on the given user account.
+ *
+ * @see user_mail_tokens()
+ * @see drupal_mail()
+ *
+ * @param $op
+ *  The operation being performed on the account.  Possible values:
+ *  'register_admin_created': Welcome message for user created by the admin
+ *  'register_no_approval_required': Welcome message when user self-registers
+ *  'register_pending_approval': Welcome message, user pending admin approval
+ *  'password_reset': Password recovery request
+ *  'status_activated': Account activated
+ *  'status_blocked': Account blocked
+ *  'status_deleted': Account deleted
+ *
+ * @param $account
+ *  The user object of the account being notified.  Must contain at
+ *  least the fields 'uid', 'name', and 'mail'.
+ * @param $language
+ *  Optional language to use for the notification, overriding account language.
+ * @return
+ *  The return value from drupal_mail_send(), if ends up being called.
+ */
+function _user_mail_notify($op, $account, $language = NULL) {
+  // By default, we always notify except for deleted and blocked.
+  $default_notify = ($op != 'status_deleted' && $op != 'status_blocked');
+  $notify = variable_get('user_mail_'. $op .'_notify', $default_notify);
+  if ($notify) {
+    $params['account'] = $account;
+    $language = $language ? $language : user_preferred_language($account);
+    $mail = drupal_mail('user', $op, $account->mail, $language, $params);
+    if ($op == 'register_pending_approval') {
+      // If a user registered requiring admin approval, notify the admin, too.
+      // We use the site default language for this.
+      drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params);
+    }
+  }
+  return empty($mail) ? NULL : $mail['result'];
+}
+
+/**
+ * Add javascript and string translations for dynamic password validation
+ * (strength and confirmation checking).
+ *
+ * This is an internal function that makes it easier to manage the translation
+ * strings that need to be passed to the javascript code.
+ */
+function _user_password_dynamic_validation() {
+  static $complete = FALSE;
+  global $user;
+  // Only need to do once per page.
+  if (!$complete) {
+    drupal_add_js(drupal_get_path('module', 'user') .'/user.js', 'module');
+
+    drupal_add_js(array(
+      'password' => array(
+        'strengthTitle' => t('Password strength:'),
+        'lowStrength' => t('Low'),
+        'mediumStrength' => t('Medium'),
+        'highStrength' => t('High'),
+        'tooShort' => t('It is recommended to choose a password that contains at least six characters. It should include numbers, punctuation, and both upper and lowercase letters.'),
+        'needsMoreVariation' => t('The password does not include enough variation to be secure. Try:'),
+        'addLetters' => t('Adding both upper and lowercase letters.'),
+        'addNumbers' => t('Adding numbers.'),
+        'addPunctuation' => t('Adding punctuation.'),
+        'sameAsUsername' => t('It is recommended to choose a password different from the username.'),
+        'confirmSuccess' => t('Yes'),
+        'confirmFailure' => t('No'),
+        'confirmTitle' => t('Passwords match:'),
+        'username' => (isset($user->name) ? $user->name : ''))),
+      'setting');
+    $complete = TRUE;
+  }
+}
+
+/**
+ * Implementation of hook_hook_info().
+ */
+function user_hook_info() {
+  return array(
+    'user' => array(
+      'user' => array(
+        'insert' => array(
+          'runs when' => t('After a user account has been created'),
+        ),
+        'update' => array(
+          'runs when' => t("After a user's profile has been updated"),
+        ),
+        'delete' => array(
+          'runs when' => t('After a user has been deleted')
+        ),
+        'login' => array(
+          'runs when' => t('After a user has logged in')
+        ),
+        'logout' => array(
+          'runs when' => t('After a user has logged out')
+        ),
+        'view' => array(
+          'runs when' => t("When a user's profile is being viewed")
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_action_info().
+ */
+function user_action_info() {
+  return array(
+    'user_block_user_action' => array(
+      'description' => t('Block current user'),
+      'type' => 'user',
+      'configurable' => FALSE,
+      'hooks' => array(),
+    ),
+    'user_block_ip_action' => array(
+      'description' => t('Ban IP address of current user'),
+      'type' => 'user',
+      'configurable' => FALSE,
+      'hooks' => array(),
+    ),
+  );
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Blocks the current user.
+ */
+function user_block_user_action(&$object, $context = array()) {
+  if (isset($object->uid)) {
+    $uid = $object->uid;
+  }
+  elseif (isset($context['uid'])) {
+    $uid = $context['uid'];
+  }
+  else {
+    global $user;
+    $uid = $user->uid;
+  }
+  db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid);
+  sess_destroy_uid($uid);
+  watchdog('action', 'Blocked user %name.', array('%name' => check_plain($user->name)));
+}
+
+/**
+ * Implementation of a Drupal action.
+ * Adds an access rule that blocks the user's IP address.
+ */
+function user_block_ip_action() {
+  $ip = ip_address();
+  db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $ip, 'host', 0);
+  watchdog('action', 'Banned IP address %ip', array('%ip' => $ip));
+}
+
+/**
+ * Submit handler for the user registration form.
+ *
+ * This function is shared by the installation form and the normal registration form,
+ * which is why it can't be in the user.pages.inc file.
+ */
+function user_register_submit($form, &$form_state) {
+  global $base_url;
+  $admin = user_access('administer users');
+
+  $mail = $form_state['values']['mail'];
+  $name = $form_state['values']['name'];
+  if (!variable_get('user_email_verification', TRUE) || $admin) {
+    $pass = $form_state['values']['pass'];
+  }
+  else {
+    $pass = user_password();
+  };
+  $notify = isset($form_state['values']['notify']) ? $form_state['values']['notify'] : NULL;
+  $from = variable_get('site_mail', ini_get('sendmail_from'));
+  if (isset($form_state['values']['roles'])) {
+    // Remove unset roles.
+    $roles = array_filter($form_state['values']['roles']);
+  }
+  else {
+    $roles = array();
+  }
+
+  if (!$admin && array_intersect(array_keys($form_state['values']), array('uid', 'roles', 'init', 'session', 'status'))) {
+    watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
+    $form_state['redirect'] = 'user/register';
+    return;
+  }
+  // The unset below is needed to prevent these form values from being saved as
+  // user data.
+  unset($form_state['values']['form_token'], $form_state['values']['submit'], $form_state['values']['op'], $form_state['values']['notify'], $form_state['values']['form_id'], $form_state['values']['affiliates'], $form_state['values']['destination']);
+
+  $merge_data = array('pass' => $pass, 'init' => $mail, 'roles' => $roles);
+  if (!$admin) {
+    // Set the user's status because it was not displayed in the form.
+    $merge_data['status'] = variable_get('user_register', 1) == 1;
+  }
+  $account = user_save('', array_merge($form_state['values'], $merge_data));
+  // Terminate if an error occured during user_save().
+  if (!$account) {
+    drupal_set_message(t("Error saving user account."), 'error');
+    $form_state['redirect'] = '';
+    return;
+  }
+  $form_state['user'] = $account;
+
+  watchdog('user', 'New user: %name (%email).', array('%name' => $name, '%email' => $mail), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit'));
+
+  // The first user may login immediately, and receives a customized welcome e-mail.
+  if ($account->uid == 1) {
+    drupal_set_message(t('Welcome to Drupal. You are now logged in as user #1, which gives you full control over your website.'));
+    if (variable_get('user_email_verification', TRUE)) {
+      drupal_set_message(t('</p><p> Your password is <strong>%pass</strong>. You may change your password below.</p>', array('%pass' => $pass)));
+    }
+
+    user_authenticate(array_merge($form_state['values'], $merge_data));
+
+    $form_state['redirect'] = 'user/1/edit';
+    return;
+  }
+  else {
+    // Add plain text password into user account to generate mail tokens.
+    $account->password = $pass;
+    if ($admin && !$notify) {
+      drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => url("user/$account->uid"), '%name' => $account->name)));
+    }
+    else if (!variable_get('user_email_verification', TRUE) && $account->status && !$admin) {
+      // No e-mail verification is required, create new user account, and login
+      // user immediately.
+      _user_mail_notify('register_no_approval_required', $account);
+      if (user_authenticate(array_merge($form_state['values'], $merge_data))) {
+        drupal_set_message(t('Registration successful. You are now logged in.'));
+      }
+      $form_state['redirect'] = '';
+      return;
+    }
+    else if ($account->status || $notify) {
+      // Create new user account, no administrator approval required.
+      $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
+      _user_mail_notify($op, $account);
+      if ($notify) {
+        drupal_set_message(t('Password and further instructions have been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => url("user/$account->uid"), '%name' => $account->name)));
+      }
+      else {
+        drupal_set_message(t('Your password and further instructions have been sent to your e-mail address.'));
+        $form_state['redirect'] = '';
+        return;
+      }
+    }
+    else {
+      // Create new user account, administrator approval required.
+      _user_mail_notify('register_pending_approval', $account);
+      drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, a welcome message with further instructions has been sent to your e-mail address.'));
+      $form_state['redirect'] = '';
+      return;
+
+    }
+  }
+}
+
+/**
+ * Form builder; The user registration form.
+ *
+ * @ingroup forms
+ * @see user_register_validate()
+ * @see user_register_submit()
+ */
+function user_register() {
+  global $user;
+
+  $admin = user_access('administer users');
+
+  // If we aren't admin but already logged on, go to the user page instead.
+  if (!$admin && $user->uid) {
+    drupal_goto('user/'. $user->uid);
+  }
+
+  $form = array();
+
+  // Display the registration form.
+  if (!$admin) {
+    $form['user_registration_help'] = array('#value' => filter_xss_admin(variable_get('user_registration_help', '')));
+  }
+
+  // Merge in the default user edit fields.
+  $form = array_merge($form, user_edit_form($form_state, NULL, NULL, TRUE));
+  if ($admin) {
+    $form['account']['notify'] = array(
+     '#type' => 'checkbox',
+     '#title' => t('Notify user of new account')
+    );
+    // Redirect back to page which initiated the create request;
+    // usually admin/user/user/create.
+    $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
+  }
+
+  // Create a dummy variable for pass-by-reference parameters.
+  $null = NULL;
+  $extra = _user_forms($null, NULL, NULL, 'register');
+
+  // Remove form_group around default fields if there are no other groups.
+  if (!$extra) {
+    foreach (array('name', 'mail', 'pass', 'status', 'roles', 'notify') as $key) {
+      if (isset($form['account'][$key])) {
+        $form[$key] = $form['account'][$key];
+      }
+    }
+    unset($form['account']);
+  }
+  else {
+    $form = array_merge($form, $extra);
+  }
+
+  if (variable_get('configurable_timezones', 1)) {
+    // Override field ID, so we only change timezone on user registration,
+    // and never touch it on user edit pages.
+    $form['timezone'] = array(
+      '#type' => 'hidden',
+      '#default_value' => variable_get('date_default_timezone', NULL),
+      '#id' => 'edit-user-register-timezone',
+    );
+
+    // Add the JavaScript callback to automatically set the timezone.
+    drupal_add_js('
+// Global Killswitch
+if (Drupal.jsEnabled) {
+  $(document).ready(function() {
+    Drupal.setDefaultTimezone();
+  });
+}', 'inline');
+  }
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Create new account'), '#weight' => 30);
+  $form['#validate'][] = 'user_register_validate';
+
+  return $form;
+}
+
+function user_register_validate($form, &$form_state) {
+  user_module_invoke('validate', $form_state['values'], $form_state['values'], 'account');
+}
+
+/**
+ * Retrieve a list of all form elements for the specified category.
+ */
+function _user_forms(&$edit, $account, $category, $hook = 'form') {
+  $groups = array();
+  foreach (module_list() as $module) {
+    if ($data = module_invoke($module, 'user', $hook, $edit, $account, $category)) {
+      $groups = array_merge_recursive($data, $groups);
+    }
+  }
+  uasort($groups, '_user_sort');
+
+  return empty($groups) ? FALSE : $groups;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/user/user.pages.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,365 @@
+<?php
+// $Id: user.pages.inc,v 1.11 2008/01/08 10:35:43 goba Exp $
+
+/**
+ * @file
+ * User page callback file for the user module.
+ */
+
+/**
+ * Menu callback; Retrieve a JSON object containing autocomplete suggestions for existing users.
+ */
+function user_autocomplete($string = '') {
+  $matches = array();
+  if ($string) {
+    $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10);
+    while ($user = db_fetch_object($result)) {
+      $matches[$user->name] = check_plain($user->name);
+    }
+  }
+
+  drupal_json($matches);
+}
+
+/**
+ * Form builder; Request a password reset.
+ *
+ * @ingroup forms
+ * @see user_pass_validate()
+ * @see user_pass_submit()
+ */
+function user_pass() {
+  $form['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Username or e-mail address'),
+    '#size' => 60,
+    '#maxlength' => max(USERNAME_MAX_LENGTH, EMAIL_MAX_LENGTH),
+    '#required' => TRUE,
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('E-mail new password'));
+
+  return $form;
+}
+
+function user_pass_validate($form, &$form_state) {
+  $name = trim($form_state['values']['name']);
+  // Try to load by email.
+  $account = user_load(array('mail' => $name, 'status' => 1));
+  if (!$account) {
+    // No success, try to load by name.
+    $account = user_load(array('name' => $name, 'status' => 1));
+  }
+  if (isset($account->uid)) {
+    form_set_value(array('#parents' => array('account')), $account, $form_state);
+  }
+  else {
+    form_set_error('name', t('Sorry, %name is not recognized as a user name or an e-mail address.', array('%name' => $name)));
+  }
+}
+
+function user_pass_submit($form, &$form_state) {
+  global $language;
+
+  $account = $form_state['values']['account'];
+  // Mail one time login URL and instructions using current language.
+  _user_mail_notify('password_reset', $account, $language);
+  watchdog('user', 'Password reset instructions mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail));
+  drupal_set_message(t('Further instructions have been sent to your e-mail address.'));
+
+  $form_state['redirect'] = 'user';
+  return;
+}
+
+/**
+ * Menu callback; process one time login link and redirects to the user page on success.
+ */
+function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) {
+  global $user;
+
+  // Check if the user is already logged in. The back button is often the culprit here.
+  if ($user->uid) {
+    drupal_set_message(t('You have already used this one-time login link. It is not necessary to use this link to login anymore. You are already logged in.'));
+    drupal_goto();
+  }
+  else {
+    // Time out, in seconds, until login URL expires. 24 hours = 86400 seconds.
+    $timeout = 86400;
+    $current = time();
+    // Some redundant checks for extra security ?
+    if ($timestamp < $current && $account = user_load(array('uid' => $uid, 'status' => 1)) ) {
+      // No time out for first time login.
+      if ($account->login && $current - $timestamp > $timeout) {
+        drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
+        drupal_goto('user/password');
+      }
+      else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
+        // First stage is a confirmation form, then login
+        if ($action == 'login') {
+          watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
+          // Set the new user.
+          $user = $account;
+          // user_authenticate_finalize() also updates the login timestamp of the
+          // user, which invalidates further use of the one-time login link.
+          user_authenticate_finalize($form_state['values']);
+          drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'));
+          drupal_goto('user/'. $user->uid .'/edit');
+        }
+        else {
+          $form['message'] = array('#value' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to login to the site and change your password.</p>', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout))));
+          $form['help'] = array('#value' => '<p>'. t('This login can be used only once.') .'</p>');
+          $form['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
+          $form['#action'] = url("user/reset/$uid/$timestamp/$hashed_pass/login");
+          return $form;
+        }
+      }
+      else {
+        drupal_set_message(t('You have tried to use a one-time login link which has either been used or is no longer valid. Please request a new one using the form below.'));
+        drupal_goto('user/password');
+      }
+    }
+    else {
+      // Deny access, no more clues.
+      // Everything will be in the watchdog's URL for the administrator to check.
+      drupal_access_denied();
+    }
+  }
+}
+
+/**
+ * Menu callback; logs the current user out, and redirects to the home page.
+ */
+function user_logout() {
+  global $user;
+
+  watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
+
+  // Destroy the current session:
+  session_destroy();
+  module_invoke_all('user', 'logout', NULL, $user);
+
+  // Load the anonymous user
+  $user = drupal_anonymous_user();
+
+  drupal_goto();
+}
+
+/**
+ * Menu callback; Displays a user or user profile page.
+ */
+function user_view($account) {
+  drupal_set_title(check_plain($account->name));
+  // Retrieve all profile fields and attach to $account->content.
+  user_build_content($account);
+
+  // To theme user profiles, copy modules/user/user_profile.tpl.php
+  // to your theme directory, and edit it as instructed in that file's comments.
+  return theme('user_profile', $account);
+}
+
+/**
+ * Process variables for user-profile.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $account
+ *
+ * @see user-picture.tpl.php
+ */
+function template_preprocess_user_profile(&$variables) {
+  $variables['profile'] = array();
+  // Sort sections by weight
+  uasort($variables['account']->content, 'element_sort');
+  // Provide keyed variables so themers can print each section independantly.
+  foreach (element_children($variables['account']->content) as $key) {
+    $variables['profile'][$key] = drupal_render($variables['account']->content[$key]);
+  }
+  // Collect all profiles to make it easier to print all items at once.
+  $variables['user_profile'] = implode($variables['profile']);
+}
+
+/**
+ * Process variables for user-profile-item.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $element
+ *
+ * @see user-profile-item.tpl.php
+ */
+function template_preprocess_user_profile_item(&$variables) {
+  $variables['title'] = $variables['element']['#title'];
+  $variables['value'] = $variables['element']['#value'];
+  $variables['attributes'] = '';
+  if (isset($variables['element']['#attributes'])) {
+    $variables['attributes'] = drupal_attributes($variables['element']['#attributes']);
+  }
+}
+
+/**
+ * Process variables for user-profile-category.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $element
+ *
+ * @see user-profile-category.tpl.php
+ */
+function template_preprocess_user_profile_category(&$variables) {
+  $variables['title'] = check_plain($variables['element']['#title']);
+  $variables['profile_items'] = $variables['element']['#children'];
+  $variables['attributes'] = '';
+  if (isset($variables['element']['#attributes'])) {
+    $variables['attributes'] = drupal_attributes($variables['element']['#attributes']);
+  }
+}
+
+/**
+ * Form builder; Present the form to edit a given user or profile category.
+ *
+ * @ingroup forms
+ * @see user_edit_validate()
+ * @see user_edit_submit()
+ */
+function user_edit($account, $category = 'account') {
+  drupal_set_title(check_plain($account->name));
+  return drupal_get_form('user_profile_form', $account, $category);
+}
+
+/**
+ * Form builder; edit a user account or one of their profile categories.
+ *
+ * @ingroup forms
+ * @see user_profile_form_validate()
+ * @see user_profile_form_submit()
+ * @see user_edit_delete_submit()
+ */
+function user_profile_form($form_state, $account, $category = 'account') {
+
+  $edit = (empty($form_state['values'])) ? (array)$account : $form_state['values'];
+
+  $form = _user_forms($edit, $account, $category);
+  $form['_category'] = array('#type' => 'value', '#value' => $category);
+  $form['_account'] = array('#type' => 'value', '#value' => $account);
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 30);
+  if (user_access('administer users')) {
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#weight' => 31,
+      '#submit' => array('user_edit_delete_submit'),
+    );
+  }
+  $form['#attributes']['enctype'] = 'multipart/form-data';
+
+  return $form;
+}
+
+/**
+ * Validation function for the user account and profile editing form.
+ */
+function user_profile_form_validate($form, &$form_state) {
+  user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']);
+  // Validate input to ensure that non-privileged users can't alter protected data.
+  if ((!user_access('administer users') && array_intersect(array_keys($form_state['values']), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) {
+    watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
+    // set this to a value type field
+    form_set_error('category', t('Detected malicious attempt to alter protected user fields.'));
+  }
+}
+
+/**
+ * Submit function for the user account and profile editing form.
+ */
+function user_profile_form_submit($form, &$form_state) {
+  $account = $form_state['values']['_account'];
+  $category = $form_state['values']['_category'];
+  unset($form_state['values']['_account'], $form_state['values']['op'], $form_state['values']['submit'], $form_state['values']['delete'], $form_state['values']['form_token'], $form_state['values']['form_id'], $form_state['values']['_category']);
+  user_module_invoke('submit', $form_state['values'], $account, $category);
+  user_save($account, $form_state['values'], $category);
+
+  // Clear the page cache because pages can contain usernames and/or profile information:
+  cache_clear_all();
+
+  drupal_set_message(t('The changes have been saved.'));
+  return;
+}
+
+/**
+ * Submit function for the 'Delete' button on the user edit form.
+ */
+function user_edit_delete_submit($form, &$form_state) {
+  $destination = '';
+  if (isset($_REQUEST['destination'])) {
+    $destination = drupal_get_destination();
+    unset($_REQUEST['destination']);
+  }
+  // Note: We redirect from user/uid/edit to user/uid/delete to make the tabs disappear.
+  $form_state['redirect'] = array("user/". $form_state['values']['_account']->uid ."/delete", $destination);
+}
+
+/**
+ * Form builder; confirm form for user deletion.
+ *
+ * @ingroup forms
+ * @see user_confirm_delete_submit()
+ */
+function user_confirm_delete(&$form_state, $account) {
+
+  $form['_account'] = array('#type' => 'value', '#value' => $account);
+
+  return confirm_form($form,
+    t('Are you sure you want to delete the account %name?', array('%name' => $account->name)),
+    'user/'. $account->uid,
+    t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'),
+    t('Delete'), t('Cancel'));
+}
+
+/**
+ * Submit function for the confirm form for user deletion.
+ */
+function user_confirm_delete_submit($form, &$form_state) {
+  user_delete($form_state['values'], $form_state['values']['_account']->uid);
+  drupal_set_message(t('%name has been deleted.', array('%name' => $form_state['values']['_account']->name)));
+
+  if (!isset($_REQUEST['destination'])) {
+    $form_state['redirect'] = 'admin/user/user';
+  }
+}
+
+function user_edit_validate($form, &$form_state) {
+  user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']);
+  // Validate input to ensure that non-privileged users can't alter protected data.
+  if ((!user_access('administer users') && array_intersect(array_keys($form_state['values']), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) {
+    watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING);
+    // set this to a value type field
+    form_set_error('category', t('Detected malicious attempt to alter protected user fields.'));
+  }
+}
+
+function user_edit_submit($form, &$form_state) {
+  $account = $form_state['values']['_account'];
+  $category = $form_state['values']['_category'];
+  unset($form_state['values']['_account'], $form_state['values']['op'], $form_state['values']['submit'], $form_state['values']['delete'], $form_state['values']['form_token'], $form_state['values']['form_id'], $form_state['values']['_category']);
+  user_module_invoke('submit', $form_state['values'], $account, $category);
+  user_save($account, $form_state['values'], $category);
+
+  // Clear the page cache because pages can contain usernames and/or profile information:
+  cache_clear_all();
+
+  drupal_set_message(t('The changes have been saved.'));
+  return;
+}
+
+/**
+ * Access callback for path /user.
+ *
+ * Displays user profile if user is logged in, or login form for anonymous
+ * users.
+ */
+function user_page() {
+  global $user;
+  if ($user->uid) {
+    menu_set_active_item('user/'. $user->uid);
+    return menu_execute_active_handler();
+  }
+  else {
+    return drupal_get_form('user_login');
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/profiles/default/default.profile	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,151 @@
+<?php
+// $Id: default.profile,v 1.22 2007/12/17 12:43:34 goba Exp $
+
+/**
+ * Return an array of the modules to be enabled when this profile is installed.
+ *
+ * @return
+ *   An array of modules to enable.
+ */
+function default_profile_modules() {
+  return array('color', 'comment', 'help', 'menu', 'taxonomy', 'dblog');
+}
+
+/**
+ * Return a description of the profile for the initial installation screen.
+ *
+ * @return
+ *   An array with keys 'name' and 'description' describing this profile,
+ *   and optional 'language' to override the language selection for
+ *   language-specific profiles.
+ */
+function default_profile_details() {
+  return array(
+    'name' => 'Drupal',
+    'description' => 'Select this profile to enable some basic Drupal functionality and the default theme.'
+  );
+}
+
+/**
+ * Return a list of tasks that this profile supports.
+ *
+ * @return
+ *   A keyed array of tasks the profile will perform during
+ *   the final stage. The keys of the array will be used internally,
+ *   while the values will be displayed to the user in the installer
+ *   task list.
+ */
+function default_profile_task_list() {
+}
+
+/**
+ * Perform any final installation tasks for this profile.
+ *
+ * The installer goes through the profile-select -> locale-select
+ * -> requirements -> database -> profile-install-batch
+ * -> locale-initial-batch -> configure -> locale-remaining-batch
+ * -> finished -> done tasks, in this order, if you don't implement
+ * this function in your profile.
+ *
+ * If this function is implemented, you can have any number of
+ * custom tasks to perform after 'configure', implementing a state
+ * machine here to walk the user through those tasks. First time,
+ * this function gets called with $task set to 'profile', and you
+ * can advance to further tasks by setting $task to your tasks'
+ * identifiers, used as array keys in the hook_profile_task_list()
+ * above. You must avoid the reserved tasks listed in
+ * install_reserved_tasks(). If you implement your custom tasks,
+ * this function will get called in every HTTP request (for form
+ * processing, printing your information screens and so on) until
+ * you advance to the 'profile-finished' task, with which you
+ * hand control back to the installer. Each custom page you
+ * return needs to provide a way to continue, such as a form
+ * submission or a link. You should also set custom page titles.
+ *
+ * You should define the list of custom tasks you implement by
+ * returning an array of them in hook_profile_task_list(), as these
+ * show up in the list of tasks on the installer user interface.
+ *
+ * Remember that the user will be able to reload the pages multiple
+ * times, so you might want to use variable_set() and variable_get()
+ * to remember your data and control further processing, if $task
+ * is insufficient. Should a profile want to display a form here,
+ * it can; the form should set '#redirect' to FALSE, and rely on
+ * an action in the submit handler, such as variable_set(), to
+ * detect submission and proceed to further tasks. See the configuration
+ * form handling code in install_tasks() for an example.
+ *
+ * Important: Any temporary variables should be removed using
+ * variable_del() before advancing to the 'profile-finished' phase.
+ *
+ * @param $task
+ *   The current $task of the install system. When hook_profile_tasks()
+ *   is first called, this is 'profile'.
+ * @param $url
+ *   Complete URL to be used for a link or form action on a custom page,
+ *   if providing any, to allow the user to proceed with the installation.
+ *
+ * @return
+ *   An optional HTML string to display to the user. Only used if you
+ *   modify the $task, otherwise discarded.
+ */
+function default_profile_tasks(&$task, $url) {
+
+  // Insert default user-defined node types into the database. For a complete
+  // list of available node type attributes, refer to the node type API
+  // documentation at: http://api.drupal.org/api/HEAD/function/hook_node_info.
+  $types = array(
+    array(
+      'type' => 'page',
+      'name' => st('Page'),
+      'module' => 'node',
+      'description' => st("A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page."),
+      'custom' => TRUE,
+      'modified' => TRUE,
+      'locked' => FALSE,
+      'help' => '',
+      'min_word_count' => '',
+    ),
+    array(
+      'type' => 'story',
+      'name' => st('Story'),
+      'module' => 'node',
+      'description' => st("A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments."),
+      'custom' => TRUE,
+      'modified' => TRUE,
+      'locked' => FALSE,
+      'help' => '',
+      'min_word_count' => '',
+    ),
+  );
+
+  foreach ($types as $type) {
+    $type = (object) _node_type_set_defaults($type);
+    node_type_save($type);
+  }
+
+  // Default page to not be promoted and have comments disabled.
+  variable_set('node_options_page', array('status'));
+  variable_set('comment_page', COMMENT_NODE_DISABLED);
+
+  // Don't display date and author information for page nodes by default.
+  $theme_settings = variable_get('theme_settings', array());
+  $theme_settings['toggle_node_info_page'] = FALSE;
+  variable_set('theme_settings', $theme_settings);
+
+  // Update the menu router information.
+  menu_rebuild();
+}
+
+/**
+ * Implementation of hook_form_alter().
+ *
+ * Allows the profile to alter the site-configuration form. This is
+ * called through custom invocation, so $form_state is not populated.
+ */
+function default_form_alter(&$form, $form_state, $form_id) {
+  if ($form_id == 'install_configure') {
+    // Set default for site name field.
+    $form['site_information']['site_name']['#default_value'] = $_SERVER['SERVER_NAME'];
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robots.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,63 @@
+# $Id: robots.txt,v 1.9 2007/06/27 22:37:44 goba Exp $
+#
+# robots.txt
+#
+# This file is to prevent the crawling and indexing of certain parts
+# of your site by web crawlers and spiders run by sites like Yahoo!
+# and Google. By telling these "robots" where not to go on your site,
+# you save bandwidth and server resources.
+#
+# This file will be ignored unless it is at the root of your host:
+# Used:    http://example.com/robots.txt
+# Ignored: http://example.com/site/robots.txt
+#
+# For more information about the robots.txt standard, see:
+# http://www.robotstxt.org/wc/robots.html
+#
+# For syntax checking, see:
+# http://www.sxw.org.uk/computing/robots/check.html
+
+User-agent: *
+Crawl-delay: 10
+# Directories
+Disallow: /database/
+Disallow: /includes/
+Disallow: /misc/
+Disallow: /modules/
+Disallow: /sites/
+Disallow: /themes/
+Disallow: /scripts/
+Disallow: /updates/
+Disallow: /profiles/
+# Files
+Disallow: /xmlrpc.php
+Disallow: /cron.php
+Disallow: /update.php
+Disallow: /install.php
+Disallow: /INSTALL.txt
+Disallow: /INSTALL.mysql.txt
+Disallow: /INSTALL.pgsql.txt
+Disallow: /CHANGELOG.txt
+Disallow: /MAINTAINERS.txt
+Disallow: /LICENSE.txt
+Disallow: /UPGRADE.txt
+# Paths (clean URLs)
+Disallow: /admin/
+Disallow: /comment/reply/
+Disallow: /contact/
+Disallow: /logout/
+Disallow: /node/add/
+Disallow: /search/
+Disallow: /user/register/
+Disallow: /user/password/
+Disallow: /user/login/
+# Paths (no clean URLs)
+Disallow: /?q=admin/
+Disallow: /?q=comment/reply/
+Disallow: /?q=contact/
+Disallow: /?q=logout/
+Disallow: /?q=node/add/
+Disallow: /?q=search/
+Disallow: /?q=user/password/
+Disallow: /?q=user/register/
+Disallow: /?q=user/login/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/code-clean.sh	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,10 @@
+#!/bin/sh
+# $Id: code-clean.sh,v 1.8 2005/08/11 13:02:08 dries Exp $
+
+find . -name "*~" -type f | xargs rm -f
+find . -name ".#*" -type f | xargs rm -f
+find . -name "*.rej" -type f | xargs rm -f
+find . -name "*.orig" -type f | xargs rm -f
+find . -name "DEADJOE" -type f | xargs rm -f
+find . -type f | grep -v ".psp" | grep -v ".gif" | grep -v ".jpg" | grep -v ".png" | grep -v ".tgz" | grep -v ".ico" | grep -v "druplicon" | xargs perl -wi -pe 's/\s+$/\n/'
+find . -type f | grep -v ".psp" | grep -v ".gif" | grep -v ".jpg" | grep -v ".png" | grep -v ".tgz" | grep -v ".ico" | grep -v "druplicon" | xargs perl -wi -pe 's/\t/  /g'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/code-style.pl	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,195 @@
+#!/usr/bin/perl -w
+# $Id: code-style.pl,v 1.14 2007/02/15 11:40:19 dries Exp $
+
+use Pod::Usage;
+use Getopt::Long qw(GetOptions);
+Getopt::Long::Configure ("bundling");
+
+my %opt = (  "help" => 0,
+    'debug' => 0,
+  );
+
+if(!GetOptions(\%opt,
+    'help|?',
+    'debug',
+    )) {
+  pod2usage(-exitval => 1, 'verbose'=>0);
+}
+
+pod2usage(-exitval => 0, -verbose => 2) if($opt{'help'});
+
+$debug = $opt{'debug'};
+
+$comment = 0; #flag used to signal we're inside /* */
+$program = 0; #flag used to signal we're inside <?php ?>
+#read the file
+while (<>) {
+  $org=$_;
+  s/\\["']//g;
+  # please don't use nested comments for now... thanks!
+  # handles comments // style, but don't mess with http://
+  s/\/\/[^:].*//;
+  # handles comments /**/ on a single line
+  s/\/\*.*\*\///g;
+  # handles comments /**/ over several lines
+  if ($comment == 1) {
+    if (s/.*\*\///) {
+      $comment = 0;
+    }
+    else {
+      next;
+    }
+  }
+  if (s/\/\*.*//) {
+    $comment = 1;
+  }
+  if (/^\s*#/) {
+    next;
+  }
+
+  if (s/<\?php//) {
+    $program = 1;
+  }
+  if (/\?>/) {
+    $program = 0;
+  }
+
+  # enforce "bar". foo() ."bar" syntax
+  if (/^("[^"]*"|[^"])*("[^"]*")\.[^ ]/ && $program) {
+    $msg = "'\".' -> '\". '";
+  }
+  elsif (/^("[^"]*"|[^"])*("[^"]*")\s+\./ && $program) {
+    $msg = "'\" .' -> '\".'";
+  }
+  # enforce "bar". foo() ."bar" syntax
+  elsif (/^("[^"]*"|[^"])*[^ "]\.("[^"]*")/ && $program) {
+    $msg = "'.\"' -> '.\"'";
+  }
+  elsif (/^("[^"]*"|[^"])*[^ "]\.\s+("[^"]*")/ && $program) {
+    $msg = "'. \"' -> '.\"'";
+  }
+  # XHTML requires closing tag
+  elsif (/<br>/i) {
+    $msg = "'<br>' -> '<br />'";
+  }
+  elsif (/\$REQUEST_URI/i) {
+    $msg = "the use of REQUEST_URI is prone to XSS exploits and does not work on IIS; use request_uri() instead";
+  }
+  elsif (/\"REQUEST_URI\"/i) {
+    $msg = "the use of REQUEST_URI is prone to XSS exploits and does not work on IIS; use request_uri() instead";
+  }
+
+  # XHTML compatibility mode suggests a blank before /
+  # i.e. <br />
+  elsif (/<[a-z][^>]*[^ >]\/>/i) {
+    $msg = "'<foo/".">' -> '<foo />'";
+  }
+  # we write '{' on the same line, not on the next
+  elsif (/^\s*{/ && $program) {
+    $msg = "take '{' to previous line";
+  }
+  elsif (/([a-z])([A-Z])/) {
+    $msg = "no mixed case function or variable names, use lower case and _";
+  }
+  elsif (/<[\/]*[A-Z]+[^>]*>/) {
+    $msg = "XHTML demands tags to be lowercase";
+  }
+
+  # trying to recognize splitted lines
+  # there are only a few valid last characters in programming mode,
+  # only sometimes it is ( if you use if/else with a single statement
+
+  # from here on we need no more strings
+  while (s/^([^"]*)"[^"]*"/$1#/) {};
+  while (s/^([^']*)'[^']*'/$1#/) {};
+
+  # it should be 'if (' all the time
+  if (/(^|[^a-zA-Z])(if|else|elseif|while|foreach|switch|return|for)\(/) {
+    $msg = "'(' -> ' ('";
+  }
+  #elsif (/[^;{}:\s\n]\s*\n*$/ && $program && !/^[\s}]*(if|else)/) {
+  #  $msg = "don't split lines";
+  #}
+  elsif (/\}\s*else/) {
+    $msg = "'} else' -> '}\\nelse'";
+  }
+  elsif (/[^{\s\n]\s*\n*$/ && $program && /^\s*(if|else)/) {
+    $msg = "every if/else needs a { at eol";
+  }
+  elsif (/([\(\[]) / && $program) {
+    $msg = "'$1 ' -> '$1'";
+  }
+  elsif (/\S ([\)\]])/ && $program) {
+    $msg = "' $1' -> '$1'";
+  }
+  # but no brackets
+  elsif (/([a-z-A-Z_][a-zA-Z0-9_-]*)\s+\(/ && $program) {
+    if ($1 ne "switch" and $1 ne "if" and $1 ne "while" and $1 ne "foreach" and $1 ne "return" and $1 ne "for" and $1 ne "elseif") {
+      $msg = "'$1 (' -> '$1('";
+    }
+  }
+  # there should be a space before '{'
+  if (/[^ ]{/ && $program) {
+    $msg = "missing space before '{'";
+  }
+  # there should be a space after ','
+  elsif (/[,][^ \n\r]/ && $program) {
+    $msg = "missing space after ','";
+  }
+  # spaces before and after, only foreach may use $foo=>bar
+  elsif (/[^ =|\-|\+](\+|\-)[^ =>|\-|\+]/ && $program && !/foreach/) {
+    $msg = "'$1' -> ' $1 '";
+  }
+  elsif (/[^ =](\*|==|\.=|=>|=|\|\|)[^ =>]/ && $program && !/foreach/) {
+    $msg = "'$1' -> ' $1 '";
+  }
+  # ensure $bar["foo"] and $bar[$foo] and $bar[0]
+  elsif (/\[[^#][^\]]*\]/ && !/\[[0-9\$][^\]]*\]/ && !/\[\]/) {
+    $msg = "only [\"foo\"], [\$foo] or [0] is allowed";
+  }
+  # first try to find missing quotes after = in (X)HTML tags
+  elsif (/<[^>]*=[a-zA-Z0-9][^>]*>/) {
+    $msg = "=... -> =\"...\"";
+  }
+  if (defined $msg) {
+    if ($debug==0) {
+      print $ARGV .":". $. .": $msg : ". $org;
+    }
+    undef $msg;
+  }
+  elsif ($debug==1) {
+    print $org;
+  }
+} continue {
+  close ARGV if eof;
+}
+
+__END__
+
+=head1 NAME
+
+code-style.pl - Review drupal code for style
+
+=head1 SYNOPSIS
+
+  code-style.pl [options] <filename>
+
+  Options:
+
+  -? --help  detailed help message
+
+=head1 DESCRIPTION
+
+Originally written for Drupal (http://drupal.org/) to ensure stylish
+code.  This program reviews PHP code, and tries to show as many code
+improvements as possible with no false positives.
+
+=head1 OPTIONS
+
+  --comment
+
+=head1 EXAMPLES
+
+ ./code-style.pl ../index.php
+
+=cut
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/cron-curl.sh	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,4 @@
+#!/bin/sh
+# $Id: cron-curl.sh,v 1.3 2006/08/22 07:38:24 dries Exp $
+
+curl --silent --compressed http://example.com/cron.php
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/cron-lynx.sh	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,4 @@
+#!/bin/sh
+# $Id: cron-lynx.sh,v 1.3 2006/08/22 07:38:24 dries Exp $
+
+/usr/bin/lynx -source http://example.com/cron.php > /dev/null 2>&1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/drupal.sh	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,144 @@
+#!/usr/bin/php
+<?php
+// $Id: drupal.sh,v 1.4 2007/07/02 14:41:37 dries Exp $
+
+/**
+ * Drupal shell execution script
+ *
+ * Check for your PHP interpreter - on Windows you'll probably have to
+ * replace line 1 with
+ *   #!c:/program files/php/php.exe
+ *
+ * @param path  Drupal's absolute root directory in local file system (optional).
+ * @param URI   A URI to execute, including HTTP protocol prefix.
+ */
+$script = basename(array_shift($_SERVER['argv']));
+
+if (in_array('--help', $_SERVER['argv'])) {
+  echo <<<EOF
+
+Execute a Drupal page from the shell.
+
+Usage:        {$script} [OPTIONS] "<URI>"
+Example:      {$script} "http://mysite.org/node"
+
+All arguments are long options.
+
+  --help      This page.
+
+  --root      Set the working directory for the script to the specified path.
+              To execute Drupal this has to be the root directory of your
+              Drupal installation, f.e. /home/www/foo/drupal (assuming Drupal
+              running on Unix). Current directory is not required.
+              Use surrounding quotation marks on Windows.
+
+  --verbose   This option displays the options as they are set, but will
+              produce errors from setting the session.
+
+  URI         The URI to execute, i.e. http://default/foo/bar for executing
+              the path '/foo/bar' in your site 'default'.  URI has to be
+              enclosed by quotation marks if there are ampersands in it
+              (f.e. index.php?q=node&foo=bar).  Prefix 'http://' is required,
+              and the domain must exist in Drupal's sites-directory.
+
+              If the given path and file exists it will be executed directly,
+              i.e. if URI is set to http://default/bar/foo.php
+              and bar/foo.php exists, this script will be executed without
+              bootstrapping Drupal.  To execute Drupal's cron.php, specify
+              http://default/cron.php as the URI.
+
+
+To run this script without --root argument invoke it from the root directory
+of your Drupal installation with
+
+  ./scripts/{$script}
+\n
+EOF;
+  exit;
+}
+
+// define default settings
+$cmd = 'index.php';
+$_SERVER['HTTP_HOST']       = 'default';
+$_SERVER['PHP_SELF']        = '/index.php';
+$_SERVER['REMOTE_ADDR']     = '127.0.0.1';
+$_SERVER['SERVER_SOFTWARE'] = 'PHP CLI';
+$_SERVER['REQUEST_METHOD']  = 'GET';
+$_SERVER['QUERY_STRING']    = '';
+$_SERVER['PHP_SELF']        = $_SERVER['REQUEST_URI'] = '/';
+
+// toggle verbose mode
+if (in_array('--verbose', $_SERVER['argv'])) {
+  $_verbose_mode = true;
+}
+else {
+  $_verbose_mode = false;
+}
+
+// parse invocation arguments
+while ($param = array_shift($_SERVER['argv'])) {
+  switch ($param) {
+    case '--root':
+      // change working directory
+      $path = array_shift($_SERVER['argv']);
+      if (is_dir($path)) {
+        chdir($path);
+        if ($_verbose_mode) {
+          echo "cwd changed to: {$path}\n";
+        }
+      }
+      else {
+        echo "\nERROR: {$path} not found.\n\n";
+      }
+      break;
+
+    default:
+      if (substr($param, 0, 2) == '--') {
+        // ignore unknown options
+        break;
+      }
+      else {
+        // parse the URI
+        $path = parse_url($param);
+
+        // set site name
+        if (isset($path['host'])) {
+          $_SERVER['HTTP_HOST'] = $path['host'];
+        }
+
+        // set query string
+        if (isset($path['query'])) {
+          $_SERVER['QUERY_STRING'] = $path['query'];
+          parse_str($path['query'], $_GET);
+          $_REQUEST = $_GET;
+        }
+
+        // set file to execute or Drupal path (clean urls enabled)
+        if (isset($path['path']) && file_exists(substr($path['path'], 1))) {
+          $_SERVER['PHP_SELF'] = $_SERVER['REQUEST_URI'] = $path['path'];
+          $cmd = substr($path['path'], 1);
+        }
+        else if (isset($path['path'])) {
+          if (!isset($_GET['q'])) {
+            $_REQUEST['q'] = $_GET['q'] = $path['path'];
+          }
+        }
+
+        // display setup in verbose mode
+        if ($_verbose_mode) {
+          echo "Hostname set to: {$_SERVER['HTTP_HOST']}\n";
+          echo "Script name set to: {$cmd}\n";
+          echo "Path set to: {$_GET['q']}\n";
+        }
+      }
+      break;
+  }
+}
+
+if (file_exists($cmd)) {
+  include $cmd;
+}
+else {
+  echo "\nERROR: {$cmd} not found.\n\n";
+}
+exit();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sites/all/README.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,9 @@
+// $Id: README.txt,v 1.3 2006/12/23 15:35:51 dries Exp $
+
+This directory should be used to place downloaded and custom modules
+and themes which are common to all sites. This will allow you to
+more easily update Drupal core files. These modules and themes should
+be placed in subdirectories called modules and themes as follows:
+
+  sites/all/modules
+  sites/all/themes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sites/default/default.settings.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,228 @@
+<?php
+// $Id: default.settings.php,v 1.8 2007/12/20 09:35:10 goba Exp $
+
+/**
+ * @file
+ * Drupal site-specific configuration file.
+ *
+ * IMPORTANT NOTE:
+ * This file may have been set to read-only by the Drupal installation
+ * program. If you make changes to this file, be sure to protect it again
+ * after making your modifications. Failure to remove write permissions
+ * to this file is a security risk.
+ *
+ * The configuration file to be loaded is based upon the rules below.
+ *
+ * The configuration directory will be discovered by stripping the
+ * website's hostname from left to right and pathname from right to
+ * left. The first configuration file found will be used and any
+ * others will be ignored. If no other configuration file is found
+ * then the default configuration file at 'sites/default' will be used.
+ *
+ * For example, for a fictitious site installed at
+ * http://www.drupal.org/mysite/test/, the 'settings.php'
+ * is searched in the following directories:
+ *
+ *  1. sites/www.drupal.org.mysite.test
+ *  2. sites/drupal.org.mysite.test
+ *  3. sites/org.mysite.test
+ *
+ *  4. sites/www.drupal.org.mysite
+ *  5. sites/drupal.org.mysite
+ *  6. sites/org.mysite
+ *
+ *  7. sites/www.drupal.org
+ *  8. sites/drupal.org
+ *  9. sites/org
+ *
+ * 10. sites/default
+ *
+ * If you are installing on a non-standard port number, prefix the
+ * hostname with that number. For example,
+ * http://www.drupal.org:8080/mysite/test/ could be loaded from
+ * sites/8080.www.drupal.org.mysite.test/.
+ */
+
+/**
+ * Database settings:
+ *
+ * Note that the $db_url variable gets parsed using PHP's built-in
+ * URL parser (i.e. using the "parse_url()" function) so make sure
+ * not to confuse the parser. If your username, password
+ * or database name contain characters used to delineate
+ * $db_url parts, you can escape them via URI hex encodings:
+ *
+ *   : = %3a   / = %2f   @ = %40
+ *   + = %2b   ( = %28   ) = %29
+ *   ? = %3f   = = %3d   & = %26
+ *
+ * To specify multiple connections to be used in your site (i.e. for
+ * complex custom modules) you can also specify an associative array
+ * of $db_url variables with the 'default' element used until otherwise
+ * requested.
+ *
+ * You can optionally set prefixes for some or all database table names
+ * by using the $db_prefix setting. If a prefix is specified, the table
+ * name will be prepended with its value. Be sure to use valid database
+ * characters only, usually alphanumeric and underscore. If no prefixes
+ * are desired, leave it as an empty string ''.
+ *
+ * To have all database names prefixed, set $db_prefix as a string:
+ *
+ *   $db_prefix = 'main_';
+ *
+ * To provide prefixes for specific tables, set $db_prefix as an array.
+ * The array's keys are the table names and the values are the prefixes.
+ * The 'default' element holds the prefix for any tables not specified
+ * elsewhere in the array. Example:
+ *
+ *   $db_prefix = array(
+ *     'default'   => 'main_',
+ *     'users'     => 'shared_',
+ *     'sessions'  => 'shared_',
+ *     'role'      => 'shared_',
+ *     'authmap'   => 'shared_',
+ *     'sequences' => 'shared_',
+ *   );
+ *
+ * Database URL format:
+ *   $db_url = 'mysql://username:password@localhost/databasename';
+ *   $db_url = 'mysqli://username:password@localhost/databasename';
+ *   $db_url = 'pgsql://username:password@localhost/databasename';
+ */
+$db_url = 'mysql://username:password@localhost/databasename';
+$db_prefix = '';
+
+/**
+ * Access control for update.php script
+ *
+ * If you are updating your Drupal installation using the update.php script
+ * being not logged in as administrator, you will need to modify the access
+ * check statement below. Change the FALSE to a TRUE to disable the access
+ * check. After finishing the upgrade, be sure to open this file again
+ * and change the TRUE back to a FALSE!
+ */
+$update_free_access = FALSE;
+
+/**
+ * Base URL (optional).
+ *
+ * If you are experiencing issues with different site domains,
+ * uncomment the Base URL statement below (remove the leading hash sign)
+ * and fill in the URL to your Drupal installation.
+ *
+ * You might also want to force users to use a given domain.
+ * See the .htaccess file for more information.
+ *
+ * Examples:
+ *   $base_url = 'http://www.example.com';
+ *   $base_url = 'http://www.example.com:8888';
+ *   $base_url = 'http://www.example.com/drupal';
+ *   $base_url = 'https://www.example.com:8888/drupal';
+ *
+ * It is not allowed to have a trailing slash; Drupal will add it
+ * for you.
+ */
+# $base_url = 'http://www.example.com';  // NO trailing slash!
+
+/**
+ * PHP settings:
+ *
+ * To see what PHP settings are possible, including whether they can
+ * be set at runtime (ie., when ini_set() occurs), read the PHP
+ * documentation at http://www.php.net/manual/en/ini.php#ini.list
+ * and take a look at the .htaccess file to see which non-runtime
+ * settings are used there. Settings defined here should not be
+ * duplicated there so as to avoid conflict issues.
+ */
+ini_set('arg_separator.output',     '&amp;');
+ini_set('magic_quotes_runtime',     0);
+ini_set('magic_quotes_sybase',      0);
+ini_set('session.cache_expire',     200000);
+ini_set('session.cache_limiter',    'none');
+ini_set('session.cookie_lifetime',  2000000);
+ini_set('session.gc_maxlifetime',   200000);
+ini_set('session.save_handler',     'user');
+ini_set('session.use_only_cookies', 1);
+ini_set('session.use_trans_sid',    0);
+ini_set('url_rewriter.tags',        '');
+
+/**
+ * Drupal automatically generates a unique session cookie name for each site
+ * based on on its full domain name. If you have multiple domains pointing at
+ * the same Drupal site, you can either redirect them all to a single domain
+ * (see comment in .htaccess), or uncomment the line below and specify their
+ * shared base domain. Doing so assures that users remain logged in as they
+ * cross between your various domains.
+ */
+# $cookie_domain = 'example.com';
+
+/**
+ * Variable overrides:
+ *
+ * To override specific entries in the 'variable' table for this site,
+ * set them here. You usually don't need to use this feature. This is
+ * useful in a configuration file for a vhost or directory, rather than
+ * the default settings.php. Any configuration setting from the 'variable'
+ * table can be given a new value. Note that any values you provide in
+ * these variable overrides will not be modifiable from the Drupal
+ * administration interface.
+ *
+ * Remove the leading hash signs to enable.
+ */
+# $conf = array(
+#   'site_name' => 'My Drupal site',
+#   'theme_default' => 'minnelli',
+#   'anonymous' => 'Visitor',
+/**
+ * A custom theme can be set for the off-line page. This applies when the site
+ * is explicitly set to off-line mode through the administration page or when
+ * the database is inactive due to an error. It can be set through the
+ * 'maintenance_theme' key. The template file should also be copied into the
+ * theme. It is located inside 'modules/system/maintenance-page.tpl.php'.
+ * Note: This setting does not apply to installation and update pages.
+ */
+#   'maintenance_theme' => 'minnelli',
+/**
+ * reverse_proxy accepts a boolean value.
+ *
+ * Enable this setting to determine the correct IP address of the remote
+ * client by examining information stored in the X-Forwarded-For headers.
+ * X-Forwarded-For headers are a standard mechanism for identifying client
+ * systems connecting through a reverse proxy server, such as Squid or
+ * Pound. Reverse proxy servers are often used to enhance the performance
+ * of heavily visited sites and may also provide other site caching,
+ * security or encryption benefits. If this Drupal installation operates
+ * behind a reverse proxy, this setting should be enabled so that correct
+ * IP address information is captured in Drupal's session management,
+ * logging, statistics and access management systems; if you are unsure
+ * about this setting, do not have a reverse proxy, or Drupal operates in
+ * a shared hosting environment, this setting should be set to disabled.
+ */
+#   'reverse_proxy' => TRUE,
+/**
+ * reverse_proxy accepts an array of IP addresses.
+ *
+ * Each element of this array is the IP address of any of your reverse
+ * proxies. Filling this array Drupal will trust the information stored
+ * in the X-Forwarded-For headers only if Remote IP address is one of
+ * these, that is the request reaches the web server from one of your
+ * reverse proxies. Otherwise, the client could directly connect to
+ * your web server spoofing the X-Forwarded-For headers.
+ */
+#   'reverse_proxy_addresses' => array('a.b.c.d', ...),
+# );
+
+/**
+ * String overrides:
+ *
+ * To override specific strings on your site with or without enabling locale
+ * module, add an entry to this list. This functionality allows you to change
+ * a small number of your site's default English language interface strings.
+ *
+ * Remove the leading hash signs to enable.
+ */
+# $conf['locale_custom_strings_en'] = array(
+#   'forum'      => 'Discussion board',
+#   '@count min' => '@count minutes',
+# );
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/README.txt	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+This directory is reserved for core theme files. Custom or contributed
+themes should be placed in their own subdirectory of the sites/all/themes
+directory. For multisite installations, they can also be placed in a subdirectory
+under /sites/{sitename}/themes/, where {sitename} is the name of your site
+(e.g., www.example.com). This will allow you to more easily update Drupal core files.
+
+For more details, see: http://drupal.org/node/176043
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+<?php
+// $Id: block.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
+?>
+  <div class="block block-<?php print $block->module; ?>" id="block-<?php print $block->module; ?>-<?php print $block->delta; ?>">
+    <h2 class="title"><?php print $block->subject; ?></h2>
+    <div class="content"><?php print $block->content; ?></div>
+ </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/bluemarine.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: bluemarine.info,v 1.4 2007/06/08 05:50:57 dries Exp $
+name = Bluemarine
+description = Table-based multi-column theme with a marine and ash color scheme.
+version = VERSION
+core = 6.x
+engine = phptemplate
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/box.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+<?php
+// $Id: box.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
+?>
+  <div class="box">
+    <?php if ($title) { ?><h2 class="title"><?php print $title; ?></h2><?php } ?>
+    <div class="content"><?php print $content; ?></div>
+ </div>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/comment.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,20 @@
+<?php
+// $Id: comment.tpl.php,v 1.7 2008/01/04 19:24:23 goba Exp $
+?>
+  <div class="comment<?php print ' '. $status; ?>">
+    <?php if ($picture) {
+    print $picture;
+  } ?>
+<h3 class="title"><?php print $title; ?></h3><?php if ($new != '') { ?><span class="new"><?php print $new; ?></span><?php } ?>
+    <div class="submitted"><?php print $submitted; ?></div>
+    <div class="content">
+     <?php print $content; ?>
+     <?php if ($signature): ?>
+      <div class="clear-block">
+       <div>—</div>
+       <?php print $signature ?>
+      </div>
+     <?php endif; ?>
+    </div>
+    <div class="links">&raquo; <?php print $links; ?></div>
+  </div>
Binary file themes/bluemarine/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/node.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,13 @@
+<?php
+// $Id: node.tpl.php,v 1.7 2007/08/07 08:39:36 goba Exp $
+?>
+  <div class="node<?php if ($sticky) { print " sticky"; } ?><?php if (!$status) { print " node-unpublished"; } ?>">
+    <?php if ($picture) {
+      print $picture;
+    }?>
+    <?php if ($page == 0) { ?><h2 class="title"><a href="<?php print $node_url?>"><?php print $title?></a></h2><?php }; ?>
+    <span class="submitted"><?php print $submitted?></span>
+    <div class="taxonomy"><?php print $terms?></div>
+    <div class="content"><?php print $content?></div>
+    <?php if ($links) { ?><div class="links">&raquo; <?php print $links?></div><?php }; ?>
+  </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,63 @@
+<?php
+// $Id: page.tpl.php,v 1.28 2008/01/24 09:42:52 goba Exp $
+?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language->language ?>" xml:lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+
+<head>
+  <title><?php print $head_title ?></title>
+  <?php print $head ?>
+  <?php print $styles ?>
+  <?php print $scripts ?>
+  <script type="text/javascript"><?php /* Needed to avoid Flash of Unstyle Content in IE */ ?> </script>
+</head>
+
+<body>
+
+<table border="0" cellpadding="0" cellspacing="0" id="header">
+  <tr>
+    <td id="logo">
+      <?php if ($logo) { ?><a href="<?php print $front_page ?>" title="<?php print t('Home') ?>"><img src="<?php print $logo ?>" alt="<?php print t('Home') ?>" /></a><?php } ?>
+      <?php if ($site_name) { ?><h1 class='site-name'><a href="<?php print $front_page ?>" title="<?php print t('Home') ?>"><?php print $site_name ?></a></h1><?php } ?>
+      <?php if ($site_slogan) { ?><div class='site-slogan'><?php print $site_slogan ?></div><?php } ?>
+    </td>
+    <td id="menu">
+      <?php if (isset($secondary_links)) { ?><?php print theme('links', $secondary_links, array('class' => 'links', 'id' => 'subnavlist')) ?><?php } ?>
+      <?php if (isset($primary_links)) { ?><?php print theme('links', $primary_links, array('class' => 'links', 'id' => 'navlist')) ?><?php } ?>
+      <?php print $search_box ?>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2"><div><?php print $header ?></div></td>
+  </tr>
+</table>
+
+<table border="0" cellpadding="0" cellspacing="0" id="content">
+  <tr>
+    <?php if ($left) { ?><td id="sidebar-left">
+      <?php print $left ?>
+    </td><?php } ?>
+    <td valign="top">
+      <?php if ($mission) { ?><div id="mission"><?php print $mission ?></div><?php } ?>
+      <div id="main">
+        <?php print $breadcrumb ?>
+        <h1 class="title"><?php print $title ?></h1>
+        <div class="tabs"><?php print $tabs ?></div>
+        <?php if ($show_messages) { print $messages; } ?>
+        <?php print $help ?>
+        <?php print $content; ?>
+        <?php print $feed_icons; ?>
+      </div>
+    </td>
+    <?php if ($right) { ?><td id="sidebar-right">
+      <?php print $right ?>
+    </td><?php } ?>
+  </tr>
+</table>
+
+<div id="footer">
+  <?php print $footer_message ?>
+  <?php print $footer ?>
+</div>
+<?php print $closure ?>
+</body>
+</html>
Binary file themes/bluemarine/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/style-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,37 @@
+/* $Id: style-rtl.css,v 1.5 2007/12/17 15:05:09 goba Exp $ */
+body {
+  direction: rtl;
+}
+#logo img {
+  float: right;
+}
+#menu {
+  padding: 0.5em 0.5em 0 0.5em;
+  text-align: left;
+}
+#navlist {
+  padding: 0 0 1.2em 0.8em;
+}
+#subnavlist {
+  padding: 0.5em 0 0.4em 1.2em;
+}
+ul.links li {
+  border-right: 1px solid #9cf;
+  border-left: none;
+}
+.block, .box {
+  padding: 0 1.5em 0 0;
+}
+.node .taxonomy {
+  padding-right: 1.5em;
+}
+.node .picture {
+  float: left;
+}
+.comment .new {
+  text-align: left;
+  float: left;
+}
+.comment .picture {
+  float: left;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/bluemarine/style.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,348 @@
+/* $Id: style.css,v 1.23 2007/12/17 15:05:09 goba Exp $ */
+
+/*
+** HTML elements
+*/
+body {
+  margin: 0;
+  padding: 0;
+  color: #000;
+  background-color: #fff;
+  font: 76% Verdana, Arial, Helvetica, sans-serif;
+}
+tr.odd td, tr.even td {
+  padding: 0.3em;
+}
+h1, h2, h3, h4, h5, h6 {
+  margin-bottom: 0.5em;
+}
+h1 {
+  font-size: 1.3em;
+}
+h2 {
+  font-size: 1.2em;
+}
+h3, h4, h5, h6 {
+  font-size: 1.1em;
+}
+p {
+  margin-top: 0.5em;
+  margin-bottom: 0.9em;
+}
+a {
+  text-decoration: none;
+  font-weight: bold;
+}
+a:link {
+  color: #39c;
+}
+a:visited {
+  color: #369;
+}
+a:hover {
+  color: #39c;
+  text-decoration: underline;
+}
+fieldset {
+  border: 1px solid #ccc;
+}
+pre {
+  background-color: #eee;
+  padding: 0.75em 1.5em;
+  font-size: 12px;
+  border: 1px solid #ddd;
+}
+table {
+  /* make <td> sizes relative to body size! */
+  font-size: 1em;
+}
+.form-item label {
+  font-size: 1em;
+  color: #222;
+}
+.item-list .title {
+  font-size: 1em;
+  color: #222;
+}
+.links {
+  margin-bottom: 0;
+}
+.comment .links {
+  margin-bottom: 0;
+}
+
+/*
+** Page layout blocks / IDs
+*/
+#header, #content {
+  width: 100%;
+}
+#header {
+  background-color: #69c;
+}
+#logo {
+  vertical-align: middle;
+  border: 0;
+}
+#logo img {
+  float: left; /* LTR */
+  padding: 0 1em;
+  border: 0;
+}
+#menu {
+  padding: 0.5em 0.5em 0 0.5em; /* LTR */
+  text-align: right; /* LTR */
+  vertical-align: middle;
+}
+#navlist {
+  font-size: 1.0em;
+  padding: 0 0.8em 1.2em 0; /* LTR */
+  color: #9cf;
+}
+#navlist a {
+  font-weight: bold;
+  color: #fff;
+}
+#subnavlist {
+  padding: 0.5em 1.2em 0.4em 0; /* LTR */
+  font-size: 0.8em;
+  color: #9cf;
+}
+#subnavlist a {
+  font-weight: bold;
+  color: #9cf;
+}
+ul.links li {
+  border-left: 1px solid #9cf; /* LTR */
+}
+ul.links li.first {
+  border: none;
+}
+#search .form-text, #search .form-submit {
+  border: 1px solid #369;
+  font-size: 1.1em;
+  height: 1.5em;
+  vertical-align: middle;
+}
+#search .form-text {
+  width: 8em;
+  padding: 0 0.5em;
+}
+#mission {
+  background-color: #369;
+  padding: 1.5em 2em;
+  color: #fff;
+}
+#mission a, #mission a:visited {
+  color: #9cf;
+  font-weight: bold;
+}
+.site-name {
+  margin: 0.6em 0 0 ;
+  padding: 0;
+  font-size: 2em;
+}
+.site-name a:link, .site-name a:visited {
+  color: #fff;
+}
+.site-name a:hover {
+  color: #369;
+  text-decoration: none;
+}
+.site-slogan {
+  font-size: 1em;
+  color: #eee;
+  display: block;
+  margin: 0;
+  font-style: italic;
+  font-weight: bold;
+}
+#main {
+  /* padding in px not ex because IE messes up 100% width tables otherwise */
+  padding: 10px;
+}
+#mission, .node .content, .comment .content {
+  line-height: 1.4em;
+}
+#help {
+  font-size: 0.9em;
+  margin-bottom: 1em;
+}
+.breadcrumb {
+  margin-bottom: .5em;
+}
+.messages {
+  background-color: #eee;
+  border: 1px solid #ccc;
+  padding: 0.3em;
+  margin-bottom: 1em;
+}
+.error {
+  border-color: red;
+}
+#sidebar-left, #sidebar-right {
+  background-color: #ddd;
+  width: 16em;
+  /* padding in px not ex because IE messes up 100% width tables otherwise */
+  padding: 10px;
+  vertical-align: top;
+}
+#footer {
+  background-color: #eee;
+  padding: 1em;
+  font-size: 0.8em;
+}
+
+/*
+** Common declarations for child classes of node, comment, block, box, etc.
+** If you want any of them styled differently for a specific parent, add
+** additional rules /with only the differing properties!/ to .parent .class.
+** See .comment .title for an example.
+*/
+.title, .title a {
+  font-weight: bold;
+  font-size: 1.3em;
+  color: #777;
+  margin: 0 auto;  /* decrease default margins for h<x>.title */
+}
+.submitted {
+  color: #999;
+  font-size: 0.8em;
+}
+.links {
+  color: #999;
+}
+.links a {
+  font-weight: bold;
+}
+.block, .box {
+  padding: 0 0 1.5em 0; /* LTR */
+}
+.block {
+  border-bottom: 1px solid #bbb;
+  padding-bottom: 0.75em;
+  margin-bottom: 1.5em;
+}
+.block .title {
+  margin-bottom: .25em;
+}
+.box .title {
+  font-size: 1.1em;
+}
+.node {
+  margin: .5em 0 2em; /* LTR */
+}
+.sticky {
+  padding: .5em;
+  background-color: #eee;
+  border: solid 1px #ddd;
+}
+.node .content, .comment .content {
+  margin: .5em 0 .5em;
+}
+.node .taxonomy {
+  color: #999;
+  font-size: 0.8em;
+  padding-left: 1.5em; /* LTR */
+}
+.node .picture {
+  border: 1px solid #ddd;
+  float: right; /* LTR */
+  margin: 0.5em;
+}
+.comment {
+  border: 1px solid #abc;
+  padding: .5em;
+  margin-bottom: 1em;
+}
+.comment .title a {
+  font-size: 1.1em;
+  font-weight: normal;
+}
+.comment .new {
+  text-align: right; /* LTR */
+  font-weight: bold;
+  font-size: 0.8em;
+  float: right; /* LTR */
+  color: red;
+}
+.comment .picture {
+  border: 1px solid #abc;
+  float: right; /* LTR */
+  margin: 0.5em;
+}
+
+/*
+** Module specific styles
+*/
+#aggregator .feed-source {
+  background-color: #eee;
+  border: 1px solid #ccc;
+  padding: 1em;
+  margin: 1em 0;
+}
+#aggregator .news-item .categories, #aggregator .source, #aggregator .age {
+  color: #999;
+  font-style: italic;
+  font-size: 0.9em;
+}
+#aggregator .title {
+  margin-bottom: 0.5em;
+  font-size: 1em;
+}
+#aggregator h3 {
+  margin-top: 1em;
+}
+#forum table {
+  width: 100%;
+}
+#forum td {
+  padding: 0.5em;
+}
+#forum td.forum, #forum td.posts {
+  background-color: #eee;
+}
+#forum td.topics, #forum td.last-reply {
+  background-color: #ddd;
+}
+#forum td.container {
+  background-color: #ccc;
+}
+#forum td.container a {
+  color: #555;
+}
+#forum td.statistics, #forum td.settings, #forum td.pager {
+  height: 1.5em;
+  border: 1px solid #bbb;
+}
+#forum td .name {
+  color: #96c;
+}
+#forum td .links {
+  padding-top: 0.7em;
+  font-size: 0.9em;
+}
+#profile .profile {
+  clear: both;
+  border: 1px solid #abc;
+  padding: .5em;
+  margin: 1em 0em;
+}
+#profile .profile .name {
+  padding-bottom: 0.5em;
+}
+.block-forum h3 {
+  margin-bottom: .5em;
+}
+div.admin-panel .description {
+  color: #999;
+}
+div.admin-panel .body {
+  background: #f4f4f4;
+}
+div.admin-panel h3 {
+  background-color: #69c;
+  color: #fff;
+  padding: 5px 8px 5px;
+  margin: 0;
+}
Binary file themes/chameleon/background.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/chameleon.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,13 @@
+; $Id: chameleon.info,v 1.4 2007/07/01 23:27:31 goba Exp $
+name = Chameleon
+description = Minimalist tabled theme with light colors.
+regions[left] = Left sidebar
+regions[right] = Right sidebar
+features[] = logo
+features[] = favicon
+features[] = name
+features[] = slogan
+stylesheets[all][] = style.css
+stylesheets[all][] = common.css
+version = VERSION
+core = 6.x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/chameleon.theme	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,182 @@
+<?php
+// $Id: chameleon.theme,v 1.76 2008/01/24 09:42:53 goba Exp $
+
+/**
+ * @file
+ * A slim, CSS-driven theme which does not depend on a template engine like phptemplate
+ */
+
+/**
+ * Implementation of hook_theme. Auto-discover theme functions.
+ */
+function chameleon_theme($existing, $type, $theme, $path) {
+  return drupal_find_theme_functions($existing, array($theme));
+}
+
+function chameleon_page($content, $show_blocks = TRUE, $show_messages = TRUE) {
+  $language = $GLOBALS['language']->language;
+  $direction = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
+
+  if (theme_get_setting('toggle_favicon')) {
+    drupal_set_html_head('<link rel="shortcut icon" href="'. check_url(theme_get_setting('favicon')) .'" type="image/x-icon" />');
+  }
+
+  $title = drupal_get_title();
+
+  // Get blocks before so that they can alter the header (JavaScript, Stylesheets etc.)
+  $blocks_left = theme_blocks('left');
+  $blocks_right = theme_blocks('right');
+
+  $output  = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
+  $output .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"$language\" xml:lang=\"$language\" dir=\"$direction\">\n";
+  $output .= "<head>\n";
+  $output .= " <title>". ($title ? strip_tags($title) ." | ". variable_get("site_name", "Drupal") : variable_get("site_name", "Drupal") ." | ". variable_get("site_slogan", "")) ."</title>\n";
+  $output .= drupal_get_html_head();
+  $output .= drupal_get_css();
+  $output .= drupal_get_js();
+  $output .= "</head>";
+  $output .= "<body>\n";
+  $output .= " <div id=\"header\">";
+
+  if ($logo = theme_get_setting('logo')) {
+    $output .= "  <a href=\"". url() ."\" title=\"". t('Home') ."\"><img src=\"$logo\" alt=\"". t('Home') ."\" /></a>";
+  }
+  if (theme_get_setting('toggle_name')) {
+    $output .= "  <h1 class=\"site-name title\">". l(variable_get('site_name', 'drupal'), "") ."</h1>";
+  }
+  if (theme_get_setting('toggle_slogan')) {
+    $output .= "  <div class=\"site-slogan\">". variable_get('site_slogan', '') ."</div>";
+  }
+
+  $output .= "</div>\n";
+
+  $primary_links = theme('links', menu_primary_links(), array('class' => 'links', 'id' => 'navlist'));
+  $secondary_links = theme('links', menu_secondary_links(), array('class' => 'links', 'id' => 'subnavlist'));
+  if (isset($primary_links) || isset($secondary_links)) {
+    $output .= ' <div class="navlinks">';
+    if (isset($primary_links)) {
+      $output .= $primary_links;
+    }
+    if (isset($secondary_links)) {
+      $output .= $secondary_links;
+    }
+    $output .= " </div>\n";
+  }
+
+  $output .= " <table id=\"content\">\n";
+  $output .= "  <tr>\n";
+
+  if ($show_blocks && !empty($blocks_left)) {
+    $output .= "   <td id=\"sidebar-left\">$blocks_left</td>\n";
+  }
+
+  $output .= "   <td id=\"main\">\n";
+
+  if ($title) {
+    $output .= theme("breadcrumb", drupal_get_breadcrumb());
+    $output .= "<h2>$title</h2>";
+  }
+
+  if ($tabs = theme('menu_local_tasks')) {
+    $output .= $tabs;
+  }
+
+  if ($show_messages) {
+    $output .= theme('status_messages');
+  }
+
+  $output .= theme('help');
+
+  $output .= "\n<!-- begin content -->\n";
+  $output .= $content;
+  $output .= drupal_get_feeds();
+  $output .= "\n<!-- end content -->\n";
+
+  if ($footer = variable_get('site_footer', '')) {
+    $output .= " <div id=\"footer\">$footer</div>\n";
+  }
+
+  $output .= "   </td>\n";
+
+  if ($show_blocks && !empty($blocks_right)) {
+    $output .= "   <td id=\"sidebar-right\">$blocks_right</td>\n";
+  }
+
+  $output .= "  </tr>\n";
+  $output .= " </table>\n";
+
+  $output .=  theme_closure();
+  $output .= " </body>\n";
+  $output .= "</html>\n";
+
+  return $output;
+}
+
+function chameleon_node($node, $teaser = 0, $page = 0) {
+
+  $output  = "<div class=\"node". ((!$node->status) ? ' node-unpublished' : '') . (($node->sticky) ? ' sticky' : '') ."\">\n";
+
+  if (!$page) {
+    $output .= " <h2 class=\"title\">". ($teaser ? l($node->title, "node/$node->nid") : check_plain($node->title)) ."</h2>\n";
+  }
+
+  $output .= " <div class=\"content\">\n";
+
+  if ($teaser && $node->teaser) {
+    $output .= $node->teaser;
+  }
+  elseif (isset($node->body)) {
+    $output .= $node->body;
+  }
+
+  $output .= " </div>\n";
+
+  $submitted['node_submitted'] = theme_get_setting("toggle_node_info_$node->type") ? array(
+    'title' => t("By !author at @date", array('!author' => theme('username', $node), '@date' => format_date($node->created, 'small'))),
+    'html' => TRUE) : array();
+
+  $terms = array();
+  if (module_exists('taxonomy')) {
+    $terms = taxonomy_link("taxonomy terms", $node);
+  }
+
+  $links = array_merge($submitted, $terms);
+  if (isset($node->links)) {
+    $links = array_merge($links, $node->links);
+  }
+  if (count($links)) {
+    $output .= '<div class="links">'. theme('links', $links, array('class' => 'links inline')) ."</div>\n";
+  }
+
+  $output .= "</div>\n";
+
+  return $output;
+}
+
+function chameleon_comment($comment, $node, $links = array()) {
+  $submitted['comment_submitted'] = array(
+    'title' => t('By !author at @date', array('!author' => theme('username', $comment), '@date' => format_date($comment->timestamp, 'small'))),
+    'html' => TRUE);
+
+  $output  = "<div class=\"comment". ' '. $status ."\">\n";
+  $output .= " <h3 class=\"title\">". l($comment->subject, $_GET['q'], array('fragment' => "comment-$comment->cid")) ."</h3>\n";
+  $output .= " <div class=\"content\">". $comment->comment;
+  if (!empty($signature)) {
+    $output .= "  <div class=\"clear-block\">";
+    $output .= "<div>—</div>\n";
+    $output .= $signature ."\n";
+    $output .= "  </div>\n";
+  }
+  $output .= " </div>\n";
+  $output .= " <div class=\"links\">". theme('links', array_merge($submitted, $links)) ."</div>\n";
+  $output .= "</div>\n";
+
+  return $output;
+}
+
+function chameleon_help() {
+  if ($help = menu_get_active_help()) {
+    return '<div class="help">'. $help .'</div><hr />';
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/common-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,19 @@
+/* $Id: common-rtl.css,v 1.2 2007/06/05 09:15:02 dries Exp $ */
+
+body {
+  direction: rtl;
+}
+.navlinks {
+  padding: 0em 0em 1.5em 0.5em;
+}
+.primary a {
+  padding: 0em 0em 0em 0.5em;
+}
+.secondary a {
+  padding: 0em 0em 0em 0.5em;
+}
+
+#header img {
+  float: right;
+  padding: 0em 0em .5em 2em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/common.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,151 @@
+/* $Id: common.css,v 1.12 2007/06/04 11:10:38 goba Exp $ */
+
+/*
+** HTML elements
+*/
+a, a:link, a:active {
+  font-weight: bold;
+  text-decoration: none;
+}
+a:hover {
+  text-decoration: underline;
+}
+body {
+  margin: 0;
+  padding: 3em;
+  font-size: .9em;
+  line-height: 1.3em;
+}
+blockquote {
+  font-style: italic;
+}
+table {
+  margin: 0;
+  padding: .5em;
+  border-collapse: collapse;
+}
+code, pre {
+ font-size: 1em;
+}
+pre {
+ font-size: 0.8em;
+ padding: 1em;
+ background: #eee;
+}
+li {
+ padding-bottom: .3em;
+}
+h1, h2, h3, h4, h5, h6 {
+  margin-bottom: .25em;
+}
+h1 {
+  font-size: 1.3em;
+}
+h2 {
+  font-size: 1.2em;
+}
+h3 {
+  font-size: 1.1em;
+}
+h4, h5, h6 {
+  font-size: 1em;
+}
+p {
+  margin: 0 0 .5em 0;
+}
+br {
+  line-height: 0.6em;
+}
+
+/*
+** Page layout blocks / IDs
+*/
+#header {
+  margin-bottom: 2em;
+}
+#help {
+  font-size: 0.8em;
+}
+#content {
+  clear: both;
+}
+#sidebar-left, #sidebar-right {
+  vertical-align: top;
+  padding: 10px;
+}
+#main {
+  padding-left: 1em;
+  padding-right: 1em;
+  vertical-align: top;
+}
+#footer {
+ font-size: 0.8em;
+ padding-top: 2em;
+ text-align: center;
+}
+
+/*
+** Common declarations for child classes of node, comment, block, box etc
+*/
+.title {
+  margin: 0 0 .25em 0;
+}
+.content {
+  margin: 0 0 .5em 0;
+}
+ul.links.inline {
+  font-size: 0.8em;
+  line-height: 1.25em;
+}
+.block {
+  width: 180px;
+}
+.messages {
+  padding: 0.3em;
+  margin: 0.5em 0em 0.5em 0em;
+}
+.status {
+  border: 1px solid #3a3;
+  color: #3a3;
+}
+.error, form-item input.error {
+  border: 1px solid red;
+  color: red;
+}
+
+/*
+** Common navigation links added on the admin/build/themes/settings page
+*/
+.navlinks {
+  padding: 0em 0.5em 1.5em 0em; /* LTR */
+  clear: both;
+}
+.primary a {
+  font-size: 1.0em;
+  padding: 0em 0.5em 0em 0em; /* LTR */
+}
+.secondary a {
+  font-size: 0.9em;
+  padding: 0em 0.5em 0em 0em; /* LTR */
+}
+
+/*
+** Logo Image Positioning
+*/
+#header img {
+  float: left; /* LTR */
+  padding: 0em 2em .5em 0em; /* LTR */
+}
+#header {
+  clear: both;
+}
+/*
+** Module specific styles
+*/
+.form-item textarea {
+  font-size: 1em;
+}
+#aggregator .feed-source {
+  border: 1px solid gray;
+  padding: 1em;
+}
Binary file themes/chameleon/logo.png has changed
Binary file themes/chameleon/marvin/bullet.png has changed
Binary file themes/chameleon/marvin/druplicon-watermark-rtl.png has changed
Binary file themes/chameleon/marvin/druplicon-watermark.png has changed
Binary file themes/chameleon/marvin/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/marvin/marvin.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+; $Id: marvin.info,v 1.4 2007/06/08 05:50:57 dries Exp $
+name = Marvin
+description = Boxy tabled theme in all grays.
+regions[left] = Left sidebar
+regions[right] = Right sidebar
+version = VERSION
+core = 6.x
+base theme = chameleon
Binary file themes/chameleon/marvin/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/marvin/style-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,33 @@
+/* $Id: style-rtl.css,v 1.2 2007/11/27 12:09:27 goba Exp $ */
+
+body {
+  background-image: url(druplicon-watermark-rtl.png);
+  background-position: top left;
+}
+
+p {
+  margin: 0 0 1em 1em;
+}
+
+ul.links li {
+  border-left: none;
+  border-right: 1px solid #888;
+}
+
+/*
+** Common declarations for child classes of node, comment, block, box etc
+*/
+.node .submitted {
+  float: right;
+  padding: 0.5em 1em 0.5em 0;
+}
+.node .taxonomy {
+  float: left;
+}
+.node .content {
+  padding-left: 0;
+  padding-right: 1em;
+}
+.node .links {
+  padding: 1em 0.2em 1em 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/marvin/style.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,124 @@
+/* $Id: style.css,v 1.8 2007/06/04 11:10:38 goba Exp $ */
+
+/*
+** HTML elements
+*/
+body {
+  background: #fff url(druplicon-watermark.png) no-repeat top right; /* LTR */
+  font-family: arial, helvetica, sans-serif;
+}
+a:link {
+  color: #656
+}
+a:visited {
+  color: #656
+}
+a:active {
+  color: #ccc
+}
+h2 {
+  background-color: #eaeaea;
+  border: solid 1px #777;
+  font-size: 1.1em;
+  margin: 0.5em 0em 0.5em 0em;
+  padding: 0.5em;
+}
+h2.title {
+  background-color: #fff;
+  border: solid 1px #888;
+  margin-top: 1em;
+}
+p {
+  margin: 0 1em 1em 0; /* LTR */
+  padding: 0;
+}
+table {
+  font-size: 1em;
+}
+
+/*
+** Page layout blocks / IDs
+*/
+#main {
+  width: 80%;
+}
+#header .title {
+  padding-top: .75em;
+}
+#subnavlist {
+  font-size: 0.8em;
+}
+ul.links li {
+  border-left: 1px solid #888; /* LTR */
+}
+ul.links li.first {
+  border: none;
+}
+
+/*
+** Common declarations for child classes of node, comment, block, box etc
+*/
+.node .submitted {
+  color: #7c7c7c;
+  font-size: 0.9em;
+  float: left; /* LTR */
+  padding: 0.5em 0em 0.5em 1em; /* LTR */
+}
+.node .taxonomy {
+  color: #7c7c7c;
+  font-size: 0.9em;
+  float: right; /* LTR */
+}
+.node .content {
+  clear: both;
+  padding-left: 1em; /* LTR */
+}
+.node .links {
+  padding: 1em 0 1em 0.2em; /* LTR */
+}
+.comment {
+  border: solid 1px #777;
+  margin: 0.5em 0;
+  padding: 0.5em;
+}
+.block {
+  margin-bottom: 10px;
+  font-size: 0.9em;
+}
+.block .content {
+  border: solid 1px #888;
+  border-top: none;
+  margin: 0;
+  padding: 5px;
+}
+.block h2.title {
+  margin: 0;
+}
+
+/*
+** Administration page styles
+*/
+div.admin-panel .description {
+  color: #999;
+}
+
+div.admin-panel .body {
+  background: #f4f4f4;
+}
+
+div.admin-panel h3 {
+  background-color: #888;
+  color: #fff;
+  padding: 5px 8px 5px;
+  margin: 0;
+}
+
+/*
+** Module specific styles
+*/
+.item-list ul li {
+  list-style-image: url(bullet.png);
+}
+.path, .path a, .path a:visited {
+  color: #888;
+}
Binary file themes/chameleon/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/style-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,29 @@
+/* $Id: style-rtl.css,v 1.3 2007/11/27 12:09:27 goba Exp $ */
+
+body {
+  padding: 5em 3em 0 0;
+}
+/**
+ * Yes - right is on the left and left is on the right!
+ * Welcome to design by tables..
+ */
+#sidebar-left {
+  border-right: none;
+  border-left: 1px solid gray;
+}
+#sidebar-right {
+  border-left: none;
+  border-right: 1px solid gray;
+}
+
+ul.links li {
+  border-left: none;
+  border-right: 1px solid #000;
+}
+ul.links li.first {
+  border-left: none;
+  border-right: none;
+}
+div.links {
+  text-align: left;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/chameleon/style.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,115 @@
+/* $Id: style.css,v 1.7 2007/06/04 11:10:38 goba Exp $ */
+
+/*
+** HTML elements
+*/
+a, a:link, a:active {
+  color: #930;
+}
+a:visited {
+  color: #630;
+}
+body {
+  padding: 5em 0 0 3em; /* LTR */
+  background-image: url(background.png);
+  background-repeat: repeat-x;
+  font-family: trebuchet ms, tahoma, verdana, arial, helvetica;
+  border-top: 10px solid gray;
+}
+ul {
+  list-style-type: disc;
+}
+
+/*
+** Page layout blocks / IDs
+*/
+#main {
+  width: 500px;
+}
+#sidebar-left {
+  border-right: 1px solid gray; /* LTR */
+}
+#sidebar-right {
+  border-left: 1px solid gray; /* LTR */
+}
+
+/*
+** Common declarations for child classes of node, comment, block, box etc
+*/
+#header .title {
+  font-size: 2em;
+  font-weight: bold;
+  padding-top: .75em;
+}
+#header .title a,
+#header .title a:link,
+#header .title a:visited,
+#header .title a:active {
+  text-decoration: none;
+  color: #aaa;
+}
+#header .title a:hover {
+  color: #930;
+}
+#header .site-slogan {
+  margin-top: -0.1em;
+  font-size: 0.8em;
+}
+#subnavlist {
+  font-size: 0.8em;
+}
+ul.links li {
+  border-left: 1px solid #000; /* LTR */
+}
+ul.links li.first {
+  border-left: none; /* LTR */
+}
+.node .title {
+  font-size: 1.2em;
+}
+.node .title a,
+.node .title a:link,
+.node .title a:active,
+.node .title a:visited {
+  text-decoration: none;
+  font-weight: normal;
+}
+.node .title a:hover {
+  text-decoration: underline;
+}
+div.links {
+  margin: 1em 0 3em 0;
+  text-align: right; /* LTR */
+}
+.comment .content, .block .content, .menu {
+  font-size: 0.9em;
+}
+.block {
+  padding-bottom: 1em;
+}
+.block .title {
+  font-size: 1em;
+}
+
+/*
+** Module specific styles
+*/
+.item-list ul li {
+  list-style: square;
+}
+
+/*
+** Administration page styles
+*/
+div.admin-panel .description {
+  color: #999;
+}
+div.admin-panel .body {
+  background: #eee;
+}
+div.admin-panel h3 {
+  background-color: #999;
+  color: #fff;
+  padding: 5px 8px 5px;
+  margin: 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/engines/phptemplate/phptemplate.engine	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,27 @@
+<?php
+// $Id: phptemplate.engine,v 1.69 2007/10/02 16:19:23 dries Exp $
+
+/**
+ * @file
+ * Handles integration of templates written in pure php with the Drupal theme system.
+ */
+
+function phptemplate_init($template) {
+  $file = dirname($template->filename) .'/template.php';
+  if (file_exists($file)) {
+    include_once "./$file";
+  }
+}
+
+/**
+ * Implementation of hook_theme to tell Drupal what templates the engine
+ * and the current theme use. The $existing argument will contain hooks
+ * pre-defined by Drupal so that we can use that information if
+ * we need to.
+ */
+function phptemplate_theme($existing, $type, $theme, $path) {
+  $templates = drupal_find_theme_functions($existing, array('phptemplate', $theme));
+  $templates += drupal_find_theme_templates($existing, '.tpl.php', $path);
+  return $templates;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,11 @@
+<?php
+// $Id: block.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
+?>
+<div id="block-<?php print $block->module .'-'. $block->delta; ?>" class="clear-block block block-<?php print $block->module ?>">
+
+<?php if (!empty($block->subject)): ?>
+  <h2><?php print $block->subject ?></h2>
+<?php endif;?>
+
+  <div class="content"><?php print $block->content ?></div>
+</div>
Binary file themes/garland/color/base.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/color/color.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,75 @@
+<?php
+// $Id: color.inc,v 1.4 2007/12/14 17:00:14 goba Exp $
+
+$info = array(
+
+  // Pre-defined color schemes.
+  'schemes' => array(
+    '#0072b9,#027ac6,#2385c2,#5ab5ee,#494949' => t('Blue Lagoon (Default)'),
+    '#464849,#2f416f,#2a2b2d,#5d6779,#494949' => t('Ash'),
+    '#55c0e2,#000000,#085360,#007e94,#696969' => t('Aquamarine'),
+    '#d5b048,#6c420e,#331900,#971702,#494949' => t('Belgian Chocolate'),
+    '#3f3f3f,#336699,#6598cb,#6598cb,#000000' => t('Bluemarine'),
+    '#d0cb9a,#917803,#efde01,#e6fb2d,#494949' => t('Citrus Blast'),
+    '#0f005c,#434f8c,#4d91ff,#1a1575,#000000' => t('Cold Day'),
+    '#c9c497,#0c7a00,#03961e,#7be000,#494949' => t('Greenbeam'),
+    '#ffe23d,#a9290a,#fc6d1d,#a30f42,#494949' => t('Mediterrano'),
+    '#788597,#3f728d,#a9adbc,#d4d4d4,#707070' => t('Mercury'),
+    '#5b5fa9,#5b5faa,#0a2352,#9fa8d5,#494949' => t('Nocturnal'),
+    '#7db323,#6a9915,#b5d52a,#7db323,#191a19' => t('Olivia'),
+    '#12020b,#1b1a13,#f391c6,#f41063,#898080' => t('Pink Plastic'),
+    '#b7a0ba,#c70000,#a1443a,#f21107,#515d52' => t('Shiny Tomato'),
+    '#18583d,#1b5f42,#34775a,#52bf90,#2d2d2d' => t('Teal Top'),
+  ),
+
+  // Images to copy over.
+  'copy' => array(
+    'images/menu-collapsed.gif',
+    'images/menu-collapsed-rtl.gif',
+    'images/menu-expanded.gif',
+    'images/menu-leaf.gif',
+  ),
+
+  // CSS files (excluding @import) to rewrite with new color scheme.
+  'css' => array(
+    'style.css',
+  ),
+
+  // Coordinates of gradient (x, y, width, height).
+  'gradient' => array(0, 37, 760, 121),
+
+  // Color areas to fill (x, y, width, height).
+  'fill' => array(
+    'base' => array(0, 0, 760, 568),
+    'link' => array(107, 533, 41, 23),
+  ),
+
+  // Coordinates of all the theme slices (x, y, width, height)
+  // with their filename as used in the stylesheet.
+  'slices' => array(
+    'images/body.png'                      => array(0, 37, 1, 280),
+    'images/bg-bar.png'                    => array(202, 530, 76, 14),
+    'images/bg-bar-white.png'              => array(202, 506, 76, 14),
+    'images/bg-tab.png'                    => array(107, 533, 41, 23),
+    'images/bg-navigation.png'             => array(0, 0, 7, 37),
+    'images/bg-content-left.png'           => array(40, 117, 50, 352),
+    'images/bg-content-right.png'          => array(510, 117, 50, 352),
+    'images/bg-content.png'                => array(299, 117, 7, 200),
+    'images/bg-navigation-item.png'        => array(32, 37, 17, 12),
+    'images/bg-navigation-item-hover.png'  => array(54, 37, 17, 12),
+    'images/gradient-inner.png'            => array(646, 307, 112, 42),
+
+    'logo.png'                             => array(622, 51, 64, 73),
+    'screenshot.png'                       => array(0, 37, 400, 240),
+  ),
+
+  // Reference color used for blending. Matches the base.png's colors.
+  'blend_target' => '#ffffff',
+
+  // Preview files.
+  'preview_image' => 'color/preview.png',
+  'preview_css' => 'color/preview.css',
+
+  // Base file for image generation.
+  'base_image' => 'color/base.png',
+);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/color/preview.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,58 @@
+/* $Id: preview.css,v 1.1 2006/10/29 13:17:38 unconed Exp $ */
+
+/* Positioning */
+#preview {
+  overflow: hidden;
+  max-width: 100%;
+}
+#preview, #preview #img {
+  width: 596px;
+  height: 371px;
+}
+#preview #gradient {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 19px;
+  height: 120px;
+  z-index: 2;
+}
+#preview #text {
+  position: absolute;
+  left: 80px;
+  width: 436px;
+  top: 160px;
+  height: 120px;
+  z-index: 4;
+}
+#preview #img {
+  position: relative;
+  z-index: 3;
+}
+#preview #gradient .gradient-line {
+  height: 10px;
+  overflow: hidden;
+}
+
+/* Basic styles to match */
+#preview {
+  font: 12px/170% Verdana;
+}
+#preview h2 {
+  margin: 0;
+  padding: 0;
+  font-weight: normal;
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: 160%;
+  line-height: 130%;
+}
+#preview p {
+  margin: .5em 0;
+}
+#preview a:link, #preview a:visited {
+  text-decoration: none;
+  font-weight: normal;
+}
+#preview a:hover {
+  text-decoration: underline;
+}
\ No newline at end of file
Binary file themes/garland/color/preview.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/comment.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,33 @@
+<?php
+// $Id: comment.tpl.php,v 1.10 2008/01/04 19:24:24 goba Exp $
+?>
+<div class="comment<?php print ($comment->new) ? ' comment-new' : ''; print ' '. $status; print ' '. $zebra; ?>">
+
+  <div class="clear-block">
+  <?php if ($submitted): ?>
+    <span class="submitted"><?php print $submitted; ?></span>
+  <?php endif; ?>
+
+  <?php if ($comment->new) : ?>
+    <span class="new"><?php print drupal_ucfirst($new) ?></span>
+  <?php endif; ?>
+
+  <?php print $picture ?>
+
+    <h3><?php print $title ?></h3>
+
+    <div class="content">
+      <?php print $content ?>
+      <?php if ($signature): ?>
+      <div class="clear-block">
+        <div>—</div>
+        <?php print $signature ?>
+      </div>
+      <?php endif; ?>
+    </div>
+  </div>
+
+  <?php if ($links): ?>
+    <div class="links"><?php print $links ?></div>
+  <?php endif; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/fix-ie-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,63 @@
+/* $Id: fix-ie-rtl.css,v 1.2 2007/11/09 22:14:41 goba Exp $ */
+
+body {
+  /* Center layout */
+  text-align: center;
+  /* Allow text resizing */
+  font-size: 80%;
+}
+
+#squeeze {
+  zoom: 1;
+  direction: ltr;
+}
+
+#squeeze .left-corner{
+  direction: rtl
+}
+
+#header-region, #wrapper #container {
+  /* Reset text alignment */
+  text-align: right;
+}
+
+#wrapper #container #center {
+  /* Reduce amount of damage done by extremely wide content */
+  overflow: hidden;
+}
+
+#wrapper #container #center .right-corner .left-corner {
+  /* Because of the lack of min-height, we use height as an alternative */
+  height: 400px;
+}
+
+fieldset {
+  /* Don't draw backgrounds on fieldsets in IE, as they look really bad. */
+  background: none;
+}
+
+/* Prevent fieldsets from shifting when changing collapsed state. */
+html.js fieldset.collapsible {
+  position: relative;
+  top: -1em;
+}
+
+html.js fieldset.collapsed {
+  top: 0;
+  margin-bottom: 1em;
+}
+
+tr.menu-disabled {
+  /* Use filter to emulate CSS3 opacity */
+  filter: alpha(opacity=50);
+}
+
+#header-region {
+  /* Because of the lack of min-height, we use height as an alternative */
+  height: 1em;
+}
+
+#attach-hide label, #uploadprogress div.message {
+  /* Fading elements in IE causes the text to bleed unless they have a background. */
+  background-color: #ffffff;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/fix-ie.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,69 @@
+/* $Id: fix-ie.css,v 1.8.2.1 2008/02/05 09:27:26 goba Exp $ */
+
+/**
+ * Garland, for Drupal 6.x
+ * Stefan Nagtegaal, iStyledThis [dot] nl
+ * Steven Wittens, acko [dot] net
+ */
+
+body {
+  /* Center layout */
+  text-align: center;
+  /* Allow text resizing */
+  font-size: 80%;
+}
+
+#header-region, #wrapper #container {
+  /* Reset text alignment */
+  text-align: left; /* LTR */
+}
+
+#wrapper #container #center {
+  /* Reduce amount of damage done by extremely wide content */
+  overflow: hidden;
+}
+
+#wrapper #container #center .right-corner .left-corner {
+  /* Because of the lack of min-height, we use height as an alternative */
+  height: 400px;
+}
+
+fieldset {
+  /* Don't draw backgrounds on fieldsets in IE, as they look really bad. */
+  background: none;
+}
+
+ul.primary {
+  /* Fix missing top margin */
+  position: relative; /* LTR */
+/*  top: 0.5em; */
+}
+
+/* Prevent fieldsets from shifting when changing collapsed state. */
+html.js fieldset.collapsible {
+  position: relative;
+  top: -1em;
+}
+html.js fieldset.collapsed {
+  top: 0;
+  margin-bottom: 1em;
+}
+
+tr.menu-disabled {
+  /* Use filter to emulate CSS3 opacity */
+  filter: alpha(opacity=50);
+}
+
+#header-region {
+  /* Because of the lack of min-height, we use height as an alternative */
+  height: 1em;
+}
+
+tr.taxonomy-term-preview {
+  filter: alpha(opacity=50);
+}
+
+#attach-hide label, #uploadprogress div.message {
+  /* Fading elements in IE causes the text to bleed unless they have a background. */
+  background-color: #ffffff;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/garland.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,8 @@
+; $Id: garland.info,v 1.5 2007/07/01 23:27:32 goba Exp $
+name = Garland
+description = Tableless, recolorable, multi-column, fluid width theme (default).
+version = VERSION
+core = 6.x
+engine = phptemplate
+stylesheets[all][] = style.css
+stylesheets[print][] = print.css
Binary file themes/garland/images/bg-bar-white.png has changed
Binary file themes/garland/images/bg-bar.png has changed
Binary file themes/garland/images/bg-content-left.png has changed
Binary file themes/garland/images/bg-content-right.png has changed
Binary file themes/garland/images/bg-content.png has changed
Binary file themes/garland/images/bg-navigation-item-hover.png has changed
Binary file themes/garland/images/bg-navigation-item.png has changed
Binary file themes/garland/images/bg-navigation.png has changed
Binary file themes/garland/images/bg-tab.png has changed
Binary file themes/garland/images/body.png has changed
Binary file themes/garland/images/gradient-inner.png has changed
Binary file themes/garland/images/menu-collapsed-rtl.gif has changed
Binary file themes/garland/images/menu-collapsed.gif has changed
Binary file themes/garland/images/menu-expanded.gif has changed
Binary file themes/garland/images/menu-leaf.gif has changed
Binary file themes/garland/images/task-list.png has changed
Binary file themes/garland/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/maintenance-page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,91 @@
+<?php
+// $Id: maintenance-page.tpl.php,v 1.3 2008/01/24 09:42:53 goba Exp $
+
+/**
+ * @file maintenance-page.tpl.php
+ *
+ * This is an override of the default maintenance page. Used for Garland and
+ * Minnelli, this file should not be moved or modified since the installation
+ * and update pages depend on this file.
+ *
+ * This mirrors closely page.tpl.php for Garland in order to share the same
+ * styles.
+ */
+?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+  <head>
+    <title><?php print $head_title ?></title>
+    <?php print $head ?>
+    <?php print $styles ?>
+    <?php print $scripts ?>
+    <!--[if lt IE 7]>
+      <?php print phptemplate_get_ie_styles(); ?>
+    <![endif]-->
+  </head>
+  <body<?php print phptemplate_body_class($left, $right); ?>>
+
+<!-- Layout -->
+  <div id="header-region" class="clear-block"><?php print $header; ?></div>
+
+    <div id="wrapper">
+    <div id="container" class="clear-block">
+
+      <div id="header">
+        <div id="logo-floater">
+        <?php
+          // Prepare header
+          $site_fields = array();
+          if ($site_name) {
+            $site_fields[] = check_plain($site_name);
+          }
+          if ($site_slogan) {
+            $site_fields[] = check_plain($site_slogan);
+          }
+          $site_title = implode(' ', $site_fields);
+          if ($site_fields) {
+            $site_fields[0] = '<span>'. $site_fields[0] .'</span>';
+          }
+          $site_html = implode(' ', $site_fields);
+
+          if ($logo || $site_title) {
+            print '<h1><a href="'. check_url($base_path) .'" title="'. $site_title .'">';
+            if ($logo) {
+              print '<img src="'. check_url($logo) .'" alt="'. $site_title .'" id="logo" />';
+            }
+            print $site_html .'</a></h1>';
+          }
+        ?>
+        </div>
+
+      </div> <!-- /header -->
+
+      <?php if ($left): ?>
+        <div id="sidebar-left" class="sidebar">
+          <?php if ($search_box): ?><div class="block block-theme"><?php print $search_box ?></div><?php endif; ?>
+          <?php print $left ?>
+        </div>
+      <?php endif; ?>
+
+      <div id="center"><div id="squeeze"><div class="right-corner"><div class="left-corner">
+          <?php if ($title): print '<h2'. ($tabs ? ' class="with-tabs"' : '') .'>'. $title .'</h2>'; endif; ?>
+          <?php print $help; ?>
+          <?php print $messages; ?>
+          <div class="clear-block">
+            <?php print $content ?>
+          </div>
+          <div id="footer"><?php print $footer_message . $footer ?></div>
+      </div></div></div></div> <!-- /.left-corner, /.right-corner, /#squeeze, /#center -->
+
+      <?php if ($right): ?>
+        <div id="sidebar-right" class="sidebar">
+          <?php print $right ?>
+        </div>
+      <?php endif; ?>
+
+    </div> <!-- /container -->
+  </div>
+<!-- /layout -->
+
+  </body>
+</html>
Binary file themes/garland/minnelli/color/base.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/minnelli/color/color.inc	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,75 @@
+<?php
+// $Id: color.inc,v 1.4 2007/12/14 17:00:14 goba Exp $
+
+$info = array(
+
+  // Pre-defined color schemes.
+  'schemes' => array(
+    '#0072b9,#027ac6,#2385c2,#5ab5ee,#494949' => t('Blue Lagoon (Default)'),
+    '#464849,#2f416f,#2a2b2d,#5d6779,#494949' => t('Ash'),
+    '#55c0e2,#000000,#085360,#007e94,#696969' => t('Aquamarine'),
+    '#d5b048,#6c420e,#331900,#971702,#494949' => t('Belgian Chocolate'),
+    '#3f3f3f,#336699,#6598cb,#6598cb,#000000' => t('Bluemarine'),
+    '#d0cb9a,#917803,#efde01,#e6fb2d,#494949' => t('Citrus Blast'),
+    '#0f005c,#434f8c,#4d91ff,#1a1575,#000000' => t('Cold Day'),
+    '#c9c497,#0c7a00,#03961e,#7be000,#494949' => t('Greenbeam'),
+    '#ffe23d,#a9290a,#fc6d1d,#a30f42,#494949' => t('Mediterrano'),
+    '#788597,#3f728d,#a9adbc,#d4d4d4,#707070' => t('Mercury'),
+    '#5b5fa9,#5b5faa,#0a2352,#9fa8d5,#494949' => t('Nocturnal'),
+    '#7db323,#6a9915,#b5d52a,#7db323,#191a19' => t('Olivia'),
+    '#12020b,#1b1a13,#f391c6,#f41063,#898080' => t('Pink Plastic'),
+    '#b7a0ba,#c70000,#a1443a,#f21107,#515d52' => t('Shiny Tomato'),
+    '#18583d,#1b5f42,#34775a,#52bf90,#2d2d2d' => t('Teal Top'),
+  ),
+
+  // Images to copy over.
+  'copy' => array(
+    '../images/menu-collapsed.gif',
+    '../images/menu-collapsed-rtl.gif',
+    '../images/menu-expanded.gif',
+    '../images/menu-leaf.gif',
+  ),
+
+  // CSS files (excluding @import) to rewrite with new color scheme.
+  'css' => array(
+    '../style.css',
+  ),
+
+  // Coordinates of gradient (x, y, width, height).
+  'gradient' => array(0, 37, 760, 121),
+
+  // Color areas to fill (x, y, width, height).
+  'fill' => array(
+    'base' => array(0, 0, 760, 568),
+    'link' => array(107, 533, 41, 23),
+  ),
+
+  // Coordinates of all the theme slices (x, y, width, height)
+  // with their filename as used in the stylesheet.
+  'slices' => array(
+    '../images/body.png'                      => array(0, 37, 1, 280),
+    '../images/bg-bar.png'                    => array(202, 530, 76, 14),
+    '../images/bg-bar-white.png'              => array(202, 506, 76, 14),
+    '../images/bg-tab.png'                    => array(107, 533, 41, 23),
+    '../images/bg-navigation.png'             => array(0, 0, 7, 37),
+    '../images/bg-content-left.png'           => array(40, 117, 50, 352),
+    '../images/bg-content-right.png'          => array(510, 117, 50, 352),
+    '../images/bg-content.png'                => array(299, 117, 7, 200),
+    '../images/bg-navigation-item.png'        => array(32, 37, 17, 12),
+    '../images/bg-navigation-item-hover.png'  => array(54, 37, 17, 12),
+    '../images/gradient-inner.png'            => array(646, 307, 112, 42),
+
+    'logo.png'                                => array(622, 51, 64, 73),
+    'screenshot.png'                          => array(0, 37, 400, 240),
+  ),
+
+  // Reference color used for blending. Matches the base.png's colors.
+  'blend_target' => '#ffffff',
+
+  // Preview files.
+  'preview_image' => 'color/preview.png',
+  'preview_css' => '../color/preview.css',
+
+  // Base file for image generation.
+  'base_image' => 'color/base.png',
+);
Binary file themes/garland/minnelli/color/preview.png has changed
Binary file themes/garland/minnelli/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/minnelli/minnelli.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,20 @@
+/* $Id: minnelli.css,v 1.3.2.1 2008/02/05 09:27:26 goba Exp $ */
+
+/**
+ * Minnelli, for Drupal 6.x
+ * Stefan Nagtegaal, iStyledThis [dot] nl
+ * Steven Wittens, acko [dot] net
+ */
+
+body #wrapper #container {
+  width: 560px;
+}
+
+body.sidebars #wrapper #container {
+  width: 980px;
+}
+
+body.sidebar-left #wrapper #container,
+body.sidebar-right #wrapper #container {
+  width: 770px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/minnelli/minnelli.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+; $Id: minnelli.info,v 1.7 2007/12/04 20:58:44 goba Exp $
+name = Minnelli
+description = Tableless, recolorable, multi-column, fixed width theme.
+version = VERSION
+core = 6.x
+base theme = garland
+stylesheets[all][] = minnelli.css
Binary file themes/garland/minnelli/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/node.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,32 @@
+<?php
+// $Id: node.tpl.php,v 1.5 2007/10/11 09:51:29 goba Exp $
+?>
+<div id="node-<?php print $node->nid; ?>" class="node<?php if ($sticky) { print ' sticky'; } ?><?php if (!$status) { print ' node-unpublished'; } ?>">
+
+<?php print $picture ?>
+
+<?php if ($page == 0): ?>
+  <h2><a href="<?php print $node_url ?>" title="<?php print $title ?>"><?php print $title ?></a></h2>
+<?php endif; ?>
+
+  <?php if ($submitted): ?>
+    <span class="submitted"><?php print $submitted; ?></span>
+  <?php endif; ?>
+
+  <div class="content clear-block">
+    <?php print $content ?>
+  </div>
+
+  <div class="clear-block">
+    <div class="meta">
+    <?php if ($taxonomy): ?>
+      <div class="terms"><?php print $terms ?></div>
+    <?php endif;?>
+    </div>
+
+    <?php if ($links): ?>
+      <div class="links"><?php print $links; ?></div>
+    <?php endif; ?>
+  </div>
+
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,95 @@
+<?php
+// $Id: page.tpl.php,v 1.18 2008/01/24 09:42:53 goba Exp $
+?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+  <head>
+    <title><?php print $head_title ?></title>
+    <?php print $head ?>
+    <?php print $styles ?>
+    <?php print $scripts ?>
+    <!--[if lt IE 7]>
+      <?php print phptemplate_get_ie_styles(); ?>
+    <![endif]-->
+  </head>
+  <body<?php print phptemplate_body_class($left, $right); ?>>
+
+<!-- Layout -->
+  <div id="header-region" class="clear-block"><?php print $header; ?></div>
+
+    <div id="wrapper">
+    <div id="container" class="clear-block">
+
+      <div id="header">
+        <div id="logo-floater">
+        <?php
+          // Prepare header
+          $site_fields = array();
+          if ($site_name) {
+            $site_fields[] = check_plain($site_name);
+          }
+          if ($site_slogan) {
+            $site_fields[] = check_plain($site_slogan);
+          }
+          $site_title = implode(' ', $site_fields);
+          if ($site_fields) {
+            $site_fields[0] = '<span>'. $site_fields[0] .'</span>';
+          }
+          $site_html = implode(' ', $site_fields);
+
+          if ($logo || $site_title) {
+            print '<h1><a href="'. check_url($front_page) .'" title="'. $site_title .'">';
+            if ($logo) {
+              print '<img src="'. check_url($logo) .'" alt="'. $site_title .'" id="logo" />';
+            }
+            print $site_html .'</a></h1>';
+          }
+        ?>
+        </div>
+
+        <?php if (isset($primary_links)) : ?>
+          <?php print theme('links', $primary_links, array('class' => 'links primary-links')) ?>
+        <?php endif; ?>
+        <?php if (isset($secondary_links)) : ?>
+          <?php print theme('links', $secondary_links, array('class' => 'links secondary-links')) ?>
+        <?php endif; ?>
+
+      </div> <!-- /header -->
+
+      <?php if ($left): ?>
+        <div id="sidebar-left" class="sidebar">
+          <?php if ($search_box): ?><div class="block block-theme"><?php print $search_box ?></div><?php endif; ?>
+          <?php print $left ?>
+        </div>
+      <?php endif; ?>
+
+      <div id="center"><div id="squeeze"><div class="right-corner"><div class="left-corner">
+          <?php print $breadcrumb; ?>
+          <?php if ($mission): print '<div id="mission">'. $mission .'</div>'; endif; ?>
+          <?php if ($tabs): print '<div id="tabs-wrapper" class="clear-block">'; endif; ?>
+          <?php if ($title): print '<h2'. ($tabs ? ' class="with-tabs"' : '') .'>'. $title .'</h2>'; endif; ?>
+          <?php if ($tabs): print '<ul class="tabs primary">'. $tabs .'</ul></div>'; endif; ?>
+          <?php if ($tabs2): print '<ul class="tabs secondary">'. $tabs2 .'</ul>'; endif; ?>
+          <?php if ($show_messages && $messages): print $messages; endif; ?>
+          <?php print $help; ?>
+          <div class="clear-block">
+            <?php print $content ?>
+          </div>
+          <?php print $feed_icons ?>
+          <div id="footer"><?php print $footer_message . $footer ?></div>
+      </div></div></div></div> <!-- /.left-corner, /.right-corner, /#squeeze, /#center -->
+
+      <?php if ($right): ?>
+        <div id="sidebar-right" class="sidebar">
+          <?php if (!$left && $search_box): ?><div class="block block-theme"><?php print $search_box ?></div><?php endif; ?>
+          <?php print $right ?>
+        </div>
+      <?php endif; ?>
+
+    </div> <!-- /container -->
+  </div>
+<!-- /layout -->
+
+  <?php print $closure ?>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/print.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,55 @@
+/* $Id: print.css,v 1.3 2007/09/06 21:23:32 goba Exp $ */
+
+/**
+ * Garland, for Drupal 5.0
+ * Stefan Nagtegaal, iStyledThis [dot] nl
+ * Steven Wittens, acko [dot] net`
+ *
+ * If you use a customized color scheme, you must regenerate it after
+ * modifying this file.
+ */
+
+body, input, textarea, select {
+  color: #000;
+  background: none;
+}
+
+ul.primary-links, ul.secondary-links,
+#header-region, .sidebar {
+  display: none;
+}
+
+body.sidebars, body.sideber-left, body.sidebar-right, body {
+  width: 640px;
+}
+
+body.sidebar-left #center, body.sidebar-right #center, body.sidebars #center,
+body.sidebar-left #squeeze, body.sidebar-right #squeeze, body.sidebars #squeeze {
+  margin: 0;
+}
+
+#wrapper,
+#wrapper #container .breadcrumb,
+#wrapper #container #center,
+#wrapper #container #center .right-corner,
+#wrapper #container #center .right-corner .left-corner,
+#wrapper #container #footer,
+#wrapper #container #center #squeeze {
+  position: static;
+  left: 0;
+  padding: 0;
+  margin: 0;
+  width: auto;
+  float: none;
+  clear: both;
+  background: none;
+}
+
+#wrapper #container #header {
+  height: 130px;
+}
+
+#wrapper #container #header h1, #wrapper #container #header h1 a:link, #wrapper #container #header h1 a:visited {
+  text-shadow: none;
+  color: #000;
+}
Binary file themes/garland/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/style-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,293 @@
+/* $Id: style-rtl.css,v 1.6 2007/12/17 15:05:10 goba Exp $ */
+
+html {
+  direction: rtl;
+}
+
+/**
+ * Generic elements
+ */
+body {
+  direction: rtl;
+}
+
+ol li, ul li {
+  margin: 0.4em .5em 0.4em 0;
+}
+
+ul.menu, .item-list ul {
+  margin: 0.35em -0.5em 0 0;
+}
+
+ul.menu ul, .item-list ul ul {
+  margin-left: 0;
+  margin-right: 0em;
+}
+
+ol li, ul li, ul.menu li, .item-list ul li, li.leaf {
+  margin: 0.15em .5em 0.15em 0;
+}
+
+ul li, ul.menu li, .item-list ul li, li.leaf {
+  padding: 0 1.5em .2em 0;
+  background: transparent url("images/menu-leaf.gif") no-repeat 100% .35em;
+}
+
+ol li {
+  margin-left: 0;
+  margin-right: 2em;
+}
+
+ul li.expanded {
+  background: transparent url("images/menu-expanded.gif") no-repeat 100% .35em;
+}
+
+ul li.collapsed {
+  background: transparent url("images/menu-collapsed-rtl.gif") no-repeat 100% .35em;
+}
+
+ul.inline li {
+  padding: 0 0 0 1em;
+}
+
+ol.task-list {
+  margin-left: 0;
+  margin-right: 0;
+}
+
+ol.task-list li {
+  padding: 0.5em 2em 0.5em 1em;
+}
+
+ol.task-list li.active {
+  background: transparent url(images/task-list.png) no-repeat 97px 50%;
+}
+
+ol.task-list li.done {
+  background: transparent url(../../misc/watchdog-ok.png) no-repeat 100% 50%;
+}
+
+ol.task-list li.active {
+  margin-right: 0;
+  margin-left: 1em;
+}
+
+dl {
+  margin: 0.5em 1.5em 1em 0;
+}
+
+dl dt {
+}
+
+dl dd {
+  margin: 0 1.5em .5em 0;
+}
+
+.form-button, .form-submit {
+  margin: 2em 0 1em 0.5em;
+}
+
+#header-region h2 {
+  margin: 0 0 0 1em;
+}
+
+#wrapper {
+  background: #edf5fa url("images/body.png") repeat-x 50% 0;
+}
+
+#wrapper #container #header h1 img {
+  padding-right: 0;
+  padding-left: 20px;
+  float: right;
+}
+
+#sidebar-left .block-region {
+  margin: 0 0 0 15px;
+}
+
+#sidebar-right .block-region {
+  margin: 0 15px 0px 0;
+}
+
+/* Now we add the backgrounds for the main content shading */
+#wrapper #container #center #squeeze {
+  background: #fff url("images/bg-content.png") repeat-x 50% 0;
+}
+
+#wrapper #container .breadcrumb {
+  position: absolute;
+  top: 15px;
+  left: 0;
+  right: 35px;
+  z-index: 3;
+}
+
+/**
+ * Primary navigation
+ */
+ul.primary-links {
+  float: left;
+width:70%;
+}
+
+ul.primary-links li {
+  float: right;
+}
+
+/**
+ * Secondary navigation
+ */
+ul.secondary-links {
+  float: left;
+  clear: left;
+}
+
+ul.secondary-links li {
+  float: right;
+}
+
+ul.primary {
+  float: right;
+}
+ul.secondary {
+  clear: both;
+  text-align: right;
+}
+h2.with-tabs {
+  float: right;
+  margin: 0 0 0 2em;
+}
+
+ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited,
+ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited {
+  margin: 0 1px 0 0;
+
+}
+ul.primary li a:after {
+  /* Fix Firefox 2 RTL bug. */
+  content: " ";
+}
+
+ul.links li, ul.inline li {
+  padding-left: 1em;
+  padding-right: 0;
+}
+
+.node .links, .comment .links {
+  text-align: right;
+}
+
+.node .links ul.links li, .comment .links ul.links li {}
+.terms ul.links li {
+  padding-right: 1em;
+  padding-left: 0;
+}
+
+.picture, .comment .submitted {
+  padding-left: 0;
+  float: left;
+  clear: left;
+  padding-right: 1em;
+}
+
+.new {
+  float: left;
+}
+
+.terms {
+  float: left;
+}
+
+.indented {
+  margin-left: 0;
+  margin-right: 25px;
+}
+
+html.js fieldset.collapsible legend a {
+  padding-left: 0;
+  padding-right: 2em;
+  background: url("images/menu-expanded.gif") no-repeat 100% 50%;
+}
+
+html.js fieldset.collapsed legend a {
+  background: url("images/menu-collapsed-rtl.gif") no-repeat 100% 50%;
+}
+
+/**
+ * Syndication Block
+ */
+#block-node-0 h2 {
+  float: right;
+  padding-right: 0;
+  padding-left: 20px;
+}
+
+#block-node-0 img {
+  float: left;
+}
+
+#block-node-0 .content {
+  clear: left;
+}
+
+/**
+ * Login Block
+ */
+#user-login-form ul {
+  text-align: right;
+}
+
+div.admin .left {
+  float: right;
+}
+
+div.admin .right {
+  float: left;
+}
+
+/* Fix Opera, IE6 and IE7 header width */
+#wrapper #container #header {
+  position: relative;
+  width: 100%;
+}
+
+#wrapper #container #header #logo-floater {
+  width: 100%;
+  left: 0;
+  top:0;
+}
+
+/**
+ * Fixes for IE7 - Does not break other browsers
+ */
+
+/* Position:relative on these breaks IE7. */
+ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited,
+ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited {
+  position: static;
+}
+
+/* Fix right and left cloumns position breaking on window resize */
+#container {
+  position: relative;
+}
+
+#center {
+  position: relative;
+}
+
+#sidebar-right{
+  position: absolute;
+  right: 0;
+}
+
+/**
+ * Apply hasLayout to elements in IE7, using standard property "min-height"
+ * (see http://www.satzansatz.de/cssd/onhavinglayout.html)
+ */
+
+/* Fix background bleed in center column. */
+#squeeze,
+#squeeze .right-corner {
+  min-height: 1%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/style.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,1079 @@
+/* $Id: style.css,v 1.38.2.1 2008/02/05 09:27:26 goba Exp $ */
+
+/**
+ * Garland, for Drupal 6.x
+ * Stefan Nagtegaal, iStyledThis [dot] nl
+ * Steven Wittens, acko [dot] net`
+ *
+ * If you use a customized color scheme, you must regenerate it after
+ * modifying this file.
+ */
+
+/**
+ * Generic elements
+ */
+body {
+  margin: 0;
+  padding: 0;
+  background: #edf5fa;
+  font: 12px/170% Verdana, sans-serif;
+  color: #494949;
+}
+
+input {
+  font: 12px/100% Verdana, sans-serif;
+  color: #494949;
+}
+
+textarea, select {
+  font: 12px/160% Verdana, sans-serif;
+  color: #494949;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin: 0;
+  padding: 0;
+  font-weight: normal;
+  font-family: Helvetica, Arial, sans-serif;
+}
+
+h1 {
+  font-size: 170%;
+}
+
+h2 {
+  font-size: 160%;
+  line-height: 130%;
+}
+
+h3 {
+  font-size: 140%;
+}
+
+h4 {
+  font-size: 130%;
+}
+
+h5 {
+  font-size: 120%;
+}
+
+h6 {
+  font-size: 110%;
+}
+
+ul, quote, code, fieldset {
+  margin: .5em 0;
+}
+
+p {
+  margin: 0.6em 0 1.2em;
+  padding: 0;
+}
+
+a:link, a:visited {
+  color: #027AC6;
+  text-decoration: none;
+}
+
+a:hover {
+  color: #0062A0;
+  text-decoration: underline;
+}
+
+a:active, a.active {
+  color: #5895be;
+}
+
+hr {
+  margin: 0;
+  padding: 0;
+  border: none;
+  height: 1px;
+  background: #5294c1;
+}
+
+ul {
+  margin: 0.5em 0 1em;
+  padding: 0;
+}
+
+ol {
+  margin: 0.75em 0 1.25em;
+  padding: 0;
+}
+
+ol li, ul li {
+  margin: 0.4em 0 0.4em .5em; /* LTR */
+}
+
+ul.menu, .item-list ul {
+  margin: 0.35em 0 0 -0.5em; /* LTR */
+  padding: 0;
+}
+
+ul.menu ul, .item-list ul ul {
+  margin-left: 0em; /* LTR */
+}
+
+ol li, ul li, ul.menu li, .item-list ul li, li.leaf {
+  margin: 0.15em 0 0.15em .5em; /* LTR */
+}
+
+ul li, ul.menu li, .item-list ul li, li.leaf {
+  padding: 0 0 .2em 1.5em;
+  list-style-type: none;
+  list-style-image: none;
+  background: transparent url(images/menu-leaf.gif) no-repeat 1px .35em; /* LTR */
+}
+
+ol li {
+  padding: 0 0 .3em;
+  margin-left: 2em; /* LTR */
+}
+
+ul li.expanded {
+  background: transparent url(images/menu-expanded.gif) no-repeat 1px .35em; /* LTR */
+}
+
+ul li.collapsed {
+  background: transparent url(images/menu-collapsed.gif) no-repeat 0px .35em; /* LTR */
+}
+
+ul li.leaf a, ul li.expanded a, ul li.collapsed a {
+  display: block;
+}
+
+ul.inline li {
+  background: none;
+  margin: 0;
+  padding: 0 1em 0 0; /* LTR */
+}
+
+ol.task-list {
+  margin-left: 0; /* LTR */
+  list-style-type: none;
+  list-style-image: none;
+}
+ol.task-list li {
+  padding: 0.5em 1em 0.5em 2em; /* LTR */
+}
+ol.task-list li.active {
+  background: transparent url(images/task-list.png) no-repeat 3px 50%; /* LTR */
+}
+ol.task-list li.done {
+  color: #393;
+  background: transparent url(../../misc/watchdog-ok.png) no-repeat 0px 50%; /* LTR */
+}
+ol.task-list li.active {
+  margin-right: 1em; /* LTR */
+}
+
+fieldset ul.clear-block li {
+  margin: 0;
+  padding: 0;
+  background-image: none;
+}
+
+dl {
+  margin: 0.5em 0 1em 1.5em; /* LTR */
+}
+
+dl dt {
+}
+
+dl dd {
+  margin: 0 0 .5em 1.5em; /* LTR */
+}
+
+img, a img {
+  border: none;
+}
+
+table {
+  margin: 1em 0;
+  width: 100%;
+}
+
+thead th {
+  border-bottom: 2px solid #d3e7f4;
+  color: #494949;
+  font-weight: bold;
+}
+
+th a:link, th a:visited {
+  color: #6f9dbd;
+}
+
+td, th {
+  padding: .3em .5em;
+}
+
+tr.even, tr.odd, tbody th {
+  border: solid #d3e7f4;
+  border-width: 1px 0;
+}
+
+tr.odd, tr.info {
+  background-color: #edf5fa;
+}
+
+tr.even {
+  background-color: #fff;
+}
+
+tr.drag {
+  background-color: #fffff0;
+}
+
+tr.drag-previous {
+  background-color: #ffd;
+}
+
+tr.odd td.active {
+  background-color: #ddecf5;
+}
+
+tr.even td.active {
+  background-color: #e6f1f7;
+}
+
+td.region, td.module, td.container, td.category {
+  border-top: 1.5em solid #fff;
+  border-bottom: 1px solid #b4d7f0;
+  background-color: #d4e7f3;
+  color: #455067;
+  font-weight: bold;
+}
+
+tr:first-child td.region, tr:first-child td.module, tr:first-child td.container, tr:first-child td.category {
+  border-top-width: 0;
+}
+
+span.form-required {
+  color: #ffae00;
+}
+
+span.submitted, .description {
+  font-size: 0.92em;
+  color: #898989;
+}
+
+.description {
+  line-height: 150%;
+  margin-bottom: 0.75em;
+  color: #898989;
+}
+
+.messages, .preview {
+  margin: .75em 0 .75em;
+  padding: .5em 1em;
+}
+
+.messages ul {
+  margin: 0;
+}
+
+.form-checkboxes, .form-radios, .form-checkboxes .form-item, .form-radios .form-item {
+  margin: 0.25em 0;
+}
+
+#center form {
+  margin-bottom: 2em;
+}
+
+.form-button, .form-submit {
+  margin: 2em 0.5em 1em 0; /* LTR */
+}
+
+#dblog-form-overview .form-submit,
+.confirmation .form-submit,
+.search-form .form-submit,
+.poll .form-submit,
+fieldset .form-button, fieldset .form-submit,
+.sidebar .form-button, .sidebar .form-submit,
+table .form-button, table .form-submit {
+  margin: 0;
+}
+
+.box {
+  margin-bottom: 2.5em;
+}
+
+/**
+ * Layout
+ */
+#header-region {
+  min-height: 1em;
+  background: #d2e6f3 url(images/bg-navigation.png) repeat-x 50% 100%;
+}
+
+#header-region .block {
+  display: block;
+  margin: 0 1em;
+}
+
+#header-region .block-region {
+  display: block;
+  margin: 0 0.5em 1em;
+  padding: 0.5em;
+  position: relative;
+  top: 0.5em;
+}
+
+#header-region * {
+  display: inline;
+  line-height: 1.5em;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+/* Prevent the previous directive from showing the content of script elements in Mozilla browsers. */
+#header-region script {
+  display: none;
+}
+
+#header-region p, #header-region img {
+  margin-top: 0.5em;
+}
+
+#header-region h2 {
+  margin: 0 1em 0 0; /* LTR */
+}
+
+#header-region h3, #header-region label, #header-region li {
+  margin: 0 1em;
+  padding: 0;
+  background: none;
+}
+
+#wrapper {
+  background: #edf5fa url(images/body.png) repeat-x 50% 0;
+}
+
+#wrapper #container {
+  margin: 0 auto;
+  padding: 0 20px;
+  max-width: 1270px;
+}
+
+#wrapper #container #header {
+  height: 80px;
+}
+
+#wrapper #container #header #logo-floater {
+  position: absolute;
+}
+
+#wrapper #container #header h1, #wrapper #container #header h1 a:link, #wrapper #container #header h1 a:visited {
+  line-height: 120px;
+  position: relative;
+  z-index: 2;
+  white-space: nowrap;
+}
+
+#wrapper #container #header h1 span {
+  font-weight: bold;
+}
+
+#wrapper #container #header h1 img {
+  padding-top: 16px;
+  padding-right: 20px; /* LTR */
+  float: left; /* LTR */
+}
+
+/* With 3 columns, require a minimum width of 1000px to ensure there is enough horizontal space. */
+body.sidebars {
+  min-width: 980px;
+}
+/* With 2 columns, require a minimum width of 800px. */
+body.sidebar-left, body.sidebar-right {
+  min-width: 780px;
+}
+
+/* We must define 100% width to avoid the body being too narrow for near-empty pages */
+#wrapper #container #center {
+  float: left;
+  width: 100%;
+}
+
+/* So we move the #center container over the sidebars to compensate */
+body.sidebar-left #center {
+  margin-left: -210px;
+}
+body.sidebar-right #center {
+  margin-right: -210px;
+}
+body.sidebars #center {
+  margin: 0 -210px;
+}
+
+/* And add blanks left and right for the sidebars to fill */
+body.sidebar-left #squeeze {
+  margin-left: 210px;
+}
+body.sidebar-right #squeeze {
+  margin-right: 210px;
+}
+body.sidebars #squeeze {
+  margin: 0 210px;
+}
+
+/* We ensure the sidebars are still clickable using z-index */
+#wrapper #container .sidebar {
+  margin: 60px 0 5em;
+  width: 210px;
+  float: left;
+  z-index: 2;
+  position: relative;
+}
+
+#wrapper #container .sidebar .block {
+  margin: 0 0 1.5em 0;
+}
+
+#sidebar-left .block {
+  padding: 0 15px 0 0px;
+}
+
+#sidebar-right .block {
+  padding: 0 0px 0 15px;
+}
+
+.block .content {
+  margin: 0.5em 0;
+}
+
+#sidebar-left .block-region {
+  margin: 0 15px 0 0px; /* LTR */
+}
+
+#sidebar-right .block-region {
+  margin: 0 0px 0 15px; /* LTR */
+}
+
+.block-region {
+  padding: 1em;
+  background: transparent;
+  border: 2px dashed #b4d7f0;
+  text-align: center;
+  font-size: 1.3em;
+}
+
+/* Now we add the backgrounds for the main content shading */
+#wrapper #container #center #squeeze {
+  background: #fff url(images/bg-content.png) repeat-x 50% 0;
+  position: relative;
+}
+
+#wrapper #container #center .right-corner {
+  background: transparent url(images/bg-content-right.png) no-repeat 100% 0;
+  position: relative;
+  left: 10px;
+}
+
+#wrapper #container #center .right-corner .left-corner {
+  padding: 60px 25px 5em 35px;
+  background: transparent url(images/bg-content-left.png) no-repeat 0 0;
+  margin-left: -10px;
+  position: relative;
+  left: -10px;
+  min-height: 400px;
+}
+
+#wrapper #container #footer {
+  float: none;
+  clear: both;
+  text-align: center;
+  margin: 4em 0 -3em;
+  color: #898989;
+}
+
+#wrapper #container .breadcrumb {
+  position: absolute;
+  top: 15px;
+  left: 35px; /* LTR */
+  z-index: 3;
+}
+
+body.sidebar-left #footer {
+  margin-left: -210px;
+}
+
+body.sidebar-right #footer {
+  margin-right: -210px;
+}
+
+body.sidebars #footer {
+  margin: 0 -210px;
+}
+
+/**
+ * Header
+ */
+#wrapper #container #header h1, #wrapper #container #header h1 a:link, #wrapper #container #header h1 a:visited {
+  color: #fff;
+  font-weight: normal;
+  text-shadow: #1659ac 0px 1px 3px;
+  font-size: 1.5em;
+}
+
+#wrapper #container #header h1 a:hover {
+  text-decoration: none;
+}
+
+#wrapper #container .breadcrumb {
+  font-size: 0.92em;
+}
+
+#wrapper #container .breadcrumb, #wrapper #container .breadcrumb a {
+  color: #529ad6;
+}
+
+#mission {
+  padding: 1em;
+  background-color: #fff;
+  border: 1px solid #e0e5fb;
+  margin-bottom: 2em;
+}
+
+/**
+ * Primary navigation
+ */
+ul.primary-links {
+  margin: 0;
+  padding: 0;
+  float: right; /* LTR */
+  position: relative;
+  z-index: 4;
+}
+
+ul.primary-links li {
+  margin: 0;
+  padding: 0;
+  float: left; /* LTR */
+  background-image: none;
+}
+
+ul.primary-links li a, ul.primary-links li a:link, ul.primary-links li a:visited {
+  display: block;
+  margin: 0 1em;
+  padding: .75em 0 0;
+  color: #fff;
+  background: transparent url(images/bg-navigation-item.png) no-repeat 50% 0;
+}
+
+ul.primary-links li a:hover, ul.primary-links li a.active {
+  color: #fff;
+  background: transparent url(images/bg-navigation-item-hover.png) no-repeat 50% 0;
+}
+
+/**
+ * Secondary navigation
+ */
+ul.secondary-links {
+  margin: 0;
+  padding: 18px 0 0;
+  float: right; /* LTR */
+  clear: right; /* LTR */
+  position: relative;
+  z-index: 4;
+}
+
+ul.secondary-links li {
+  margin: 0;
+  padding: 0;
+  float: left; /* LTR */
+  background-image: none;
+}
+
+ul.secondary-links li a, ul.secondary-links li a:link, ul.secondary-links li a:visited {
+  display: block;
+  margin: 0 1em;
+  padding: .75em 0 0;
+  color: #cde3f1;
+  background: transparent;
+}
+
+ul.secondary-links li a:hover, ul.secondary-links li a.active {
+  color: #cde3f1;
+  background: transparent;
+}
+
+/**
+ * Local tasks
+ */
+ul.primary, ul.primary li, ul.secondary, ul.secondary li {
+  border: 0;
+  background: none;
+  margin: 0;
+  padding: 0;
+}
+
+#tabs-wrapper {
+  margin: 0 -26px 1em;
+  padding: 0 26px;
+  border-bottom: 1px solid #e9eff3;
+  position: relative;
+}
+ul.primary {
+  padding: 0.5em 0 10px;
+  float: left; /* LTR */
+}
+ul.secondary {
+  clear: both;
+  text-align: left; /* LTR */
+  border-bottom: 1px solid #e9eff3;
+  margin: -0.2em -26px 1em;
+  padding: 0 26px 0.6em;
+}
+h2.with-tabs {
+  float: left; /* LTR */
+  margin: 0 2em 0 0; /* LTR */
+  padding: 0;
+}
+
+ul.primary li a, ul.primary li.active a, ul.primary li a:hover, ul.primary li a:visited,
+ul.secondary li a, ul.secondary li.active a, ul.secondary li a:hover, ul.secondary li a:visited {
+  border: 0;
+  background: transparent;
+  padding: 4px 1em;
+  margin: 0 0 0 1px; /* LTR */
+  height: auto;
+  text-decoration: none;
+  position: relative;
+  top: -1px;
+}
+ul.primary li.active a, ul.primary li.active a:link, ul.primary li.active a:visited, ul.primary li a:hover,
+ul.secondary li.active a, ul.secondary li.active a:link, ul.secondary li.active a:visited, ul.secondary li a:hover {
+  background: url(images/bg-tab.png) repeat-x 0 50%;
+  color: #fff;
+}
+ul.primary li.active a,
+ul.secondary li.active a {
+  font-weight: bold;
+}
+
+/**
+ * Nodes & comments
+ */
+.node {
+  border-bottom: 1px solid #e9eff3;
+  margin: -1.5em -26px 1.5em;
+  padding: 1.5em 26px;
+}
+
+ul.links li, ul.inline li {
+  margin-left: 0;
+  margin-right: 0;
+  padding-left: 0; /* LTR */
+  padding-right: 1em; /* LTR */
+  background-image: none;
+}
+
+.node .links, .comment .links {
+  text-align: left; /* LTR */
+}
+
+.node .links ul.links li, .comment .links ul.links li {}
+.terms ul.links li {
+  margin-left: 0;
+  margin-right: 0;
+  padding-right: 0;
+  padding-left: 1em;
+}
+
+.picture, .comment .submitted {
+  float: right; /* LTR */
+  clear: right; /* LTR */
+  padding-left: 1em; /* LTR */
+}
+
+.new {
+  color: #ffae00;
+  font-size: 0.92em;
+  font-weight: bold;
+  float: right; /* LTR */
+}
+
+.terms {
+  float: right; /* LTR */
+}
+
+.preview .node, .preview .comment, .sticky {
+  margin: 0;
+  padding: 0.5em 0;
+  border: 0;
+  background: 0;
+}
+
+.sticky {
+  padding: 1em;
+  background-color: #fff;
+  border: 1px solid #e0e5fb;
+  margin-bottom: 2em;
+}
+
+#comments {
+  position: relative;
+  top: -1px;
+  border-bottom: 1px solid #e9eff3;
+  margin: -1.5em -25px 0;
+  padding: 0 25px;
+}
+
+#comments h2.comments {
+  margin: 0 -25px;
+  padding: .5em 25px;
+  background: #fff url(images/gradient-inner.png) repeat-x 0 0;
+}
+
+.comment {
+  margin: 0 -25px;
+  padding: 1.5em 25px 1.5em;
+  border-top: 1px solid #e9eff3;
+}
+
+.indented {
+  margin-left: 25px; /* LTR */
+}
+
+.comment h3 a.active {
+  color: #494949;
+}
+
+.node .content, .comment .content {
+  margin: 0.6em 0;
+}
+
+/**
+ * Aggregator.module
+ */
+#aggregator {
+  margin-top: 1em;
+}
+#aggregator .feed-item-title {
+  font-size: 160%;
+  line-height: 130%;
+}
+#aggregator .feed-item {
+  border-bottom: 1px solid #e9eff3;
+  margin: -1.5em -31px 1.75em;
+  padding: 1.5em 31px;
+}
+#aggregator .feed-item-categories {
+  font-size: 0.92em;
+}
+#aggregator .feed-item-meta {
+  font-size: 0.92em;
+  color: #898989;
+}
+
+/**
+ * Color.module
+ */
+#palette .form-item {
+  border: 1px solid #fff;
+}
+#palette .item-selected {
+  background: #fff url(images/gradient-inner.png) repeat-x 0 0;
+  border: 1px solid #d9eaf5;
+}
+
+/**
+ * Menu.module
+ */
+tr.menu-disabled {
+  opacity: 0.5;
+}
+tr.odd td.menu-disabled {
+  background-color: #edf5fa;
+}
+tr.even td.menu-disabled {
+  background-color: #fff;
+}
+
+/**
+ * Poll.module
+ */
+.poll .bar {
+  background: #fff url(images/bg-bar-white.png) repeat-x 0 0;
+  border: solid #f0f0f0;
+  border-width: 0 1px 1px;
+}
+
+.poll .bar .foreground {
+  background: #71a7cc url(images/bg-bar.png) repeat-x 0 100%;
+}
+
+.poll .percent {
+  font-size: .9em;
+}
+
+/**
+ * Autocomplete.
+ */
+#autocomplete li {
+  cursor: default;
+  padding: 2px;
+  margin: 0;
+}
+
+/**
+ * Collapsible fieldsets
+ */
+fieldset {
+  margin: 1em 0;
+  padding: 1em;
+  border: 1px solid #d9eaf5;
+  background: #fff url(images/gradient-inner.png) repeat-x 0 0;
+}
+
+/* Targets IE 7. Fixes background image in field sets. */
+*:first-child+html fieldset {
+  padding: 0 1em 1em;
+  background-position: 0 .75em;
+  background-color: transparent;
+}
+
+*:first-child+html fieldset > .description, *:first-child+html fieldset .fieldset-wrapper .description {
+  padding-top: 1em;
+}
+
+fieldset legend {
+  /* Fix disappearing legend in FFox */
+  display: block;
+}
+
+*:first-child+html fieldset legend, *:first-child+html fieldset.collapsed legend {
+  display: inline;
+}
+
+html.js fieldset.collapsed {
+  background: transparent;
+  padding-top: 0;
+  padding-bottom: .6em;
+}
+
+html.js fieldset.collapsible legend a {
+  padding-left: 2em; /* LTR */
+  background: url(images/menu-expanded.gif) no-repeat 0% 50%; /* LTR */
+}
+
+html.js fieldset.collapsed legend a {
+  background: url(images/menu-collapsed.gif) no-repeat 0% 50%; /* LTR */
+}
+
+/**
+ * Syndication icons and block
+ */
+#block-node-0 h2 {
+  float: left; /* LTR */
+  padding-right: 20px; /* LTR */
+}
+
+#block-node-0 img, .feed-icon {
+  float: right; /* LTR */
+  padding-top: 4px;
+}
+
+#block-node-0 .content {
+  clear: right; /* LTR */
+}
+
+/**
+ * Login Block
+ */
+#user-login-form {
+  text-align: center;
+}
+#user-login-form ul {
+  text-align: left; /* LTR */
+}
+
+/**
+ * User profiles.
+ */
+.profile {
+  margin-top: 1.5em;
+}
+.profile h3 {
+  border-bottom: 0;
+  margin-bottom: 1em;
+}
+.profile dl {
+  margin: 0;
+}
+.profile dt {
+  font-weight: normal;
+  color: #898989;
+  font-size: 0.92em;
+  line-height: 1.3em;
+  margin-top: 1.4em;
+  margin-bottom: 0.45em;
+}
+.profile dd {
+  margin-bottom: 1.6em;
+}
+
+/**
+ * Admin Styles
+ */
+div.admin-panel,
+div.admin-panel .description,
+div.admin-panel .body,
+div.admin,
+div.admin .left,
+div.admin .right,
+div.admin .expert-link,
+div.item-list,
+.menu {
+  margin: 0;
+  padding: 0;
+}
+
+div.admin .left {
+  float: left; /* LTR */
+  width: 48%;
+}
+div.admin .right {
+  float: right; /* LTR */
+  width: 48%;
+}
+
+div.admin-panel {
+  background: #fff url(images/gradient-inner.png) repeat-x 0 0;
+  padding: 1em 1em 1.5em;
+}
+div.admin-panel .description {
+  margin-bottom: 1.5em;
+}
+div.admin-panel dl {
+  margin: 0;
+}
+div.admin-panel dd {
+  color: #898989;
+  font-size: 0.92em;
+  line-height: 1.3em;
+  margin-top: -.2em;
+  margin-bottom: .65em;
+}
+
+table.system-status-report th {
+  border-color: #d3e7f4;
+}
+
+#autocomplete li.selected, tr.selected td, tr.selected td.active {
+  background: #027ac6;
+  color: #fff;
+}
+
+tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active {
+  color: #d3e7f4;
+}
+
+tr.taxonomy-term-preview {
+  opacity: 0.5;
+}
+
+tr.taxonomy-term-divider-top {
+  border-bottom: none;
+}
+
+tr.taxonomy-term-divider-bottom {
+  border-top: 1px dotted #CCC;
+}
+
+/**
+ * CSS support
+ */
+
+/*******************************************************************
+ * Color Module: Don't touch                                       *
+ *******************************************************************/
+
+/**
+ * Generic elements.
+ */
+.messages {
+  background-color: #fff;
+  border: 1px solid #b8d3e5;
+}
+
+.preview {
+  background-color: #fcfce8;
+  border: 1px solid #e5e58f;
+}
+
+div.status {
+  color: #33a333;
+  border-color: #c7f2c8;
+}
+
+div.error, tr.error {
+  color: #a30000;
+  background-color: #FFCCCC;
+}
+
+.form-item input.error, .form-item textarea.error {
+  border: 1px solid #c52020;
+  color: #363636;
+}
+
+/**
+ * dblog.module
+ */
+tr.dblog-user {
+  background-color: #fcf9e5;
+}
+
+tr.dblog-user td.active {
+  background-color: #fbf5cf;
+}
+
+tr.dblog-content {
+  background-color: #fefefe;
+}
+
+tr.dblog-content td.active {
+  background-color: #f5f5f5;
+}
+
+tr.dblog-warning {
+  background-color: #fdf5e6;
+}
+
+tr.dblog-warning td.active {
+  background-color: #fdf2de;
+}
+
+tr.dblog-error {
+  background-color: #fbe4e4;
+}
+
+tr.dblog-error td.active {
+  background-color: #fbdbdb;
+}
+tr.dblog-page-not-found, tr.dblog-access-denied {
+  background: #d7ffd7;
+}
+tr.dblog-page-not-found td.active, tr.dblog-access-denied td.active {
+  background: #c7eec7;
+}
+
+/**
+ * Status report colors.
+ */
+table.system-status-report tr.error, table.system-status-report tr.error th {
+  background-color: #fcc;
+  border-color: #ebb;
+  color: #200;
+}
+table.system-status-report tr.warning, table.system-status-report tr.warning th {
+  background-color: #ffd;
+  border-color: #eeb;
+}
+table.system-status-report tr.ok, table.system-status-report tr.ok th {
+  background-color: #dfd;
+  border-color: #beb;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/garland/template.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,102 @@
+<?php
+// $Id: template.php,v 1.16 2007/10/11 09:51:29 goba Exp $
+
+/**
+ * Sets the body-tag class attribute.
+ *
+ * Adds 'sidebar-left', 'sidebar-right' or 'sidebars' classes as needed.
+ */
+function phptemplate_body_class($left, $right) {
+  if ($left != '' && $right != '') {
+    $class = 'sidebars';
+  }
+  else {
+    if ($left != '') {
+      $class = 'sidebar-left';
+    }
+    if ($right != '') {
+      $class = 'sidebar-right';
+    }
+  }
+
+  if (isset($class)) {
+    print ' class="'. $class .'"';
+  }
+}
+
+/**
+ * Return a themed breadcrumb trail.
+ *
+ * @param $breadcrumb
+ *   An array containing the breadcrumb links.
+ * @return a string containing the breadcrumb output.
+ */
+function phptemplate_breadcrumb($breadcrumb) {
+  if (!empty($breadcrumb)) {
+    return '<div class="breadcrumb">'. implode(' › ', $breadcrumb) .'</div>';
+  }
+}
+
+/**
+ * Allow themable wrapping of all comments.
+ */
+function phptemplate_comment_wrapper($content, $node) {
+  if (!$content || $node->type == 'forum') {
+    return '<div id="comments">'. $content .'</div>';
+  }
+  else {
+    return '<div id="comments"><h2 class="comments">'. t('Comments') .'</h2>'. $content .'</div>';
+  }
+}
+
+/**
+ * Override or insert PHPTemplate variables into the templates.
+ */
+function phptemplate_preprocess_page(&$vars) {
+  $vars['tabs2'] = menu_secondary_local_tasks();
+
+  // Hook into color.module
+  if (module_exists('color')) {
+    _color_page_alter($vars);
+  }
+}
+
+/**
+ * Returns the rendered local tasks. The default implementation renders
+ * them as tabs. Overridden to split the secondary tasks.
+ *
+ * @ingroup themeable
+ */
+function phptemplate_menu_local_tasks() {
+  return menu_primary_local_tasks();
+}
+
+function phptemplate_comment_submitted($comment) {
+  return t('!datetime — !username',
+    array(
+      '!username' => theme('username', $comment),
+      '!datetime' => format_date($comment->timestamp)
+    ));
+}
+
+function phptemplate_node_submitted($node) {
+  return t('!datetime — !username',
+    array(
+      '!username' => theme('username', $node),
+      '!datetime' => format_date($node->created),
+    ));
+}
+
+/**
+ * Generates IE CSS links for LTR and RTL languages.
+ */
+function phptemplate_get_ie_styles() {
+  global $language;
+
+  $iecss = '<link type="text/css" rel="stylesheet" media="all" href="'. base_path() . path_to_theme() .'/fix-ie.css" />';
+  if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
+    $iecss .= '<style type="text/css" media="all">@import "'. base_path() . path_to_theme() .'/fix-ie-rtl.css";</style>';
+  }
+
+  return $iecss;
+}
Binary file themes/pushbutton/arrow-next-hover-rtl.png has changed
Binary file themes/pushbutton/arrow-next-hover.png has changed
Binary file themes/pushbutton/arrow-next-rtl.png has changed
Binary file themes/pushbutton/arrow-next-visited-rtl.png has changed
Binary file themes/pushbutton/arrow-next-visited.png has changed
Binary file themes/pushbutton/arrow-next.png has changed
Binary file themes/pushbutton/arrow-prev-hover-rtl.png has changed
Binary file themes/pushbutton/arrow-prev-hover.png has changed
Binary file themes/pushbutton/arrow-prev-rtl.png has changed
Binary file themes/pushbutton/arrow-prev-visited-rtl.png has changed
Binary file themes/pushbutton/arrow-prev-visited.png has changed
Binary file themes/pushbutton/arrow-prev.png has changed
Binary file themes/pushbutton/arrow-up-hover.png has changed
Binary file themes/pushbutton/arrow-up-visited.png has changed
Binary file themes/pushbutton/arrow-up.png has changed
Binary file themes/pushbutton/background.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/block.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,7 @@
+<?php
+// $Id: block.tpl.php,v 1.2 2007/08/07 08:39:36 goba Exp $
+?>
+<div class="<?php print "block block-$block->module" ?>" id="<?php print "block-$block->module-$block->delta"; ?>">
+  <div class="title"><h3><?php print $block->subject ?></h3></div>
+  <div class="content"><?php print $block->content ?></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/box.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,9 @@
+<?php
+// $Id: box.tpl.php,v 1.3 2007/08/07 08:39:36 goba Exp $
+?>
+<div class="box">
+  <?php if ($title): ?>
+  <h2 class="title"><?php print $title ?></h2>
+  <?php endif; ?>
+  <div class="content"><?php print $content ?></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/comment.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,22 @@
+<?php
+// $Id: comment.tpl.php,v 1.6 2008/01/04 19:24:24 goba Exp $
+?>
+<div class="comment<?php print ' '. $status; ?>">
+  <?php if ($picture) : ?>
+    <?php print $picture ?>
+  <?php endif; ?>
+  <h3 class="title"><?php print $title ?></h3>
+  <div class="submitted"><?php print $submitted ?><?php if ($comment->new) : ?><span class="new"> *<?php print $new ?></span><?php endif; ?></div>
+  <div class="content">
+    <?php print $content ?>
+    <?php if ($signature): ?>
+      <div class="clear-block">
+        <div>—</div>
+        <?php print $signature ?>
+      </div>
+    <?php endif; ?>
+  </div>
+  <!-- BEGIN: links -->
+  <div class="links">&raquo; <?php print $links ?></div>
+  <!-- END: links -->
+</div>
Binary file themes/pushbutton/forum-container-rtl.jpg has changed
Binary file themes/pushbutton/forum-container.jpg has changed
Binary file themes/pushbutton/forum-link-rtl.png has changed
Binary file themes/pushbutton/forum-link.png has changed
Binary file themes/pushbutton/header-a.jpg has changed
Binary file themes/pushbutton/header-b-rtl.jpg has changed
Binary file themes/pushbutton/header-b.jpg has changed
Binary file themes/pushbutton/header-c.png has changed
Binary file themes/pushbutton/icon-block-rtl.png has changed
Binary file themes/pushbutton/icon-block.png has changed
Binary file themes/pushbutton/icon-comment-rtl.png has changed
Binary file themes/pushbutton/icon-comment.png has changed
Binary file themes/pushbutton/logo-active-rtl.jpg has changed
Binary file themes/pushbutton/logo-active.jpg has changed
Binary file themes/pushbutton/logo-background-rtl.jpg has changed
Binary file themes/pushbutton/logo-background.jpg has changed
Binary file themes/pushbutton/logo-hover-rtl.jpg has changed
Binary file themes/pushbutton/logo-hover.jpg has changed
Binary file themes/pushbutton/logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/node.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,15 @@
+<?php
+// $Id: node.tpl.php,v 1.4 2007/08/07 08:39:36 goba Exp $
+?>
+<div class="node<?php if ($sticky) { print " sticky"; } ?><?php if (!$status) { print " node-unpublished"; } ?>">
+  <?php print $picture ?>
+  <?php if ($page == 0): ?>
+    <h1 class="title"><a href="<?php print $node_url ?>"><?php print $title ?></a></h1>
+  <?php endif; ?>
+    <span class="submitted"><?php print $submitted ?></span>
+    <div class="taxonomy"><?php print $terms ?></div>
+    <div class="content"><?php print $content ?></div>
+    <?php if ($links): ?>
+    <div class="links">&raquo; <?php print $links ?></div>
+    <?php endif; ?>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/page.tpl.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,120 @@
+<?php
+// $Id: page.tpl.php,v 1.25 2008/01/24 09:42:53 goba Exp $
+?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language->language ?>" xml:lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
+<head>
+  <title><?php print $head_title ?></title>
+  <meta http-equiv="Content-Style-Type" content="text/css" />
+  <?php print $head ?>
+  <?php print $styles ?>
+  <?php print $scripts ?>
+</head>
+
+<body bgcolor="#ffffff">
+
+<div class="hide"><a href="#content" title="<?php print t('Skip navigation') ?>." accesskey="2"><?php print t('Skip navigation') ?></a>.</div>
+
+<table id="primary-menu" summary="Navigation elements." border="0" cellpadding="0" cellspacing="0" width="100%">
+  <tr>
+    <td id="home" width="10%">
+      <?php if ($logo) : ?>
+        <a href="<?php print $front_page ?>" title="<?php print t('Home') ?>"><img src="<?php print($logo) ?>" alt="<?php print t('Home') ?>" border="0" /></a>
+      <?php endif; ?>
+    </td>
+
+    <td id="site-info" width="20%">
+      <?php if ($site_name) : ?>
+        <div class='site-name'><a href="<?php print $front_page ?>" title="<?php print t('Home') ?>"><?php print($site_name) ?></a></div>
+      <?php endif;?>
+      <?php if ($site_slogan) : ?>
+        <div class='site-slogan'><?php print($site_slogan) ?></div>
+      <?php endif;?>
+    </td>
+    <td class="primary-links" width="70%" align="center" valign="middle">
+      <?php print theme('links', $primary_links, array('class' => 'links', 'id' => 'navlist')) ?>
+    </td>
+  </tr>
+</table>
+
+<table id="secondary-menu" summary="Navigation elements." border="0" cellpadding="0" cellspacing="0" width="100%">
+  <tr>
+    <td class="secondary-links" width="75%"  align="center" valign="middle">
+      <?php print theme('links', $secondary_links, array('class' => 'links', 'id' => 'subnavlist')) ?>
+    </td>
+    <td width="25%" align="center" valign="middle">
+      <?php print $search_box ?>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2"><div><?php print $header ?></div></td>
+  </tr>
+</table>
+
+<table id="content" border="0" cellpadding="15" cellspacing="0" width="100%">
+  <tr>
+    <?php if ($left != ""): ?>
+    <td id="sidebar-left">
+      <?php print $left ?>
+    </td>
+    <?php endif; ?>
+
+    <td valign="top">
+      <?php if ($mission != ""): ?>
+      <div id="mission"><?php print $mission ?></div>
+      <?php endif; ?>
+
+      <div id="main">
+        <?php if ($title != ""): ?>
+          <?php print $breadcrumb ?>
+          <h1 class="title"><?php print $title ?></h1>
+
+          <?php if ($tabs != ""): ?>
+            <div class="tabs"><?php print $tabs ?></div>
+          <?php endif; ?>
+
+        <?php endif; ?>
+
+        <?php if ($show_messages && $messages != ""): ?>
+          <?php print $messages ?>
+        <?php endif; ?>
+
+        <?php if ($help != ""): ?>
+            <div id="help"><?php print $help ?></div>
+        <?php endif; ?>
+
+      <!-- start main content -->
+      <?php print $content; ?>
+      <?php print $feed_icons; ?>
+      <!-- end main content -->
+
+      </div><!-- main -->
+    </td>
+    <?php if ($right != ""): ?>
+    <td id="sidebar-right">
+      <?php print $right ?>
+    </td>
+    <?php endif; ?>
+  </tr>
+</table>
+
+<table id="footer-menu" summary="Navigation elements." border="0" cellpadding="0" cellspacing="0" width="100%">
+  <tr>
+    <td align="center" valign="middle">
+    <?php if (isset($primary_links)) : ?>
+      <?php print theme('links', $primary_links, array('class' => 'links primary-links')) ?>
+    <?php endif; ?>
+    <?php if (isset($secondary_links)) : ?>
+      <?php print theme('links', $secondary_links, array('class' => 'links secondary-links')) ?>
+    <?php endif; ?>
+    </td>
+  </tr>
+</table>
+
+<?php if ($footer_message || $footer) : ?>
+<div id="footer-message">
+    <p><?php print $footer_message . $footer;?></p>
+</div>
+<?php endif; ?>
+<?php print $closure;?>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/pushbutton.info	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,6 @@
+; $Id: pushbutton.info,v 1.4 2007/06/08 05:50:58 dries Exp $
+name = Pushbutton
+description = Tabled, multi-column theme in blue and orange tones.
+version = VERSION
+core = 6.x
+engine = phptemplate
Binary file themes/pushbutton/screenshot.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/style-rtl.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,184 @@
+/* $Id: style-rtl.css,v 1.4 2008/01/25 21:20:26 goba Exp $ */
+
+body {
+  direction: rtl;
+}
+
+blockquote {
+  border-right: 4px solid #69c;
+  border-left: none;
+  margin: 25px 50px 25px 100px;
+  text-align: right;
+}
+
+#primary-menu tr {
+  background: transparent url(header-a.jpg) right bottom repeat;
+}
+
+td#home {
+  background: transparent url(logo-background-rtl.jpg) right top repeat;
+}
+
+td#home a:link img, td#home a:visited img {
+  background: transparent url(logo-active-rtl.jpg) repeat;
+}
+
+td#home a:hover img {
+  background: transparent url(logo-hover-rtl.jpg) repeat;
+}
+
+#primary-menu .primary-links {
+  background: transparent url(header-b-rtl.jpg) right top no-repeat;
+}
+
+ul.links li {
+  border-right: 1px solid #ff8c00;
+  border-left: none;
+}
+
+#navlist li {
+  border-right: 1px solid #369;
+  border-left: none;
+}
+
+#subnavlist li, ul.primary-links li, ul.secondary-links li {
+  border-right: 1px solid #fff;
+  border-left: none;
+}
+
+.tabs ul.primary {
+  padding: 0 10px 3px 0;
+  height: 1.2em; /* FF hack, see float below */
+  line-height: 1em; /* FF hack, see float below */
+}
+/* @begin FF hacks, copied from Wikipedia's RTL tabs implementation */
+.tabs ul { clear:right;}
+.tabs li {padding: 0 0 0 10px; float: right;}
+/* @end FF hacks */
+
+.tabs ul.primary li a {
+  background: #fff url(tabs-off-rtl.png) right top no-repeat;
+  border-style: none none none solid;
+  margin-left: 10px;
+  margin-right: 0;
+}
+.tabs ul.primary li.active a {
+  background: #369 url(tabs-on-rtl.png) right top no-repeat;
+  border-right: none;
+  border-left: 2px solid #369;
+}
+
+.tabs ul.primary li a:hover {
+  background-color:#FFFAF0;
+  color:#FF4500;
+}
+
+.tabs ul.secondary {
+  padding: 10px 0 60px 0;
+  line-height: 220%;
+}
+
+.tabs ul.secondary li {
+  padding: 0 10px 0 0;
+  border-left: none;
+}
+
+.tabs ul.secondary li a {
+  background: #fff url(tabs-option-off-rtl.png) right center no-repeat;
+  padding: 10px 25px 10px 0;
+}
+
+.tabs ul.secondary li a.active {
+  background: #fff url(tabs-option-on.png) right center no-repeat;
+}
+
+.tabs ul.secondary li a:hover {
+  background: #fff url(tabs-option-hover-rtl.png) right center no-repeat;
+}
+
+#menu {
+  text-align: left;
+}
+
+.node .content {
+  text-align: right;
+}
+
+.comment .content {
+  text-align: right;
+}
+
+.block .title h3 {
+  padding: 10px 30px 10px 5px;
+  background: transparent url(icon-block-rtl.png) right center no-repeat;
+}
+
+.node .picture {
+  float: left;
+}
+
+.comment .title {
+  padding: 10px 19px 12px 0;
+  background: transparent url(icon-comment-rtl.png) right center no-repeat;
+}
+
+.comment .new {
+  margin-right: 2px;
+  margin-left: 0;
+}
+
+.comment .picture {
+  float: left;
+}
+
+.nav .links .next a:link {
+  padding: 17px 0 17px 17px;
+  background: transparent url(arrow-next-rtl.png) left center no-repeat;
+}
+.nav .links .next a:visited {
+  padding: 17px 0 17px 17px;
+  background: transparent url(arrow-next-visited-rtl.png) left center no-repeat;
+}
+.nav .links .next a:hover {
+  padding: 17px 0 17px 17px;
+  background: transparent url(arrow-next-hover-rtl.png) left center no-repeat;
+}
+.nav .links .prev a:link {
+  padding: 17px 17px 17px 0;
+  background: transparent url(arrow-prev-rtl.png) right center no-repeat;
+}
+.nav .links .prev a:visited {
+  padding: 17px 17px 17px 0;
+  background: transparent url(arrow-prev-visited-rtl.png) right center no-repeat;
+}
+.nav .links .prev a:hover {
+  padding: 17px 17px 17px 0;
+  background: transparent url(arrow-prev-hover-rtl.png) right center no-repeat;
+}
+
+#tracker th {
+  border-right: 1px solid #fafafa;
+  border-left: 1px solid #ddd ;
+}
+
+#tracker th img {
+  float: left;
+}
+
+#tracker td {
+  padding: 1em 0 1em 1em;
+}
+
+#forum td.container {
+  background: #369 url(forum-container-rtl.jpg) left top no-repeat;
+}
+
+#forum td.container a {
+  padding: 20px 35px 20px 0;
+  background: transparent url(forum-link-rtl.png) right center no-repeat;
+}
+
+/* IE hack */
+div, div.name a, .tabs ul li, .tabs ul li a{zoom:1}
+.tabs ul.primary li a, .tabs ul.primary li.active a, .tabs ul.secondary li, .tabs ul.secondary li, div.tabs a.active {zoom:1}
+.tabs ul.primary li.active a{zoom:1}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/pushbutton/style.css	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,613 @@
+/* $Id: style.css,v 1.24 2007/12/22 23:24:26 goba Exp $ */
+
+/*
+** HTML elements
+*/
+body {
+  color: #000;
+  background-color: #fff;
+  margin: 0;
+  padding: 0;
+}
+body, p, td, li, ul, ol {
+  font-family: Verdana, Helvetica, Arial, sans-serif;
+}
+h1, h2, h3, h4, h5, h6 {
+  font-family: "Trebuchet MS", Geneva, Arial, Helvetica, SunSans-Regular, Verdana, sans-serif;
+  margin: 0;
+}
+h1 {
+  color: #369;
+  font-size: 1.6em;
+}
+tr.odd td, tr.even td {
+  padding: 0.3em;
+}
+a:link {
+  text-decoration: none;
+  font-weight: bold;
+  color: #ff8c00;
+}
+a:visited {
+  text-decoration: none;
+  font-weight: bold;
+  color: #c96;
+}
+a:hover, a:active {
+  font-weight: bold;
+  color: #ff4500;
+  text-decoration: underline;
+}
+fieldset {
+  border: 1px solid #ccc;
+}
+p {
+  margin: 0 0 1.3em 0;
+  padding: 0;
+}
+blockquote {
+  border-left: 4px solid #69c;  /* LTR */
+  padding: 0 15px;
+  margin: 25px 100px 25px 50px; /* LTR */
+  color: #696969;
+  text-align: left; /* LTR */
+  font-size: 1.2em;
+  line-height: 1.3em;
+  font-family: "Trebuchet MS", Geneva, Arial, Helvetica, SunSans-Regular, Verdana, sans-serif;
+}
+pre {
+  background-color: #eee;
+  padding: 0.75em 1.5em;
+  font-size: 1.2em;
+  border: 1px solid #ddd;
+}
+.form-item {
+  margin-top: 1em;
+}
+.form-item label {
+  color: #369;
+}
+.item-list .title {
+  color: #369;
+  font-size: 0.85em;
+}
+
+/*
+** Page layout blocks / IDs
+*/
+#primary-menu {
+  border-collapse: separate;
+  background-color: #e0edfb;
+  border-bottom: 3px solid #69c;
+}
+#primary-menu tr {
+  background: transparent url(header-a.jpg) left bottom repeat; /* LTR */
+}
+td#home {
+  background: transparent url(logo-background.jpg) left top repeat; /* LTR */
+}
+td#home a:link img, td#home a:visited img {
+  background: transparent url(logo-active.jpg) repeat; /* LTR */
+  width: 144px;
+  height: 63px;
+}
+td#home a:hover img {
+  background: transparent url(logo-hover.jpg) repeat; /* LTR */
+  width: 144px;
+  height: 63px;
+}
+.primary-links, .primary-links a:link, .primary-links a:visited {
+  color: #369;
+}
+.primary-links a:hover {
+  color: #000;
+}
+#primary-menu .primary-links {
+  background: transparent url(header-b.jpg) left top no-repeat; /* LTR */
+  font-size: 0.79em;
+}
+#primary-menu .primary-links h1, #primary-menu .primary-links h2, #primary-menu .primary-links h3 {
+  font-size: 2.3em;
+  color: #369;
+}
+#secondary-menu {
+  border-collapse: separate;
+  background-color: #369;
+  border-bottom: 3px solid #69c;
+}
+.secondary-links, .secondary-links a:link, .secondary-links a:visited {
+  color: #e4e9eb;
+}
+.secondary-links a:hover {
+  color: #fff;
+  text-decoration: underline;
+}
+#secondary-menu .secondary-links {
+  font-size: 0.85em;
+}
+ul.links li {
+  border-left: 1px solid #ff8c00; /* LTR */
+}
+#navlist li {
+  border-left: 1px solid #369; /* LTR */
+}
+#subnavlist li, ul.primary-links li, ul.secondary-links li {
+  border-left: 1px solid #fff; /* LTR */
+}
+#navlist li.first, #subnavlist li.first, ul.links li.first {
+  border: none;
+}
+.tabs {
+  margin: 15px 0;
+}
+.tabs ul.primary {
+  border-collapse: collapse;
+  padding: 0 0 3px 10px; /* LTR */
+  white-space: nowrap;
+  list-style: none;
+  margin: 0;
+  height: auto;
+  line-height: normal;
+  border-bottom: 2px solid #369;
+}
+.tabs ul.primary li {
+  display: inline;
+}
+.tabs ul.primary li a {
+  padding: 3px 10px;
+  background: #fff url(tabs-off.png) left top no-repeat; /* LTR */
+  border-color: #69C;
+  border-width: 2px;
+  border-style: none solid none none; /* LTR */
+  height: auto;
+  margin-right: 10px; /* LTR */
+  text-decoration: none;
+  text-transform: lowercase;
+}
+.tabs ul.primary li.active a {
+  background: #369 url(tabs-on.png) left top no-repeat; /* LTR */
+  border-right: 2px solid #369; /* LTR */
+  color: #fff;
+}
+.tabs ul.primary li a:hover {
+  background-color: #fffaf0;
+  color: #ff4500;
+}
+.tabs ul.secondary {
+  border-collapse: collapse;
+  padding: 10px 0;
+  margin: 0;
+  white-space: nowrap;
+  width: 100%;
+  list-style: none;
+  height: auto;
+  line-height: normal;
+  border-bottom: none;
+}
+.tabs ul.secondary li {
+  display: inline;
+  height: auto;
+  padding: 0 0 0 10px; /* LTR */
+  text-decoration: none;
+  border-right: none; /* LTR */
+}
+.tabs ul.secondary li a {
+  background: #fff url(tabs-option-off.png) left center no-repeat; /* LTR */
+  padding: 10px 0 10px 25px; /* LTR */
+  margin: 0;
+}
+.tabs ul.secondary li a.active {
+  background: #fff url(tabs-option-on.png) left center no-repeat; /* LTR */
+  color: #369;
+  border-bottom: none;
+}
+.tabs ul.secondary li a:hover {
+  background: #fff url(tabs-option-hover.png) left center no-repeat; /* LTR */
+  color: #FF4500;
+}
+#content {
+  background-color: #fff;
+}
+#contentstart {
+  background-color: #fff;
+}
+#menu {
+  padding: 0.5em 0.5em 0 0.5em;
+  text-align: right; /* LTR */
+  vertical-align: middle;
+}
+#search .form-text, #search .form-submit {
+  border: 1px solid #369;
+  font-size: 0.85em;
+  margin: 0.2em;
+}
+#search .form-text {
+  width: 9em;
+}
+#search .form-submit {
+  height: 1.5em;
+}
+#mission {
+  background-color: #fff;
+  color: #696969;
+  border-top: 2px solid #dcdcdc;
+  border-bottom: 2px solid #dcdcdc;
+  padding: 10px;
+  margin: 20px 35px 0 35px;
+  font-family: "Trebuchet MS", Geneva, Arial, Helvetica, SunSans-Regular, Verdana, sans-serif;
+  font-size: 1.1em;
+  font-weight: normal;
+}
+#site-info {
+  background-color: #bdd3ea;
+  background-image: url(header-c.png);
+  font-family: "Trebuchet MS", Geneva, Arial, Helvetica, SunSans-Regular, Verdana, sans-serif;
+}
+.site-name {
+  font-size: 1.2em;
+}
+.site-name a:link, .site-name a:visited {
+  color: #fff;
+}
+.site-name a:hover {
+  color: #ff8c00;
+  text-decoration: none;
+}
+.site-slogan {
+  font-size: 0.8em;
+  font-weight: bold;
+}
+#main {
+  /* padding in px not ex because IE messes up 100% width tables otherwise */
+  padding: 30px 35px 50px 35px;
+  background: transparent url(background.png) center center no-repeat;
+  /* fix background overlapping text in IE (aka Peekaboo Bug) */
+  position: relative;
+}
+#mission, .node .content, .comment .content {
+  line-height: 1.4;
+}
+#help {
+  font-size: 0.9em;
+  margin-bottom: 1em;
+}
+.breadcrumb {
+  margin-bottom: .5em;
+}
+.messages {
+  background-color: #eee;
+  border: 1px solid #ccc;
+  padding: 0.3em;
+  margin-bottom: 1em;
+}
+.error {
+  border-color: red;
+}
+.nav {
+  padding: 0;
+  margin: 0;
+}
+#sidebar-left, #sidebar-right {
+  font-size: 0.75em;
+  width: 175px;
+  /* padding in px not ex because IE messes up 100% width tables otherwise */
+  padding: 25px 10px 75px 10px;
+  vertical-align: top;
+  background: #FFFAF0;
+}
+#sidebar-left {
+  border-right: 3px solid #f5f5f5;
+}
+#sidebar-right {
+  border-left: 3px solid #f5f5f5;
+}
+#sidebar-left li, #sidebar-right li {
+  font-size: 1em;
+}
+.node .content {
+  text-align: left; /* LTR */
+  font-size: 0.85em;
+  line-height: 1.3;
+}
+.comment .content {
+  text-align: left; /* LTR */
+  font-size: 0.85em;
+  line-height: 1.3;
+}
+#footer-message {
+  padding: 15px 100px 30px 100px;
+  font-size: 0.85em;
+  text-align: center;
+  color: #aaa;
+}
+table#footer-menu {
+  border-top: 3px solid #69c;
+  border-bottom: 3px solid #69c;
+  background-color: #369;
+  color: #e4e9eb;
+}
+#footer-menu td {
+  padding: 5px;
+  font-size: 0.75em;
+}
+#footer-menu .primary-links, #footer-menu a:link, #footer-menu a:visited {
+  color: #e4e9eb;
+}
+#footer-menu a:hover {
+  color: #fff;
+  text-decoration: underline;
+}
+#footer-menu .primary-links h1, #footer-menu .primary-links h2, #footer-menu .primary-links h3 {
+  font-size: 1.3em;
+  color: #e4e9eb;
+}
+/*
+** Common declarations for child classes of node, comment, block, box, etc.
+** If you want any of them styled differently for a specific parent, add
+** additional rules /with only the differing properties!/ to .parent .class.
+** See .comment .title for an example.
+*/
+#content .title, #content .title a {
+  color: #369;
+}
+.content h1 {
+  color: #369;
+  font-size: 1.9em;
+}
+.content h2 {
+  color: #58b;
+  font-size: 1.7em;
+}
+.content h3 {
+  color: #69c;
+  font-size: 1.5em;
+}
+.content h4 {
+  color: #8be;
+  font-size: 1.3em;
+}
+.content h5 {
+  color: #96c6f6;
+  font-size: 1.15em;
+}
+.submitted {
+  color: #999;
+  font-size: 0.79em;
+}
+div.links {
+  color: #ff8c00;
+}
+.links a {
+  font-weight: bold;
+}
+.box {
+  padding: 0 0 1.5em 0;
+}
+.box {
+  padding: 0;
+  margin: 0;
+}
+.box h2 {
+  font-size: 9px;
+}
+.block .title h3 {
+  border-bottom: 2px solid #69c;
+  color: #369;
+  font-size: 18px;
+  font-weight: bold;
+  padding: 10px 5px 10px 30px; /* LTR */
+  margin-bottom: .25em;
+  background: transparent url(icon-block.png) left center no-repeat; /* LTR */
+}
+.block .content {
+  padding: 5px;
+}
+.block {
+  margin-bottom: 1.5em;
+}
+.box .title {
+  font-size: 1.1em;
+}
+.node {
+  margin: .5em 0 2.5em 0;
+}
+.node .content, .comment .content {
+  margin: .5em 0 .5em 0;
+}
+.node .taxonomy {
+  color: #999;
+  font-size: 0.83em;
+  padding: 1.5em;
+}
+.node .picture {
+  border: 1px solid #fff;
+  float: right; /* LTR */
+  margin: 0.5em;
+}
+.comment {
+  border: 1px solid #abc;
+  padding: .5em;
+  margin-bottom: 1em;
+}
+.comment .title {
+  font-size: 1em;
+  padding: 10px 0 12px 19px; /* LTR */
+  background: transparent url(icon-comment.png) left center no-repeat; /* LTR */
+}
+.comment .new {
+  font-weight: bold;
+  font-size: 1em;
+  margin-left: 2px; /* LTR */
+  color: red;
+}
+.comment .picture {
+  border: 1px solid #fff;
+  float: right; /* LTR */
+  margin: 10px;
+}
+div.links {
+  font-size: 0.75em;
+}
+div.links .prev, div.links .next, div.links .up {
+  font-size: 1.15em;
+}
+.titles .prev, .titles .next {
+  font-size: 0.85em;
+  font-weight: bold;
+  color: #444;
+}
+.hide {
+  display: none
+}
+.nav .links .next a:link {
+  padding: 17px 17px 17px 0; /* LTR */
+  background: transparent url(arrow-next.png) right center no-repeat; /* LTR */
+}
+.nav .links .next a:visited {
+  padding: 17px 17px 17px 0; /* LTR */
+  background: transparent url(arrow-next-visited.png) right center no-repeat; /* LTR */
+}
+.nav .links .next a:hover {
+  padding: 17px 17px 17px 0; /* LTR */
+  background: transparent url(arrow-next-hover.png) right center no-repeat; /* LTR */
+}
+.nav .links .prev a:link {
+  padding: 17px 0 17px 17px; /* LTR */
+  background: transparent url(arrow-prev.png) left center no-repeat; /* LTR */
+}
+.nav .links .prev a:visited {
+  padding: 17px 0 17px 17px; /* LTR */
+  background: transparent url(arrow-prev-visited.png) left center no-repeat; /* LTR */
+}
+.nav .links .prev a:hover {
+  padding: 17px 0 17px 17px; /* LTR */
+  background: transparent url(arrow-prev-hover.png) left center no-repeat; /* LTR */
+}
+.nav .links .up a:link {
+  padding: 11px 0 17px 0;
+  background: transparent url(arrow-up.png) center top no-repeat;
+}
+.nav .links .up a:visited {
+  padding: 11px 0 17px 0;
+  background: transparent url(arrow-up-visited.png) center top no-repeat;
+}
+.nav .links .up a:hover {
+  padding: 11px 0 17px 0;
+  background: transparent url(arrow-up-hover.png) center top no-repeat;
+}
+
+/*
+** Administration page styles
+*/
+div.admin-panel .description {
+  color: #999;
+}
+div.admin-panel h3 {
+  background-color: #369;
+  color: #fff;
+  padding: 5px 8px 5px;
+  margin: 0;
+}
+div.admin-panel .body {
+  background: #fffaf0;
+}
+
+/*
+** Module specific styles
+*/
+.content .active {
+  color: #369;
+}
+#aggregator .feed-source {
+  background-color: #eee;
+  border: 1px solid #ccc;
+  padding: 1em;
+  margin: 1em 0 1em 0;
+}
+#aggregator .news-item .source {
+  color: #999;
+  font-style: italic;
+  font-size: 0.85em;
+}
+#aggregator .title {
+  font-size: 1em;
+}
+#aggregator h3 {
+  margin-top: 1em;
+}
+#tracker th {
+  text-align: center;
+  background-color: #f5f5f5;
+  border-bottom: 1px solid #ddd;
+  border-right: 1px solid #ddd; /* LTR */
+  border-left: 1px solid #fafafa; /* LTR */
+}
+#tracker th img {
+  float: right; /* LTR */
+}
+#tracker tr.even, #tracker tr.odd {
+  background-color: #fff;
+}
+#tracker td {
+  vertical-align: top;
+  padding: 1em 1em 1em 0; /* LTR */
+  border-bottom: 1px solid #bbb;
+}
+#forum {
+  margin: 15px 0 15px 0;
+  background-color: #fff;
+}
+#forum table {
+  width: 100%;
+  border: 2px solid #69c;
+}
+#forum table tr th {
+  text-align: center;
+  background: #69c;
+  color: #fff;
+  font-size: 0.75em;
+  border-bottom: 1px solid #aaa;
+}
+#forum table tr th a  {
+  color: #fff;
+  text-decoration: underline;
+}
+#forum table tr th img {
+  margin: 0;
+}
+#forum tr.odd {
+  background: #e0edfb;
+}
+#forum tr.even {
+  background: #fff;
+}
+#forum td {
+  padding: 0.5em;
+}
+#forum td.container {
+  color: #000;
+  background: #369 url(forum-container.jpg) right top no-repeat; /* LTR */
+  border: 2px solid #69c;
+}
+#forum td.container a {
+  color: #e4e9eb;
+  padding: 20px 0 20px 35px; /* LTR */
+  background: transparent url(forum-link.png) left center no-repeat; /* LTR */
+}
+#forum td.container a:visited {
+  color: #e4e9eb;
+}
+#forum td.statistics, #forum td.settings, #forum td.pager {
+  height: 1.5em;
+  border: 1px solid #bbb;
+}
+#forum td .name {
+  color: #96c;
+}
+#forum td .links {
+  padding-top: 0.7em;
+  font-size: 0.9em;
+}
+.block-forum h3 {
+  margin-bottom: .5em;
+}
Binary file themes/pushbutton/tabs-off-rtl.png has changed
Binary file themes/pushbutton/tabs-off.png has changed
Binary file themes/pushbutton/tabs-on-rtl.png has changed
Binary file themes/pushbutton/tabs-on.png has changed
Binary file themes/pushbutton/tabs-option-hover-rtl.png has changed
Binary file themes/pushbutton/tabs-option-hover.png has changed
Binary file themes/pushbutton/tabs-option-off-rtl.png has changed
Binary file themes/pushbutton/tabs-option-off.png has changed
Binary file themes/pushbutton/tabs-option-on.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/update.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,661 @@
+<?php
+// $Id: update.php,v 1.252 2008/02/03 18:41:16 goba Exp $
+
+/**
+ * @file
+ * Administrative page for handling updates from one Drupal version to another.
+ *
+ * Point your browser to "http://www.example.com/update.php" and follow the
+ * instructions.
+ *
+ * If you are not logged in as administrator, you will need to modify the access
+ * check statement inside your settings.php file. After finishing the upgrade,
+ * be sure to open settings.php again, and change it back to its original state!
+ */
+
+/**
+ * Global flag to identify update.php run, and so avoid various unwanted
+ * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing
+ * and translation, and solve some theming issues. This flag is checked on several
+ * places in Drupal code (not just update.php).
+ */
+define('MAINTENANCE_MODE', 'update');
+
+/**
+ * Add a column to a database using syntax appropriate for PostgreSQL.
+ * Save result of SQL commands in $ret array.
+ *
+ * Note: when you add a column with NOT NULL and you are not sure if there are
+ * already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL
+ * won't work when the table is not empty, and db_add_column() will fail.
+ * To have an empty string as the default, you must use: 'default' => "''"
+ * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL
+ * version will set values of the added column in old rows to the
+ * DEFAULT value.
+ *
+ * @param $ret
+ *   Array to which results will be added.
+ * @param $table
+ *   Name of the table, without {}
+ * @param $column
+ *   Name of the column
+ * @param $type
+ *   Type of column
+ * @param $attributes
+ *   Additional optional attributes. Recognized attributes:
+ *     not null => TRUE|FALSE
+ *     default  => NULL|FALSE|value (the value must be enclosed in '' marks)
+ * @return
+ *   nothing, but modifies $ret parameter.
+ */
+function db_add_column(&$ret, $table, $column, $type, $attributes = array()) {
+  if (array_key_exists('not null', $attributes) and $attributes['not null']) {
+    $not_null = 'NOT NULL';
+  }
+  if (array_key_exists('default', $attributes)) {
+    if (is_null($attributes['default'])) {
+      $default_val = 'NULL';
+      $default = 'default NULL';
+    }
+    elseif ($attributes['default'] === FALSE) {
+      $default = '';
+    }
+    else {
+      $default_val = "$attributes[default]";
+      $default = "default $attributes[default]";
+    }
+  }
+
+  $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type");
+  if (!empty($default)) {
+    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default");
+  }
+  if (!empty($not_null)) {
+    if (!empty($default)) {
+      $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val");
+    }
+    $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL");
+  }
+}
+
+/**
+ * Change a column definition using syntax appropriate for PostgreSQL.
+ * Save result of SQL commands in $ret array.
+ *
+ * Remember that changing a column definition involves adding a new column
+ * and dropping an old one. This means that any indices, primary keys and
+ * sequences from serial-type columns are dropped and might need to be
+ * recreated.
+ *
+ * @param $ret
+ *   Array to which results will be added.
+ * @param $table
+ *   Name of the table, without {}
+ * @param $column
+ *   Name of the column to change
+ * @param $column_new
+ *   New name for the column (set to the same as $column if you don't want to change the name)
+ * @param $type
+ *   Type of column
+ * @param $attributes
+ *   Additional optional attributes. Recognized attributes:
+ *     not null => TRUE|FALSE
+ *     default  => NULL|FALSE|value (with or without '', it won't be added)
+ * @return
+ *   nothing, but modifies $ret parameter.
+ */
+function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) {
+  if (array_key_exists('not null', $attributes) and $attributes['not null']) {
+    $not_null = 'NOT NULL';
+  }
+  if (array_key_exists('default', $attributes)) {
+    if (is_null($attributes['default'])) {
+      $default_val = 'NULL';
+      $default = 'default NULL';
+    }
+    elseif ($attributes['default'] === FALSE) {
+      $default = '';
+    }
+    else {
+      $default_val = "$attributes[default]";
+      $default = "default $attributes[default]";
+    }
+  }
+
+  $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old");
+  $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type");
+  $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old");
+  if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); }
+  if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); }
+  $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old");
+}
+
+/**
+ * Perform one update and store the results which will later be displayed on
+ * the finished page.
+ *
+ * An update function can force the current and all later updates for this
+ * module to abort by returning a $ret array with an element like:
+ * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong');
+ * The schema version will not be updated in this case, and all the
+ * aborted updates will continue to appear on update.php as updates that
+ * have not yet been run.
+ *
+ * @param $module
+ *   The module whose update will be run.
+ * @param $number
+ *   The update number to run.
+ * @param $context
+ *   The batch context array
+ */
+function update_do_one($module, $number, &$context) {
+  // If updates for this module have been aborted
+  // in a previous step, go no further.
+  if (!empty($context['results'][$module]['#abort'])) {
+    return;
+  }
+
+  $function = $module .'_update_'. $number;
+  if (function_exists($function)) {
+    $ret = $function($context['sandbox']);
+  }
+
+  if (isset($ret['#finished'])) {
+    $context['finished'] = $ret['#finished'];
+    unset($ret['#finished']);
+  }
+
+  if (!isset($context['results'][$module])) {
+    $context['results'][$module] = array();
+  }
+  if (!isset($context['results'][$module][$number])) {
+    $context['results'][$module][$number] = array();
+  }
+  $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
+
+  if (!empty($ret['#abort'])) {
+    $context['results'][$module]['#abort'] = TRUE;
+  }
+  // Record the schema update if it was completed successfully.
+  if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) {
+    drupal_set_installed_schema_version($module, $number);
+  }
+
+  $context['message'] = 'Updating '. check_plain($module) .' module';
+}
+
+function update_selection_page() {
+  $output = '<p>The version of Drupal you are updating from has been automatically detected. You can select a different version, but you should not need to.</p>';
+  $output .= '<p>Click Update to start the update process.</p>';
+
+  drupal_set_title('Drupal database update');
+  $output .= drupal_get_form('update_script_selection_form');
+
+  update_task_list('select');
+
+  return $output;
+}
+
+function update_script_selection_form() {
+  $form = array();
+  $form['start'] = array(
+    '#tree' => TRUE,
+    '#type' => 'fieldset',
+    '#title' => 'Select versions',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  // Ensure system.module's updates appear first
+  $form['start']['system'] = array();
+
+  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
+  foreach ($modules as $module => $schema_version) {
+    $updates = drupal_get_schema_versions($module);
+    // Skip incompatible module updates completely, otherwise test schema versions.
+    if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
+      // module_invoke returns NULL for nonexisting hooks, so if no updates
+      // are removed, it will == 0.
+      $last_removed = module_invoke($module, 'update_last_removed');
+      if ($schema_version < $last_removed) {
+        $form['start'][$module] = array(
+          '#value'  => '<em>'. $module .'</em> module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update <em>'. $module .'</em> module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.',
+          '#prefix' => '<div class="warning">',
+          '#suffix' => '</div>',
+        );
+        $form['start']['#collapsed'] = FALSE;
+        continue;
+      }
+      $updates = drupal_map_assoc($updates);
+      $updates[] = 'No updates available';
+      $default = $schema_version;
+      foreach (array_keys($updates) as $update) {
+        if ($update > $schema_version) {
+          $default = $update;
+          break;
+        }
+      }
+      $form['start'][$module] = array(
+        '#type' => 'select',
+        '#title' => $module .' module',
+        '#default_value' => $default,
+        '#options' => $updates,
+      );
+    }
+  }
+
+  $form['has_js'] = array(
+    '#type' => 'hidden',
+    '#default_value' => FALSE,
+    '#attributes' => array('id' => 'edit-has_js'),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Update',
+  );
+  return $form;
+}
+
+function update_batch() {
+  global $base_url;
+
+  $operations = array();
+  // Set the installed version so updates start at the correct place.
+  foreach ($_POST['start'] as $module => $version) {
+    drupal_set_installed_schema_version($module, $version - 1);
+    $updates = drupal_get_schema_versions($module);
+    $max_version = max($updates);
+    if ($version <= $max_version) {
+      foreach ($updates as $update) {
+        if ($update >= $version) {
+          $operations[] = array('update_do_one', array($module, $update));
+        }
+      }
+    }
+  }
+  $batch = array(
+    'operations' => $operations,
+    'title' => 'Updating',
+    'init_message' => 'Starting updates',
+    'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
+    'finished' => 'update_finished',
+  );
+  batch_set($batch);
+  batch_process($base_url .'/update.php?op=results', $base_url .'/update.php');
+}
+
+function update_finished($success, $results, $operations) {
+  // clear the caches in case the data has been updated.
+  drupal_flush_all_caches();
+
+  $_SESSION['update_results'] = $results;
+  $_SESSION['update_success'] = $success;
+  $_SESSION['updates_remaining'] = $operations;
+}
+
+function update_results_page() {
+  drupal_set_title('Drupal database update');
+  // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
+  $links[] = '<a href="'. base_path() .'">Main page</a>';
+  $links[] = '<a href="'. base_path() .'?q=admin">Administration pages</a>';
+
+  update_task_list();
+  // Report end result
+  if (module_exists('dblog')) {
+    $log_message = ' All errors have been <a href="'. base_path() .'?q=admin/reports/dblog">logged</a>.';
+  }
+  else {
+    $log_message = ' All errors have been logged.';
+  }
+
+  if ($_SESSION['update_success']) {
+    $output = '<p>Updates were attempted. If you see no failures below, you may proceed happily to the <a href="'. base_path() .'?q=admin">administration pages</a>. Otherwise, you may need to update your database manually.'. $log_message .'</p>';
+  }
+  else {
+    list($module, $version) = array_pop(reset($_SESSION['updates_remaining']));
+    $output = '<p class="error">The update process was aborted prematurely while running <strong>update #'. $version .' in '. $module .'.module</strong>.'. $log_message;
+    if (module_exists('dblog')) {
+      $output .= ' You may need to check the <code>watchdog</code> database table manually.';
+    }
+    $output .= '</p>';
+  }
+
+  if (!empty($GLOBALS['update_free_access'])) {
+    $output .= "<p><strong>Reminder: don't forget to set the <code>\$update_free_access</code> value in your <code>settings.php</code> file back to <code>FALSE</code>.</strong></p>";
+  }
+
+  $output .= theme('item_list', $links);
+
+  // Output a list of queries executed
+  if (!empty($_SESSION['update_results'])) {
+    $output .= '<div id="update-results">';
+    $output .= '<h2>The following queries were executed</h2>';
+    foreach ($_SESSION['update_results'] as $module => $updates) {
+      $output .= '<h3>'. $module .' module</h3>';
+      foreach ($updates as $number => $queries) {
+        if ($number != '#abort') {
+          $output .= '<h4>Update #'. $number .'</h4>';
+          $output .= '<ul>';
+          foreach ($queries as $query) {
+            if ($query['success']) {
+              $output .= '<li class="success">'. $query['query'] .'</li>';
+            }
+            else {
+              $output .= '<li class="failure"><strong>Failed:</strong> '. $query['query'] .'</li>';
+            }
+          }
+          if (!count($queries)) {
+            $output .= '<li class="none">No queries</li>';
+          }
+        }
+        $output .= '</ul>';
+      }
+    }
+    $output .= '</div>';
+  }
+  unset($_SESSION['update_results']);
+  unset($_SESSION['update_success']);
+
+  return $output;
+}
+
+function update_info_page() {
+  // Change query-strings on css/js files to enforce reload for all users.
+  _drupal_flush_css_js();
+  // Flush the cache of all data for the update status module.
+  if (db_table_exists('cache_update')) {
+    cache_clear_all('*', 'cache_update', TRUE);
+  }
+
+  update_task_list('info');
+  drupal_set_title('Drupal database update');
+  $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>';
+  $output .= "<ol>\n";
+  $output .= "<li><strong>Back up your database</strong>. This process will change your database values and in case of emergency you may need to revert to a backup.</li>\n";
+  $output .= "<li><strong>Back up your code</strong>. Hint: when backing up module code, do not leave that backup in the 'modules' or 'sites/*/modules' directories as this may confuse Drupal's auto-discovery mechanism.</li>\n";
+  $output .= '<li>Put your site into <a href="'. base_path() .'?q=admin/settings/site-maintenance">maintenance mode</a>.</li>'."\n";
+  $output .= "<li>Install your new files in the appropriate location, as described in the handbook.</li>\n";
+  $output .= "</ol>\n";
+  $output .= "<p>When you have performed the steps above, you may proceed.</p>\n";
+  $output .= '<form method="post" action="update.php?op=selection"><input type="submit" value="Continue" /></form>';
+  $output .= "\n";
+  return $output;
+}
+
+function update_access_denied_page() {
+  drupal_set_title('Access denied');
+  return '<p>Access denied. You are not authorized to access this page. Please log in as the admin user (the first user you created). If you cannot log in, you will have to edit <code>settings.php</code> to bypass this access check. To do this:</p>
+<ol>
+ <li>With a text editor find the settings.php file on your system. From the main Drupal directory that you installed all the files into, go to <code>sites/your_site_name</code> if such directory exists, or else to <code>sites/default</code> which applies otherwise.</li>
+ <li>There is a line inside your settings.php file that says <code>$update_free_access = FALSE;</code>. Change it to <code>$update_free_access = TRUE;</code>.</li>
+ <li>As soon as the update.php script is done, you must change the settings.php file back to its original form with <code>$update_free_access = FALSE;</code>.</li>
+ <li>To avoid having this problem in future, remember to log in to your website as the admin user (the user you first created) before you backup your database at the beginning of the update process.</li>
+</ol>';
+}
+
+/**
+ * Create the batch table.
+ *
+ * This is part of the Drupal 5.x to 6.x migration.
+ */
+function update_create_batch_table() {
+
+  // If batch table exists, update is not necessary
+  if (db_table_exists('batch')) {
+    return;
+  }
+
+  $schema['batch'] = array(
+    'fields' => array(
+      'bid'       => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+      'token'     => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
+      'timestamp' => array('type' => 'int', 'not null' => TRUE),
+      'batch'     => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
+    ),
+    'primary key' => array('bid'),
+    'indexes' => array('token' => array('token')),
+  );
+
+  $ret = array();
+  db_create_table($ret, 'batch', $schema['batch']);
+  return $ret;
+}
+
+/**
+ * Disable anything in the {system} table that is not compatible with the
+ * current version of Drupal core.
+ */
+function update_fix_compatibility() {
+  $ret = array();
+  $incompatible = array();
+  $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
+  while ($result = db_fetch_object($query)) {
+    if (update_check_incompatibility($result->name, $result->type)) {
+      $incompatible[] = $result->name;
+    }
+  }
+  if (!empty($incompatible)) {
+    $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')");
+  }
+  return $ret;
+}
+
+/**
+ * Helper function to test compatibility of a module or theme.
+ */
+function update_check_incompatibility($name, $type = 'module') {
+  static $themes, $modules;
+
+  // Store values of expensive functions for future use.
+  if (empty($themes) || empty($modules)) {
+    $themes = system_theme_data();
+    $modules = module_rebuild_cache();
+  }
+
+  if ($type == 'module' && isset($modules[$name])) {
+    $file = $modules[$name];
+  }
+  else if ($type == 'theme' && isset($themes[$name])) {
+    $file = $themes[$name];
+  }
+  if (!isset($file)
+      || !isset($file->info['core'])
+      || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY
+      || version_compare(phpversion(), $file->info['php']) < 0) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Perform Drupal 5.x to 6.x updates that are required for update.php
+ * to function properly.
+ *
+ * This function runs when update.php is run the first time for 6.x,
+ * even before updates are selected or performed.  It is important
+ * that if updates are not ultimately performed that no changes are
+ * made which make it impossible to continue using the prior version.
+ * Just adding columns is safe.  However, renaming the
+ * system.description column to owner is not.  Therefore, we add the
+ * system.owner column and leave it to system_update_6008() to copy
+ * the data from description and remove description. The same for
+ * renaming locales_target.locale to locales_target.language, which
+ * will be finished by locale_update_6002().
+ */
+function update_fix_d6_requirements() {
+  $ret = array();
+
+  if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) {
+    $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE);
+    db_add_field($ret, 'cache', 'serialized', $spec);
+    db_add_field($ret, 'cache_filter', 'serialized', $spec);
+    db_add_field($ret, 'cache_page', 'serialized', $spec);
+    db_add_field($ret, 'cache_menu', 'serialized', $spec);
+
+    db_add_field($ret, 'system', 'info', array('type' => 'text'));
+    db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
+    if (db_table_exists('locales_target')) {
+      db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => ''));
+    }
+    if (db_table_exists('locales_source')) {
+      db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default'));
+      db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none'));
+    }
+    variable_set('update_d6_requirements', TRUE);
+
+    // Create the cache_block table. See system_update_6027() for more details.
+    $schema['cache_block'] = array(
+      'fields' => array(
+        'cid'        => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
+        'data'       => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'),
+        'expire'     => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+        'created'    => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+        'headers'    => array('type' => 'text', 'not null' => FALSE),
+        'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0)
+      ),
+      'indexes' => array('expire' => array('expire')),
+      'primary key' => array('cid'),
+    );
+    db_create_table($ret, 'cache_block', $schema['cache_block']);
+  }
+
+  return $ret;
+}
+
+/**
+ * Add the update task list to the current page.
+ */
+function update_task_list($active = NULL) {
+  // Default list of tasks.
+  $tasks = array(
+    'info' => 'Overview',
+    'select' => 'Select updates',
+    'run' => 'Run updates',
+    'finished' => 'Review log',
+  );
+
+  drupal_set_content('left', theme('task_list', $tasks, $active));
+}
+
+/**
+ * Check update requirements and report any errors.
+ */
+function update_check_requirements() {
+  // Check the system module requirements only.
+  $requirements = module_invoke('system', 'requirements', 'update');
+  $severity = drupal_requirements_severity($requirements);
+
+  // If there are issues, report them.
+  if ($severity != REQUIREMENT_OK) {
+    foreach ($requirements as $requirement) {
+      if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
+        $message = isset($requirement['description']) ? $requirement['description'] : '';
+        if (isset($requirement['value']) && $requirement['value']) {
+          $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
+        }
+        drupal_set_message($message, 'warning');
+      }
+    }
+  }
+}
+
+// Some unavoidable errors happen because the database is not yet up-to-date.
+// Our custom error handler is not yet installed, so we just suppress them.
+ini_set('display_errors', FALSE);
+
+require_once './includes/bootstrap.inc';
+
+// We only load DRUPAL_BOOTSTRAP_CONFIGURATION for the update requirements
+// check to avoid reaching the PHP memory limit.
+$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
+if (empty($op)) {
+  // Minimum load of components.
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+  require_once './includes/install.inc';
+  require_once './includes/file.inc';
+  require_once './modules/system/system.install';
+
+  // Load module basics.
+  include_once './includes/module.inc';
+  $module_list['system']['filename'] = 'modules/system/system.module';
+  $module_list['filter']['filename'] = 'modules/filter/filter.module';
+  module_list(TRUE, FALSE, FALSE, $module_list);
+  drupal_load('module', 'system');
+  drupal_load('module', 'filter');
+
+  // Set up $language, since the installer components require it.
+  drupal_init_language();
+
+  // Set up theme system for the maintenance page.
+  drupal_maintenance_theme();
+
+  // Check the update requirements for Drupal.
+  update_check_requirements();
+
+  // Display the warning messages (if any) in a dedicated maintenance page,
+  // or redirect to the update information page if no message.
+  $messages = drupal_set_message();
+  if (!empty($messages['warning'])) {
+    drupal_maintenance_theme();
+    print theme('update_page', '<form method="post" action="update.php?op=info"><input type="submit" value="Continue" /></form>', FALSE);
+    exit;
+  }
+  install_goto('update.php?op=info');
+}
+
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+drupal_maintenance_theme();
+
+// This must happen *after* drupal_bootstrap(), since it calls
+// variable_(get|set), which only works after a full bootstrap.
+update_create_batch_table();
+
+// Turn error reporting back on. From now on, only fatal errors (which are
+// not passed through the error handler) will cause a message to be printed.
+ini_set('display_errors', TRUE);
+
+// Access check:
+if (!empty($update_free_access) || $user->uid == 1) {
+
+  include_once './includes/install.inc';
+  include_once './includes/batch.inc';
+  drupal_load_updates();
+
+  update_fix_d6_requirements();
+  update_fix_compatibility();
+
+  $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
+  switch ($op) {
+    // update.php ops
+    case 'info':
+      $output = update_info_page();
+      break;
+
+    case 'selection':
+      $output = update_selection_page();
+      break;
+
+    case 'Update':
+      update_batch();
+      break;
+
+    case 'results':
+      $output = update_results_page();
+      break;
+
+    // Regular batch ops : defer to batch processing API
+    default:
+      update_task_list('run');
+      $output = _batch_page();
+      break;
+  }
+}
+else {
+  $output = update_access_denied_page();
+}
+if (isset($output) && $output) {
+  // We defer the display of messages until all updates are done.
+  $progress_page = ($batch = batch_get()) && isset($batch['running']);
+  print theme('update_page', $output, !$progress_page);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmlrpc.php	Tue Dec 23 14:28:28 2008 +0100
@@ -0,0 +1,14 @@
+<?php
+// $Id: xmlrpc.php,v 1.15 2005/12/10 19:26:47 dries Exp $
+
+/**
+ * @file
+ * PHP page for handling incoming XML-RPC requests from clients.
+ */
+
+include_once './includes/bootstrap.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+include_once './includes/xmlrpc.inc';
+include_once './includes/xmlrpcs.inc';
+
+xmlrpc_server(module_invoke_all('xmlrpc'));