Index: /trunk/htaccess_default
===================================================================
--- /trunk/htaccess_default	(revision 1081)
+++ /trunk/htaccess_default	(revision 1081)
@@ -0,0 +1,52 @@
+#############################
+##### EDIT THIS SECTION #####
+#############################
+
+##### Re-directing Begin #####
+Options +Indexes +FollowSymlinks
+RewriteEngine on
+## If Hotaru is installed in a subfolder, change the below line to RewriteBase /name-of-subfolder
+RewriteBase /
+## If installed in a subfolder you may need to add ## to the beginning of the next line
+RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.*index\.php\ HTTP/
+
+## Remove these two lines if you have a sub-domain like  http://bookmarking.myhotarusite.com  or http://localhost
+## Keep if your site url looks like http://www.myhotarusite.com
+RewriteCond %{HTTP_HOST} !^www\.
+RewriteRule ^\/?(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
+##### Re-directing End #####
+
+################################################
+##### DON'T EDIT ANYTHING BELOW THIS POINT #####
+################################################
+
+##### URL Rewriting Begin #####
+
+##### CORE ADMIN #####
+RewriteRule ^admin/?$ admin_index.php [L]
+RewriteRule ^admin/([a-zA-Z0-9_-]+)/?$ admin_index.php?page=$1 [L]
+RewriteRule ^admin/plugins/?$ admin_index.php?page=plugins [L]
+RewriteRule ^admin/settings/?$ admin_index.php?page=settings [L]
+RewriteRule ^admin/maintenance/?$ admin_index.php?page=maintenance [L]
+RewriteRule ^admin/plugin_settings/plugin/([a-zA-Z0-9_-]+)/?$ admin_index.php?page=plugin_settings&plugin=$1 [L]
+
+##### GENERIC RULES #####
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/?$ index.php?page=$1 [L]
+
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/([^/]*)/?$ index.php?$1=$2 [L]
+
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/([^/]*)/([^/]*)/?$ index.php?page=$1&$2=$3 [L]
+
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/([^/]*)/([^/]*)/([^/]*)/?$ index.php?$1=$2&$3=$4 [L]
+
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/([^/]*)/([^/]*)/([^/]*)/([^/]*)/?$ index.php?page=$1&$2=$3&$4=$5 [L]
+
+RewriteCond %{REQUEST_URI} !\.(css|php|png|jpg|gif|ico|js|inc|txt)$
+RewriteRule ^([^/]*)/([^/]*)/([^/]*)/([^/]*)/([^/]*)/([^/]*)/?$ index.php?$1=$2&$3=$4&$5=$6 [L]
+
+##### URL Rewriting End #####
Index: /trunk/admin_index.php
===================================================================
--- /trunk/admin_index.php	(revision 1081)
+++ /trunk/admin_index.php	(revision 1081)
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Includes settings and constructs Hotaru.
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+
+// includes
+require_once('hotaru_settings.php');
+require_once('Hotaru.php');
+$h = new Hotaru('', true);  // $admin set to true
+$h->start('admin');
+
+?>
Index: /trunk/install/install_language.php
===================================================================
--- /trunk/install/install_language.php	(revision 1081)
+++ /trunk/install/install_language.php	(revision 1081)
@@ -0,0 +1,94 @@
+<?php
+
+/* **************************************************************************************************** 
+ *  File: /install/install_language.php
+ *  Purpose: A language file for Install. It's used whenever the install file needs to output language.
+ *  Notes: ---
+ *  License:
+ *
+ *   This file is part of Hotaru CMS (http://www.hotarucms.org/).
+ *
+ *   Hotaru CMS 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 3 of 
+ *   the License, or (at your option) any later version.
+ *
+ *   Hotaru CMS 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 Hotaru CMS. If not, 
+ *   see http://www.gnu.org/licenses/.
+ *   
+ *   Copyright (C) 2009 Hotaru CMS - http://www.hotarucms.org/
+ *
+ **************************************************************************************************** */
+
+/* Upgrade Step 1 */
+$lang["upgrade_title"] = "Hotaru CMS Upgrade";
+$lang["upgrade_step1"] = "Step 1/2: Welcome Back";
+if (isset($old_version)) {
+    $lang["upgrade_step1_details"] = "To upgrade Hotaru from " . $old_version . " to " . $h->version . ", click 'Next'...";
+} else {
+    $lang["upgrade_step1_details"] = "To upgrade Hotaru to version " . $h->version . ", click 'Next'...";
+}
+
+/* Upgrade Step 2 */
+$lang["upgrade_step2"] = "Step 2/2: Upgrade Complete";
+$lang["upgrade_step2_details"] = "Congratulations! You have successfully upgraded Hotaru CMS.";
+$lang["upgrade_home"] = "Finish";
+
+/* Install Common */
+$lang["install_title"] = "Hotaru CMS Setup";
+$lang["install_next"] = "Next";
+$lang["install_back"] = "Back";
+$lang["install_trouble"] = "Having trouble? Visit the forums at <a href='http://hotarucms.org'>HotaruCMS.org</a> for help.";
+
+/* Install Step 1 */
+$lang["install_step1"] = "Step 1/5: Welcome";
+$lang["install_step1_welcome"] = "Welcome to Hotaru CMS. Click \"Next\" to begin setting up your site...";
+
+/* Install Step 2 */
+$lang["install_step2"] = "Step 2/5: Database Setup";
+$lang["install_step2_instructions"] = "To set up a database for Hotaru CMS, you'll need to do the following";
+$lang["install_step2_instructions1"] = "Create a database called <i>hotaru</i> in your web host's control panel. Make a note of your username and password!";
+$lang["install_step2_instructions2"] = "Copy <pre>/hotaru_settings_default.php</pre> and rename it <pre>/hotaru_settings.php</pre>.";
+$lang["install_step2_instructions3"] = "Open <pre>/hotaru_settings.php</pre> and fill in the \"Database Details\" section.";
+$lang["install_step2_instructions4"] = "Fill in the <pre>baseurl</pre>, e.g. <i>http://www.myhotarusite.com/</i>. Don't forget the trailing slash (/)";
+$lang["install_step2_instructions5"] = "Save and upload <pre>hotaru_settings.php</pre> to your server, then click \"Next\"...";
+$lang["install_step2_warning"] = "<b>Warning</b>";
+$lang["install_step2_warning_note"] = "When you click \"Next\", new database tables will be created, deleting any old ones you may have!";
+
+
+/* Install Step 3 */
+$lang["install_step3"] = "Step 3/5: Create Database Tables";
+$lang["install_step3_creating_table"] = "Creating table";
+$lang["install_step3_already_exists"] = "It seems there are already tables for Hotaru CMS in the database.";
+$lang["install_step3_continue"] = "Click \"Next\" to continue.";
+$lang["install_step3_rebuild_note"] = "<i>Note</i>: If you'd like to start fresh, ";
+$lang["install_step3_rebuild_link"] = "delete and rebuild the database tables";
+$lang["install_step3_success"] = "Database tables created successfully. Click \"Next\" to configure Hotaru CMS.";
+
+/* Install Step 4 */
+$lang["install_step4"] = "Step 4/5: Admin Registration";
+$lang["install_step4_instructions"] = "Register yourself as a site administrator";
+$lang["install_step4_username"] = "Username:";
+$lang["install_step4_email"] = "Email:";
+$lang["install_step4_password"] = "Password:";
+$lang["install_step4_password_verify"] = "Password (again):";
+$lang["install_step4_csrf_error"] = "Ah! You've triggered a CSRF error. That's only supposed to happen when someone tries hacking into the site...";
+$lang["install_step4_username_error"] = "Your username must be at least 4 characters and can contain letters, dashes and underscores only";
+$lang["install_step4_password_error"] = "The password must be at least 8 characters and can only contain letters, numbers and these symbols: @ * # - _";
+$lang["install_step4_password_match_error"] = "The password fields don't match";
+$lang["install_step4_email_error"] = "That doesn't parse as a valid email address";
+$lang["install_step4_make_note"] = "Make a note of your new username, email and password before clicking \"Next\"...";
+$lang["install_step4_update_success"] = "Updated successfully";
+$lang["install_step4_form_update"] = "Update";
+
+/* Install Step 5 */
+$lang["install_step5"] = "Step 5/5: Completion";
+$lang["install_step5_installation_complete"] = "Installation has been successfully completed.";
+$lang["install_step5_installation_delete"] = "You <b>must</b> delete the install folder or someone else could run the install script and wipe everything!";
+$lang["install_step5_installation_go_play"] = "Done? Okay, go and play with your new Hotaru site!";
+$lang["install_home"] = "Get Started!";
+
+?>
Index: /trunk/install/install_functions.php
===================================================================
--- /trunk/install/install_functions.php	(revision 1081)
+++ /trunk/install/install_functions.php	(revision 1081)
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Install function for the Hotaru CMS installer.
+ * 
+ * Steps through the set-up process, creating database tables and registering 
+ * the Admin user. Note: You must delete this file after installation as it 
+ * poses a serious security risk if left.
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+
+
+/**
+ * Initialize Database
+ *
+ * @return object
+ */
+function init_database()
+{
+    $ezSQL = new ezSQL_mysql(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
+    $ezSQL->query("SET NAMES 'utf8'");
+    
+    return $ezSQL;
+}
+    
+    
+/**
+ * Initialize Inspekt
+ *
+ * @return object
+ */
+function init_inspekt_cage()
+{
+    $cage = Inspekt::makeSuperCage(); 
+
+    // Add Hotaru custom methods
+    $cage->addAccessor('testAlnumLines');
+    $cage->addAccessor('testPage');
+    $cage->addAccessor('testUsername');
+    $cage->addAccessor('testPassword');
+    $cage->addAccessor('getFriendlyUrl');
+    $cage->addAccessor('getMixedString1');
+    $cage->addAccessor('getMixedString2');
+    $cage->addAccessor('getHtmLawed');
+    
+    return $cage;
+}
+
+
+/**
+ * Delete all files in the specified directory except placeholder.txt
+ *
+ * @param string $dir - path to the cache folder
+ * @return bool
+ */    
+function delete_files($dir)
+{
+    $handle=opendir($dir);
+
+    while (($file = readdir($handle))!==false) {
+        if ($file != 'placeholder.txt') {
+            if (@unlink($dir.'/'.$file)) {
+                $success = true;
+            } else {
+                $success = false;
+            }
+        }
+    }
+    
+    closedir($handle);
+    
+    return $success;
+}
+
+
+/**
+ * List all plugin created tables
+ */
+function list_plugin_tables()
+{
+    global $db;
+    
+    // These should match the tables created in the install script.
+    $core_tables = array(
+        'hotaru_settings',
+        'hotaru_users',
+        'hotaru_plugins',
+        'hotaru_pluginsettings',
+        'hotaru_pluginhooks',
+        'hotaru_blocked'
+    );
+    
+    $plugin_tables = array();
+        
+    $db->select(DB_NAME);
+    
+    if (!$db->get_col("SHOW TABLES",0)) { return $plugin_tables; }
+    
+    foreach ( $db->get_col("SHOW TABLES",0) as $table_name )
+    {
+        if (!in_array($table_name, $core_tables)) {
+            array_push($plugin_tables, $table_name);
+        }
+    }
+    
+    return $plugin_tables;
+}
+
+
+/**
+ * Delete plugin database table
+ *
+ * @param string $table_name - table to drop
+ */
+function drop_table($table_name)
+{
+    global $db;
+    
+    $db->query("DROP TABLE " . $table_name);
+}
Index: /trunk/install/install_tables.php
===================================================================
--- /trunk/install/install_tables.php	(revision 1081)
+++ /trunk/install/install_tables.php	(revision 1081)
@@ -0,0 +1,510 @@
+<?php
+/**
+ * Install database tables for Hotaru CMS.
+ * 
+ * Steps through the set-up process, creating database tables and registering 
+ * the Admin user. Note: You must delete this file after installation as it 
+ * poses a serious security risk if left.
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+
+/**
+ * Create database tables
+ *
+ * @param string $table_name
+ *
+ * Note: Deletes the table if it already exists, then makes it again
+ */
+function create_table($table_name)
+{
+    global $db, $lang, $h;
+    
+    $sql = 'DROP TABLE IF EXISTS `' . DB_PREFIX . $table_name . '`;';
+    $db->query($sql);
+
+    
+    // BLOCKED TABLE - blocked IPs, users, email types, etc...
+    
+    if ($table_name == "blocked") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `blocked_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `blocked_type` varchar(64) NULL,
+          `blocked_value` text NULL,
+          `blocked_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `blocked_updateby` int(20) NOT NULL DEFAULT 0,
+          INDEX  (`blocked_type`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Blocked IPs, users, emails, etc';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+    }
+    
+    
+    // CATEGORIES TABLE - categories
+    
+    if ($table_name == "categories") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `category_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `category_parent` int(11) NOT NULL DEFAULT '1',
+          `category_name` varchar(64) NOT NULL DEFAULT '',
+          `category_safe_name` varchar(64) NOT NULL DEFAULT '',
+          `rgt` int(11) NOT NULL DEFAULT '0',
+          `lft` int(11) NOT NULL DEFAULT '0',
+          `category_order` int(11) NOT NULL DEFAULT '0',
+          `category_desc` text NULL,
+          `category_keywords` varchar(255) NOT NULL,
+          `category_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `category_updateby` int(20) NOT NULL DEFAULT 0, 
+          UNIQUE KEY `key` (`category_name`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Categories';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+        
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (category_name, category_safe_name) VALUES (%s, %s)";
+        $db->query($db->prepare($sql, urlencode('All'), urlencode('all')));
+    }
+        
+        
+    // COMMENTS TABLE - comments
+    
+    if ($table_name == "comments") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `comment_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `comment_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `comment_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `comment_post_id` int(20) NOT NULL DEFAULT '0',
+          `comment_user_id` int(20) NOT NULL DEFAULT '0',
+          `comment_parent` int(20) DEFAULT '0',
+          `comment_date` timestamp NOT NULL,
+          `comment_status` varchar(32) NOT NULL DEFAULT 'approved',
+          `comment_content` text NOT NULL,
+          `comment_votes_up` smallint(11) NOT NULL DEFAULT '0',
+          `comment_votes_down` smallint(11) NOT NULL DEFAULT '0',
+          `comment_subscribe` tinyint(1) NOT NULL DEFAULT '0',
+          `comment_updateby` int(20) NOT NULL DEFAULT 0,
+          FULLTEXT (`comment_content`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Post Comments';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // COMMENT VOTES TABLE - comment votes
+    
+    if ($table_name == "commentvotes") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+            `cvote_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+            `cvote_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+            `cvote_post_id` int(11) NOT NULL DEFAULT '0',
+            `cvote_comment_id` int(11) NOT NULL DEFAULT '0',
+            `cvote_user_id` int(11) NOT NULL DEFAULT '0',
+            `cvote_user_ip` varchar(32) NOT NULL DEFAULT '0',
+            `cvote_date` timestamp NOT NULL,
+            `cvote_rating` smallint(11) NOT NULL DEFAULT '0',
+            `cvote_reason` tinyint(3) NOT NULL DEFAULT 0,
+            `cvote_updateby` int(20) NOT NULL DEFAULT 0
+            ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Comment Votes';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // MISCDATA TABLE - for storing default permissions, etc.
+    
+    if ($table_name == "miscdata") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `miscdata_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `miscdata_key` varchar(64) NOT NULL,
+          `miscdata_value` text NOT NULL DEFAULT '',
+          `miscdata_default` text NOT NULL DEFAULT '',
+          `miscdata_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `miscdata_updateby` int(20) NOT NULL DEFAULT 0
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Miscellaneous Data';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+        
+        // Add Hotaru version number to the database (referred to when upgrading)
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+        $db->query($db->prepare($sql, 'hotaru_version', $h->version, $h->version));
+
+        // Default permissions
+        $perms['options']['can_access_admin'] = array('yes', 'no');
+        $perms['can_access_admin']['admin'] = 'yes';
+        $perms['can_access_admin']['supermod'] = 'yes';
+        $perms['can_access_admin']['default'] = 'no';
+        $perms = serialize($perms);
+        
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+        $db->query($db->prepare($sql, 'permissions', $perms, $perms));
+        
+        // default settings
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+        $db->query($db->prepare($sql, 'user_settings', '', ''));
+        
+        // site announcement
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+        $db->query($db->prepare($sql, 'site_announcement', '', ''));
+    }
+    
+    
+
+    
+    
+    // PLUGINS TABLE
+    
+    if ($table_name == "plugins") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `plugin_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `plugin_enabled` tinyint(1) NOT NULL DEFAULT '0',
+          `plugin_name` varchar(64) NOT NULL DEFAULT '',
+          `plugin_folder` varchar(64) NOT NULL DEFAULT '',
+          `plugin_class` varchar(64) NOT NULL DEFAULT '',
+          `plugin_extends` varchar(64) NOT NULL DEFAULT '',
+          `plugin_type` varchar(32) NOT NULL DEFAULT '',
+          `plugin_desc` varchar(255) NOT NULL DEFAULT '',
+          `plugin_requires` varchar(255) NOT NULL DEFAULT '',
+          `plugin_version` varchar(32) NOT NULL DEFAULT '0.0',
+          `plugin_order` int(11) NOT NULL DEFAULT 0,
+          `plugin_author` varchar(32) NOT NULL DEFAULT '',
+          `plugin_authorurl` varchar(128) NOT NULL DEFAULT '',
+          `plugin_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `plugin_updateby` int(20) NOT NULL DEFAULT 0,
+          UNIQUE KEY `key` (`plugin_folder`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Application Plugins';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+    }
+    
+    // PLUGIN HOOKS TABLE
+    
+    if ($table_name == "pluginhooks") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `phook_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `plugin_folder` varchar(64) NOT NULL DEFAULT '',
+          `plugin_hook` varchar(128) NOT NULL DEFAULT '',
+          `plugin_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `plugin_updateby` int(20) NOT NULL DEFAULT 0,
+          INDEX  (`plugin_folder`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Plugins Hooks';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+    }
+    
+    // PLUGIN SETTINGS TABLE
+    
+    if ($table_name == "pluginsettings") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `psetting_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `plugin_folder` varchar(64) NOT NULL,
+          `plugin_setting` varchar(64) NULL,
+          `plugin_value` text NULL,
+          `plugin_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `plugin_updateby` int(20) NOT NULL DEFAULT 0,
+          INDEX  (`plugin_folder`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Plugins Settings';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+    }
+    
+    
+    // POSTS TABLE - stories/news
+    
+    if ($table_name == "posts") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `post_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `post_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `post_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `post_author` int(20) NOT NULL DEFAULT 0,
+          `post_date` timestamp NOT NULL,
+          `post_status` varchar(32) NOT NULL DEFAULT 'processing',
+          `post_type` varchar(32) NULL,
+          `post_category` int(20) NOT NULL DEFAULT 1,
+          `post_tags` text NULL,
+          `post_title` varchar(255) NULL, 
+          `post_orig_url` varchar(255) NULL, 
+          `post_domain` varchar(255) NULL, 
+          `post_url` varchar(255) NULL, 
+          `post_content` text NULL,
+          `post_votes_up` smallint(11) NOT NULL DEFAULT '0',
+          `post_votes_down` smallint(11) NOT NULL DEFAULT '0',
+          `post_comments` enum('open', 'closed') NOT NULL DEFAULT 'open',
+          `post_subscribe` tinyint(1) NOT NULL DEFAULT '0',
+          `post_updateby` int(20) NOT NULL DEFAULT 0, 
+          FULLTEXT (`post_title`, `post_domain`, `post_url`, `post_content`, `post_tags`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Story Posts';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // POSTMETA TABLE - extra information for posts
+    
+    if ($table_name == "postmeta") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `postmeta_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `postmeta_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `postmeta_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `postmeta_postid` int(20) NOT NULL DEFAULT 0,
+          `postmeta_key` varchar(255) NULL,
+          `postmeta_value` text NULL,
+           `postmeta_updateby` int(20) NOT NULL DEFAULT 0, 
+          INDEX  (`postmeta_postid`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Post Meta';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // POSTVOTES TABLE - votes
+    
+    if ($table_name == "postvotes") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `vote_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `vote_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `vote_post_id` int(11) NOT NULL DEFAULT '0',
+          `vote_user_id` int(11) NOT NULL DEFAULT '0',
+          `vote_user_ip` varchar(32) NOT NULL DEFAULT '0',
+          `vote_date` timestamp NOT NULL,
+          `vote_type` varchar(32) NULL,
+          `vote_rating` smallint(11) NOT NULL DEFAULT '0',
+          `vote_reason` tinyint(3) NOT NULL DEFAULT 0,
+          `vote_updateby` int(20) NOT NULL DEFAULT 0,
+           INDEX  (`vote_post_id`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Post Votes';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    } 
+    
+    
+    // SETTINGS TABLE
+    
+    if ($table_name == "settings") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `settings_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `settings_name` varchar(64) NOT NULL,
+          `settings_value` text NOT NULL DEFAULT '',
+          `settings_default` text NOT NULL DEFAULT '',
+          `settings_note` text NOT NULL DEFAULT '',
+          `settings_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `settings_updateby` int(20) NOT NULL DEFAULT 0,
+          UNIQUE KEY `key` (`settings_name`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Application Settings';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+        
+        // Default settings:
+        
+        // Friendly urls
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'SITE_OPEN', 'true', 'true', ''));
+        
+        // Site name
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'SITE_NAME', 'Hotaru CMS', 'Hotaru CMS', ''));
+        
+        // Main theme
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'THEME', 'default/', 'default/', 'You need the "\/"'));
+        
+        // Admin theme
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'ADMIN_THEME', 'admin_default/', 'admin_default/', 'You need the "\/"'));
+        
+        // Friendly urls
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'FRIENDLY_URLS', 'false', 'false', ''));
+        
+        // Site email
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'SITE_EMAIL', 'admin@mysite.com', 'admin@mysite.com', 'Must be changed'));
+        
+        // Database cache
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'DB_CACHE_ON', 'false', 'false', ''));
+        
+        // Database cache duration (hours)
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %d, %d, %s)";
+        $db->query($db->prepare($sql, 'DB_CACHE_DURATION', 12, 12, 'Hours'));
+        
+        // RSS cache
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'RSS_CACHE_ON', 'true', 'true', ''));
+        
+        // RSS cache duration (hours)
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %d, %d, %s)";
+        $db->query($db->prepare($sql, 'RSS_CACHE_DURATION', 60, 60, 'Minutes'));
+        
+        // CSS/JavaScript cache
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'CSS_JS_CACHE_ON', 'true', 'true', ''));
+        
+        // HTML cache
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'HTML_CACHE_ON', 'true', 'true', ''));
+        
+        // Debug
+        $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (settings_name, settings_value, settings_default, settings_note) VALUES (%s, %s, %s, %s)";
+        $db->query($db->prepare($sql, 'DEBUG', 'false', 'false', ''));
+    }
+    
+    
+    // TAGS TABLE - tags
+    
+    if ($table_name == "tags") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `tags_post_id` int(11) NOT NULL DEFAULT '0',
+          `tags_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `tags_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `tags_date` timestamp NOT NULL,
+          `tags_word` varchar(64) NOT NULL DEFAULT '',
+          `tags_updateby` int(20) NOT NULL DEFAULT 0, 
+          UNIQUE KEY `tags_post_id` (`tags_post_id`,`tags_word`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Post Tags';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // TEMPDATA TABLE - temporary data
+    
+    if ($table_name == "tempdata") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `tempdata_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `tempdata_key` varchar(255) NULL,
+          `tempdata_value` text NULL,
+          `tempdata_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `tempdata_updateby` int(20) NOT NULL DEFAULT 0
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Temporary Data';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // TOKENS TABLE - used to prevent against CSRF attacks
+    
+    if ($table_name == "tokens") {
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `token_id` INT(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `token_sid` varchar(32) NOT NULL,
+          `token_key` CHAR(32) NOT NULL,
+          `token_stamp` INT(11) NOT NULL default '0',
+          `token_action` varchar(64),
+          INDEX  (`token_key`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Tokens for CSRF protection';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql);
+    }
+    
+    
+    // USERS TABLE
+    
+    if ($table_name == "users") {    
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `user_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `user_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+          `user_username` varchar(32) NOT NULL,
+          `user_role` varchar(32) NOT NULL DEFAULT 'member',
+          `user_date` timestamp NOT NULL,
+          `user_password` varchar(64) NOT NULL DEFAULT '',
+          `user_password_conf` varchar(128) NULL,
+          `user_email` varchar(128) NOT NULL DEFAULT '',
+          `user_email_valid` tinyint(3) NOT NULL DEFAULT 0,
+          `user_email_conf` varchar(128) NULL,
+          `user_permissions` text NOT NULL DEFAULT '',
+          `user_ip` varchar(32) NOT NULL DEFAULT '0',
+          `user_lastlogin` timestamp NULL,
+          `user_lastvisit` timestamp NULL,
+          `user_updateby` int(20) NOT NULL DEFAULT 0,
+          UNIQUE KEY `key` (`user_username`),
+          KEY `user_email` (`user_email`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Users and Roles';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // USERMETA TABLE - extra information for posts
+    
+    if ($table_name == "usermeta") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `usermeta_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `usermeta_userid` int(20) NOT NULL DEFAULT 0,
+          `usermeta_key` varchar(255) NULL,
+          `usermeta_value` text NULL,
+          `usermeta_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `usermeta_updateby` int(20) NOT NULL DEFAULT 0, 
+          INDEX  (`usermeta_userid`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='User Meta';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // USERACTIVITY TABLE - record user activity
+    
+    if ($table_name == "useractivity") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `useract_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `useract_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+          `useract_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `useract_userid` int(20) NOT NULL DEFAULT 0,
+          `useract_status` varchar(32) NOT NULL DEFAULT 'show',
+          `useract_key` varchar(255) NULL,
+          `useract_value` text NULL,
+          `useract_key2` varchar(255) NULL,
+          `useract_value2` text NULL,
+          `useract_date` timestamp NOT NULL,
+          `useract_updateby` int(20) NOT NULL DEFAULT 0, 
+          INDEX  (`useract_userid`)
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='User Activity';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+    
+    
+    // WIDGETS TABLE - widgets
+    
+    if ($table_name == "widgets") {
+        //echo "table doesn't exist. Stopping before creation."; exit;
+        $sql = "CREATE TABLE `" . DB_PREFIX . $table_name . "` (
+          `widget_id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+          `widget_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+          `widget_plugin` varchar(32) NOT NULL DEFAULT '',
+          `widget_function` varchar(255) NULL, 
+          `widget_args` varchar(255) NULL, 
+          `widget_updateby` int(20) NOT NULL DEFAULT 0
+        ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Widgets';";
+        echo $lang['install_step3_creating_table'] . ": '" . $table_name . "'...<br />\n";
+        $db->query($sql); 
+    }
+}
+?>
Index: /trunk/install/reset-fonts-grids.css
===================================================================
--- /trunk/install/reset-fonts-grids.css	(revision 1081)
+++ /trunk/install/reset-fonts-grids.css	(revision 1081)
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}
Index: /trunk/install/install.php
===================================================================
--- /trunk/install/install.php	(revision 1081)
+++ /trunk/install/install.php	(revision 1081)
@@ -0,0 +1,421 @@
+<?php
+/**
+ * Install Hotaru CMS
+ * 
+ * Steps through the set-up process, creating database tables and registering 
+ * the Admin user. Note: You must delete this file after installation as it 
+ * poses a serious security risk if left.
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+
+// start session:
+session_start();
+
+// Remove any cookies set in a previous installation:
+setcookie("hotaru_user", "", time()-3600, "/");
+setcookie("hotaru_key", "", time()-3600, "/");
+// --------------------------------------------------
+
+require_once('../hotaru_settings.php');
+require_once('install_tables.php');
+require_once('install_functions.php');
+require_once(BASE . 'Hotaru.php');
+require_once(EXTENSIONS . 'csrf/csrf_class.php'); // protection against CSRF attacks
+require_once(EXTENSIONS . 'Inspekt/Inspekt.php'); // sanitation
+require_once(EXTENSIONS . 'ezSQL/ez_sql_core.php'); // database
+require_once(EXTENSIONS . 'ezSQL/mysql/ez_sql_mysql.php'); // database
+$h  = new Hotaru('install'); // must come before language inclusion
+require_once(INSTALL . 'install_language.php');    // language file for install
+    
+$db = init_database();
+$cage = init_inspekt_cage();
+
+$step = $cage->get->getInt('step');        // Installation steps.
+
+switch ($step) {
+    case 1:
+        installation_welcome();     // "Welcome to Hotaru CMS. 
+        break;
+    case 2:
+        database_setup();           // DB name, user, password, prefix...
+        break;
+    case 3:
+        database_creation();        // Creates the database tables
+        break;
+    case 4:
+        register_admin();           // Username and password for Admin user...
+        break;
+    case 5:
+        installation_complete();    // Delete "install" folder. Visit your site"
+        break;
+    default:
+        // Anything other than step=2, 3 or 4 will return user to step 1
+        installation_welcome();
+        break;        
+}
+
+exit;
+
+
+/**
+ * HTML header
+ *
+ * @return string returns the html output for the page header
+ */
+function html_header()
+{
+    global $lang;
+    
+    $header = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 3.2//EN'>\n";
+    $header .= "<HTML><HEAD>\n";
+    $header .= "<meta http-equiv=Content-Type content='text/html; charset=UTF-8'>\n";
+    
+    // Title
+    $header .= "<TITLE>" . $lang['install_title'] . "</TITLE>\n";
+    $header .= "<META HTTP-EQUIV='Content-Type' CONTENT='text'>\n";
+    $header .= "<link rel='stylesheet' type='text/css' href='" . BASEURL . "install/reset-fonts-grids.css' type='text/css'>\n";
+    $header .= "<link rel='stylesheet' type='text/css' href='" . BASEURL . "install/install_style.css'>\n";
+    $header .= "</HEAD>\n";
+    
+    // Body start
+    $header .= "<BODY>\n";
+    $header .= "<div id='doc' class='yui-t7 install'>\n";
+    $header .= "<div id='hd' role='banner'>";
+    $header .= "<img align='left' src='" . BASEURL . "content/admin_themes/admin_default/images/hotaru.png' style='height:60px; width:60px;'>";
+    $header .= "<h1>" . $lang['install_title'] . "</h1></div>\n"; 
+    $header .= "<div id='bd' role='main'>\n";
+    $header .= "<div class='yui-g'>\n";
+    
+    return $header;
+}
+
+
+/**
+ * HTML footer
+ *
+ * @return string returns the html output for the page footer
+ */
+function html_footer()
+{
+    global $lang;
+    
+    $footer = "<div class='clear'></div>\n"; // clear floats
+    
+    // Footer content (a link to the forums)
+    $footer .= "<div id='ft' role='contentinfo'>";
+    $footer .= "<p>" . $lang['install_trouble'] . "</p>";
+    $footer .= "</div>\n"; // close "ft" div
+    
+    $footer .= "</div>\n"; // close "yui-g" div
+    $footer .= "</div>\n"; // close "main" div
+    $footer .= "</div>\n"; // close "yui-t7 install" div
+    
+    $footer .= "</BODY>\n";
+    $footer .= "</HTML>\n";
+    
+    return $footer;
+}
+
+
+/**
+ * Step 1 of installation - Welcome message
+ */
+function installation_welcome()
+{
+    global $lang;
+    
+    echo html_header();
+    
+    // Step title
+    echo "<h2>" . $lang['install_step1'] . "</h2>\n";
+    
+    // Step content
+    echo "<div class='install_content'>" . $lang['install_step1_welcome'] . "</div>\n";
+    
+    // Next button
+    echo "<div class='next'><a href='install.php?step=2'>" . $lang['install_next'] . "</a></div>\n";
+    
+    echo html_footer();
+}
+
+
+/**
+ * Step 2 of installation - asks to put database info in hotaru_settings.php 
+ */
+function database_setup()
+{
+    global $lang;
+    
+    echo html_header();
+    
+    // Step title
+    echo "<h2>" . $lang['install_step2'] . "</h2>\n";
+    
+    // Step content
+    echo "<div class='install_content'>" . $lang['install_step2_instructions'] . ":</div>\n";
+    
+    echo "<ol class='install_content'>\n";
+    echo "<li>" . $lang['install_step2_instructions1'] . "</li>\n";
+    echo "<li>" . $lang['install_step2_instructions2'] . "</li>\n";
+    echo "<li>" . $lang['install_step2_instructions3'] . "</li>\n";
+    echo "<li>" . $lang['install_step2_instructions4'] . "</li>\n";
+    echo "<li>" . $lang['install_step2_instructions5'] . "</li>\n";
+    echo "</ol>\n";
+
+    // Warning message
+    echo "<div class='install_content'><span style='color: red;'>" . $lang['install_step2_warning'] . "</span>: " . $lang['install_step2_warning_note'] . "</div>\n";
+
+    // Previous/Next buttons
+    echo "<div class='back'><a href='install.php?step=1'>" . $lang['install_back'] . "</a></div>\n";
+    echo "<div class='next'><a href='install.php?step=3'>" . $lang['install_next'] . "</a></div>\n";
+    
+    echo html_footer();
+}
+
+
+/**
+ * Step 3 of installation - Creates database tables
+ */
+function database_creation()
+{
+    global $lang;
+    
+    // delete existing cache
+    delete_files(CACHE . 'db_cache');
+    delete_files(CACHE . 'css_js_cache');
+    delete_files(CACHE . 'rss_cache');
+
+    echo html_header();
+    
+    // Step title
+    echo "<h2>" . $lang['install_step3'] . "</h2>\n";
+    
+    // delete *all* plugin tables:
+    $plugin_tables = list_plugin_tables();
+    foreach ($plugin_tables as $pt) {
+        drop_table($pt); // table name
+    }
+    
+    //create tables
+    $tables = array('blocked', 'categories', 'comments', 'commentvotes', 'miscdata', 'plugins', 'pluginhooks', 'pluginsettings', 'posts', 'postmeta', 'postvotes', 'settings', 'tags', 'tempdata', 'tokens', 'users', 'usermeta', 'useractivity', 'widgets');
+    foreach ($tables as $table_name) {
+        create_table($table_name);
+    } 
+
+    // Step content
+    echo "<div class='install_content'>" . $lang['install_step3_success'] . "</div>\n";
+
+    // Previous/Next buttons
+    echo "<div class='back'><a href='install.php?step=2'>" . $lang['install_back'] . "</a></div>\n";
+    echo "<div class='next'><a href='install.php?step=4'>" . $lang['install_next'] . "</a></div>\n";
+    
+    echo html_footer();
+}
+
+
+/**
+ * Step 4 of installation - registers the site Admin.
+ */
+function register_admin()
+{
+    global $lang;   //already included so Hotaru can't re-include it
+    global $db;
+    global $h;
+
+    $h  = new Hotaru(); // overwrites current global with fully initialized Hotaru object
+    
+    echo html_header();
+    
+    // Step title
+    echo "<h2>" . $lang['install_step4'] . "</h2>\n";
+
+    // Step content
+    echo "<div class='install_content'>" . $lang['install_step4_instructions'] . ":<br />\n";
+    
+    $error = 0;
+    if ($h->cage->post->getInt('step') == 4) 
+    {
+        // Test CSRF
+        if (!$h->csrf()) {
+            $h->message = $lang['install_step4_csrf_error'];
+            $h->messageType = 'red';
+            $h->showMessage();
+            $error = 1;
+        }
+        
+        // Test username
+        $name_check = $h->cage->post->testUsername('username');
+        // alphanumeric, dashes and underscores okay, case insensitive
+        if ($name_check) {
+            $user_name = $name_check;
+        } else {
+            $h->message = $lang['install_step4_username_error'];
+            $h->messageType = 'red';
+            $h->showMessage();
+            $error = 1;
+        }
+
+        // Test password
+        $password_check = $h->cage->post->testPassword('password');    
+        if ($password_check) {
+            $password2_check = $h->cage->post->testPassword('password2');
+            if ($password_check == $password2_check) {
+                // success
+                $user_password = $h->currentUser->generateHash($password_check);
+            } else {
+                $h->message = $lang['install_step4_password_match_error'];
+                $h->messageType = 'red';
+                $h->showMessage();
+                $error = 1;
+            }
+        } else {
+            $password_check = "";
+            $password2_check = "";
+            $h->message = $lang['install_step4_password_error'];
+            $h->messageType = 'red';
+            $h->showMessage();
+            $error = 1;
+        }
+
+        // Test email
+        $email_check = $h->cage->post->testEmail('email');
+        if ($email_check) {
+            $user_email = $email_check;
+        } else {
+            $h->message = $lang['install_step4_email_error'];
+            $h->messageType = 'red';
+            $h->showMessage();
+            $error = 1;
+        }
+    }
+    
+    // Show success message
+    if (($h->cage->post->getInt('step') == 4) && $error == 0) {
+        $h->message = $lang['install_step4_update_success'];
+        $h->messageType = 'green';
+        $h->showMessage();
+    }
+    
+    if ($error == 0) {
+    
+        $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_role = %s";
+        
+        if (!$admin_name = $h->db->get_var($h->db->prepare($sql, 'admin')))
+        {
+            // Insert default settings
+            $sql = "INSERT INTO " . TABLE_USERS . " (user_username, user_role, user_date, user_password, user_email, user_permissions) VALUES (%s, %s, CURRENT_TIMESTAMP, %s, %s, %s)";
+            $h->db->query($h->db->prepare($sql, 'admin', 'admin', 'password', 'admin@mysite.com', serialize($h->currentUser->getDefaultPermissions($h, 'admin'))));
+            $user_name = 'admin';
+            $user_email = 'admin@mysite.com';
+            $user_password = 'password';
+        } 
+        else 
+        {
+            $user_info = $h->currentUser->getUserBasic($h, 0, $admin_name);
+            // On returning to this page via back or next, the fields are empty at this point, so...
+            if (!isset($user_name)) { $user_name = ""; }
+            if (!isset($user_email)){ $user_email = ""; } 
+            if (!isset($user_password)) { $user_password = ""; }
+            if (($user_name != "") && ($user_email != "") && ($user_password != "")) {
+                // There's been a change so update...
+                $sql = "UPDATE " . TABLE_USERS . " SET user_username = %s, user_role = %s, user_date = CURRENT_TIMESTAMP, user_password = %s, user_email = %s, user_email_valid = %d WHERE user_role = %s";
+                $h->db->query($h->db->prepare($sql, $user_name, 'admin', $user_password, $user_email, 1, 'admin'));
+                $next_button = true;
+            } else {
+                $user_id = $user_info->user_id;
+                $user_name = $user_info->user_username;
+                $user_email = $user_info->user_email;
+                $user_password = $user_info->user_password;
+            }
+        }
+    }
+
+    // Registration form
+    echo "<form name='install_admin_reg_form' action='" . BASEURL . "install/install.php?step=4' method='post'>\n";
+
+    echo "<table>";
+
+    // Username
+    echo "<tr><td>" . $lang["install_step4_username"] . "&nbsp; </td><td><input type='text' size=30 name='username' value='" . $user_name . "' /></td></tr>\n";
+
+    // Email
+    echo "<tr><td>" . $lang["install_step4_email"] . "&nbsp; </td><td><input type='text' size=30 name='email' value='" . $user_email . "' /></td></tr>\n";
+    
+    // Password
+    echo "<tr><td>" . $lang["install_step4_password"] . "&nbsp; </td><td><input type='password' size=30 name='password' value='' /></td></tr>\n";
+
+    // Password verify
+    echo "<tr><td>" . $lang["install_step4_password_verify"] . "&nbsp; </td><td><input type='password' size=30 name='password2' value='' /></td></tr>\n";
+
+    echo "<input type='hidden' name='csrf' value='" . $h->csrfToken . "' />\n";
+    echo "<input type='hidden' name='step' value='4' />\n";
+    echo "<input type='hidden' name='updated' value='true' />\n";
+    
+    // Update button
+    echo "<tr><td>&nbsp;</td><td style='text-align:right;'><input id='update' type='submit' value='" . $lang['install_step4_form_update'] . "' /></td></tr>\n";
+    
+    echo "</table>";
+    echo "</form>\n";
+
+    // Make note of password message
+    echo $lang["install_step4_make_note"] . "</div>\n";
+
+    // Previous/Next buttons
+    echo "<div class='back'><a href='install.php?step=3'>" . $lang['install_back'] . "</a></div>\n";
+    if ($h->cage->post->getAlpha('updated') == 'true' && isset($next_button)) {
+        // active "next" link if user has been updated
+        echo "<div class='next'><a href='install.php?step=5'>" . $lang['install_next'] . "</a></div>\n";
+    } else {
+        // link disbaled until "update" button pressed
+        echo "<div class='next'>" . $lang['install_next'] . "</div>\n";
+    }
+    
+    echo html_footer();
+}
+    
+    
+/**
+ * Step 5 of installation - shows completion.
+ */
+function installation_complete()
+{
+    global $lang;
+    
+    echo html_header();
+
+    // Step title
+    echo "<h2>" . $lang['install_step5'] . "</h2>\n";
+    
+    // Step content
+    echo "<div class='install_content'>" . $lang['install_step5_installation_complete'] . "</div>\n";
+    echo "<div class='install_content'>" . $lang['install_step5_installation_delete'] . "</div>\n";
+    echo "<div class='install_content'>" . $lang['install_step5_installation_go_play'] . "</div>\n";
+
+    // Previous/Next buttons
+    echo "<div class='back'><a href='install.php?step=4'>" . $lang['install_back'] . "</a></div>\n";
+    echo "<div class='next'><a href='" . BASEURL . "'>" . $lang['install_home'] . "</a></div>\n";
+    
+    echo html_footer();    
+}
+
+?>
Index: /trunk/install/install_style.css
===================================================================
--- /trunk/install/install_style.css	(revision 1081)
+++ /trunk/install/install_style.css	(revision 1081)
@@ -0,0 +1,147 @@
+/* ******* INSTALL CSS STYLE ************************************************************************** 
+ * Stylesheet: install_style.css
+ * Stylesheet author: Nick Ramsay
+ * Version: 0.1
+ * License:
+ *
+ *   This file is part of Hotaru CMS (http://www.hotarucms.org/).
+ *
+ *   Hotaru CMS 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 3 of 
+ *   the License, or (at your option) any later version.
+ *
+ *   Hotaru CMS 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 Hotaru CMS. If not, 
+ *   see http://www.gnu.org/licenses/.
+ *   
+ *   Copyright (C) 2009 Hotaru CMS - http://www.hotarucms.org/
+ *
+ **************************************************************************************************** */
+
+html, body { 
+	margin-top: 2.0em;
+	min-height: 100%; height: 100%;
+	background-color: #f0f0f0;
+	text-align: center;
+	font-size: 1.0em;
+}
+
+h1	{ 
+	font-size: 1.5em;
+	margin-left: 1.0em;
+	padding-top: 0.6em;
+}
+
+img {
+    padding-right: 1.0em;
+}
+
+h2 { 
+	font-size: 1.2em; 
+	padding-bottom: 0.4em;
+}
+
+a { 
+	text-decoration: none; 
+	color: #0000ff;	
+}
+
+a:hover	{ 
+	text-decoration: underline; 
+}
+
+pre {
+	display: inline;
+}
+
+li {
+	list-style: decimal outside;
+	margin-left: 2em;
+	padding: 0.2em;
+}
+
+div, p { 
+	padding: 0.4em; 
+}
+
+form {
+	padding: 1.0em;
+}
+
+td {	padding: 0.5em;
+
+}
+
+small {
+	font-size: 0.8em;
+}
+
+.install {
+	padding: 1.0em;
+	-moz-border-radius: 0.5em;
+	-webkit-border-radius: 0.5em;
+	background-color: #ffffff;
+	border: 0.1em solid #333333;
+}
+
+.install_content {
+	padding: 0.4em 0.8em 0.4em 0.8em; 
+}
+
+.message {
+	margin: 1.0em auto 0.0em auto;
+	width: 90%;
+	padding: 0.8em;
+	-moz-border-radius: 0.5em;
+	-webkit-border-radius: 0.5em;
+	text-align: center;
+	font-size: 1.2em;
+	font-weight: bold;
+}
+
+.green {
+	background-color: #99FF66;
+}
+
+.red {
+	background-color: #FF6A55;
+	color: #ffffff;
+}
+
+#update {
+	padding: 0.3em 0.5em 0.3em 0.5em;
+	background-color: #f6f6f6;
+	-moz-border-radius: 0.5em;
+	-webkit-border-radius: 0.5em;
+	border: 0.1em solid #666;
+	font-weight: bold;
+	color: #00f;
+}
+
+.next, .back	{
+	margin-top: 0.8em;
+	float: right;
+	font-size: 1.2em;
+	padding: 0.3em 0.5em 0.3em 0.5em;
+	background-color: #f6f6f6;
+	-moz-border-radius: 0.5em;
+	-webkit-border-radius: 0.5em;
+	border: 0.1em solid #666;
+}
+
+.next	{ float: right; }
+.back	{ float: left; }
+
+.next a, .back a {
+	font-weight: bold;
+}
+
+#ft {
+	margin-top: 3.0em;
+	padding-bottom: 0em;
+	text-align: center;
+	font-size: 0.8em;
+}
Index: /trunk/install/upgrade.php
===================================================================
--- /trunk/install/upgrade.php	(revision 1081)
+++ /trunk/install/upgrade.php	(revision 1081)
@@ -0,0 +1,346 @@
+<?php
+/**
+ * Upgrade Hotaru CMS
+ * 
+ * Steps through the set-up process, creating database tables and registering 
+ * the Admin user. Note: You must delete this file after installation as it 
+ * poses a serious security risk if left.
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+
+require_once('../hotaru_settings.php');
+require_once(BASE . 'Hotaru.php');
+$h = new Hotaru(); // must come before language inclusion
+$sql = "SELECT miscdata_value FROM " . TABLE_MISCDATA . " WHERE miscdata_key = %s";
+$old_version = $h->db->get_var($h->db->prepare($sql, "hotaru_version"));
+require_once(INSTALL . 'install_language.php');    // language file for install
+
+// delete existing cache
+$h->deleteFiles(CACHE . 'db_cache');
+$h->deleteFiles(CACHE . 'css_js_cache');
+$h->deleteFiles(CACHE . 'rss_cache');
+
+$step = $h->cage->get->getInt('step');        // Installation steps.
+
+switch ($step) {
+    case 1:
+        upgrade_welcome();     // "Welcome to Hotaru CMS. 
+        break;
+    case 2:
+        do_upgrade($old_version);
+        upgrade_complete();    // Delete "install" folder. Visit your site"
+        break;
+    default:
+        // Anything other than step=2
+        upgrade_welcome();
+        break;        
+}
+
+exit;
+
+
+/**
+ * HTML header
+ *
+ * @return string returns the html output for the page header
+ */
+function html_header()
+{
+    global $lang;
+    
+    $header = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 3.2//EN'>\n";
+    $header .= "<HTML><HEAD>\n";
+    $header .= "<meta http-equiv=Content-Type content='text/html; charset=UTF-8'>\n";
+    
+    // Title
+    $header .= "<TITLE>" . $lang['upgrade_title'] . "</TITLE>\n";
+    $header .= "<META HTTP-EQUIV='Content-Type' CONTENT='text'>\n";
+    $header .= "<link rel='stylesheet' type='text/css' href='" . BASEURL . "install/reset-fonts-grids.css' type='text/css'>\n";
+    $header .= "<link rel='stylesheet' type='text/css' href='" . BASEURL . "install/install_style.css'>\n";
+    $header .= "</HEAD>\n";
+    
+    // Body start
+    $header .= "<BODY>\n";
+    $header .= "<div id='doc' class='yui-t7 install'>\n";
+    $header .= "<div id='hd' role='banner'>";
+    $header .= "<img align='left' src='" . BASEURL . "content/admin_themes/admin_default/images/hotaru.png' style='height:60px; width:60px;'>";
+    $header .= "<h1>" . $lang['upgrade_title'] . "</h1></div>\n"; 
+    $header .= "<div id='bd' role='main'>\n";
+    $header .= "<div class='yui-g'>\n";
+    
+    return $header;
+}
+
+
+/**
+ * HTML footer
+ *
+ * @return string returns the html output for the page footer
+ */
+function html_footer()
+{
+    global $lang;
+    
+    $footer = "<div class='clear'></div>\n"; // clear floats
+    
+    // Footer content (a link to the forums)
+    $footer .= "<div id='ft' role='contentinfo'>";
+    $footer .= "<p>" . $lang['install_trouble'] . "</p>";
+    $footer .= "</div>\n"; // close "ft" div
+    
+    $footer .= "</div>\n"; // close "yui-g" div
+    $footer .= "</div>\n"; // close "main" div
+    $footer .= "</div>\n"; // close "yui-t7 install" div
+    
+    $footer .= "</BODY>\n";
+    $footer .= "</HTML>\n";
+    
+    return $footer;
+}
+
+
+/**
+ * Step 1 of installation - Welcome message
+ */
+function upgrade_welcome()
+{
+    global $lang;
+    
+    echo html_header();
+    
+    // Step title
+    echo "<h2>" . $lang['upgrade_step1'] . "</h2>\n";
+    
+    // Step content
+    echo "<div class='install_content'>" . $lang['upgrade_step1_details'] . "</div>\n";
+    
+    // Next button
+    echo "<div class='next'><a href='upgrade.php?step=2'>" . $lang['install_next'] . "</a></div>\n";
+    
+    echo html_footer();
+}
+
+    
+/**
+ * Step 2 of upgrade - shows completion.
+ */
+function upgrade_complete()
+{
+    global $lang;
+    
+    echo html_header();
+
+    // Step title
+    echo "<h2>" . $lang['upgrade_step2'] . "</h2>\n";
+    
+    // Step content
+    echo "<div class='install_content'>" . $lang['upgrade_step2_details'] . "</div>\n";
+
+    // Next button
+    echo "<div class='next'><a href='" . BASEURL . "'>" . $lang['upgrade_home'] . "</a></div>\n";
+    
+    echo html_footer();    
+}
+
+/**
+ * Do Upgrade
+ */
+function do_upgrade($old_version)
+{
+    global $h;
+
+    // can't upgrade from pre-1.0 versions of Hotaru.
+    
+    // 1.0 to 1.0.1
+    if ($old_version == "1.0") {
+
+        // Change "positive" to 10
+        $sql = "UPDATE " . TABLE_POSTVOTES . " SET vote_rating = %d WHERE vote_rating = %s";
+        $h->db->query($h->db->prepare($sql, 10, 'positive'));
+        
+        // Change "negative" to -10
+        $sql = "UPDATE " . TABLE_POSTVOTES . " SET vote_rating = %d WHERE vote_rating = %s";
+        $h->db->query($h->db->prepare($sql, -10, 'negative'));
+        
+        // Change "alert" to -999
+        $sql = "UPDATE " . TABLE_POSTVOTES . " SET vote_rating = %d WHERE vote_rating = %s";
+        $h->db->query($h->db->prepare($sql, -999, 'alert'));
+        
+        // Alter the PostVotes table so the vote rating is an INT
+        $sql = "ALTER TABLE " . TABLE_POSTVOTES . " CHANGE vote_rating vote_rating smallint(11) NOT NULL DEFAULT %d";
+        $h->db->query($h->db->prepare($sql, 0));
+    
+        // check there are default permissions present and add if necessary
+        $sql = "SELECT miscdata_id FROM " . TABLE_MISCDATA . " WHERE miscdata_key = %s";
+        $result = $h->db->get_var($h->db->prepare($sql, 'permissions'));
+        if (!$result) {
+            // Default permissions
+            $perms['options']['can_access_admin'] = array('yes', 'no');
+            $perms['can_access_admin']['admin'] = 'yes';
+            $perms['can_access_admin']['supermod'] = 'yes';
+            $perms['can_access_admin']['default'] = 'no';
+            $perms = serialize($perms);
+            
+            $sql = "INSERT INTO " . TABLE_MISCDATA . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+            $h->db->query($h->db->prepare($sql, 'permissions', $perms, $perms));
+        }
+        
+        // check there are default user_settings present and add if necessary
+        $sql = "SELECT miscdata_id FROM " . TABLE_MISCDATA . " WHERE miscdata_key = %s";
+        $result = $h->db->get_var($h->db->prepare($sql, 'user_settings'));
+        if (!$result) {
+            // default settings
+            $sql = "INSERT INTO " . TABLE_MISCDATA . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+            $h->db->query($h->db->prepare($sql, 'user_settings', '', ''));
+        }
+        
+        // update "old version" for next set of upgrades
+        $old_version = "1.0.1";
+    }
+    
+    
+    // 1.0.1 to 1.0.2
+    if ($old_version == "1.0.1") {
+        
+        // Add new user_lastactivity field
+        $exists = $h->db->column_exists('users', 'user_lastvisit');
+        if (!$exists) {
+            // Alter the Users table to include user_lastvisit
+            $sql = "ALTER TABLE " . TABLE_USERS . " ADD user_lastvisit TIMESTAMP NULL AFTER user_lastlogin";
+            $h->db->query($h->db->prepare($sql));
+        }
+        
+        // Add site announcement record
+        $sql = "SELECT miscdata_id FROM " . TABLE_MISCDATA . " WHERE miscdata_key = %s";
+        $result = $h->db->get_var($h->db->prepare($sql, 'site_announcement'));
+        if (!$result) {
+            // site announcement
+            $sql = "INSERT INTO " . DB_PREFIX . $table_name . " (miscdata_key, miscdata_value, miscdata_default) VALUES (%s, %s, %s)";
+            $h->db->query($h->db->prepare($sql, 'site_announcement', '', ''));
+        }
+        
+        // update "old version" for next set of upgrades
+        $old_version = "1.0.2";
+    }
+    
+    
+    // 1.0.2 to 1.0.3
+    if ($old_version == "1.0.2") {
+        // nothing to do...
+        
+        // update "old version" for next set of upgrades
+        $old_version = "1.0.3";
+    }
+    
+    
+    // 1.0.3 to 1.0.4
+    if ($old_version == "1.0.3") {
+        
+        // remove language pack option from settings
+        $sql = "DELETE FROM " . TABLE_SETTINGS . " WHERE settings_name = %s";
+        $h->db->query($h->db->prepare($sql, 'LANGUAGE_PACK'));
+        
+        // Drop temporary cvotes_temp table if it already exists
+        $sql = 'DROP TABLE IF EXISTS `' . DB_PREFIX . 'cvotes_temp`;';
+        $h->db->query($sql);
+        
+        // create a temp table to store old comment votes
+        $sql = "CREATE TABLE `" . DB_PREFIX . "cvotes_temp` (
+              `cvote_archived` enum('Y','N') NOT NULL DEFAULT 'N',
+              `cvote_updatedts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
+              `cvote_post_id` int(11) NOT NULL DEFAULT '0',
+              `cvote_comment_id` int(11) NOT NULL DEFAULT '0',
+              `cvote_user_id` int(11) NOT NULL DEFAULT '0',
+              `cvote_user_ip` varchar(32) NOT NULL DEFAULT '0',
+              `cvote_date` timestamp NOT NULL,
+              `cvote_rating` smallint(11) NOT NULL DEFAULT '0',
+              `cvote_reason` tinyint(3) NOT NULL DEFAULT 0,
+              `cvote_updateby` int(20) NOT NULL DEFAULT 0
+            ) ENGINE=" . DB_ENGINE . " DEFAULT CHARSET=" . DB_CHARSET . " COLLATE=" . DB_COLLATE . " COMMENT='Comment Votes';";
+        $h->db->query($sql);
+        
+        // get old cvote data
+        $sql = "SELECT * FROM " . TABLE_COMMENTVOTES;
+        $old_cvotes = $h->db->get_results($h->db->prepare($sql));
+        if ($old_cvotes) {
+            $columns    = "cvote_post_id, cvote_comment_id, cvote_user_id, cvote_user_ip, cvote_date, cvote_rating, cvote_reason, cvote_updateby";
+            foreach ($old_cvotes as $cvote) {
+                if ($cvote->cvote_rating == 'negative') { $rating = -10; } else { $rating = 10; }
+                $sql = "INSERT INTO " . DB_PREFIX . "cvotes_temp (" . $columns . ") VALUES(%d, %d, %d, %s, %s, %d, %d, %d)";
+                $h->db->query($h->db->prepare($sql, $cvote->cvote_post_id, $cvote->cvote_comment_id, $cvote->cvote_user_id, $cvote->cvote_user_ip, $cvote->cvote_date, $rating, $cvote->cvote_reason, $cvote->cvote_updateby));
+            }
+        }
+        
+        // drop old commentvotes table
+        $h->db->query("DROP TABLE " . TABLE_COMMENTVOTES);
+        
+        // rename new table
+        $h->db->query("RENAME TABLE " . DB_PREFIX . "cvotes_temp TO " . DB_PREFIX . "commentvotes");
+        
+        // add new comment_votes_down column to comments table
+        $exists = $h->db->column_exists('comments', 'comment_votes_down');
+        if (!$exists) {
+            // Alter the Users table to include user_lastvisit
+            $sql = "ALTER TABLE " . TABLE_COMMENTS . " ADD comment_votes_down smallint(11) NOT NULL DEFAULT '0' AFTER comment_votes";
+            $h->db->query($h->db->prepare($sql));
+        }
+        
+        // rename comment_votes column to comments_votes_up
+        $exists = $h->db->column_exists('comments', 'comment_votes_up');
+        if (!$exists) {
+            // Alter the Users table to include user_lastvisit
+            $sql = "ALTER TABLE " . TABLE_COMMENTS . " CHANGE comment_votes comment_votes_up smallint(11) NOT NULL DEFAULT '0'";
+            $h->db->query($h->db->prepare($sql));
+        }
+        
+        // move any negative comment vote counts to the down column
+        $sql = "SELECT comment_id, comment_votes_up FROM " . TABLE_COMMENTS . " WHERE comment_votes_up < %d";
+        $negatives = $h->db->get_results($h->db->prepare($sql, 0));
+        if ($negatives) {
+            foreach ($negatives as $neg) {
+                $sql = "UPDATE " . TABLE_COMMENTS . " SET comment_votes_up = %d, comment_votes_down = %d WHERE comment_id = %d";
+                $h->db->query($h->db->prepare($sql, 0, abs($neg->comment_votes_up), $neg->comment_id));
+            }
+        }
+
+        // update "old version" for next set of upgrades
+        $old_version = "1.0.4";
+    }
+    
+    // 1.0.4 to 1.0.5
+    if ($old_version == "1.0.4") {
+        
+        // remove true/false "Notes" from admin settings
+        $sql = "UPDATE " . TABLE_SETTINGS . " SET settings_note = %s WHERE settings_note = %s";
+        $h->db->query($h->db->prepare($sql, '', 'true/false'));
+        
+        // update "old version" for next set of upgrades
+        $old_version = "1.0.5";
+    }
+    
+    // Update Hotaru version number to the database (referred to when upgrading)
+    $sql = "UPDATE " . TABLE_MISCDATA . " SET miscdata_key = %s, miscdata_value = %s, miscdata_default = %s WHERE miscdata_key = %s";
+    $h->db->query($h->db->prepare($sql, 'hotaru_version', $h->version, $h->version, 'hotaru_version'));
+}
+
+?>
Index: /trunk/javascript/hotaru.js
===================================================================
--- /trunk/javascript/hotaru.js	(revision 1081)
+++ /trunk/javascript/hotaru.js	(revision 1081)
@@ -0,0 +1,131 @@
+/* **************************************************************************************************** 
+ *  File: /javascript/hotaru.js
+ *  Purpose: A mixed bag of Ajax, JQuery and other JavaScript functions
+ *  Notes: ---
+ *  License:
+ *
+ *   This file is part of Hotaru CMS (http://www.hotarucms.org/).
+ *
+ *   Hotaru CMS 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 3 of 
+ *   the License, or (at your option) any later version.
+ *
+ *   Hotaru CMS 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 Hotaru CMS. If not, 
+ *   see http://www.gnu.org/licenses/.
+ *   
+ *   Copyright (C) 2009 Hotaru CMS - http://www.hotarucms.org/
+ *
+ **************************************************************************************************** */
+
+var xmlhttp=false;
+/*@cc_on @*/
+/*@if (@_jscript_version >= 5)
+  try {
+  xmlhttp=new ActiveXObject("Msxml2.XMLHTTP")
+ } catch (e) {
+  try {
+	xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")
+  } catch (E) {
+   xmlhttp=false
+  }
+ }
+@else
+ xmlhttp=false
+@end @*/
+
+
+if (!xmlhttp && typeof XMLHttpRequest != 'undefined')
+{
+  try {
+	xmlhttp = new XMLHttpRequest ();
+  }
+  catch (e) {
+  	xmlhttp = false}
+}
+
+function myXMLHttpRequest ()
+{
+  var xmlhttplocal;
+
+  if (!xmlhttplocal && typeof XMLHttpRequest != 'undefined') {
+	try {
+	  var xmlhttplocal = new XMLHttpRequest ();
+	}
+	catch (e) {
+	  var xmlhttplocal = false;
+	}
+  }
+  return (xmlhttplocal);
+}
+
+var ajax = Array ();
+var returnvalue = Array ();
+
+// Custom JQuery functions:
+
+// FADE TOGGLE
+jQuery.fn.fadeToggle = function(speed, easing, callback) {
+   return this.animate({opacity: 'toggle'}, speed, easing, callback);
+
+}; 
+
+/* ************************************* */
+
+// JQuery Function calls:
+
+$(document).ready(function(){
+
+	// Fade message
+	$(".message").css({display: "none"}).fadeIn(1000);
+	
+        
+	// Show/Hide table details (Plugin Management page and similar tables)
+	$(".table_drop_down").click(function () {
+		var target = $(this).parents("tr").next("tr");
+                target.fadeToggle();
+                return false;
+        });   
+        
+	// Hide table details (Plugin Management page and similar tables)
+	$(".table_hide_details").click(function () {
+                $(this).parents("tr.table_tr_details").fadeOut();
+                return false;
+        });  
+        
+	// Show/Hide forgot password form
+	$(".forgot_password").click(function () {
+		var target = $(this).next("form");
+                target.fadeToggle();
+                return false;
+        });  
+ 
+});
+
+
+/***********************************************
+* Disable "Enter" key in Form script- By Nurul Fadilah(nurul@REMOVETHISvolmedia.com)
+* This notice must stay intact for use
+* Visit http://www.dynamicdrive.com/ for full source code
+* Usage: <input type="text" onkeypress="return handleEnter(this, event)" id="" name="" value="" />
+***********************************************/
+
+function handleEnter (field, event) {
+	var keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
+	if (keyCode == 13) {
+		/* The following lines move the cursor to the next form field which works but we don't need it and it throws 2 Firebug errors.
+		var i;
+		for (i = 0; i < field.form.elements.length; i++)
+			if (field == field.form.elements[i])
+				break;
+		i = (i + 1) % field.form.elements.length;
+		field.form.elements[i].focus();
+		*/
+		return false;
+	} 
+	else
+	return true;
+}
Index: /trunk/Hotaru.php
===================================================================
--- /trunk/Hotaru.php	(revision 1081)
+++ /trunk/Hotaru.php	(revision 1081)
@@ -0,0 +1,1988 @@
+<?php
+/**
+ * The engine, powers everything :-)
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Hotaru
+{
+    protected $version              = "1.0.5";  // Hotaru CMS version
+    protected $isDebug              = false;    // show db queries and page loading time
+    protected $isAdmin              = false;    // flag to tell if we are in Admin or not
+    protected $sidebars             = true;     // enable or disable the sidebars
+    protected $csrfToken            = '';       // token for CSRF
+    protected $lang                 = array();  // stores language file content
+    
+    // objects
+    protected $db;                              // database object
+    protected $cage;                            // Inspekt object
+    protected $currentUser;                     // UserBase object
+    protected $plugin;                          // Plugin object
+    protected $post;                            // Post object
+    protected $avatar;                          // Avatar object
+    protected $comment;                         // Comment object
+    protected $includes;                        // for CSS/JavaScript includes
+    protected $debug;                           // Debug object
+    
+    // page info
+    protected $pageName             = '';       // e.g. index, category
+    protected $pageTitle            = '';       // e.g. Top Stories
+    protected $pageType             = '';       // e.g. post, list
+    protected $pageTemplate         = '';       // e.g. sb_list, tag_cloud
+    protected $subPage              = '';       // e.g. category (if pageName is "index")
+
+    // ALL plugins
+    protected $pluginSettings       = array();  // contains all settings for all plugins
+    protected $allPluginDetails     = array();  // contains details of all plugins
+    
+    // messages
+    protected $message              = '';       // message to display
+    protected $messageType          = 'green';  // green or red, color of message box
+    protected $messages             = array();  // for multiple messages
+    
+    // miscellaneous
+    protected $vars                 = array();  // multi-purpose
+    
+    /**
+     * CONSTRUCTOR - Initialize
+     */
+    public function __construct($start = '', $admin = false)
+    {
+        if ($admin) { $this->isAdmin = true; }      // we have this here because the checkCssJs function needs it 
+        
+        // initialize Hotaru
+        if (!$start) { 
+            require_once(LIBS . 'Initialize.php');
+            $init = new Initialize($this);
+            $this->db           = $init->db;            // database object
+            $this->cage         = $init->cage;          // Inspekt cage
+            $this->isDebug      = $init->isDebug;       // set debug
+            $this->currentUser  = new UserAuth();       // the current user
+            $this->plugin       = new Plugin();         // instantiate Plugin object
+            $this->post         = new Post();           // instantiate Post object
+            $this->includes     = new IncludeCssJs();   // instantiate Includes object
+
+            $this->checkCssJs();                        // check if we need to merge css/js
+            $this->csrf('set');                         // set a csrfToken
+        }
+    }
+    
+    
+/* *************************************************************
+ *
+ *  HOTARU FUNCTIONS
+ *
+ * *********************************************************** */
+
+
+    /**
+     * START - the top of "Hotaru", i.e. the page-building process
+     */
+    public function start($entrance = '')
+    {
+        // Set up debugging:
+        if ($this->isDebug) { 
+            require_once(LIBS . 'Debug.php');
+            $this->debug = new Debug();
+        }
+        
+        // include "main" language pack
+        $lang = new Language();
+        $this->lang = $lang->includeLanguagePack($this->lang, 'main');
+        
+        $this->getPageName();   // fills $h->pageName
+
+        switch ($entrance) {
+            case 'admin':
+                $this->isAdmin = true;
+                $this->lang = $lang->includeLanguagePack($this->lang, 'admin');
+                require_once(LIBS . 'AdminAuth.php');       // include Admin class
+                $admin = new AdminAuth();                   // new Admin object
+                $this->checkCookie();                   // check cookie reads user details
+                $this->checkAccess();                   // site closed if no access permitted
+                $page = $admin->adminInit($this);       // initialize Admin & get desired page
+                $this->adminPages($page);               // Direct to desired Admin page
+                break;
+            default:
+                $this->isAdmin = false;
+                $this->checkCookie();                   // log in user if cookie
+                $this->checkAccess();                   // site closed if no access permitted
+                if (!$entrance) { return false; }       // stop here if entrance not defined
+                $this->displayTemplate('index');        // displays the index page
+        }
+
+        if ($this->isDebug) {
+            $this->closeLog('error');
+        }
+        
+        exit;
+    }
+    
+    
+/* *************************************************************
+ *
+ *  ACCESS MODIFIERS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Access modifier to set protected properties
+     */
+    public function __set($var, $val)
+    {
+        $this->$var = $val;
+    }
+    
+    
+    /**
+     * Access modifier to get protected properties
+     * The & is necessary (http://bugs.php.net/bug.php?id=39449)
+     */
+    public function &__get($var)
+    {
+        return $this->$var;
+    }
+
+
+/* *************************************************************
+ *
+ *  DEFAULT PLUGIN HOOK ACTIONS
+ *
+ * *********************************************************** */
+ 
+     
+    /**
+     * Include language file if available
+     */
+    public function install_plugin()
+    {
+        $this->includeLanguage($this->plugin->folder);
+    }
+     
+     
+    /**
+     * Include All CSS and JavaScript files for this plugin
+     */
+    public function header_include()
+    {
+        if ($this->isAdmin) { return false; }
+        
+        // include a files that match the name of the plugin folder:
+        $this->includeJs($this->plugin->folder); // folder name, filename
+        $this->includeCss($this->plugin->folder);
+    }
+    
+    
+    /**
+     * Include All CSS and JavaScript files for this plugin in Admin
+     */
+    public function admin_header_include()
+    {
+        if (!$this->isAdmin) { return false; }
+        
+        // include a files that match the name of the plugin folder:
+        $this->includeJs($this->plugin->folder); // folder name, filename
+        $this->includeCss($this->plugin->folder);
+    }
+    
+    /**
+     * Include code as a template before the closing </body> tag
+     */
+    public function pre_close_body()
+    {
+        $this->displayTemplate($this->plugin->folder . '_footer', $this->plugin->folder);
+    }
+    
+
+    /**
+     * Display Admin sidebar link
+     */
+    public function admin_sidebar_plugin_settings()
+    {
+        $vars['plugin'] = $this->plugin->folder;
+        $vars['name'] = $this->plugin->name;
+        //$vars['name'] = make_name($this->plugin->folder);
+        return $vars;
+    }
+    
+    
+    /**
+     * Display Admin settings page
+     *
+     * @return true
+     */
+    public function admin_plugin_settings()
+    {
+        // This requires there to be a file in the plugin folder called pluginname_settings.php
+        // The file must contain a class titled PluginNameSettings
+        // The class must have a method called "settings".
+        if (($this->cage->get->testAlnumLines('plugin') != $this->plugin->folder)
+            && ($this->cage->post->testAlnumLines('plugin') != $this->plugin->folder)) 
+        { 
+            return false; 
+        }
+        
+        if (file_exists(PLUGINS . $this->plugin->folder . '/' . $this->plugin->folder . '_settings.php')) {
+            include_once(PLUGINS . $this->plugin->folder . '/' . $this->plugin->folder . '_settings.php');
+        }
+        $settings_class = make_name($this->plugin->folder, '_') . 'Settings'; // e.g. CategoriesSettings
+        $settings_class = str_replace(' ', '', $settings_class); // strip spaces
+        $settings_object = new $settings_class();
+        $settings_object->settings($this);   // call the settings function
+        return true;
+    }
+    
+    
+/* *************************************************************
+ *
+ *  PAGE HANDLING FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+    /**
+     * Determine the title tags for the header
+     *
+     * @param bool $raw -return the title only
+     * @return string - the title
+     */
+    public function getTitle($delimiter = ' &laquo; ', $raw = false)
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->getTitle($this, $delimiter, $raw);
+    }
+    
+    
+    /**
+     * Includes a template to display
+     *
+     * @param string $page page name
+     * @param string $plugin optional plugin name
+     * @param bool $include_once true or false
+     */
+    public function displayTemplate($page = '', $plugin = '', $include_once = true)
+    {
+        $pageHandling = new PageHandling();
+        $pageHandling->displayTemplate($this, $page, $plugin, $include_once);
+    }
+    
+    
+    /**
+     * Checks if current page (in url or form) matches the page parameter
+     *
+     * @param string $page page name
+     */
+    public function isPage($page = '')
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->isPage($this, $page);
+    }
+    
+    
+    /**
+     * Check to see if the Admin settings page we are looking at  
+     * matches the plugin passed to this function.
+     *
+     * @param string $folder - plugin folder
+     * @return bool
+     *
+     *  Notes: This is used in "admin_header_include" so we only include the css, 
+     *         javascript etc. for the plugin we're trying to change settings for.
+     *  Usage: $h->isSettingsPage('submit') returns true if 
+     *         page=plugin_settings and plugin=submit in the url.
+     */
+    public function isSettingsPage($folder = '')
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->isSettingsPage($this, $folder);
+    }
+
+    
+    /**
+     * Gets the current page name
+     */
+    public function getPageName()
+    {
+        $pageHandling = new PageHandling();
+        $this->pageName = $pageHandling->getPageName($this);
+        return $this->pageName;
+    }
+    
+    
+    /**
+     * Converts a friendly url into a standard one
+     *
+     * @param string $friendly_url
+     * return string $standard_url
+     */
+    public function friendlyToStandardUrl($friendly_url) 
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->friendlyToStandardUrl($this, $friendly_url);
+    }
+    
+    
+    /**
+     * Generate either default or friendly urls
+     *
+     * @param array $parameters an array of pairs, e.g. 'page' => 'about' 
+     * @param string $head either 'index' or 'admin'
+     * @return string
+     */
+    public function url($parameters = array(), $head = 'index')
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->url($this, $parameters, $head);
+    }
+    
+    
+    /**
+     * Prepare pagination
+     *
+     * @param array $items - array of all items to show
+     * @param int $items_per_page
+     * @param int $pg - current page number
+     * @return object - object of type Paginated
+     */
+    public function pagination($items = array(), $items_per_page = 10, $pg = 0)
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->pagination($this, $items, $items_per_page, $pg);
+    }
+    
+ 
+    /**
+     * Return page numbers bar
+     *
+     * @param object $pageObject - current object of type Paginated
+     * @return string - HTML for page number bar
+     */
+    public function pageBar($pageObject = NULL)
+    {
+        $pageHandling = new PageHandling();
+        return $pageHandling->pageBar($this, $pageObject);
+    }
+    
+
+/* *************************************************************
+ *
+ *  BREADCRUMB FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Build breadcrumbs
+     */
+    public function breadcrumbs()
+    {
+        require_once(LIBS . 'Breadcrumbs.php');
+        $breadcrumbs = new Breadcrumbs();
+        return $breadcrumbs->buildBreadcrumbs($this);
+    }
+    
+    
+    /**
+     * prepares the RSS link found in breadcrumbs
+     *
+     * @param string $status - post status, e.g. new, top, etc.
+     * @param array $vars - array of key -> value pairs
+     * @return string
+     */    
+    public function rssBreadcrumbsLink($status = '', $vars = array())
+    {
+        require_once(LIBS . 'Breadcrumbs.php');
+        $breadcrumbs = new Breadcrumbs();
+        return $breadcrumbs->rssBreadcrumbsLink($this, $status, $vars);
+    }
+    
+ 
+ /* *************************************************************
+ *
+ *  USERAUTH FUNCTIONS / USERBASE FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+    /* UserBase & UserAuth functions should be called directly if you want to 
+       retain the user object being used. E.g.
+       
+       $user = new UserAuth();
+       $user->getUserBasic($h);
+       $user->updateUserBasic($h);
+    */
+    
+    
+    /**
+     * check cookie and log in
+     *
+     * @return bool
+     */
+    public function checkCookie()
+    {
+        $this->currentUser->checkCookie($this);
+    }
+    
+    
+    /**
+     * Get basic user details
+     *
+     * @param int $userid 
+     * @param string $username
+     * @param bool $no_cache - set true to disable caching of SQl results
+     * @return array|false
+     *
+     * Note: Needs either userid or username, not both
+     */    
+    public function getUserBasic($userid = 0, $username = '', $no_cache = false)
+    {
+        $userbase = new UserBase();
+        return $userbase->getUserBasic($this, $userid, $username, $no_cache);
+    }
+    
+    
+    /**
+     * Default permissions
+     *
+     * @param string $role or 'all'
+     * @param string $field 'site' for site defaults and 'base' for base defaults
+     * @param book $options_only returns just the options if true
+     * @return array $perms
+     */
+    public function getDefaultPermissions($role = '', $defaults = 'site', $options_only = false) 
+    {
+        $userbase = new UserBase();
+        return $userbase->getDefaultPermissions($this, $role, $defaults, $options_only);
+    }
+    
+    
+    /**
+     * Update Default permissions
+     *
+     * @param array $new_perms from a plugin's install function
+     * @param string $defaults - either "site", "base" or "both" 
+     */
+    public function updateDefaultPermissions($new_perms = array(), $defaults = 'both') 
+    {
+        $userbase = new UserBase();
+        return $userbase->updateDefaultPermissions($this, $new_perms, $defaults);
+    }
+    
+    
+    /**
+     * Get the default user settings
+     *
+     * @param string $type either 'site' or 'base' (base for the originals)
+     * @return array
+     */
+    public function getDefaultSettings($type = 'site')
+    {
+        $userbase = new UserBase();
+        return $userbase->getDefaultSettings($this, $type);
+    }
+    
+    
+    /**
+     * Update the default user settings
+     *
+     * @param array $settings 
+     * @param string $type either 'site' or 'base' (base for the originals)
+     * @return array
+     */
+    public function updateDefaultSettings($settings, $type = 'site')
+    {
+        $userbase = new UserBase();
+        return $userbase->updateDefaultSettings($this, $settings, $type);
+    }
+    
+    
+    /**
+     * Get a user's profile or settings data
+     *
+     * @return array|false
+     */
+    public function getProfileSettingsData($type = 'user_profile', $userid = 0, $check_exists_only = false)
+    {
+        $userbase = new UserBase();
+        return $userbase->getProfileSettingsData($this, $type, $userid, $check_exists_only);
+    }
+    
+    
+    /**
+     * Physically delete a user
+     * Note: You should delete all their posts, comments, etc. first
+     *
+     * @param array $user_id (optional)
+     */
+    public function deleteUser($user_id = 0) 
+    {
+        $userbase = new UserBase();
+        return $userbase->deleteUser($this, $user_id);
+    }
+
+
+ /* *************************************************************
+ *
+ *  USERINFO FUNCTIONS
+ *
+ * *********************************************************** */
+    
+    
+    /**
+     * Get the username for a given user id
+     *
+     * @param int $id user id
+     * @return string|false
+     */
+    public function getUserNameFromId($id = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getUserNameFromId($this, $id);
+    }
+    
+    
+    /**
+     * Get the user id for a given username
+     *
+     * @param string $username
+     * @return int|false
+     */
+    public function getUserIdFromName($username = '')
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getUserIdFromName($this, $username);
+    }
+    
+    
+    /**
+     * Get the email from user id
+     *
+     * @param int $userid
+     * @return string|false
+     */
+    public function getEmailFromId($userid = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getEmailFromId($this, $userid);
+    }
+    
+    
+    /**
+     * Get the user id from email
+     *
+     * @param string $email
+     * @return string|false
+     */
+    public function getUserIdFromEmail($email = '')
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getUserIdFromEmail($this, $email);
+    }
+    
+    
+     /**
+     * Checks if the user has an 'admin' role
+     *
+     * @return bool
+     */
+    public function isAdmin($username)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->isAdmin($this->db, $username);
+    }
+    
+    
+    /**
+     * Check if a user exists
+     *
+     * @param int $userid 
+     * @param string $username
+     * @return int
+     *
+     * Notes: Returns 'no' if a user doesn't exist, else field under which found
+     */
+    public function userExists($id = 0, $username = '', $email = '')
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->userExists($this->db, $id, $username, $email);
+    }
+    
+    
+    /**
+     * Check if an username exists in the database (used in forgotten password)
+     *
+     * @param string $username user username
+     * @param string $role user role (optional)
+     * @param int $exclude - exclude a user
+     * @return string|false
+     */
+    public function nameExists($username = '', $role = '', $exclude = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->nameExists($this, $username, $role, $exclude);
+    }
+    
+    
+    /**
+     * Check if an email exists in the database (used in forgotten password)
+     *
+     * @param string $email user email
+     * @param string $role user role (optional)
+     * @param int $exclude - exclude a user
+     * @return string|false
+     */
+    public function emailExists($email = '', $role = '', $exclude = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->emailExists($this, $email, $role, $exclude);
+    }
+    
+    
+    /**
+     * Get all users with permission to (access admin)
+     *
+     * @param string $permission
+     * @param string $value - value for the permission, usually yes, no, own or mod
+     * @return array
+     */
+    public function getMods($permission = 'can_access_admin', $value = 'yes')
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getMods($this, $permission, $value);
+    }
+    
+    
+    /**
+     * Get Unique Roles
+     *
+     * @return array|false
+     */
+    public function getUniqueRoles() 
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->getUniqueRoles($this);
+    }
+    
+    
+    /**
+     * Get the ids and names of all users or those with a specified role, sorted alphabetically
+     *
+     * @param string $role - optional user role to filter to
+     * @return array
+     */
+    public function userIdNameList($role = '')
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->userIdNameList($this, $role);
+    }
+    
+    
+    /**
+     * Get full details of all users or batches of users, sorted alphabetically
+     *
+     * @param array $id_array - optional array of user ids
+     * @param int $start - LIMIT $start $range (optional)
+     * @param int $range - LIMIT $start $range (optional)
+     * @return array
+     */
+    public function userListFull($id_array = array(), $start = 0, $range = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->userListFull($this, $id_array, $start, $range);
+    }
+    
+    
+    /**
+     * Get settings for all users
+     *
+     * @param int $userid - optional user id 
+     * @return array
+     */
+    public function userSettingsList($userid = 0)
+    {
+        require_once(LIBS . 'UserInfo.php');
+        $userInfo = new UserInfo();
+        return $userInfo->userSettingsList($this, $userid);
+    }
+
+    
+ /* *************************************************************
+ *
+ *  PLUGIN FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Look for and run actions at a given plugin hook
+     *
+     * @param string $hook name of the plugin hook
+     * @param bool $perform false to check existence, true to actually run
+     * @param string $folder name of plugin folder
+     * @param array $parameters mixed values passed from plugin hook
+     * @return array | bool
+     */
+    public function pluginHook($hook = '', $folder = '', $parameters = array(), $exclude = array())
+    {
+        $pluginFunctions = new PluginFunctions();
+        return $pluginFunctions->pluginHook($this, $hook, $folder, $parameters, $exclude);
+    }
+    
+    
+    /**
+     * Get a single plugin's details for Hotaru
+     *
+     * @param string $folder - plugin folder name, else $h->plugin->folder is used
+     */
+    public function readPlugin($folder = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        $pluginFunctions->readPlugin($this, $folder);
+    }
+    
+    
+    /**
+     * Get a single property from a specified plugin
+     *
+     * @param string $property - plugin property, e.g. "plugin_version"
+     * @param string $folder - plugin folder name, else $h->plugin->folder is used
+     * @param string $field - an alternative field to use instead of $folder
+     */
+    public function getPluginProperty($property = '', $folder = '', $field = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        return $pluginFunctions->getPluginProperty($this, $property, $folder, $field);
+    }
+    
+    
+    /**
+     * Get number of active plugins
+     *
+     * @return int|false
+     */
+    public function numActivePlugins()
+    {
+        $pluginFunctions = new PluginFunctions();
+        return $pluginFunctions->numActivePlugins($this->db);
+    }
+    
+    
+    /**
+     * Get version number of plugin if active
+     *
+     * @param string $folder plugin folder name
+     * @return string|false
+     */
+    public function getPluginVersion($folder = '')
+    {
+        return $this->getPluginProperty('plugin_version', $folder);
+    }
+    
+    
+    /**
+     * Get a plugin's actual name from its folder name
+     *
+     * @param string $folder plugin folder name
+     * @return string
+     */
+    public function getPluginName($folder = '')
+    {
+        return $this->getPluginProperty('plugin_name', $folder);
+    }
+    
+
+    /**
+     * Get a plugin's folder from its class name
+     *
+     * @param string $class plugin class name
+     * @return string|false
+     */
+    public function getPluginFolderFromClass($class = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        $this->plugin->folder = $pluginFunctions->getPluginFolderFromClass($this, $class);
+    }
+    
+    
+    /**
+     * Get a plugin's class from its folder name
+     *
+     * @param string $folder plugin folder name
+     * @return string|false
+     */
+    public function getPluginClass($folder = '')
+    {
+        return $this->getPluginProperty('plugin_class', $folder);
+    }
+    
+
+    /**
+     * Determines if a plugin "type" is enabled, if not, plugin "folder"
+     *
+     * @param string $type plugin type or folder name
+     * @return bool
+     */
+    public function isActive($type = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        //return $pluginFunctions->isActive($this, $type); // dropped in favor of cache:
+        $result = $this->getPluginProperty('plugin_enabled', $type, 'type');
+        if (!$result) { $result = $this->getPluginProperty('plugin_enabled', $type); }
+        return $result;
+    }
+
+
+    /**
+     * Determines if a specific plugin is installed
+     *
+     * @param string $folder folder name
+     * @return bool
+     */
+    public function isInstalled($folder = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        $result = $this->getPluginProperty('plugin_id', $folder);
+        return $result;
+    }
+    
+    
+    /**
+     * Determines if a plugin has a settings page or not
+     *
+     * @param object $h
+     * @param string $folder plugin folder name (optional)
+     * @return bool
+     */
+    public function hasSettings($folder = '')
+    {
+        $pluginFunctions = new PluginFunctions();
+        return $pluginFunctions->hasSettings($this, $folder);
+    }
+    
+ 
+ /* *************************************************************
+ *
+ *  PLUGIN SETTINGS FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Get the value for a given plugin and setting
+     *
+     * @param string $folder name of plugin folder
+     * @param string $setting name of the setting to retrieve
+     * @return string|false
+     *
+     * Notes: If there are multiple settings with the same name,
+     * this will only get the first.
+     */
+    public function getSetting($setting = '', $folder = '')
+    {
+        $pluginSettings = new PluginSettings();
+        return $pluginSettings->getSetting($this, $setting, $folder);
+    }
+    
+    
+    /**
+     * Get an array of settings for a given plugin
+     *
+     * @param string $folder name of plugin folder
+     * @return array|false
+     *
+     * Note: Unlike "getSetting", this will get ALL settings with the same name.
+     */
+    public function getSettingsArray($folder = '')
+    {
+        $pluginSettings = new PluginSettings();
+        return $pluginSettings->getSettingsArray($this, $folder);
+    }
+    
+    
+    /**
+     * Get and unserialize serialized settings
+     *
+     * @param string $folder plugin folder name
+     * @param string $settings_name optional settings name if different from folder
+     * @return array - of submit settings
+     */
+    public function getSerializedSettings($folder = '', $settings_name = '')
+    {
+        $pluginSettings = new PluginSettings();
+        return $pluginSettings->getSerializedSettings($this, $folder, $settings_name);
+    }
+    
+    
+    /**
+     * Get and store all plugin settings in $h->pluginSettings
+     *
+     * @return array - all settings
+     */
+    public function getAllPluginSettings()
+    {
+        $pluginSettings = new PluginSettings();
+        $this->pluginSettings = $pluginSettings->getAllPluginSettings($this);
+        return $this->pluginSettings;
+    }
+    
+    
+    /**
+     * Determine if a plugin setting already exists
+     *
+     * @param string $folder name of plugin folder
+     * @param string $setting name of the setting to retrieve
+     * @return string|false
+     */
+    public function isSetting($setting = '', $folder = '')
+    {
+        $pluginSettings = new PluginSettings();
+        return $pluginSettings->isSetting($this, $setting, $folder);
+    }
+
+
+    /**
+     * Update a plugin setting
+     *
+     * @param string $folder name of plugin folder
+     * @param string $setting name of the setting
+     * @param string $setting stting value
+     */
+    public function updateSetting($setting = '', $value = '', $folder = '')
+    {
+        $pluginSettings = new PluginSettings();
+        return $pluginSettings->updateSetting($this, $setting, $value, $folder);
+    }
+
+
+ /* *************************************************************
+ *
+ *  THEME SETTINGS FUNCTIONS
+ *
+ * *********************************************************** */
+
+    /**
+     * Read and return plugin info from top of a plugin file.
+     *
+     * @param string $theme - theme folder
+     * @return array|false
+     */
+    public function readThemeMeta($theme = 'default')
+    {
+        require_once(LIBS . 'ThemeSettings.php');
+        $themeSettings = new ThemeSettings();
+        return $themeSettings->readThemeMeta($this, $theme);
+    }
+    
+    
+    /**
+     * Get and unserialize serialized settings
+     *
+     * @param string $theme theme folder name
+     * @param string $return 'value' or 'default'
+     * @return array - of theme settings
+     */
+    public function getThemeSettings($theme = '', $return = 'value')
+    {
+        require_once(LIBS . 'ThemeSettings.php');
+        $themeSettings = new ThemeSettings();
+        return $themeSettings->getThemeSettings($this, $theme, $return);
+    }
+    
+    
+    /**
+     * Update theme settings
+     *
+     * @param array $settings array of settings
+     * @param string $theme theme folder name
+     * @param string $column 'value', 'default' or 'both'
+
+     */
+    public function updateThemeSettings($settings = array(), $theme = '', $column = 'value')
+    {
+        require_once(LIBS . 'ThemeSettings.php');
+        $themeSettings = new ThemeSettings();
+        return $themeSettings->updateThemeSettings($this, $settings, $theme, $column);
+    }
+
+
+ /* *************************************************************
+ *
+ *  INCLUDE CSS & JAVASCRIPT FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+
+    /**
+     * Check if we need to combine CSS and JavaScript files (from start function )
+     */
+     public function checkCssJs()
+     {
+        if (!$this->cage->get->keyExists('combine')) { return false; }
+
+        $type = $this->cage->get->testAlpha('type');
+        $version = $this->cage->get->testInt('version');
+        $this->includes->combineIncludes($this, $type, $version);
+        return true;
+     }
+     
+     
+    /**
+     * Do Includes (called from template header.php)
+     */
+     public function doIncludes()
+     {
+        $version_js = $this->includes->combineIncludes($this, 'js');
+        $version_css = $this->includes->combineIncludes($this, 'css');
+        $this->includes->includeCombined($version_js, $version_css, $this->isAdmin);
+     }
+     
+     
+    /**
+     * Build an array of css files to combine
+     *
+     * @param $folder - the folder name of the plugin
+     * @param $filename - optional css file without an extension
+     */
+     public function includeCss($folder = '', $filename = '')
+     {
+        return $this->includes->includeCss($this, $folder, $filename);
+     }
+
+
+    /**
+     * Build an array of JavaScript files to combine
+     *
+     * @param $folder - the folder name of the plugin
+     * @param $filename - optional js file without an extension
+     */
+     public function includeJs($folder = '', $filename = '')
+     {
+        return $this->includes->includeJs($this, $folder, $filename);
+     }
+     
+     
+ /* *************************************************************
+ *
+ *  MESSAGE FUNCTIONS (success/error messages)
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Display a SINGLE success or failure message
+     *
+     * @param string $msg
+     * @param string $msg_type ('green' or 'red')
+     */
+    public function showMessage($msg = '', $msg_type = 'green')
+    {
+        require_once(LIBS . 'Messages.php');
+        $messages = new Messages();
+        $messages->showMessage($this, $msg, $msg_type);
+    }
+    
+    
+    /**
+     * Displays ALL success or failure messages
+     */
+    public function showMessages()
+    {
+        require_once(LIBS . 'Messages.php');
+        $messages = new Messages();
+        $messages->showMessages($this);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  ANNOUNCEMENT FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Displays an announcement at the top of the screen
+     *
+     * @param string $announcement - optional for non-admin pages
+     * @return array
+     */
+    public function checkAnnouncements($announcement = '') 
+    {
+        require_once(LIBS . 'Announcements.php');
+        $announce = new Announcements();
+        if ($this->isAdmin) {
+            return $announce->checkAdminAnnouncements($this);
+        } else {
+            return $announce->checkAnnouncements($this, $announcement);
+        }
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  DEBUG FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Shows number of database queries and the time it takes for a page to load
+     */
+    public function showQueriesAndTime()
+    {
+        if ($this->isDebug) {
+            $this->debug->showQueriesAndTime($this);
+        }
+    }
+    
+    /**
+     * Open file for logging
+     *
+     * @param string $type "speed", "error", etc.
+     * @param string $mode e.g. 'a' or 'w'. 
+     * @link http://php.net/manual/en/function.fopen.php
+     */
+    public function openLog($type = 'debug', $mode = 'a+')
+    {
+        $this->debug->openLog($this, $type, $mode);
+    }
+    
+    
+    /**
+     * Log performance and errors
+     *
+     * @param string $type "speed", "error", etc.
+     */
+    public function writeLog($type = 'error', $string = '')
+    {
+        $this->debug->writeLog($this, $type, $string);
+    }
+    
+    
+    /**
+     * Close log file
+     *
+     * @param string $type "speed", "error", etc.
+     */
+    public function closeLog($type = 'error')
+    {
+        $this->debug->closeLog($this, $type);
+    }
+    
+    
+     
+    
+ /* *************************************************************
+ *
+ *  RSS FEED FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Includes the SimplePie RSS file and sets the cache
+     *
+     * @param string $feed
+     * @param bool $cache
+     * @param int $cache_duration
+     *
+     * @return object|false $sp
+     */
+    public function newSimplePie($feed='', $cache=RSS_CACHE_ON, $cache_duration=RSS_CACHE_DURATION)
+    {
+        require_once(LIBS . 'Feeds.php');
+        $feeds = new Feeds();
+        return $feeds->newSimplePie($feed, $cache, $cache_duration);
+    }
+    
+    
+     /**
+     * Display Hotaru forums feed on Admin front page
+     *
+     * @param int $max_items
+     * @param int $items_with_content
+     * @param int $max_chars
+     */
+    public function adminNews($max_items = 10, $items_with_content = 3, $max_chars = 300)
+    {
+        require_once(LIBS . 'Feeds.php');
+        $feeds = new Feeds();
+        $feeds->adminNews($this->lang, $max_items, $items_with_content, $max_chars);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  ADMIN FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+     /**
+     * Admin Pages
+     */
+    public function adminPages($page = 'admin_login')
+    {
+        require_once(LIBS . 'AdminPages.php');
+        $admin = new AdminPages();
+        $admin->pages($this, $page);
+    }
+    
+    
+     /**
+     * Admin login/logout
+     *
+     * @param string $action
+     */
+    public function adminLoginLogout($action = 'logout')
+    {
+        require_once(LIBS . 'AdminAuth.php');
+        $admin = new AdminAuth();
+        return ($action == 'login') ? $admin->adminLogin($this) : $admin->adminLogout($this);
+    }
+    
+    
+     /**
+     * Admin login form
+     */
+    public function adminLoginForm()
+    {
+        require_once(LIBS . 'AdminAuth.php');
+        $admin = new AdminAuth();
+        $admin->adminLoginForm($this);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  MAINTENANCE FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Check if site is open or closed. Exit if closed
+     *
+     * @param object $h
+     */
+    public function checkAccess()
+    {
+        if (SITE_OPEN == 'true') { return true; }   // site is open, go back and continue
+        
+        // site closed, but user has admin access so go back and continue as normal
+        if ($this->currentUser->getPermission('can_access_admin') == 'yes') { return true; }
+        
+        if ($this->pageName == 'admin_login') { return true; }
+        
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        return $maintenance->siteClosed($this->lang); // displays "Site Closed for Maintenance"
+    }
+    
+    
+    /**
+     * Open or close the site for maintenance
+     *
+     * @param string $switch - 'open' or 'close'
+     */
+    public function openCloseSite($switch = 'open')
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->openCloseSite($this, $switch);
+    }
+    
+    
+    /**
+     * Optimize all database tables
+     */
+    public function optimizeTables()
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->optimizeTables($this);
+    }
+    
+    
+    /**
+     * Empty plugin database table
+     *
+     * @param string $table_name - table to empty
+     * @param string $msg - show "emptied" message or not
+     */
+    public function emptyTable($table_name = '', $msg = true)
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->emptyTable($this, $table_name, $msg);
+    }
+    
+    
+    /**
+     * Delete plugin database table
+     *
+     * @param string $table_name - table to drop
+     * @param string $msg - show "dropped" message or not
+     */
+    public function dropTable($table_name = '', $msg = true)
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->dropTable($this, $table_name, $msg);
+    }
+    
+    
+    /**
+     * Remove plugin settings
+     *
+     * @param string $folder - plugin folder name
+     * @param bool $msg - show "Removed" message or not
+     */
+    public function removeSettings($folder = '', $msg = true)
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->removeSettings($this, $folder, $msg);
+    }
+    
+    
+    /**
+     * Deletes rows from pluginsettings that match a given setting or plugin
+     *
+     * @param string $setting name of the setting to remove
+     * @param string $folder name of plugin folder
+     */
+    public function deleteSettings($setting = '', $folder = '')
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->deleteSettings($this, $setting, $folder);
+    }
+    
+    
+    /**
+     * Delete all files in the specified directory except placeholder.txt
+     *
+     * @param string $dir - path to the cache folder
+     * @return bool
+     */    
+    public function deleteFiles($dir = '')
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->deleteFiles($dir);
+    }
+    
+    
+    /**
+     * Calls the delete_files function, then displays a message.
+     *
+     * @param string $folder - path to the cache folder
+     * @param string $msg - show "cleared" message or not
+     */
+    public function clearCache($folder = '', $msg = true)
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->clearCache($this, $folder, $msg);
+    }
+    
+    
+    /**
+     * Get all files in the specified directory except placeholder.txt
+     *
+     * @param string $dir - path to the folder
+     * @param array $exclude - array of file/folder names to exclude
+     * @return array
+     */    
+    public function getFiles($dir = '', $exclude = array())
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        return $maintenance->getFiles($dir, $exclude);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  CACHING FUNCTIONS (Note: "clearCache" is in Maintenance above)
+ *
+ * *********************************************************** */
+
+
+    /**
+     * Hotaru CMS Smart Caching
+     *
+     * This function does one query on the database to get the last updated time for a 
+     * specified table. If that time is more recent than the $timeout length (e.g. 10 minutes),
+     * the database will be used. If there hasn't been an update, any cached results from the 
+     * last 10 minutes will be used.
+     *
+     * @param string $switch either "on", "off" or "html"
+     * @param string $table DB table name
+     * @param int $timeout time before DB cache expires
+     * @param string $html_sql output as HTML, or an SQL query
+     * @param string $label optional label to append to filename
+     * @return bool
+     */
+    public function smartCache($switch = 'off', $table = '', $timeout = 0, $html_sql = '', $label = '')
+    {
+        require_once(LIBS . 'Caching.php');
+        $caching = new Caching();
+        return $caching->smartCache($this, $switch, $table, $timeout, $html_sql, $label);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  BLOCKED FUNCTIONS (i.e. Admin's Blocked list)
+ *
+ * *********************************************************** */
+ 
+     /**
+     * Check if a value is blocked from registration and post submission)
+     *
+     * @param string $type - i.e. ip, url, email, user
+     * @param string $value
+     * @param bool $like - used for LIKE sql if true
+     * @return bool
+     */
+    public function isBlocked($type = '', $value = '', $operator = '=')
+    {
+        require_once(LIBS . 'Blocked.php');
+        $blocked = new Blocked();
+        return $blocked->isBlocked($this->db, $type, $value, $operator);
+    }
+    
+    
+     /**
+     * Add or update blocked items 
+     *
+     * @param string $type - e.g. url, email, ip
+     * @param string $value - item to block
+     * @param bool $msg - show a success/failure message on Maintenance page
+     * @return bool
+     */
+    public function addToBlockedList($type = '', $value = 0, $msg = false)
+    {
+        require_once(LIBS . 'Blocked.php');
+        $blocked = new Blocked();
+        return $blocked->addToBlockedList($this, $type, $value, $msg);
+    }
+
+
+ /* *************************************************************
+ *
+ *  LANGUAGE FUNCTIONS
+ *
+ * *********************************************************** */
+
+
+    /**
+     * Include a language file in a plugin
+     *
+     * @param string $folder name of plugin folder
+     * @param string $filename optional filename without file extension
+     *
+     * Note: the language file should be in a plugin folder named 'languages'.
+     * '_language.php' is appended automatically to the folder of file name.
+     */    
+    public function includeLanguage($folder = '', $filename = '')
+    {
+        require_once(LIBS . 'Language.php');
+        $language = new Language();
+        $language->includeLanguage($this, $folder, $filename);
+    }
+    
+    
+    /**
+     * Include a language file for a theme
+     *
+     * @param string $filename optional filename without '_language.php' file extension
+     *
+     * Note: the language file should be in a plugin folder named 'languages'.
+     * '_language.php' is appended automatically to the folder of file name.
+     */    
+    public function includeThemeLanguage($filename = 'main')
+    {
+        require_once(LIBS . 'Language.php');
+        $language = new Language();
+        $language->includeThemeLanguage($this, $filename);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  CSRF FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Shortcut for CSRF functions
+     *
+     * @param string $type - either "set" or "check" CSRF key
+     * @param string $script - optional name of page using the key
+     * @param int $life - minutes before the token expires
+     * @return string $key (if using $type "fetch")
+     */
+    public function csrf($type = 'check', $script = '', $life = 10)
+    {
+        $csrf = new csrf();
+        return $csrf->csrfInit($this, $type, $script, $life);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  POST FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+
+    /**
+     * Get all the parameters for the current post
+     *
+     * @param int $post_id - Optional row from the posts table in the database
+     * @param array $post_row - a post already fetched from the db, just needs reading
+     * @return bool
+     */    
+    public function readPost($post_id = 0, $post_row = NULL)
+    {
+        return $this->post->readPost($this, $post_id, $post_row);
+    }
+    
+    
+    /**
+     * Gets a single post from the database
+     *
+     * @param int $post_id - post id of the post to get
+     * @return array|false
+     */    
+    public function getPost($post_id = 0)
+    {
+        return $this->post->getPost($this, $post_id);
+    }
+    
+    
+    /**
+     * Add a post to the database
+     *
+     * @return true
+     */    
+    public function addPost()
+    {
+        $this->post->addPost($this);
+    }
+    
+    
+    /**
+     * Update a post in the database
+     *
+     * @return true
+     */    
+    public function updatePost()
+    {
+        $this->post->updatePost($this);
+    }
+    
+    
+    /**
+     * Physically delete a post from the database 
+     *
+     * There's a plugin hook in here to delete their parts, e.g. votes, coments, tags, etc.
+     */    
+    public function deletePost()
+    {
+        $this->post->deletePost($this);
+    }
+    
+    
+    /**
+     * Physically delete all posts by a specified user
+     *
+     * @param array $user_id
+     * @return bool
+     */
+    public function deletePosts($user_id = 0) 
+    {
+        return $this->post->deletePosts($this, $user_id);
+    }
+    
+    
+    /**
+     * Delete posts with "processing" status that are older than 30 minutes
+     * This is called automatically when a new post is submitted
+     */
+    public function deleteProcessingPosts()
+    {
+        $this->post->deleteProcessingPosts($this);
+    }
+    
+    
+    /**
+     * Update a post's status
+     *
+     * @param string $status
+     * @param int $post_id (optional)
+     * @return true
+     */    
+    public function changePostStatus($status = "processing", $post_id = 0)
+    {
+        return $this->post->changePostStatus($this, $status, $post_id);
+    }
+    
+    
+    /**
+     * Count how many approved posts a user has had
+     *
+     * @param int $userid (optional)
+     * @return int 
+     */
+    public function postsApproved($userid = 0)
+    {
+        return $this->post->postsApproved($this, $userid);
+    }
+    
+    
+    /**
+     * Count posts in the last X hours/minutes for this user
+     *
+     * @param int $hours
+     * @param int $minutes
+     * @param int $user_id (optional)
+     * @return int 
+     */
+    public function countPosts($hours = 0, $minutes = 0, $user_id = 0)
+    {
+        return $this->post->countPosts($this, $hours, $minutes, $user_id);
+    }
+
+
+    /**
+     * Checks for existence of a url
+     *
+     * @return array|false - array of posts
+     */    
+    public function urlExists($url = '')
+    {
+        return $this->post->urlExists($this, $url);
+    }
+    
+    
+    /**
+     * Checks for existence of a title
+     *
+     * @param str $title
+     * @return int - id of post with matching title
+     */
+    public function titleExists($title = '')
+    {
+        return $this->post->titleExists($this, $title);
+    }
+    
+    
+    /**
+     * Checks for existence of a post with given post_url
+     *
+     * @param str $post_url (slug)
+     * @return int - id of post with matching url
+     */
+    public function isPostUrl($post_url = '')
+    {
+        return $this->post->isPostUrl($this, $post_url);
+    }
+    
+    
+    /**
+     * Get Unique Post Statuses
+     *
+     * @return array|false
+     */
+    public function getUniqueStatuses() 
+    {
+        return $this->post->getUniqueStatuses($this);
+    }
+    
+    
+    /**
+     * Prepares and calls functions to send a trackback
+     * Uses $h->post->id
+     */
+    public function sendTrackback()
+    {
+        require_once(LIBS . 'Trackback.php');
+        $trackback = new Trackback();
+        return $trackback->sendTrackback($this);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  AVATAR FUNCTIONS
+ *
+ * *********************************************************** */
+ 
+
+    /**
+     * setAvatar
+     *
+     * @param $user_id
+     * @param $size avatar size in pixels
+     * @param $rating avatar rating (g, pg, r or x in Gravatar)
+     */
+    public function setAvatar($user_id = 0, $size = 32, $rating = 'g')
+    {
+        $this->avatar = new Avatar($this, $user_id, $size, $rating);
+    }
+    
+    
+    /**
+     * get the plain avatar with no surrounding HTML div
+     *
+     * @return return the avatar
+     */
+    public function getAvatar()
+    {
+        return $this->avatar->getAvatar($this);
+    }
+    
+    
+    /**
+     * option to display the avatar linked to ther user's profile
+     *
+     * @return return the avatar
+     */
+    public function linkAvatar()
+    {
+        return $this->avatar->linkAvatar($this);
+    }
+    
+    
+    /**
+     * option to display the profile-linked avatar wrapped in a div
+     *
+     * @return return the avatar
+     */
+    public function wrapAvatar()
+    {
+        return $this->avatar->wrapAvatar($this);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  CATEGORY FUNCTIONS
+ *
+ * *********************************************************** */
+
+    /**
+     * Returns the category id for a given category safe name.
+     *
+     * @param string $cat_name
+     * @return int
+     */
+    public function getCatId($cat_safe_name)
+    {
+        require_once(LIBS . 'Category.php');
+        $category = new Category();
+        return $category->getCatId($this, $cat_safe_name);
+    }
+    
+
+    /**
+     * Returns the category name for a given category id or safe name.
+     *
+     * @param int $cat_id
+     * @param string $cat_safe_name
+     * @return string
+     */
+    public function getCatName($cat_id = 0, $cat_safe_name = '')
+    {
+        require_once(LIBS . 'Category.php');
+        $category = new Category();
+        return $category->getCatName($this, $cat_id, $cat_safe_name);
+    }
+    
+
+    /**
+     * Returns the category safe name for a given category id 
+     *
+     * @param int $cat_id
+     * @return string
+     */
+    public function getCatSafeName($cat_id = 0)
+    {
+        require_once(LIBS . 'Category.php');
+        $category = new Category();
+        return $category->getCatSafeName($this, $cat_id);
+    }
+    
+    
+    /**
+     * Returns parent id
+     *
+     * @param int $cat_id
+     * @return int
+     */
+    public function getCatParent($cat_id)
+    {
+        require_once(LIBS . 'Category.php');
+        $category = new Category();
+        return $category->getCatParent($this, $cat_id);
+    }
+    
+    
+    /**
+     * Returns child ids
+     *
+     * @param int $cat_parent_id
+     * @return int
+     */
+    public function getCatChildren($cat_parent_id)
+    {
+        require_once(LIBS . 'Category.php');
+        $category = new Category();
+        return $category->getCatChildren($this, $cat_parent_id);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  COMMENT FUNCTIONS
+ *
+ * *********************************************************** */
+
+
+    /**
+     * Count comments
+     *
+     * @param bool $link - true used for "comments" link, false for top of actual comments
+     * @return string - text to show in the link, e.g. "3 comments"
+     */
+    function countComments($link = true)
+    {
+        require_once(LIBS . 'Comment.php');
+        $comment = new Comment();
+        return $comment->countComments($this, $link);
+    }
+    
+    
+    /**
+     * Physically delete all comments by a specified user (and responses)
+     *
+     * @param array $user_id
+     * @return bool
+     */
+    public function deleteComments($user_id) 
+    {
+        require_once(LIBS . 'Comment.php');
+        $comment = new Comment();
+        return $comment->deleteComments($this, $user_id);
+    }
+    
+    
+    /**
+     * Get comment from database
+     *
+     * @param int $comment_id
+     * @return array|false
+     */
+    function getComment($comment_id = 0)
+    {
+        require_once(LIBS . 'Comment.php');
+        $comment = new Comment();
+        return $comment->getComment($this, $comment_id);
+    }
+    
+    
+    /**
+     * Read comment
+     *
+     * @param array $comment_row pulled from database
+     */
+    function readComment($comment_row = array())
+    {
+        require_once(LIBS . 'Comment.php');
+        $comment = new Comment();
+        return $comment->readComment($this, $comment_row);
+    }
+    
+    
+/* *************************************************************
+ *
+ *  WIDGET FUNCTIONS
+ *
+ * *********************************************************** */
+
+    /**
+     * Add widget
+     *
+     * @param string $plugin
+     * @param string $function
+     * @param string $value
+     */
+    public function addWidget($plugin = '', $function = '', $args = '')
+    {
+        require_once(LIBS . 'Widget.php');
+        $widget = new Widget();
+        $widget->addWidget($this, $plugin, $function, $args);
+    }
+    
+    
+    /**
+     * Delete a widget from the widget db table
+     *
+     * @param string $function 
+     */
+    public function deleteWidget($function)
+    {
+        require_once(LIBS . 'Widget.php');
+        $widget = new Widget();
+        $widget->deleteWidget($this, $function);
+    }
+    
+ 
+    /**
+     * Get plugin name from widget function name
+     *
+     * @return string
+     */
+    public function getPluginFromFunction($function)
+    {
+        require_once(LIBS . 'Widget.php');
+        $widget = new Widget();
+        return $widget->getPluginFromFunction($this, $function);
+    }
+}
+?>
Index: /trunk/functions/funcs.arrays.php
===================================================================
--- /trunk/functions/funcs.arrays.php	(revision 1081)
+++ /trunk/functions/funcs.arrays.php	(revision 1081)
@@ -0,0 +1,119 @@
+<?php
+/**
+ * A collection of functions for manipulating arrays
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+ 
+/**
+ * Sort an associative array by by the key of a sub-array
+ *
+ * @param array $array associative array
+ * @param string $subkey key to sort from sub-array
+ * @param string $type "int" or "char"
+ * @param bool $sort_ascending sort order
+ * @return array
+ *
+ * Note: http://us2.php.net/manual/en/function.ksort.php
+ */
+  
+function sksort(&$array, $subkey="id", $type="int", $sort_ascending=false)
+{
+
+    if (empty($array)) { return false; }
+    
+    if (count($array)) {
+        $temp_array[key($array)] = array_shift($array);
+    }
+
+    foreach ($array as $key => $val)
+    {
+        $offset = 0;
+        $found = false;
+        foreach ($temp_array as $tmp_key => $tmp_val)
+        {
+            if ($type == "int") {
+                if (!$found && ($val[$subkey]) > ($tmp_val[$subkey])) 
+                {
+                        $temp_array = array_merge(
+                            (array)array_slice($temp_array,0,$offset),
+                            array($key => $val),
+                            array_slice($temp_array,$offset)
+                        );
+                        $found = true;
+                }
+            } else {
+            
+                if (!$found && strtolower($val[$subkey]) > strtolower($tmp_val[$subkey])) 
+                {
+                    $temp_array = array_merge(
+                        (array)array_slice($temp_array,0,$offset),
+                        array($key => $val),
+                        array_slice($temp_array,$offset)
+                    );
+                    $found = true;
+                }
+            }
+
+            $offset++;
+        }
+        if (!$found) {
+            $temp_array = array_merge($temp_array, array($key => $val));
+        }
+    }
+
+    if ($sort_ascending) { $array = array_reverse($temp_array); }
+
+    else $array = $temp_array;
+    
+    return $array;
+}
+
+/**
+ * Is in case insensitive array
+ *
+ * @link http://jp.php.net/array_unique 
+ */
+function in_iarray($str, $a)
+{
+    foreach($a as $v) {
+        if (strcasecmp($str, $v) == 0) { return true;}
+    }
+    return false;
+}
+
+
+/**
+ * Is unique in case insensitive array
+ *
+ * @link http://jp.php.net/array_unique 
+ */
+function array_iunique($a)
+{
+    $n = array();
+    foreach ($a as $k=>$v) {
+        if (!in_iarray($v, $n)) { $n[$k] = $v; }
+    }
+    return $n;
+}
+
+?>
Index: /trunk/functions/funcs.times.php
===================================================================
--- /trunk/functions/funcs.times.php	(revision 1081)
+++ /trunk/functions/funcs.times.php	(revision 1081)
@@ -0,0 +1,143 @@
+<?php
+/**
+ * A collection of functions to deal with time
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+ 
+ 
+ /**
+ * Start timer for calculating page loading time
+ *
+ * @return true
+ *
+ * Note: Function borrowed from Wordpress.org
+ */
+function timer_start()
+{
+    global $timestart;
+    
+    $mtime = explode(' ', microtime() );
+    $mtime = $mtime[1] + $mtime[0];
+    $timestart = $mtime;
+    return true;
+}
+
+
+ /**
+ * Stop the timer
+ *
+ * @param int $precision number of digits after the decimal point
+ * @return int $timetotal total timed
+ * 
+ *  Notes: Measured in seconds / Function borrowed from Wordpress.org
+ */
+function timer_stop($precision = 3)
+{
+    //if called like timer_stop(1), will echo $timetotal
+    
+    global $timestart, $timeend;
+    
+    $mtime = microtime();
+    $mtime = explode(' ',$mtime);
+    $mtime = $mtime[1] + $mtime[0];
+    $timeend = $mtime;
+    $timetotal = $timeend-$timestart;
+    $r = (function_exists('number_format_i18n')) ? 
+        number_format_i18n($timetotal, $precision) : 
+        number_format($timetotal, $precision);
+    return $r;
+}
+
+
+ /**
+ * Calculate time difference between now and a given time
+ *
+ * @param string $from time
+ * @return string
+ *
+ *  Note: Adapted from Pligg & SWCMS' txt_time_diff() function
+ */
+function time_difference($from, $lang)
+{
+    $output     = '';
+    $now        = time();
+    $diff       = $now-$from;
+    $days       = intval($diff/86400);
+    $diff       = $diff%86400;
+    $hours      = intval($diff/3600);
+    $diff       = $diff%3600;
+    $minutes    = intval($diff/60);
+
+    if ($days>1) {
+        $output .= $days . " " . $lang['main_times_days'] . " ";
+    } elseif ($days==1) { 
+        $output .= $days . " " . $lang['main_times_day'] . " ";
+    }
+
+    if ($days < 2){
+        if ($hours>1) {
+            $output .= $hours . " " . $lang['main_times_hours'] . " ";
+        } elseif ($hours==1) $output .= $hours . " " . $lang['main_times_hour'] . " ";
+    
+        if ($hours < 3){
+            if ($minutes>1) $output .= $minutes . " " . $lang['main_times_minutes'] . " ";
+            elseif ($minutes==1) $output .= $minutes . " " . $lang['main_times_minute'] . " ";
+        }
+    }
+    
+    if ($output=='') $output = $lang['main_times_seconds'] . " ";
+    return $output;
+}
+
+
+ /**
+ * Converts a timestamp into a number
+ *
+ * @param string $timestamp
+ * @return string
+ *
+ * Note: Borrowed from Pligg & SWCMS
+ */
+function unixtimestamp($timestamp)
+{
+    if (strlen($timestamp) == 14)
+    {
+        $time = substr($timestamp,0,4)."-".
+                    substr($timestamp,4,2)."-".
+                    substr($timestamp,6,2);
+        $time .= " ";
+        $time .=  substr($timestamp,8,2).":".
+                    substr($timestamp,10,2).":".
+                    substr($timestamp,12,2);
+        return strtotime($time);
+    } 
+    else 
+    {
+        if (strlen($timestamp) == 0) {
+            return 0;
+        } else {
+            return strtotime($timestamp);
+        }
+    }
+}
+?>
Index: /trunk/functions/funcs.files.php
===================================================================
--- /trunk/functions/funcs.files.php	(revision 1081)
+++ /trunk/functions/funcs.files.php	(revision 1081)
@@ -0,0 +1,109 @@
+<?php
+/**
+ * A collection of functions to deal with files
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+ 
+/**
+ * Get filenames or paths in a specified order
+ * 
+ * @param string $folder folder containing the files
+ * @param string $type 'full' path or otherwise empty for the filename only 
+ * @return array
+ */
+function getFilenames($folder, $type='full')
+{
+    $filenames  = array();
+    $handle     = opendir($folder);
+    
+    while (false !== ($file = readdir($handle)))
+    {
+        if ($file != "." && $file != ".." && $file != ".svn") {
+            if ($type == 'full') {
+                      array_push($filenames, $folder . $file);    // full path
+                  } else {
+                      array_push($filenames, $file);        // filename only
+                  }
+              }
+    }
+    
+    closedir($handle);
+    return $filenames;
+}
+
+
+/**
+ * Strip all extensions from files, e.g. .php, .js, .html
+ * 
+ * @param array $filenames array of filenames or paths
+ * @return array
+ */
+function stripAllFileExtensions($fileNames)
+{
+    $stripped = array();
+    
+    foreach ($fileNames as $fileName) 
+    {
+        array_push($stripped, stripFileExtension($fileName));
+    }
+    return $stripped;
+}
+
+
+/**
+ * Strip extensions from a single file, e.g. .php, .js, .html
+ * @param string $filename 
+ * @return string
+ */
+function stripFileExtension($fileName)
+{
+    return strtok($fileName, ".");
+}
+
+
+/**
+ * Displaya filesize in a legible format
+ *
+ * @param int $filesize 
+ * @return string
+ * @link http://us3.php.net/manual/en/features.file-upload.php
+ */
+function display_filesize($filesize)
+{
+    if(is_numeric($filesize)){
+    $decr = 1024; $step = 0;
+    $prefix = array('Byte','KB','MB','GB','TB','PB');
+       
+    while(($filesize / $decr) > 0.9)
+    {
+        $filesize = $filesize / $decr;
+        $step++;
+    }
+    return round($filesize,2).' '.$prefix[$step];
+    } else {
+
+    return 'Error displaying filesize';
+    }
+   
+}
+?>
Index: /trunk/functions/funcs.strings.php
===================================================================
--- /trunk/functions/funcs.strings.php	(revision 1081)
+++ /trunk/functions/funcs.strings.php	(revision 1081)
@@ -0,0 +1,580 @@
+<?php
+/**
+ * A collection of functions for manipulating strings
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+ 
+/**
+ * Truncate a string
+ *
+ * @param string $string
+ * @param int truncate to X characters
+ * @para, bool $dot adds ... if true
+ * @return string
+ */
+function truncate($string, $chars=0, $dot=true)
+{
+    $length = strlen($string);
+    $truncated = substr(strip_tags($string), 0, $chars);    // strips tags to prevent broken tags
+    if ($dot && ($length >= $chars)) {
+        $truncated .= '...';
+    }
+    return $truncated;
+}
+
+
+/**
+ * Strip a string from the end of a string
+ *
+ * @param string $string
+ * @param string $remove part of the string to strip
+ * @return string
+ */
+function rstrtrim($str, $remove=null)
+{
+    $str    = (string)$str;
+    $remove = (string)$remove;   
+   
+    if (empty($remove)) {
+        return rtrim($str);
+    }
+   
+    $len = strlen($remove);
+    $offset = strlen($str)-$len;
+    
+    while($offset > 0 && $offset == strpos($str, $remove, $offset))
+    {
+        $str    = substr($str, 0, $offset);
+        $offset = strlen($str)-$len;
+    }
+   
+    return rtrim($str);
+}
+
+
+/**
+ * Changes 'plugin_name' into 'Plugin Name'
+ *
+ * @param string $string e.g. a plugin folder name
+ * @param string $delim - the character to replace underscores with
+ * @return string
+ */
+function make_name($string, $delim = '_', $caps = true)
+{
+    $dep_array  = array();
+    $dep_array  = explode($delim, trim($string));
+    if ($caps) {
+        $dep_array  = array_map('ucfirst', $dep_array);
+        $string     = implode(' ', $dep_array);
+    } else {
+        $string     = ucfirst(implode(' ', $dep_array));
+    }
+
+    return $string;
+}
+
+
+/**
+ * Generates a random string
+ *
+ * @param int $length 
+ * @return string
+ * @link http://us2.php.net/manual/en/ref.strings.php (Moe 10-July-2007)
+ */
+function random_string($length = 8)
+{
+    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxwz0123456789";
+    $string = '';
+    for($i = 0; $i < $length; $i++){
+        $rand_key = mt_rand(0, strlen($chars));
+        $string .= substr($chars, $rand_key, 1);
+    }
+    return str_shuffle($string);
+}
+
+
+/**
+ * Sanitize input
+ *
+ * @param string $var the string to sanitize
+ * @param int $santype type of sanitation (1 or 2)
+ * @param string $allowable_tags
+ * @return string|false
+ *
+ * Note: Borrowed from SWCMS
+ */
+function sanitize($var, $santype = 1, $allowable_tags = '')
+{
+        // htmlentities: YES
+        if ($santype == 1) {
+                if (!get_magic_quotes_gpc()) {
+                        return htmlentities(strip_tags($var, $allowable_tags),ENT_QUOTES,'UTF-8');
+                }
+                else {
+                   return stripslashes(htmlentities(strip_tags($var, $allowable_tags),ENT_QUOTES,'UTF-8'));
+                }
+                return false;
+        }
+        
+        // htmlentities: NO
+        if ($santype == 2) {
+                if (!get_magic_quotes_gpc()) {
+                        return strip_tags($var, $allowable_tags);
+                }
+                else {
+                   return stripslashes(strip_tags($var, $allowable_tags));
+                }
+                return false;
+        }
+}
+
+
+/**
+ * Make a url friendly - a dash-separated string
+ *
+ * @param string $input url to format
+ * @return string|false
+ *
+ * Note: These functions seem to overlap each other a bit...
+ */
+function make_url_friendly($input)
+{
+    $output = replace_symbols($input);        
+    $output = mb_substr($output, 0, 240);
+    $output = mb_strtolower($output);
+    $output = trim($output);    
+        
+    //From Wordpress and http://www.bernzilla.com/item.php?id=1007
+    $output = sanitize_title_with_dashes($output);
+        
+    $output = urldecode($output); 
+    
+    if ($output) { return $output; } else { return false; }
+}
+
+
+/**
+ * Replace symbols and ascii characters with simpler alternatives
+ *
+ * @param string $input
+ * @return string
+ *
+ * Note: Adapted from SWCMS
+ */
+function replace_symbols($input)
+{
+    // FOR THIS TO WORK, THIS FUNCS.STRINGS.PHP FILE MUST BE SAVED 
+    // IN UTF-8 CHARACTER ENCODING !!!
+        
+    // Replace spaces with hyphens
+    $output = preg_replace('/\s+/' , '-' , $input); 
+    
+    // Replace other characters
+    $output = str_replace("--", "-", $output);      
+    $output = str_replace("/", "", $output);
+    $output = str_replace("\\", "", $output);
+    $output = str_replace("'", "", $output);      
+    $output = str_replace(",", "", $output);      
+    $output = str_replace(";", "", $output);      
+    $output = str_replace(":", "", $output);          
+    $output = str_replace(".", "-", $output);      
+    $output = str_replace("?", "", $output);      
+    $output = str_replace("=", "-", $output);      
+    $output = str_replace("+", "", $output); 
+    $output = str_replace("$", "", $output);          
+    $output = str_replace("&", "", $output);          
+    $output = str_replace("!", "", $output);      
+    $output = str_replace(">>", "-", $output);      
+    $output = str_replace(">", "-", $output);      
+    $output = str_replace("<<", "-", $output);      
+    $output = str_replace("<", "-", $output);      
+    $output = str_replace("*", "", $output);      
+    $output = str_replace(")", "", $output);      
+    $output = str_replace("(", "", $output);
+    $output = str_replace("[", "", $output);
+    $output = str_replace("]", "", $output);
+    $output = str_replace("^", "", $output);    
+    $output = str_replace("%", "", $output);
+    $output = str_replace("#", "", $output);
+    $output = str_replace("@", "", $output);
+    $output = str_replace("`", "", $output);
+    $output = str_replace("‘", "", $output);
+    $output = str_replace("’", "", $output);
+    $output = str_replace("“", "", $output);
+    $output = str_replace("”", "", $output);
+    $output = str_replace("~", "", $output);
+    $output = str_replace("–", "-", $output);
+    $output = str_replace("\"", "", $output);
+    $output = str_replace("|", "", $output);
+    $output = str_replace("«", "", $output);
+    $output = str_replace("»", "", $output);
+    $output = str_replace("‹", "", $output);
+    $output = str_replace("›", "", $output);
+    $output = str_replace("…", "", $output);
+    $output = str_replace("--", "-", $output);
+    $output = str_replace("---", "-", $output);
+    $output = str_replace("—", "-", $output);
+
+    return $output;
+}
+
+
+/**
+ * Get rid of any dangerous or unwanted characters
+ *
+ * @param string $title
+ *
+ * Note: Borrowed from Wordpress
+ */
+function sanitize_title_with_dashes($title)
+{
+    $title = strip_tags($title);
+    
+    // Preserve escaped octets.
+    $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
+    
+    // Remove percent signs that are not part of an octet.
+    $title = str_replace('%', '', $title);
+    
+    // Restore octets.
+    $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title);
+    
+    $title = remove_accents($title);
+    
+    if (seems_utf8($title)) {
+        if (function_exists('mb_strtolower')) {
+            $title = mb_strtolower($title, 'UTF-8');
+        }
+        $title = utf8_uri_encode($title, 200);
+    }
+    
+    $title = strtolower($title);
+    $title = preg_replace('/&.+?;/', '', $title); // kill entities
+    $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
+    $title = preg_replace('/\s+/', '-', $title);
+    $title = preg_replace('|-+|', '-', $title);
+    $title = trim($title, '-');
+
+    return $title;
+}
+
+
+/**
+ * Remove accents from characters
+ *
+ * @param string $string
+ * @return string
+ *
+ * Note: Borrowed from Wordpress
+ */
+function remove_accents($string) 
+{
+    if ( !preg_match('/[\x80-\xff]/', $string) ) {
+        return $string;
+    }
+
+    if (seems_utf8($string)) {
+        $chars = array(
+        // Decompositions for Latin-1 Supplement
+        chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
+        chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
+        chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
+        chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
+        chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
+        chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
+        chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
+        chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
+        chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
+        chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
+        chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
+        chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
+        chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
+        chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
+        chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
+        chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
+        chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
+        chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
+        chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
+        chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
+        chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
+        chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
+        chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
+        chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
+        chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
+        chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
+        chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
+        chr(195).chr(191) => 'y',
+        // Decompositions for Latin Extended-A
+        chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
+        chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
+        chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
+        chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
+        chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
+        chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
+        chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
+        chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
+        chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
+        chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
+        chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
+        chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
+        chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
+        chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
+        chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
+        chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
+        chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
+        chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
+        chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
+        chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
+        chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
+        chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
+        chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
+        chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
+        chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
+        chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
+        chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
+        chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
+        chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
+        chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
+        chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
+        chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
+        chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
+        chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
+        chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
+        chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
+        chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
+        chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
+        chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
+        chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
+        chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
+        chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
+        chr(197).chr(148) => 'R',chr(197).chr(149) => 'r',
+        chr(197).chr(150) => 'R',chr(197).chr(151) => 'r',
+        chr(197).chr(152) => 'R',chr(197).chr(153) => 'r',
+        chr(197).chr(154) => 'S',chr(197).chr(155) => 's',
+        chr(197).chr(156) => 'S',chr(197).chr(157) => 's',
+        chr(197).chr(158) => 'S',chr(197).chr(159) => 's',
+        chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
+        chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
+        chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
+        chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
+        chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
+        chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
+        chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
+        chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
+        chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
+        chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
+        chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
+        chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
+        chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
+        chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
+        chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
+        chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
+        // Euro Sign
+        chr(226).chr(130).chr(172) => 'E',
+        // GBP (Pound) Sign
+        chr(194).chr(163) => '');
+
+        $string = strtr($string, $chars);
+    } else {
+        // Assume ISO-8859-1 if not UTF-8
+        $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
+            .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
+            .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
+            .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
+            .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
+            .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
+            .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
+            .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
+            .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
+            .chr(252).chr(253).chr(255);
+
+        $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
+
+        $string = strtr($string, $chars['in'], $chars['out']);
+        $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
+        $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
+        $string = str_replace($double_chars['in'], $double_chars['out'], $string);
+    }
+
+    return $string;
+}
+
+
+/**
+ * Determine if the string is utf8
+ *
+ * @param string $str
+ * @return bool
+ *
+ * Note: Borrowed from Wordpress (by bmorel at ssi dot fr )
+ */
+function seems_utf8($str)
+{
+    $length = strlen($str);
+    for ($i=0; $i < $length; $i++)
+    {
+        if (ord($str[$i]) < 0x80) { 
+            continue; // 0bbbbbbb 
+            
+        } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
+            $n=1; // 110bbbbb
+            
+        } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
+            $n=2; // 1110bbbb
+            
+        } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
+            $n=3; // 11110bbb
+            
+        } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
+            $n=4; // 111110bb
+            
+        } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
+            $n=5; // 1111110b
+            
+        } else { 
+            return false; // Does not match any model
+        }
+        
+        for ($j=0; $j<$n; $j++) 
+        { 
+            // n bytes matching 10bbbbbb follow ?
+            if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+
+/**
+ * Encodes a utf8 string
+ *
+ * @param string $utf8_string
+ * @param int $length
+ * @return string
+ *
+ * Note: Borrowed from Wordpress
+ */
+function utf8_uri_encode( $utf8_string, $length = 0 )
+{
+    $unicode        = '';
+    $values         = array();
+    $num_octets     = 1;
+    $unicode_length = 0;
+
+    $string_length = strlen( $utf8_string );
+    for ($i = 0; $i < $string_length; $i++ )
+    {
+        $value = ord( $utf8_string[ $i ] );
+
+        if ( $value < 128 )
+        {
+            if ($length && ( $unicode_length >= $length )) {
+                break;
+            }
+            $unicode .= chr($value);
+            $unicode_length++;
+        } 
+        else 
+        {
+            if (count( $values ) == 0) {
+                $num_octets = ( $value < 224 ) ? 2 : 3;
+            }
+
+            $values[] = $value;
+
+            if ($length && ($unicode_length + ($num_octets * 3)) > $length) {
+                break;
+            }
+            
+            if (count($values) == $num_octets) 
+            {
+                if ($num_octets == 3) {
+                    $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]) . '%' . dechex($values[2]);
+                    $unicode_length += 9;
+                } else {
+                    $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]);
+                    $unicode_length += 6;
+                }
+
+                $values     = array();
+                $num_octets = 1;
+            }
+        }
+    }
+
+    return $unicode;
+}
+
+
+/**
+ * Strip domain from url
+ *
+ * @param string $url
+ * @return string|false $domain - including http://
+ */
+function get_domain($url = '')
+{
+    $parsed = parse_url($url);
+    if (isset($parsed['scheme'])){ 
+        $domain = $parsed['scheme'] . "://" . $parsed['host']; 
+        return $domain;
+    }
+    
+    return false;
+}
+
+
+/**
+ * Strip foreign characters from latin1/utf8 database yuckiness
+ *
+ * @param string $str
+ * @return string
+ */
+function strip_foreign_characters($str)
+{
+    $str = str_replace('Â', '', $str);
+    $str = str_replace('â€™', '\'', $str);
+    $str = str_replace('â€“', '-', $str);
+    $str = str_replace('â€œ', '"', $str);
+    $str = str_replace('â€', '"', $str);
+    return $str;
+}
+
+
+/**
+ * Count urls within a block of text
+ *
+ * @return int 
+ * @link http://www.liamdelahunty.com/tips/php_url_count_check_for_comment_spam.php
+ */
+function countUrls($text = '')
+{
+    //$http = substr_count($text, "http");
+    $href = substr_count($text, "href");
+    $url = substr_count($text, "[url");
+    
+    return $href + $url;
+}
+?>
Index: /trunk/libs/Breadcrumbs.php
===================================================================
--- /trunk/libs/Breadcrumbs.php	(revision 1081)
+++ /trunk/libs/Breadcrumbs.php	(revision 1081)
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Breadcrumb functions
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Breadcrumbs
+{
+    /**
+     * Build breadcrumbs
+     */
+    public function buildBreadcrumbs($h)
+    {
+        $output = '';
+        $output .= "<a href='" . BASEURL . "'>" . $h->lang['main_theme_breadcrumbs_home'] . "</a>\n"; 
+        
+        // Admin only:
+        if ($h->isAdmin) {
+            $output .= " &raquo; <a href='" . $h->url(array(), 'admin') . "'>";
+            $output .= $h->lang['admin_theme_main_admin_cp'] . "</a>\n";
+        }
+        
+        // plugin hook:
+        $crumbs = $h->pluginHook('breadcrumbs');
+        if ($crumbs) {
+            $crumbs = array_reverse($crumbs); // so the last one gets used.
+            foreach ($crumbs as $key => $value) {
+                $output .= " &raquo; " . $value;
+                return $output; // we only want the first result so return now.
+            }
+        } 
+        
+        // in case of no plugins:
+        $output .= " &raquo; " . $h->pageTitle;
+        return $output;
+    }
+    
+    
+    /**
+     * prepares the RSS breadcrumbs link
+     *
+     * @param string $status - post status, e.g. new, top, etc.
+     * @param array $vars - array of key -> value pairs
+     * @return string
+     */    
+    public function rssBreadcrumbsLink($h, $status = '', $vars)
+    {
+        if ($status) {
+            $url_array = array('page'=>'rss', 'status'=>$status);
+        } else {
+            $url_array = array('page'=>'rss'); // defaults to all
+        }
+        
+        foreach ($vars as $k => $v) {
+            $url_array[$k] = $v;
+        }
+        $rss = "<a href='" . $h->url($url_array) . "'>";
+        $rss .= " <img src='" . BASEURL . "content/themes/" . THEME . "images/rss_10.png' alt='" . $h->pageTitle . " RSS' /></a>";
+        return $rss;
+    }
+}
+?>
Index: /trunk/libs/IncludeCssJs.php
===================================================================
--- /trunk/libs/IncludeCssJs.php	(revision 1081)
+++ /trunk/libs/IncludeCssJs.php	(revision 1081)
@@ -0,0 +1,362 @@
+<?php
+/**
+ * Functions for including and merging CSS and JavaScript
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class IncludeCssJs
+{
+    protected $cssIncludes          = array();  // a list of css files to include
+    protected $cssIncludesAdmin     = array();  // a list of css files to include in Admin
+    protected $jsIncludes           = array();  // a list of js files to include
+    protected $jsIncludesAdmin      = array();  // a list of js files to include in Admin
+    protected $includeType          = '';       // 'css' or 'js'
+    
+    /**
+     * setCssIncludes
+     *
+     * @param string $file - full path to the CSS file
+     */
+    public function setCssIncludes($file, $admin = false)
+    {
+        if ($admin) { 
+            array_push($this->cssIncludesAdmin, $file);
+        } else {
+            array_push($this->cssIncludes, $file);
+        }
+    }
+    
+
+    /**
+     * getCssIncludes
+     */
+    public function getCssIncludes($admin = false)
+    {
+        if ($admin) {
+            return $this->cssIncludesAdmin;
+        } else {
+            return $this->cssIncludes;
+        }
+    }
+    
+    
+    /**
+     * setJsIncludes
+     *
+     * @param string $file - full path to the JS file
+     */
+    public function setJsIncludes($file, $admin = false)
+    {        
+        if ($admin) { 
+            array_push($this->jsIncludesAdmin, $file);        
+        } else {
+            array_push($this->jsIncludes, $file);
+        }        
+    }
+    
+    
+    /**
+     * getJsIncludes
+     */
+    public function getJsIncludes($admin = false)
+    {    
+        if ($admin) {            
+            return $this->jsIncludesAdmin;            
+        } else {
+            return $this->jsIncludes;
+        }
+    }
+    
+    
+    /**
+     * Build an array of css files to combine
+     *
+     * @param $folder - the folder name of the plugin
+     * @param $filename - optional css file without an extension
+     */
+     public function includeCss($h, $folder = '', $filename = '')
+     {
+        if (!$folder) { $folder = $h->plugin->folder; }
+        
+        // If no filename provided, the filename is assigned the plugin name.
+        if (!$filename) { $filename = $folder; }
+
+        $file_location = $this->findCssFile($folder, $filename);
+        
+        // Add this css file to the global array of css_files
+        $this->setCssIncludes($file_location, $h->isAdmin);
+        
+        return $folder; // returned for testing purposes only
+     }
+
+
+    /**
+     * Build an array of JavaScript files to combine
+     *
+     * @param $folder - the folder name of the plugin
+     * @param $filename - optional js file without an extension
+     */
+     public function includeJs($h, $folder = '', $filename = '')
+     {    
+        if (!$folder) { $folder = $h->plugin->folder; }
+                
+        // If no filename provided, the filename is assigned the plugin name.
+        if (!$filename) { $filename = $folder; }
+        
+        $file_location = $this->findJsFile($folder, $filename);
+        
+        // Add this js file to the global array of js_files
+        $this->setJsIncludes($file_location, $h->isAdmin);
+        
+        return $folder; // returned for testing purposes only
+     }
+     
+     
+    /**
+     * Find CSS file
+     *
+     * @param string $folder name of plugin folder
+     * @param string $filename optional filename without file extension
+     *
+     * Note: the css file should be in a folder named 'css' and a file of 
+     * the format plugin_name.css, e.g. rss_show.css
+     */    
+    public function findCssFile($folder = '', $filename = '')
+    {
+        if (!$folder) { return false; }
+
+        // If filename not given, make the plugin name the file name
+        if (!$filename) { $filename = $folder; }
+        
+        // First look in the theme folder for a css file...     
+        if (file_exists(THEMES . THEME . 'css/' . $filename . '.css')) {    
+            $file_location = THEMES . THEME . 'css/' . $filename . '.css';
+        
+        // If not found, look in the default theme folder for a css file...     
+        } elseif (file_exists(THEMES . 'default/css/' . $filename . '.css')) {    
+            $file_location = THEMES . 'default/css/' . $filename . '.css';
+        
+        // If still not found, look in the plugin folder for a css file... 
+        } elseif (file_exists(PLUGINS . $folder . '/css/' . $filename . '.css')) {
+            $file_location = PLUGINS . $folder . '/css/' . $filename . '.css';
+        }
+         
+        if (isset($file_location)) {
+            return $file_location;
+        }
+    }
+
+
+    /**
+     * Find JavaScript file
+     *
+     * @param string $folder name of plugin folder
+     * @param string $filename optional filename without file extension
+     *
+     * Note: the js file should be in a folder named 'javascript' and a file of the format plugin_name.js, e.g. category_manager.js
+     */    
+    public function findJsFile($folder = '', $filename = '')
+    {    
+        if (!$folder) { return false; }
+
+        // If filename not given, make the plugin name the file name
+        if (!$filename) { $filename = $folder; }
+        
+        // First look in the theme folder for a js file...     
+        if (file_exists(THEMES . THEME . 'javascript/' . $filename . '.js')) {    
+            $file_location = THEMES . THEME . 'javascript/' . $filename . '.js';
+            
+        // If not found, look in the default theme folder for a js file...     
+        } elseif (file_exists(THEMES . 'default/javascript/' . $filename . '.js')) {    
+            $file_location = THEMES . 'default/javascript/' . $filename . '.js';
+            
+        // If still not found, look in the plugin folder for a js file... 
+        } elseif (file_exists(PLUGINS . $folder . '/javascript/' . $filename . '.js')) {
+            $file_location = PLUGINS . $folder . '/javascript/' . $filename . '.js';        
+
+        // If still not found, look in the full given folder itself for a js file... 
+        } elseif (file_exists($folder . $filename . '.js')) {
+            $file_location = $folder . $filename . '.js';
+        }
+         //print $folder . $filename . '.js      ---        ';
+        if (isset($file_location)) {
+            return $file_location;
+        }
+    }
+    
+    
+    /**
+     * Combine Included CSS & JSS files
+     *
+     * @param string $type either 'css' or 'js'
+     * @param string $prefix either 'hotaru_' or ''hotaru_admin_'
+     * @return int version number or echo output to cache file
+     * @link http://www.ejeliot.com/blog/72 Based on work by Ed Eliot
+     */
+     public function combineIncludes($h, $type = 'css', $version = 0)
+     {
+        if ($h->isAdmin) {
+            $h->pluginHook('admin_header_include');
+            $prefix = 'hotaru_admin_';
+        } else {
+            $h->pluginHook('header_include');
+            $prefix = 'hotaru_';
+        }
+
+        $cache_length = 31356000;   // about one year
+        $cache = CACHE . 'css_js_cache/';
+        
+        if($type == 'css') { 
+            $content_type = 'text/css';
+            $includes = $this->getCssIncludes($h->isAdmin);
+        } else { 
+            $type = 'js'; 
+            $content_type = 'text/javascript';
+            //don't forget to get the globals js file as well            
+            $this->includeJs($h, $cache, 'JavascriptConstants')    ;
+            $this->includeJs($h, BASE . 'javascript/' , "hotaru");        
+            if ($h->isAdmin) {
+                $this->includeJs($h, ADMIN_THEMES . ADMIN_THEME. "javascript/" , rtrim(ADMIN_THEME, "/"));
+            }
+                    
+            $includes = $this->getJsIncludes($h->isAdmin);
+        }
+        
+        $includes = array_unique($includes);    // remove duplicate includes
+        if(empty($includes)) { return false; }
+        
+         /*
+            if version parameter is present then the script is being called directly, otherwise we're including it in 
+            another script with require or include. If calling directly we return code othewise we return the etag 
+            (version number) representing the latest files
+        */
+
+        
+        if ($version > 0) {        
+            // GET ACTUAL CODE - IF IT'S CACHED, SHOW THE CACHED CODE, OTHERWISE, GET INCLUDE FILES, BUILD AN ARCHIVE AND SHOW IT
+ 
+            $iETag = $version;
+            $sLastModified = gmdate('D, d M Y H:i:s', $iETag).' GMT';
+            
+            // see if the user has an updated copy in browser cache
+            if (
+                ($h->cage->server->keyExists('HTTP_IF_MODIFIED_SINCE') && $h->cage->server->testDate('HTTP_IF_MODIFIED_SINCE') == $sLastModified) ||
+                ($h->cage->server->keyExists('HTTP_IF_NONE_MATCH') && $h->cage->server->testint('HTTP_IF_NONE_MATCH') == $iETag)
+            ) {
+                header("{$h->cage->server->getRaw('SERVER_PROTOCOL')} 304 Not Modified");
+                exit;
+            }
+            
+            // create a directory for storing current and archive versions
+            if (!is_dir($cache)) {
+                mkdir($cache);
+            }    
+
+            // get code from archive folder if it exists, otherwise grab latest files, merge and save in archive folder
+            if ((CSS_JS_CACHE_ON == "true") && file_exists($cache . $prefix . $type . '_' . $iETag . '.cache')) {
+                $sCode = file_get_contents($cache . $prefix . $type . '_' . $iETag . '.cache');
+            } else {
+                // get and merge code
+                $sCode = '';
+                $aLastModifieds = array();
+        
+                foreach ($includes as $sFile) {
+                    if ($sFile) {
+                        $aLastModifieds[] = filemtime($sFile);
+                        $sCode .= file_get_contents($sFile);
+                    }
+                }
+
+                // sort dates, newest first
+                rsort($aLastModifieds);
+                
+                if ($iETag == $aLastModifieds[0]) { // check for valid etag, we don't want invalid requests to fill up archive folder
+                    $oFile = fopen($cache . $prefix . $type . '_' . $iETag . '.cache', 'w');
+                    if (flock($oFile, LOCK_EX)) {
+                        fwrite($oFile, $sCode);
+                        flock($oFile, LOCK_UN);
+                    }
+                    fclose($oFile);
+                } else {
+                    // archive file no longer exists or invalid etag specified
+                    header("{$h->cage->server->getRaw('SERVER_PROTOCOL')} 404 Not Found");
+                    exit;
+                }        
+            }
+        
+            // send HTTP headers to ensure aggressive caching
+            header('Expires: '.gmdate('D, d M Y H:i:s', time() + $cache_length).' GMT'); // 1 year from now
+            header('Content-Type: ' . $content_type);
+            //header('Content-Length: '.strlen($sCode)); // causes site loading delays: http://hotarucms.org/showthread.php?t=197
+            header("Last-Modified: $sLastModified");
+            header("ETag: $iETag");
+            header('Cache-Control: max-age=' . $cache_length);
+        
+          // output merged code
+          echo $sCode;
+
+          //if($type == 'js') { 
+            //  $global_ajax_var = "jQuery('document').ready(function($) {BASEURL = '". BASEURL ."'; ADMIN_THEME = '" . ADMIN_THEME . "'; });";    
+            //  echo  $global_ajax_var;
+          //}    
+
+          exit; // we don't want to drop out and continue building Hotaru or Admin objects when we're just including a file!
+          
+        } else {
+        
+            // get last modified dates for all files to include
+            $aLastModifieds = array();
+            foreach ($includes as $sFile) {
+                $aLastModifieds[] = filemtime($sFile);
+            }
+            // sort dates, newest first
+            rsort($aLastModifieds);
+            
+            // return latest timestamp, i.e. the most recently updated include file
+            return $aLastModifieds[0];
+        
+        }
+     }
+        
+
+    /**
+     * Included combined files
+     *
+     * @param int $version_js 
+     * @param int $version_css 
+     * @param bool $admin 
+     */
+     public function includeCombined($version_js = 0, $version_css = 0, $admin = false)
+     {        
+        if ($admin) { $index = 'admin_index'; } else { $index = 'index'; }
+        
+        if ($version_js > 0) {
+            echo "<script type='text/javascript' src='" . BASEURL . $index . ".php?combine=1&amp;type=js&amp;version=" . $version_js . "'></script>\n";
+        }
+        
+        if ($version_css > 0) {
+            echo "<link rel='stylesheet' href='" . BASEURL . $index . ".php?combine=1&amp;type=css&amp;version=" . $version_css . "' type='text/css' />\n";
+        }
+
+     }
+}
+?>
Index: /trunk/libs/UserInfo.php
===================================================================
--- /trunk/libs/UserInfo.php	(revision 1081)
+++ /trunk/libs/UserInfo.php	(revision 1081)
@@ -0,0 +1,401 @@
+<?php
+/**
+ * Functions for retrieving information about users
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class UserInfo extends UserBase
+{
+    /**
+     * Get the username for a given user id
+     *
+     * @param int $id user id
+     * @return string|false
+     */
+    public function getUserNameFromId($h, $id = 0)
+    {
+        $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_id = %d";
+        
+        $username = $h->db->get_var($h->db->prepare($sql, $id));
+        if ($username) { return $username; } else { return false; }
+    }
+    
+    
+    /**
+     * Get the user id for a given username
+     *
+     * @param string $username
+     * @return int|false
+     */
+    public function getUserIdFromName($h, $username = '')
+    {
+        $sql = "SELECT user_id FROM " . TABLE_USERS . " WHERE user_username = %s";
+        
+        $userid = $h->db->get_var($h->db->prepare($sql, $username));
+        if ($userid) { return $userid; } else { return false; }
+    }
+    
+    
+    /**
+     * Get the email from user id
+     *
+     * @param int $userid
+     * @return string|false
+     */
+    public function getEmailFromId($h, $userid = 0)
+    {
+        $sql = "SELECT user_email FROM " . TABLE_USERS . " WHERE user_id = %d";
+        
+        $email = $h->db->get_var($h->db->prepare($sql, $userid));
+        if ($email) { return $email; } else { return false; }
+    }
+    
+    
+    /**
+     * Get the user id from email
+     *
+     * @param string $email
+     * @return string|false
+     */
+    public function getUserIdFromEmail($h, $email = '')
+    {
+        $sql = "SELECT user_id FROM " . TABLE_USERS . " WHERE user_email = %s";
+        
+        $userid = $h->db->get_var($h->db->prepare($sql, $email));
+        if ($userid) { return $userid; } else { return false; }
+    }
+    
+
+     /**
+     * Checks if the user has an 'admin' role
+     *
+     * @return bool
+     */
+    public function isAdmin($db, $username)
+    {
+        $sql = "SELECT * FROM " . TABLE_USERS . " WHERE user_username = %s AND user_role = %s";
+        $role = $db->get_row($db->prepare($sql, $username, 'admin'));
+        
+        if ($role) { return true; } else { return false; }
+    }
+    
+    
+    /**
+     * Check if a user exists
+     *
+     * @param int $userid 
+     * @param string $username
+     * @return int
+     *
+     * Notes: Returns 'no' if a user doesn't exist, else field under which found
+     */
+    public function userExists($db, $id = 0, $username = '', $email = '')
+    {
+        // id found
+        if ($id != 0) {
+            if ($db->get_var($db->prepare("SELECT * FROM " . TABLE_USERS . " WHERE user_id = %d", $id))) {
+                return 'id'; // id exists
+            } 
+        } 
+        
+        // name found
+        if ($username != '') {
+            if ($db->get_var($db->prepare("SELECT * FROM " . TABLE_USERS . " WHERE user_username = %s", $username))) {
+                return 'name'; // username exists
+            }         
+        } 
+        
+        // email found
+        if ($email != '') {
+            if ($db->get_var($db->prepare("SELECT * FROM " . TABLE_USERS . " WHERE user_email = %s", $email))) {
+                return 'email'; // email exists
+            }         
+        } 
+        
+        // Error - no arguments provided
+        if (($id == 0) && ($username == '') && ($email == '')) {
+            return 'error'; // no arguments provided
+        } 
+        
+        return 'no'; // User doesn't exist
+    }
+    
+    
+    /**
+     * Check if an username exists in the database (used in forgotten password)
+     *
+     * @param string $username user username
+     * @param string $role user role (optional)
+     * @param int $exclude - exclude a user
+     * @return string|false
+     */
+    public function nameExists($h, $username = '', $role = '', $exclude = 0)
+    {
+        if (!$username) {  return false; }
+        
+        if (!$exclude) {
+            if ($role) { 
+                $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_username = %s AND user_role = %s";
+                $valid_username = $h->db->get_var($h->db->prepare($sql, $username, $role));
+            } else {
+                $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_username = %s";
+                $valid_username = $h->db->get_var($h->db->prepare($sql, $username));
+            }
+        } else {
+            if ($role) { 
+                $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_username = %s AND user_role = %s AND user_id != %d";
+                $valid_username = $h->db->get_var($h->db->prepare($sql, $username, $role, $exclude));
+            } else {
+                $sql = "SELECT user_username FROM " . TABLE_USERS . " WHERE user_username = %s AND user_id != %d";
+                $valid_username = $h->db->get_var($h->db->prepare($sql, $username, $exclude));
+            }
+        }
+
+        if ($valid_username) { return $valid_username; } else { return false; }
+    }
+    
+    
+    /**
+     * Check if an email exists in the database (used in forgotten password)
+     *
+     * @param string $email user email
+     * @param string $role user role (optional)
+     * @param int $exclude - exclude a user
+     * @return string|false
+     */
+    public function emailExists($h, $email = '', $role = '', $exclude = 0)
+    {
+        if (!$email) {  return false; }
+        
+        if (!$exclude) {
+            if ($role) { 
+                $sql = "SELECT user_email FROM " . TABLE_USERS . " WHERE user_email = %s AND user_role = %s";
+                $valid_email = $h->db->get_var($h->db->prepare($sql, $email, $role));
+            } else {
+                $sql = "SELECT user_email FROM " . TABLE_USERS . " WHERE user_email = %s";
+                $valid_email = $h->db->get_var($h->db->prepare($sql, $email));
+            }
+        } else {
+            if ($role) { 
+                $sql = "SELECT user_email FROM " . TABLE_USERS . " WHERE user_email = %s AND user_role = %s AND user_id != %d";
+                $valid_email = $h->db->get_var($h->db->prepare($sql, $email, $role, $exclude));
+            } else {
+                $sql = "SELECT user_email FROM " . TABLE_USERS . " WHERE user_email = %s AND user_id != %d";
+                $valid_email = $h->db->get_var($h->db->prepare($sql, $email, $exclude));
+            }
+        }
+
+        if ($valid_email) { return $valid_email; } else { return false; }
+    }
+    
+    
+    /**
+     * Get all users with permission to access admin
+     */
+    public function getMods($h, $permission = 'can_access_admin', $value = 'yes')
+    {
+        $sql = "SELECT user_id FROM " . TABLE_USERS . " WHERE (user_role = %s) || (user_role = %s) || (user_role = %s)";
+        $users = $h->db->get_results($h->db->prepare($sql, 'admin', 'supermod', 'moderator'));
+        
+        if (!$users) { return false; }
+        
+        $mods = array();
+        
+        foreach ($users as $user) {
+            $details = new UserBase();
+            $details->getUserBasic($h, $user->user_id);
+            if ($details->getPermission($permission) == $value) {
+                $mods[$details->id]['id'] = $details->id;
+                $mods[$details->id]['role'] = $details->role;
+                $mods[$details->id]['name'] = $details->name;
+                $mods[$details->id]['email'] = $details->email;
+            }
+        }
+        return $mods;
+    }
+    
+    
+    /**
+     * Get the ids and names of all users or those with a specified role, sorted alphabetically
+     *
+     * @param string $role - optional user role to filter to
+     * @return array
+     */
+    public function userIdNameList($h, $role = '')
+    {
+        if ($role) { 
+            $sql = "SELECT user_id, user_username FROM " . TABLE_USERS . " WHERE user_role = %s ORDER BY user_username ASC";
+            $results = $h->db->get_results($h->db->prepare($sql, $role));
+        } else {
+            $sql = "SELECT user_id, user_username FROM " . TABLE_USERS . " ORDER BY user_username ASC";
+            $results = $h->db->get_results($sql);
+        }
+        
+        return $results;
+    }
+    
+    
+    /**
+     * Get settings for all users
+     *
+     * @return array
+     */
+    public function userSettingsList($h, $userid = 0)
+    {
+        if ($userid) { 
+            $settings = $h->getProfileSettingsData($type = 'user_settings', $userid);
+            return $settings;
+        } else {
+            $sql = "SELECT usermeta_userid, usermeta_value FROM " . DB_PREFIX . "usermeta WHERE usermeta_key = %s";
+            $results = $h->db->get_results($h->db->prepare($sql, 'user_settings'));
+        }
+        
+        return $results;
+    }
+    
+    
+    /**
+     * Get full details of all users or batches of users, sorted alphabetically
+     *
+     * @param array $id_array - optional array of user ids
+     * @param int $start - LIMIT $start $range (optional)
+     * @param int $range - LIMIT $start $range (optional)
+     * @return array
+     */
+    public function userListFull($h, $id_array = array(), $start = 0, $range = 0)
+    {
+        if (!$id_array) {
+            // get all users
+            $sql = "SELECT * FROM " . TABLE_USERS . " ORDER BY user_username ASC";
+            $results = $h->db->get_results($sql);
+        } else {
+            // for grabbing 
+            if ($range) { $limit = " LIMIT " . $start . ", " . $range; }
+            $sql = "SELECT * FROM " . TABLE_USERS . " WHERE ";
+            for ($i=0; $i < count($id_array); $i++) {
+                $sql .= "user_id = %d OR ";
+            }
+            $sql = rstrtrim($sql, "OR "); // strip trailing OR
+            $sql .= " ORDER BY user_username ASC" . $limit;
+
+            $prepare_array[0] = $sql;
+            $prepare_array = array_merge($prepare_array, $id_array);
+            $results = $h->db->get_results($h->db->prepare($prepare_array));
+        }
+        return $results;
+    }
+    
+    
+    /**
+     * Stats for Admin homepage
+     *
+     * @param string $stat_type
+     * @return int
+     */
+    public function stats($h, $stat_type = '')
+    {
+        switch ($stat_type) {
+            case 'admins':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'admin'));
+                break;
+            case 'supermods':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'supermod'));
+                break;
+            case 'moderators':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'moderator'));
+                break;
+            case 'members':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'member'));
+                break;
+            case 'total_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS;
+                $users = $h->db->get_var($sql);
+                break;
+            case 'approved_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s OR user_role = %s OR user_role = %s OR  user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'admin', 'supermod', 'moderator', 'member'));
+                break;
+            case 'pending_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'pending'));
+                break;
+            case 'undermod_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'undermod'));
+                break;
+            case 'banned_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'banned'));
+                break;
+            case 'killspammed_users':
+                $sql = "SELECT count(user_id) FROM " . TABLE_USERS . " WHERE user_role = %s";
+                $users = $h->db->get_var($h->db->prepare($sql, 'killspammed'));
+                break;
+            default:
+                $users = 0;
+        }
+        
+        return $users;
+    }
+    
+    
+    /**
+     * Get Unique Roles
+     *
+     * @return array|false
+     */
+    public function getUniqueRoles($h) 
+    {
+        /* This function pulls all the different user roles from the database, 
+        or adds some defaults if not present.*/
+
+        $unique_roles = array();
+
+        // Some essentials:
+        array_push($unique_roles, 'admin');
+        array_push($unique_roles, 'supermod');
+        array_push($unique_roles, 'moderator');
+        array_push($unique_roles, 'member');
+        array_push($unique_roles, 'undermod');
+        array_push($unique_roles, 'pending');
+        array_push($unique_roles, 'suspended');
+        array_push($unique_roles, 'banned');
+        array_push($unique_roles, 'killspammed');
+        
+        // Add any other roles already in use:
+        $sql = "SELECT DISTINCT user_role FROM " . TABLE_USERS;
+        $roles = $h->db->get_results($h->db->prepare($sql));
+        if ($roles) {
+            foreach ($roles as $role) {
+                if (!in_array($role->user_role, $unique_roles)) { 
+                    array_push($unique_roles, $role->user_role);
+                }
+            }
+        }
+        
+        if ($unique_roles) { return $unique_roles; } else { return false; }
+    }
+}
Index: /trunk/libs/PluginManagement.php
===================================================================
--- /trunk/libs/PluginManagement.php	(revision 1081)
+++ /trunk/libs/PluginManagement.php	(revision 1081)
@@ -0,0 +1,699 @@
+<?php
+/**
+ * Plugin Management Functions
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class PluginManagement
+{
+    /**
+     * Get an array of plugins
+     *
+     * Reads plugin info directly from top of plugin files, then compares each 
+     * plugin to the database. If present and latest version, reads info from 
+     * the database. If not in database or newer version, uses info from plugin 
+     * file. Used by Plugin Management.
+     *
+     * @return array $allplugins
+     */
+    public function getPlugins($h)
+    {
+        $plugins_array = $this->getPluginsMeta();
+        $count = 0;
+        $allplugins = array();
+
+        if ($plugins_array) {
+            foreach ($plugins_array as $plugin_details) {
+            
+                $allplugins[$count] = array();
+                $sql = "SELECT * FROM " . TABLE_PLUGINS . " WHERE plugin_folder = %s";
+                $plugin_row = $h->db->get_row($h->db->prepare($sql, $plugin_details['folder']));
+                
+                if ($plugin_row) 
+                {
+                    // if plugin is in the database...
+                    $allplugins[$count]['name'] = $plugin_row->plugin_name;
+                    $allplugins[$count]['description'] = $plugin_row->plugin_desc;
+                    $allplugins[$count]['folder'] = $plugin_row->plugin_folder;
+                    $allplugins[$count]['author'] = $plugin_row->plugin_author;
+                    $allplugins[$count]['authorurl'] = urldecode($plugin_row->plugin_authorurl);
+                    
+                    if ($plugin_row->plugin_enabled) {
+                        $allplugins[$count]['status'] = 'active';
+                    } else {
+                        $allplugins[$count]['status'] = 'inactive';
+                    }
+                    
+                    $allplugins[$count]['version'] = $plugin_row->plugin_version;
+                    $allplugins[$count]['install'] = "installed";
+                    $allplugins[$count]['location'] = "database";
+                    $allplugins[$count]['settings'] = $h->hasSettings($allplugins[$count]['folder']); // true or false
+                    $allplugins[$count]['order'] = $plugin_row->plugin_order;
+                } 
+                else 
+                {
+                    // if plugin is not in database...
+                    $allplugins[$count]['name'] = $plugin_details['name'];
+                    $allplugins[$count]['description'] = $plugin_details['description'];
+                    $allplugins[$count]['folder'] = $plugin_details['folder'];
+                    
+                    if (isset($plugin_details['author'])) {
+                        $allplugins[$count]['author'] = $plugin_details['author'];
+                    }
+                    
+                    if (isset($plugin_details['authorurl'])) {
+                        $allplugins[$count]['authorurl'] = urldecode($plugin_details['authorurl']);
+                    }
+                    
+                    $allplugins[$count]['status'] = "inactive";
+                    $allplugins[$count]['version'] = $plugin_details['version'];
+                    $allplugins[$count]['install'] = "install";
+                    $allplugins[$count]['location'] = "folder";
+                    $allplugins[$count]['order'] = 0;
+                }
+
+                // Conditions for "active"...
+                if (($allplugins[$count]['status'] == 'active') && ($allplugins[$count]['install'] == 'install')) {
+                    $allplugins[$count]['active'] = "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/active_16.png'></a>";
+                } elseif (($allplugins[$count]['status'] == 'inactive') && ($allplugins[$count]['install'] == 'install')) {
+                    $allplugins[$count]['active'] = "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/inactive_16.png'></a>";
+                } elseif ($allplugins[$count]['status'] == 'active') {
+                    $allplugins[$count]['active'] = "<a href='" . BASEURL;
+                    $allplugins[$count]['active'] .= "admin_index.php?page=plugin_management&amp;action=deactivate&amp;plugin=";
+                    $allplugins[$count]['active'] .= $allplugins[$count]['folder'] . "'>";
+                    $allplugins[$count]['active'] .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/active_16.png'></a>";
+                } else {
+                    $allplugins[$count]['active'] = "<a href='" . BASEURL;
+                    $allplugins[$count]['active'] .= "admin_index.php?page=plugin_management&amp;action=activate&amp;plugin=";
+                    $allplugins[$count]['active'] .= $allplugins[$count]['folder'] . "'>";
+                    $allplugins[$count]['active'] .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/inactive_16.png'></a>";
+                }
+
+
+                // Conditions for "install"...
+                if ($allplugins[$count]['install'] == 'install') { 
+                    $allplugins[$count]['install'] = "<a href='" . BASEURL . "admin_index.php?page=plugin_management&amp;action=install&amp;plugin=". $allplugins[$count]['folder'] . "'><img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/install_16.png'></a>";
+                } else { 
+                    $allplugins[$count]['install'] = "<a href='" . BASEURL . "admin_index.php?page=plugin_management&amp;action=uninstall&amp;plugin=". $allplugins[$count]['folder'] . "'><img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/uninstall_16.png'></a>";
+                }
+                
+                
+
+                // Conditions for "requires"...
+                if (isset($plugin_details['requires']) && $plugin_details['requires']) {
+                    $h->plugin->requires = $plugin_details['requires'];
+                    $this->requiresToDependencies($h);
+                    
+                    // Converts plugin folder names to well formatted names...
+                    foreach ($h->plugin->dependencies as $this_plugin => $version)
+                    {
+                        $h->plugin->dependencies[$this_plugin] = $version;
+                        $allplugins[$count]['requires'][$this_plugin] = $h->plugin->dependencies[$this_plugin];
+                    }
+
+                } else {
+                    $allplugins[$count]['requires'] = array();
+                }
+
+
+                // Conditions for "order"...
+                // The order is sorted numerically in the plugin_management.php template, so we need separate order and order_output elements.
+                if ($allplugins[$count]['order'] != 0) { 
+                    $order = $allplugins[$count]['order'];
+                    $allplugins[$count]['order_output'] = "<a href='" . BASEURL;
+                    $allplugins[$count]['order_output'] .= "admin_index.php?page=plugin_management&amp;";
+                    $allplugins[$count]['order_output'] .= "action=orderup&amp;plugin=". $allplugins[$count]['folder'];
+                    $allplugins[$count]['order_output'] .= "&amp;order=" . $order . "'>";
+                    $allplugins[$count]['order_output'] .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/up_12.png'>";
+                    $allplugins[$count]['order_output'] .= "</a> \n&nbsp;<a href='" . BASEURL;
+                    $allplugins[$count]['order_output'] .= "admin_index.php?page=plugin_management&amp;";
+                    $allplugins[$count]['order_output'] .= "action=orderdown&amp;plugin=". $allplugins[$count]['folder'];
+                    $allplugins[$count]['order_output'] .= "&amp;order=" . $order . "'>";
+                    $allplugins[$count]['order_output'] .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/down_12.png'>";
+                    $allplugins[$count]['order_output'] .= "</a>\n";
+                } else {
+                    $allplugins[$count]['order_output'] = "";
+                }
+
+                $count++;
+            }
+        }
+        return $allplugins;
+    }
+    
+    
+    /**
+     * Used by array_filter to keep only installed plugins
+     */
+    public function getInstalledPlugins($var)
+    {
+        if ($var['location'] == 'database') { return $var; }
+    }
+    
+    
+    /**
+     * Used by array_filter in template to keep only installed plugins
+     */
+    public function getUninstalledPlugins($var)
+    {
+        if ($var['location'] == 'folder') { return $var; }
+    }
+    
+    
+    /**
+     * Read and return plugin info directly from plugin files.
+     */
+    public function getPluginsMeta()
+    {
+        $plugin_list = getFilenames(PLUGINS, "short");
+        $plugins_array = array();
+        foreach ($plugin_list as $plugin_folder_name)
+        {
+            if($plugin_metadata = $this->readPluginMeta($plugin_folder_name)) {
+                array_push($plugins_array, $plugin_metadata);
+            }
+        }    
+        return $plugins_array; // return plugins in alphabetical order
+    }
+    
+    
+    /**
+     * Read and return plugin info from top of a plugin file.
+     *
+     * @param string $plugin_file - a file from the /plugins folder 
+     * @return array|false
+     */
+    public function readPluginMeta($plugin_file)
+    {
+        if ($plugin_file == 'placeholder.txt') { return false; }
+        
+        // Include the generic_pmd class that reads post metadata from the a plugin
+        require_once(EXTENSIONS . 'GenericPHPConfig/class.metadata.php');
+        $metaReader = new generic_pmd();
+        $plugin_metadata = $metaReader->read(PLUGINS . $plugin_file . "/" . $plugin_file . ".php");
+        
+        if ($plugin_metadata) { return $plugin_metadata; } else { return false; }
+    }
+    
+    
+    /**
+     * Converts $h->plugin->requires into $h->plugin->dependencies array.
+     * Result is an array containing 'plugin' -> 'version' pairs
+     */
+    public function requiresToDependencies($h)
+    {
+        // unset each key from previous time here
+        foreach ($h->plugin->dependencies as $k => $v) {
+            unset($h->plugin->dependencies[$k]);
+        }
+        
+        foreach (explode(',', $h->plugin->requires) as $pair) 
+        {
+            list($k,$v) = explode (' ', trim($pair));
+            $h->plugin->dependencies[$k] = $v;
+        }
+    }
+    
+    
+    /**
+     * Add a plugin to the plugins table
+     *
+     * @param int $upgrade flag to indicate we need to show "Upgraded!" instead of "Installed!" message
+     */
+    public function install($h, $upgrade = 0)
+    {
+        // Clear the database cache to ensure stored plugins and hooks 
+        // are up-to-date.
+        $h->deleteFiles(CACHE . 'db_cache');
+        
+        // Clear the css/js cache to ensure any new ones get included
+        $h->deleteFiles(CACHE . 'css_js_cache');
+        
+        // Read meta from the top of the plugin file
+        $plugin_metadata = $this->readPluginMeta($h->plugin->folder);
+        
+        $h->plugin->enabled  = 1;    // Enable it when we add it to the database.
+        $this->assignPluginMeta($h, $plugin_metadata);
+
+        $dependency_error = 0;
+        foreach ($h->plugin->dependencies as $dependency => $version)
+        {
+            if (version_compare($version, $h->getPluginVersion($dependency), '>')) {
+                $dependency_error = 1;
+            }
+        }
+        
+        if ($dependency_error == 1)
+        {
+            foreach ($h->plugin->dependencies as $dependency => $version)
+            {
+                    if (($h->isActive($dependency) == 'inactive') 
+                        || version_compare($version, $h->getPluginVersion($dependency), '>')) {
+                        $dependency = make_name($dependency);
+                        $h->messages[$h->lang["admin_plugins_install_sorry"] . " " . $h->plugin->name . " " . $h->lang["admin_plugins_install_requires"] . " " . $dependency . " " . $version] = 'red';
+                    }
+            }
+            return false;
+        }
+                    
+        $sql = "REPLACE INTO " . TABLE_PLUGINS . " (plugin_enabled, plugin_name, plugin_folder, plugin_class, plugin_extends, plugin_type, plugin_desc, plugin_requires, plugin_version, plugin_author, plugin_authorurl, plugin_updateby) VALUES (%d, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d)";
+        $h->db->query($h->db->prepare($sql, $h->plugin->enabled, $h->plugin->name, $h->plugin->folder, $h->plugin->class, $h->plugin->extends, $h->plugin->type, $h->plugin->desc, $h->plugin->requires, $h->plugin->version, $h->plugin->author, urlencode($h->plugin->authorurl), $h->currentUser->id));
+
+        // Get the last order number - doing this after REPLACE INTO because 
+        // we don't know whether the above will insert or replace.
+        $sql = "SELECT plugin_order FROM " . TABLE_PLUGINS . " ORDER BY plugin_order DESC LIMIT 1";
+        $highest_order = $h->db->get_var($h->db->prepare($sql));
+
+        // Give the new plugin the order number + 1
+        $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_id = LAST_INSERT_ID()";
+        $h->db->query($h->db->prepare($sql, ($highest_order + 1)));
+        
+        // Add any plugin hooks to the hooks table
+        $this->addPluginHooks($h);
+        
+        // Force inclusion of a language file (if exists) because the 
+        // plugin isn't ready to include it itself yet.
+        $h->includeLanguage();
+        
+        $result = $h->pluginHook('install_plugin', $h->plugin->folder);
+        
+        // For plugins to avoid showing this success message, they need to 
+        // return a non-boolean value to $result.
+        if (!is_array($result))
+        {
+            if ($upgrade == 0) {
+                $h->messages[$h->lang["admin_plugins_install_done"]] = 'green';
+            } else {
+                $h->messages[$h->lang["admin_plugins_upgrade_done"]] = 'green';
+            }
+        }
+    }
+    
+    
+    /**
+     * Assign info from top of a plugin file to the current object.
+     *
+     * @param array $plugin_metadata 
+     * @return array|false
+     */
+    public function assignPluginMeta($h, $plugin_metadata)
+    {
+        if (!$plugin_metadata) { return false; }
+        
+        $h->plugin->name         = $plugin_metadata['name'];
+        $h->plugin->desc         = $plugin_metadata['description'];
+        $h->plugin->version      = $plugin_metadata['version'];
+        $h->plugin->folder       = $plugin_metadata['folder'];
+        $h->plugin->class        = $plugin_metadata['class'];
+        $h->plugin->hooks        = explode(',', $plugin_metadata['hooks']);
+        
+        if (isset($plugin_metadata['extends'])) {   $h->plugin->extends      = $plugin_metadata['extends']; }
+        if (isset($plugin_metadata['type'])) {      $h->plugin->type         = $plugin_metadata['type'];    }
+        if (isset($plugin_metadata['author'])) {    $h->plugin->author       = $plugin_metadata['author'];  }
+        if (isset($plugin_metadata['authorurl'])) { $h->plugin->authorurl    = $plugin_metadata['authorurl']; }
+        
+        if (isset($plugin_metadata['requires']) && $plugin_metadata['requires']) {
+            $h->plugin->requires = $plugin_metadata['requires'];
+            $this->requiresToDependencies($h);
+        }
+        
+        return true;
+    }
+    
+    
+    /**
+     * Adds all hooks for a given plugin
+     */
+    public function addPluginHooks($h)
+    {
+        foreach ($h->plugin->hooks as $hook)
+        {
+            $exists = $this->isHook($h, trim($hook));
+
+            if (!$exists) {
+                $sql = "INSERT INTO " . TABLE_PLUGINHOOKS . " (plugin_folder, plugin_hook, plugin_updateby) VALUES (%s, %s, %d)";
+                $h->db->query($h->db->prepare($sql, $h->plugin->folder, trim($hook), $h->currentUser->id));
+            }
+        }
+    }
+    
+    
+    /**
+     * Check if a plugin hook exists for a given plugin
+     *
+     * @param string $folder plugin folder name
+     * @param string $hook plugin hook name
+     * @return int|false
+     */
+    public function isHook($h, $hook = "", $folder = "")
+    {
+        if (!$folder) { $folder = $h->plugin->folder; }
+        
+        $sql = "SELECT count(*) FROM " . TABLE_PLUGINHOOKS . " WHERE plugin_folder = %s AND plugin_hook = %s";
+        if ($h->db->get_var($h->db->prepare($sql, $folder, $hook))) { return true;} else { return false; }
+    }
+    
+    
+    /**
+     * Uninstall all plugins
+     */
+    public function uninstallAll($h)
+    {            
+        // Clear the database cache to ensure plugins and hooks are up-to-date.
+        $h->deleteFiles(CACHE . 'db_cache');
+        
+        // Clear the css/js cache to ensure any new ones get included
+        $h->deleteFiles(CACHE . 'css_js_cache');
+
+        $h->db->query("TRUNCATE TABLE " . TABLE_PLUGINS);
+        $h->db->query("TRUNCATE TABLE " . TABLE_PLUGINHOOKS);
+
+        $h->messages[$h->lang["admin_plugins_uninstall_all_done"]] = 'green';
+    }
+    
+    
+    /**
+     * Delete plugin from table_plugins, pluginhooks and pluginsettings
+     *
+     * @param int $upgrade flag to disable message
+     */
+    public function uninstall($h, $upgrade = 0)
+    {    
+        // Clear the database cache to ensure plugins and hooks are up-to-date.
+        $h->deleteFiles(CACHE . 'db_cache');
+        
+        // Clear the css/js cache to ensure this plugin's files are removed
+        $h->deleteFiles(CACHE . 'css_js_cache');
+
+        $h->db->query($h->db->prepare("DELETE FROM " . TABLE_PLUGINS . " WHERE plugin_folder = %s", $h->plugin->folder));
+        $h->db->query($h->db->prepare("DELETE FROM " . TABLE_PLUGINHOOKS . " WHERE plugin_folder = %s", $h->plugin->folder));
+        
+        // Settings aren't deleted anymore, but a user can do so manually from Admin->Maintenance
+        //$h->db->query($h->db->prepare("DELETE FROM " . TABLE_PLUGINSETTINGS . " WHERE plugin_folder = %s", $h->plugin->folder));
+        
+        if ($upgrade == 0) {
+            $h->messages[$h->lang["admin_plugins_uninstall_done"]] = 'green';
+        }
+        
+        $this->refreshPluginOrder($h);
+    }
+    
+    
+    /**
+     * Removes gaps in plugin order where plugins have been uninstalled.
+     */
+    public function refreshPluginOrder($h)
+    {    
+        $sql = "SELECT * FROM " . TABLE_PLUGINS . " ORDER BY plugin_order ASC";
+        $rows = $h->db->get_results($h->db->prepare($sql));
+        
+        if ($rows) { 
+            $i = 1;
+            foreach ($rows as $row) 
+            {
+                $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_id = %d";
+                $h->db->query($h->db->prepare($sql, $i, $row->plugin_id));
+                $i++; 
+            }
+        }
+        
+        // optimize the table
+        $h->db->query("OPTIMIZE TABLE " . TABLE_PLUGINS);
+        
+        return true;
+    }
+    
+    
+    /**
+     * Orders the plugin hooks by plugin_order
+     */
+    public function sortPluginHooks($h)
+    {    
+        $sql = "SELECT p.plugin_folder, p.plugin_order, p.plugin_id, h.* FROM " . TABLE_PLUGINHOOKS . " h, " . TABLE_PLUGINS . " p WHERE p.plugin_folder = h.plugin_folder ORDER BY p.plugin_order ASC";
+        $rows = $h->db->get_results($h->db->prepare($sql));
+
+        // Drop and recreate the pluginhooks table, i.e. empty it.
+        $h->db->query($h->db->prepare("TRUNCATE TABLE " . TABLE_PLUGINHOOKS));
+            
+        // Add plugin hooks back into the hooks table
+        foreach ($rows  as $row)
+        {
+            $sql = "INSERT INTO " . TABLE_PLUGINHOOKS . " (plugin_folder, plugin_hook, plugin_updateby) VALUES (%s, %s, %d)";
+            $h->db->query($h->db->prepare($sql, $row->plugin_folder, $row->plugin_hook, $h->currentUser->id));
+        }
+        
+        // optimize the table
+        $h->db->query("OPTIMIZE TABLE " . TABLE_PLUGINHOOKS);
+        
+    }
+    
+    
+    /**
+     * Upgrade plugin
+     *
+     * @param string $folder plugin folder name
+     *
+     * Note: This function does nothing by itself other than read the latest 
+     * file's metadata.
+     */
+    public function upgrade($h)
+    {
+        // Read meta from the top of the plugin file
+        $plugin_metadata = $this->readPluginMeta($h->plugin->folder);
+        
+        $h->plugin->enabled  = 1;    // Enable it when we add it to the database.
+        $this->assignPluginMeta($h, $plugin_metadata);
+        
+        $this->uninstall($h, 1);    // 1 indicates that "upgrade" is true, used to disable the "Uninstalled" message
+        $this->install($h, 1);      // 1 indicates that "upgrade" is true. 
+    }
+    
+    
+    /**
+     * Enables or disables a plugin, installing if necessary
+     *
+     * @param int $enabled 
+     * Note: This function does not uninstall/delete a plugin.
+     */
+    public function activateDeactivate($h, $enabled = 0)
+    {    // 0 = deactivate, 1 = activate
+
+        // Clear the database cache to ensure plugins and hooks are up-to-date.
+        $h->deleteFiles(CACHE . 'db_cache');
+        
+        // Clear the css/js cache to ensure any new ones get included
+        $h->deleteFiles(CACHE . 'css_js_cache');
+        
+        // Get the enabled status for this plugin...
+        $plugin_row = $h->db->get_row($h->db->prepare("SELECT plugin_folder, plugin_enabled FROM " . TABLE_PLUGINS . " WHERE plugin_folder = %s", $h->plugin->folder));
+        
+        // If no result, then it's obviously not installed...
+        if (!$plugin_row) 
+        {
+            // If the user is activating the plugin, go and install it...
+            if ($enabled == 1) { $this->install($h); }
+        } 
+        else 
+        {
+            $this->activateDeactivateDo($h, $plugin_row, $enabled);
+        }
+    }
+    
+
+    /**
+     * Enables or disables all plugins, installing if necessary
+     *
+     * @param int $enabled 
+     * Note: This function does not uninstall/delete a plugin.
+     */
+    public function activateDeactivateAll($h, $enabled = 0)
+    {    // 0 = deactivate, 1 = activate
+        
+        // if you want to activate, find all the inactive plugins and vice-versa:
+        if ($enabled == 0) { $active_plugins = $this->activePlugins($h->db, '*', 1); }
+        if ($enabled == 1) { $active_plugins = $this->activePlugins($h->db, '*', 0); }
+
+        if (!$active_plugins) { return false; }
+                
+        /*  The problem with upgrading plugins is many of them require other plugins to work, 
+            therefore half the plugins can't be upgraded if the upgrade is attempted in a 
+            random order. So let's minimize the problem by sorting the plugins by number of 
+            requirements, i.e. plugins that have no requirements (Users, Submit, Sidebar Widgets)
+            are upgraded first, then plugins with one requirement... and finally Pligg Importer,
+            which has about 7 requirements. */ 
+        $i = 0;
+        foreach ($active_plugins as $active) {
+            $h->plugin->folder = $active->plugin_folder;
+            $ordered[$i]['name'] = $active->plugin_folder;
+            if (!$active->plugin_requires) { 
+                $ordered[$i]['req_count'] = 0; 
+            } else {
+                $requires = explode(', ', $active->plugin_requires);
+                $ordered[$i]['req_count'] = count($requires);
+            }
+            $i++;
+        }
+        
+        $ordered = sksort($ordered, 'req_count', 'int', true);
+        foreach ($ordered as $ord) {
+            $plugin_row = $h->db->get_row($h->db->prepare("SELECT plugin_folder, plugin_enabled FROM " . TABLE_PLUGINS . " WHERE plugin_folder = %s", $ord['name']));
+            $h->plugin->folder = $plugin_row->plugin_folder;
+            $this->activateDeactivateDo($h, $plugin_row, $enabled);
+        }
+    }
+    
+    
+    /**
+     * Enables or disables all plugins, installing if necessary
+     *
+     * @param int $enabled 
+     * Note: This function does not uninstall/delete a plugin.
+     */
+    public function activateDeactivateDo($h, $plugin, $enabled = 0)
+    {    // 0 = deactivate, 1 = activate
+        // The plugin is already installed. Activate or deactivate according to $enabled (the user's action).
+        if ($plugin->plugin_enabled == $enabled) { return false; }  // only update if we're changing the enabled value.
+        
+        $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_enabled = %d, plugin_updateby = %d WHERE plugin_folder = %s";
+        $h->db->query($h->db->prepare($sql, $enabled, $h->currentUser->id, $h->plugin->folder));
+        
+        if ($enabled == 1) { // Activating now...
+        
+            // Get plugin version from the database...
+            $db_version = $h->getPluginVersion($h->plugin->folder);
+            
+            // Get plugin version from the file....
+            $plugin_metadata = $this->readPluginMeta($h->plugin->folder);
+            $file_version = $plugin_metadata['version'];
+            
+            // If file version is newer the the current plugin version, then upgrade...
+            if (version_compare($file_version, $db_version, '>')) {
+                $this->upgrade($h); // runs the install function ans hows "upgraded!" message instead of "installed".
+            } else {
+                // else simply show an activated message...
+                $h->messages[$h->lang["admin_plugins_activated"]] = 'green'; 
+            }
+            
+            // Force inclusion of a language file (if exists) because the 
+            // plugin isn't ready to include it itself yet.
+            $h->includeLanguage();
+        }
+        
+        if ($enabled == 0) { 
+            $h->messages[$h->lang["admin_plugins_deactivated"]] = 'green'; 
+        }
+    }
+    
+    
+    /**
+     * Get a list of active or inactive plugins (or their descriptions, etc.)
+     *
+     * @param string $select the table column to return
+     * @param int $enabled 0 for inactive, 1 for active
+     * @return array
+     */
+    public function activePlugins($db, $select = 'plugin_folder', $enabled = 1)
+    {
+        $sql = "SELECT $select FROM " . TABLE_PLUGINS . " WHERE plugin_enabled = %d";
+        $active_plugins = $db->get_results($db->prepare($sql, $enabled));
+        
+        if ($active_plugins) { return $active_plugins; } else {return false; }
+    }
+    
+    
+    /**
+     * Updates plugin order and order of their hooks, i.e. changes the order 
+     * of plugins in pluginHook.
+     * 
+     * @param string $folder plugin folder name
+     * @param int $order current order
+     * @param string $arrow direction to move
+     */
+    public function pluginOrder($h, $order = 0, $arrow = "up")
+    {
+        if ($order == 0) {
+            $h->messages[$h->lang['admin_plugins_order_zero']] = 'red';
+            return false;
+        }
+        
+        $h->getPluginName();
+                
+        if ($arrow == "up")
+        {
+            // get row above
+            $sql= "SELECT * FROM " . TABLE_PLUGINS . " WHERE plugin_order = %d";
+            $row_above = $h->db->get_row($h->db->prepare($sql, ($order - 1)));
+            
+            if (!$row_above) {
+                $h->messages[$h->plugin->name . " " . $h->lang['admin_plugins_order_first']] = 'red';
+                return false;
+            }
+            
+            if ($row_above->plugin_order == $order) {
+                $h->messages[$h->lang['admin_plugins_order_above']] = 'red';
+                return false;
+            }
+            
+            // update row above 
+            $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_id = %d";
+            $h->db->query($h->db->prepare($sql, ($row_above->plugin_order + 1), $row_above->plugin_id)); 
+            
+            // update current plugin
+            $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_folder = %s";
+            $h->db->query($h->db->prepare($sql, ($order - 1), $h->plugin->folder)); 
+        }
+        else
+        {
+            // get row below
+            $sql= "SELECT * FROM " . TABLE_PLUGINS . " WHERE plugin_order = %d";
+            $row_below = $h->db->get_row($h->db->prepare($sql, ($order + 1)));
+            
+            if (!$row_below) {
+                $h->messages[$h->plugin->name . " " . $h->lang['admin_plugins_order_last']] = 'red';
+                return false;
+            }
+            
+            if ($row_below->plugin_order == $order) {
+                $h->messages[$h->lang['admin_plugins_order_below']] = 'red';
+                return false;
+            }
+            
+            // update row above 
+            $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_id = %d";
+            $h->db->query($h->db->prepare($sql, ($row_below->plugin_order - 1), $row_below->plugin_id)); 
+            
+            // update current plugin
+            $sql = "UPDATE " . TABLE_PLUGINS . " SET plugin_order = %d WHERE plugin_folder = %s";
+            $h->db->query($h->db->prepare($sql, ($order + 1), $h->plugin->folder)); 
+        }
+
+        $h->messages[$h->lang['admin_plugins_order_updated']] = 'green';
+
+        // Re-sort all orders and remove any accidental gaps
+        $this->refreshPluginOrder($h);
+        $this->sortPluginHooks($h);
+
+        return true;
+
+    }
+}
+?>
Index: /trunk/libs/Caching.php
===================================================================
--- /trunk/libs/Caching.php	(revision 1081)
+++ /trunk/libs/Caching.php	(revision 1081)
@@ -0,0 +1,242 @@
+<?php
+/**
+ * Cache functions
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Caching
+{
+    /**
+     * Hotaru CMS Smart Caching
+     *
+     * This function does one query on the database to get the last updated time for a 
+     * specified table. If that time is more recent than the $timeout length (e.g. 10 minutes),
+     * the database will be used. If there hasn't been an update, any cached results from the 
+     * last 10 minutes will be used.
+     *
+     * @param string $switch either "on", "off" or "html"
+     * @param string $table DB table name
+     * @param int $timeout time before DB cache expires
+     * @param string $html_sql output as HTML, or an SQL query
+     * @param string $label optional label to append to filename
+     * @return bool
+     */
+    public function smartCache($h, $switch = 'off', $table = '', $timeout = 0, $html_sql = '', $label = '')
+    {
+        if ($switch == 'html') { 
+            $html = $html_sql;
+            $result = $this->smartCacheHTML($h, $table, $timeout, $html, $label); 
+        } else {
+            $sql = $html_sql;
+            $result = $this->smartCacheDB($h, $switch, $table, $sql, $timeout);
+        }
+        
+        return $result;
+    }
+    
+    
+    /**
+     * Hotaru CMS Smart Caching HTML output
+     *
+     * This function caches blocks of HTML code
+     *
+     * @param string $table DB table name
+     * @param int $timeout timeout in minutes before cache file is deleted
+     * @return bool
+     */
+    public function smartCacheHTML($h, $table = '', $timeout = 0, $html = '', $label = '')
+    {
+        if (!$table || !$timeout || (HTML_CACHE_ON != 'true')) { return false; }
+        
+        if(isset($h->vars['last_updates'][$table])) {
+            $last_update = $h->vars['last_updates'][$table]; // cached
+        } else {
+            $last_update = $this->smartCacheSQL($h, $table);
+            $last_update = $h->vars['last_updates'][$table] = $last_update;
+        }
+        
+        $cache_length = $timeout*60;   // seconds
+        $cache = CACHE . 'html_cache/';
+        if ($label) { $label = '_' . $label; } 
+        $file = $cache . $table . $label . ".cache";
+        
+        //echo "time now: " . time() . "<br />";
+        //echo "time minus timeout: " . (time() - $timeout*60) . "<br />";
+        //echo "last update: " . $last_update . "<br />";
+        
+        if (!$html) {
+            // we only want to read the cache if it exists, hence no $html passed to this function
+            if (file_exists($file)) {
+                $content = file_get_contents($file);
+                $last_modified = filemtime($file);
+                //echo "last modified: " . $last_modified . "<br />";
+                if ($last_modified <= (time() - $cache_length)) { 
+                    // delete cache
+                    unlink($file);
+                    return false;
+                } else {
+                    if ($last_update >= $last_modified) { return false; } // there's been a recent update so don't use the cache.
+                    return $content;    // return the HTML to display
+                }
+            } else {
+                return false;
+            }
+        }
+
+        // if we're here, we need to make or rewrite the cache
+        
+        $fp = fopen($file, "w");
+
+        if (flock($fp, LOCK_EX)) { // do an exclusive lock
+            ftruncate($fp, 0);  // truncate file
+            fwrite($fp, $html); // write HTML
+            flock($fp, LOCK_UN); // release the lock
+        } else {
+            echo "Couldn't get the lock for the HTML cache!";
+        }
+        
+        fclose($fp);
+        return true; // the calling function already has the HTML to output
+    }
+    
+    
+    /**
+     * Hotaru CMS Smart Caching Database Queries
+     *
+     * This function uses the ezSQL database cache
+     *
+     * @param string $switch either "on" or "off"
+     * @param string $table DB table name
+     * @param int $timeout timeout in minutes
+     * @return bool
+     */
+    public function smartCacheDB($h, $switch = 'off', $table = '', $sql = '', $timeout = 0)
+    {
+        // Stop caching?
+        if ($switch != 'on') {
+            $h->db->cache_queries = false;               // stop using cache
+            $h->db->cache_timeout = DB_CACHE_DURATION;   // return to our default cache duration
+            return false;
+        }
+        
+        if (!$sql) { return false; }
+        
+        if (!$timeout) { $timeout = (DB_CACHE_DURATION * 60); } // hours * 60 = total minutes
+        
+        // ezSQL uses hours for its timeout. We'll use minutes and divide by 60 to get the hours.
+        if ($timeout) { 
+            $h->db->cache_timeout = $timeout/60; // mins/60 = hours
+        } else {
+            $h->db->cache_timeout = DB_CACHE_DURATION;
+        }
+        
+        // determine time of last DB table update:
+        if(isset($h->vars['last_updates'][$table])) {
+            $last_update = $h->vars['last_updates'][$table]; // cached
+        } else {
+            $last_update = $this->smartCacheSQL($h, $table);
+            $last_update = $h->vars['last_updates'][$table] = $last_update;
+        }
+        
+        // use caching?
+        if (DB_CACHE_ON == 'true') {
+            $h->db->cache_queries = true;    // start using cache
+        } else {
+            return false;   // don't use caching
+        }
+        
+        // check existence of a cache file for this query:
+        $cache_file = CACHE . 'db_cache/' . md5($sql);
+        if (!file_exists($cache_file)) {
+            // no cache file so return and pull data direct from DB, caching the query at the same time.
+            return true; 
+        }
+        
+        // check if the cache file is older than our timeout:
+        $file_modified = filemtime($cache_file);
+        if ($file_modified < (time() - $timeout*60)) { 
+            unlink($cache_file); // delete old cache file so we can make a new one with fresh data
+            return true; 
+        }
+        
+        // check if the $last_update is more recent than the cache file:
+        if ($file_modified < $last_update) { 
+            unlink($cache_file); // delete old cache file so we can make a new one with fresh data
+            return true; 
+        }
+
+        return true;
+    }
+    
+    
+
+    /**
+     * Picks the right SQL and gets the last_update time in seconds
+     *
+     * @param string $table DB table name
+     * @return int $last_update
+     */
+    public function smartCacheSQL($h, $table = '')
+    {
+        /* Get the last time the table was updated */
+        switch ($table) {
+            case 'categories':
+                $sql = "SELECT category_updatedts FROM " . DB_PREFIX . "categories ORDER BY category_updatedts DESC";
+                break;
+            case 'tags':
+                $sql = "SELECT tags_updatedts FROM " . DB_PREFIX . "tags ORDER BY tags_updatedts DESC";
+                break;
+            case 'posts':
+                $sql = "SELECT post_updatedts FROM " . DB_PREFIX . "posts ORDER BY post_updatedts DESC";
+                break;
+            case 'postvotes':
+                $sql = "SELECT vote_updatedts FROM " . DB_PREFIX . "postvotes ORDER BY vote_updatedts DESC";
+                break;
+            case 'comments':
+                $sql = "SELECT comment_updatedts FROM " . DB_PREFIX . "comments ORDER BY comment_updatedts DESC";
+                break;
+            case 'commentvotes':
+                $sql = "SELECT cvote_updatedts FROM " . DB_PREFIX . "commentvotes ORDER BY cvote_updatedts DESC";
+                break;
+            case 'users':
+                $sql = "SELECT user_updatedts FROM " . DB_PREFIX . "users ORDER BY user_updatedts DESC";
+                break;
+            case 'useractivity':
+                $sql = "SELECT useract_updatedts FROM " . DB_PREFIX . "useractivity ORDER BY useract_updatedts DESC";
+                break;
+            case 'usermeta':
+                $sql = "SELECT usermeta_updatedts FROM " . DB_PREFIX . "usermeta ORDER BY usermeta_updatedts DESC";
+                break;
+            case 'miscdata':
+                $sql = "SELECT miscdata_updatedts FROM " . DB_PREFIX . "miscdata ORDER BY miscdata_updatedts DESC";
+                break;
+            default:
+                return false;
+        }
+        
+        // run DB query:
+        $last_update = unixtimestamp($h->db->get_var($sql));
+        
+        return $last_update;
+    }
+}
+?>
Index: /trunk/libs/Tags.php
===================================================================
--- /trunk/libs/Tags.php	(revision 1081)
+++ /trunk/libs/Tags.php	(revision 1081)
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Tag functions
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class TagFunctions
+{
+    /**
+     * Add tags to the tags table
+     */
+    public function addTags($h, $post_id = 0, $new_tags = '')
+    {
+        // Tags table
+        if ($new_tags) {
+            $tags_array = explode(',', $new_tags);
+            if ($tags_array) {
+                foreach ($tags_array as $tag) {
+                    $sql = "INSERT INTO " . TABLE_TAGS . " SET tags_post_id = %d, tags_date = CURRENT_TIMESTAMP, tags_word = %s, tags_updateby = %d";
+                    $h->db->query($h->db->prepare($sql, $post_id, urlencode(str_replace(' ', '_', trim($tag))), $h->currentUser->id));
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Delete tags from the tags table
+     */
+    public function deleteTags($h, $post_id = 0)
+    {
+        $sql = "DELETE FROM " . TABLE_TAGS . " WHERE tags_post_id = %d";
+        $h->db->query($h->db->prepare($sql, $post_id));
+    }
+}
+?>
Index: /trunk/libs/Plugin.php
===================================================================
--- /trunk/libs/Plugin.php	(revision 1081)
+++ /trunk/libs/Plugin.php	(revision 1081)
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Plugin Class
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Plugin
+{
+    protected $id               = 0;
+    protected $folder           = '';
+    protected $enabled          = 0;
+    protected $name             = '';
+    protected $class            = '';
+    protected $extends          = '';
+    protected $type             = '';
+    protected $desc             = '';
+    protected $version          = 0;
+    protected $order            = 0;
+    protected $author           = '';
+    protected $authorurl        = '';
+    protected $requires         = '';
+    protected $dependencies     = array();
+    protected $hooks            = array();
+    
+    /**
+     * Access modifier to set protected properties
+     */
+    public function __set($var, $val)
+    {
+        $this->$var = $val;
+    }
+    
+    
+    /**
+     * Access modifier to get protected properties
+     * The & is necessary (http://bugs.php.net/bug.php?id=39449)
+     */
+    public function &__get($var)
+    {
+        return $this->$var;
+    }
+}
+?>
Index: /trunk/libs/UserAuth.php
===================================================================
--- /trunk/libs/UserAuth.php	(revision 1081)
+++ /trunk/libs/UserAuth.php	(revision 1081)
@@ -0,0 +1,549 @@
+<?php
+/**
+ * Functions for authnticating, logging in and registering users
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class UserAuth extends UserBase
+{
+    /**
+     * check cookie and log in
+     *
+     * @return bool
+     */
+    public function checkCookie($h)
+    {
+        // Check for a cookie. If present then the user is logged in.
+        $h_user = $h->cage->cookie->testUsername('hotaru_user');
+        
+        if((!$h_user) || (!$h->cage->cookie->keyExists('hotaru_key'))) { 
+            $this->setLoggedOutUser($h);
+            return false; 
+        }
+        
+        $user_info=explode(":", base64_decode($h->cage->cookie->getRaw('hotaru_key')));
+        
+        if (($h_user != $user_info[0]) || (crypt($user_info[0], 22) != $user_info[1])) { 
+            $this->setLoggedOutUser($h);
+            return false; 
+        }
+
+        $this->name = $h_user;
+        if ($h_user) {
+            $this->getUserBasic($h, 0, $this->name);
+            $this->loggedIn = true;
+            // update user_lastvisit field when a new session is created
+            if (!session_id()) {
+                $this->updateUserLastVisit($h);
+            }
+            
+            $h->pluginHook('userauth_checkcookie_success');
+        } else {
+            $this->setLoggedOutUser($h);
+            return false; 
+        }
+                
+        return true;
+    }
+    
+    
+    /**
+     * Log a user in if their username and password are valid
+     *
+     * @param string $username
+     * @param string $password
+     * @return bool
+     */
+    public function loginCheck($h, $username = '', $password = '')
+    {
+        // Read the current user's basic details
+        $userX = $this->getUserBasic($h, 0, $username);
+        if (!$userX) { return false; }
+        
+        // destroy the cookie for the following usergroups:
+        $no_cookie = array('killspammed', 'banned', 'suspended');
+        if (in_array($userX->user_role, $no_cookie)) {
+            $this->destroyCookieAndSession();
+            return false;
+        }
+        
+        $salt_length = 9;
+        $result = '';
+        
+        // Allow plugin to bypass the password check with their own methods, e.g. RPX
+        $plugin_result = $h->pluginHook('userbase_logincheck', '', array($username, $password));
+        
+        if (!$plugin_result)
+        {
+            // nothing or (false) was returned from the plugins, so confirm the username and password match:
+            $password = $this->generateHash($password, substr($userX->user_password, 0, $salt_length));
+            $sql = "SELECT user_username, user_password FROM " . TABLE_USERS . " WHERE user_username = %s AND user_password = %s";
+            $result = $h->db->get_row($h->db->prepare($sql, $username, $password));
+        } 
+        elseif ($plugin_result)
+        {
+            // a positive result was returned from the plugin(s)
+            // let's hope the plugin did its own authentication because we've skipped the usual username/passowrd check!
+            $result = true;
+        } 
+        
+        if ($result) { return true; } else { return false; }
+    }
+    
+    
+    /**
+     * Generate a hash for the password
+     *
+     * @param string $plainText - the password
+     * @param mixed $salt
+     *
+     * Note: Adapted from SocialWebCMS
+     */
+    public function generateHash($plainText, $salt = null)
+    {
+        $salt_length = 9;
+        if ($salt === null) {
+            $salt = substr(md5(uniqid(rand(), true)), 0, $salt_length); }
+        else {
+            $salt = substr($salt, 0, $salt_length);
+            }
+        return $salt . sha1($salt . $plainText);
+    }
+
+
+    /**
+     * Give logged out user default permissions
+     */    
+    public function setLoggedOutUser($h)
+    {
+        $default_perms = $this->getDefaultPermissions($h);
+        unset($default_perms['options']);  // don't need this for individual users
+        $this->setAllPermissions($default_perms);
+    }
+    
+    
+    /**
+     * Update last login
+     *
+     * @return bool
+     */
+    public function updateUserLastLogin($h)
+    {
+        if ($this->id != 0) {
+            $sql = "UPDATE " . TABLE_USERS . " SET user_lastlogin = CURRENT_TIMESTAMP WHERE user_id = %d";
+            $h->db->query($h->db->prepare($sql, $this->id));
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+
+    /**
+     * Update last visit (new session started)
+     *
+     * @return bool
+     */
+    public function updateUserLastVisit($h)
+    {
+        if ($this->id != 0) {
+            $sql = "UPDATE " . TABLE_USERS . " SET user_lastvisit = CURRENT_TIMESTAMP WHERE user_id = %d";
+            $h->db->query($h->db->prepare($sql, $this->id));
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    
+    /**
+     * Set a 30-day cookie
+     *
+     * @param string $remember checkbox with value "checked" or empty
+     * @return bool
+     */
+    public function setCookie($h, $remember)
+    {
+        if (!$this->name)
+        { 
+            echo $h->lang['main_userbase_cookie_error'];
+            return false;
+        } else {
+            $strCookie=base64_encode(
+                join(':', array($this->name, crypt($this->name, 22)))
+            );
+            
+            if ($remember) { 
+                // 2592000 = 60 seconds * 60 mins * 24 hours * 30 days
+                $month = 2592000 + time(); 
+            } else { 
+                $month = 0; 
+            }
+            
+            if (strpos(BASEURL, "localhost") !== false) {
+                setcookie("hotaru_user", $this->name, $month, "/");
+                setcookie("hotaru_key", $strCookie, $month, "/");
+            } else {
+                $parsed = parse_url(BASEURL); 
+
+                // now we need a dot in front of that so cookies work across subdomains:
+                setcookie("hotaru_user", $this->name, $month, "/", "." . $parsed['host']);
+                setcookie("hotaru_key", $strCookie, $month, "/", "." . $parsed['host']);
+            }
+            return true;
+        }
+    }
+    
+    
+    /**
+     * Delete cookie and destroy session
+     */
+    public function destroyCookieAndSession()
+    {
+        // setting a cookie with a negative time expires it
+        
+        if (strpos(BASEURL, "localhost") !== false) {
+            setcookie("hotaru_user", "", time()-3600, "/");
+            setcookie("hotaru_key", "", time()-3600, "/");
+        } else {
+            $parsed = parse_url(BASEURL); 
+            
+            // now we need a dot in front of that so cookies are cleared across subdomains:
+            setcookie("hotaru_user", "", time()-3600, "/", "." . $parsed['host']);
+            setcookie("hotaru_key", "", time()-3600, "/", "." . $parsed['host']);
+        }
+        
+        session_destroy(); // sessions are used in CSRF
+        
+        $this->loggedIn = false;
+    }
+    
+    
+     /**
+     * Change username or email
+     *
+     * @param int $userid
+     * @return bool
+     */
+    public function updateAccount($h, $userid = 0)
+    {
+        // $viewee is the person whose account is being modified
+        
+        $viewee = new UserBase($h);
+        
+        // Get the details of the account to show.
+        // If no account is specified, assume it's your own.
+        
+        if (!$userid) {
+            $userid = $this->id; 
+        }
+        
+        $viewee->getUserBasic($h, $userid);
+
+        $error = 0;
+        
+        // fill checks
+        $checks['userid_check'] = '';
+        $checks['username_check'] = '';
+        $checks['email_check'] = '';
+        $checks['role_check'] = '';
+        $checks['password_check_old'] = '';
+        $checks['password_check_new'] = '';
+        $checks['password_check_new2'] = '';
+        
+        // Updating account info (username and email address)
+        if ($h->cage->post->testAlnumLines('update_type') == 'update_general') {
+        
+            // check CSRF key
+            if (!$h->csrf()) {
+                $h->messages[$h->lang['error_csrf']] = 'red';
+                $error = 1;
+            }
+    
+            $username_check = $h->cage->post->testUsername('username'); // alphanumeric, dashes and underscores okay, case insensitive
+            if (!$username_check) {
+                $h->messages[$h->lang['main_user_account_update_username_error']] = 'red';
+                $error = 1;
+            } elseif($h->nameExists($username_check, '', $viewee->id)) {
+                $h->messages[$h->lang['main_user_account_update_username_exists']] = 'red';
+                $error = 1;
+            } else {
+                //success
+                $viewee->username = $username_check;
+            }
+                                
+            $email_check = $h->cage->post->testEmail('email');
+            if (!$email_check) {
+                $h->messages[$h->lang['main_user_account_update_email_error']] = 'red';
+                $error = 1;
+            } elseif($h->emailExists($email_check, '', $viewee->id)) {
+                $h->messages[$h->lang['main_user_account_update_email_exists']] = 'red';
+                $error = 1;
+            } else {
+                //success
+                $viewee->email = $email_check;
+            }
+            
+            $role_check = $h->cage->post->testAlnumLines('user_role'); // from Users plugin account page
+            // compare with current role and update if different
+            if (!$error && $role_check && ($role_check != $viewee->role)) {
+                $viewee->role = $role_check;
+                $new_perms = $viewee->getDefaultPermissions($h, $role_check);
+                $viewee->setAllPermissions($h, $new_perms);
+                $viewee->updatePermissions($h);
+                if ($role_check == 'killspammed' || $role_check == 'deleted') {
+                    $h->deleteComments($viewee->id); // includes child comments from *other* users
+                    $h->deletePosts($viewee->id); // includes tags and votes for self-submitted posts
+                    
+                    $h->pluginHook('userbase_killspam', '', array('target_user' => $viewee->id));
+                    
+                    if ($role_check == 'deleted') { 
+                        $h->deleteUser($viewee->id); 
+                        $checks['username_check'] = 'deleted';
+                        $h->message = $h->lang["users_account_deleted"];
+                        $h->messageType = 'red';
+                        return $checks; // This will then show a red "deleted" notice
+                    }
+                }
+            }
+            
+            // If we've just edited our own account, let's refresh the cookie so it uses our latest username:
+            if ($h->currentUser->id == $h->cage->post->testInt('userid')) {
+                $h->currentUser->setCookie($h, false);           // delete the cookie
+                $h->currentUser->getUserBasic($h, $h->currentUser->id, '', true);    // re-read the database record to get updated info
+                $h->currentUser->setCookie($h, true);            // create a new, updated cookie
+            }
+        }
+        
+        if (!isset($username_check) && !isset($email_check)) {
+            $username_check = $viewee->name;
+            $email_check = $viewee->email;
+            $role_check = $viewee->role;
+            // do nothing
+        } elseif ($error == 0) {
+            $exists = $h->userExists(0, $username_check, $email_check);
+            if (($exists != 'no') && ($exists != 'error')) { // user exists
+                //success
+                $viewee->updateUserBasic($h, $userid);
+                // only update the cookie if it's your own account:
+                if ($userid == $this->id) { 
+                $h->currentUser->setCookie($h, false);           // delete the cookie
+                $h->currentUser->getUserBasic($h, $h->currentUser->id, '', true);    // re-read the database record to get updated info
+                $h->currentUser->setCookie($h, true);            // create a new, updated cookie
+                }
+                $h->messages[$h->lang['main_user_account_update_success']] = 'green';
+            } else {
+                //fail
+                $h->messages[$h->lang["main_user_account_update_unexpected_error"]] = 'red';
+            }
+        } else {
+            // error must = 1 so fall through and display the form again
+        }
+        
+        //update checks
+        $this->updatePassword($h, $userid);
+        $userid_check = $viewee->id; 
+        $checks['userid_check'] = $userid_check;
+        $checks['username_check'] = $username_check;
+        $checks['email_check'] = $email_check;
+        $checks['role_check'] = $role_check;
+                
+        return $checks;
+    }
+    
+    
+     /**
+     * Enable a user to change their password
+     *
+     * @return bool
+     */
+    public function updatePassword($h, $userid)
+    {
+        // we don't want to edit the password if this isn't our own account.
+        if ($userid != $this->id) { return false; }
+        
+        $error = 0;
+        
+        // Updating password
+        if ($h->cage->post->testAlnumLines('update_type') == 'update_password') {
+        
+            // check CSRF key
+            if (!$h->csrf()) {
+                $h->messages[$h->lang['error_csrf']] = 'red';
+                $error = 1;
+            }
+            
+            
+            $password_check_old = $h->cage->post->testPassword('password_old');    
+            
+            if ($this->loginCheck($h, $this->name, $password_check_old)) {
+                // safe, the old password matches the password for this user.
+            } else {
+                $h->messages[$h->lang['main_user_account_update_password_error_old']] = 'red';
+                $error = 1;
+            }
+        
+            $password_check_new = $h->cage->post->testPassword('password_new');    
+            if ($password_check_new) {
+                $password_check_new2 = $h->cage->post->testPassword('password_new2');    
+                if ($password_check_new2) { 
+                    if ($password_check_new == $password_check_new2) {
+                        // safe, the two new password fields match
+                    } else {
+                        $h->messages[$h->lang['main_user_account_update_password_error_match']] = 'red';
+                        $error = 1;
+                    }
+                } else {
+                    $h->messages[$h->lang['main_user_account_update_password_error_new']] = 'red';
+                    $error = 1;
+                }
+            } else {
+                $h->messages[$h->lang['main_user_account_update_password_error_not_provided']] = 'red';
+                $error = 1;
+            }
+                        
+        }
+                
+        if (!isset($password_check_old) && !isset($password_check_new) && !isset($password_check_new2)) {
+            $password_check_old = "";
+            $password_check_new = "";
+            $password_check_new2 = "";
+            // do nothing
+        } elseif ($error == 0) {
+            $exists = $h->userExists(0, $this->name, $this->email);
+            if (($exists != 'no') && ($exists != 'error')) { // user exists
+                //success
+                $this->password = $this->generateHash($password_check_new);
+                $this->updateUserBasic($h, $this->id); // update the database record for this user
+                $this->setCookie($h, false);           // delete the cookie
+                $this->getUserBasic($h, $this->id, '', true);    // re-read the database record to get updated info
+                $this->setCookie($h, true);            // create a new, updated cookie
+                $h->messages[$h->lang['main_user_account_update_password_success']] = 'green';
+            } else {
+                //fail
+                $h->messages[$h->lang["main_user_account_update_unexpected_error"]] = 'red';
+            }
+        } else {
+            // error must = 1 so fall through and display the form again
+        }
+    }
+    
+    
+     /**
+     * Send a confirmation code to a user who has forgotten his/her password
+     *
+     * @param string $email - already validated above
+     */
+    public function sendPasswordConf($h, $userid, $email)
+    {
+        // generate the email confirmation code
+        $pass_conf = md5(crypt(md5($email),md5($email)));
+        
+        // store the hash in the user table
+        $sql = "UPDATE " . TABLE_USERS . " SET user_password_conf = %s WHERE user_id = %d";
+        $h->db->query($h->db->prepare($sql, $pass_conf, $userid));
+        
+        $line_break = "\r\n\r\n";
+        $next_line = "\r\n";
+
+        if ($h->isActive('signin')) { 
+            $url = BASEURL . 'index.php?page=login&plugin=user_signin&userid=' . $userid . '&passconf=' . $pass_conf; 
+        } else { 
+            $url = BASEURL . 'admin_index.php?page=admin_login&userid=' . $userid . '&passconf=' . $pass_conf; 
+        }
+        
+        // send email
+        $subject = $h->lang['main_user_email_password_conf_subject'];
+        $body = $h->lang['main_user_email_password_conf_body_hello'] . " " . $h->getUserNameFromId($userid);
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_welcome'];
+        $body .= $h->lang['main_user_email_password_conf_body_click'];
+        $body .= $line_break;
+        $body .= $url;
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_no_request'];
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_regards'];
+        $body .= $next_line;
+        $body .= $h->lang['main_user_email_password_conf_body_sign'];
+        $to = $email;
+        $headers = "From: " . SITE_EMAIL . "\r\nReply-To: " . SITE_EMAIL . "\r\nX-Priority: 3\r\n";
+    
+        mail($to, $subject, $body, $headers);    
+        
+        return true;
+    }
+    
+    
+     /**
+     * Reset the user's password to soemthing random and email it.
+     *
+     * @param string $passconf - confirmation code clicked in email
+     */
+    public function newRandomPassword($h, $userid, $passconf)
+    {
+        $email = $h->getEmailFromId($userid);
+        
+        // check the email and confirmation code are a pair
+        $pass_conf_check = md5(crypt(md5($email),md5($email)));
+        if ($pass_conf_check != $passconf) {
+            return false;
+        }
+        
+        // update the password to something random
+        $temp_pass = random_string(10);
+        $sql = "UPDATE " . TABLE_USERS . " SET user_password = %s WHERE user_id = %d";
+        $h->db->query($h->db->prepare($sql, $this->generateHash($temp_pass), $userid));
+        $line_break = "\r\n\r\n";
+        $next_line = "\r\n";
+        
+        if ($h->isActive('signin')) { 
+            $url = BASEURL . 'index.php?page=login&plugin=user_signin'; 
+        } else { 
+            $url = BASEURL . 'admin_index.php?page=admin_login'; 
+        }
+        
+        // send email
+        $subject = $h->lang['main_user_email_new_password_subject'];
+        $body = $h->lang['main_user_email_password_conf_body_hello'] . " " . $h->getUserNameFromId($userid);
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_requested'];
+        $body .= $line_break;
+        $body .= $temp_pass;
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_remember'];
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_pass_change'];
+        $body .= $line_break;
+        $body .= $url; 
+        $body .= $line_break;
+        $body .= $h->lang['main_user_email_password_conf_body_regards'];
+        $body .= $next_line;
+        $body .= $h->lang['main_user_email_password_conf_body_sign'];
+        $to = $email;
+        $headers = "From: " . SITE_EMAIL . "\r\nReply-To: " . SITE_EMAIL . "\r\nX-Priority: 3\r\n";
+    
+        mail($to, $subject, $body, $headers);    
+        
+        return true;
+    }
+}
Index: /trunk/libs/Initialize.php
===================================================================
--- /trunk/libs/Initialize.php	(revision 1081)
+++ /trunk/libs/Initialize.php	(revision 1081)
@@ -0,0 +1,267 @@
+<?php
+/**
+ * Initialize Hotaru
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Initialize
+{
+    protected $db;                          // database object
+    protected $cage;                        // Inspekt object
+    protected $isDebug          = false;    // show db queries and page loading time
+
+
+    /**
+     * Initialize Hotaru with the essentials
+     */
+    public function __construct($h)
+    {
+        // session to be used by CSRF, etc.
+        if (!isset($_SESSION['HotaruCMS'])) {
+            session_start();
+            $_SESSION['HotaruCMS'] = time();
+        }
+
+        // The order here is important!
+        $this->setDefaultTimezone();
+        $this->errorReporting(); 
+        $this->getFiles();
+        $this->db = $this->initDatabase();
+        $this->cage = $this->initInspektCage();
+        
+        $this->readSettings();
+        $this->setUpDatabaseCache();
+        $this->isDebug = $this->checkDebug();
+
+		$this->setUpJsConstants();
+
+        return $this;
+    }
+    
+    
+    /**
+     * Access modifier to set protected properties
+     */
+    public function __set($var, $val)
+    {
+        $this->$var = $val;  
+    }
+    
+    
+    /**
+     * Access modifier to get protected properties
+     */
+    public function __get($var)
+    {
+        return $this->$var;
+    }
+    
+    
+    /**
+     * Error reporting
+     */
+    public function errorReporting()
+    {
+        // display errors
+        ini_set('display_errors', 1); // Gets disabled later in checkDebug()
+        error_reporting(E_ALL);
+        
+        // log errors to a file - the custom error handler below wasn't catching fatal errors, so using PHP's one
+        ini_set('error_log', CACHE . 'debug_logs/error_log.txt');
+        /*
+        require_once(EXTENSIONS . 'SWCMS/swcms_error_handler.php'); // error_handler class
+        $error_handler = new swcms_error_handler(0, 0, 1, NULL, CACHE . 'debug_logs/error_log.txt');
+        set_error_handler(array($error_handler, "handler"));
+        */
+    }
+
+
+    /**
+     * Set the timezone
+     */
+    public function setDefaultTimezone()
+    {
+        // set timezone
+        $version = explode('.', phpversion());
+        if($version[0] > 4){
+            $tmz = date_default_timezone_get();
+            date_default_timezone_set($tmz);
+        }
+    }
+
+
+    /**
+     * Include necessary files
+     */
+    public function getFiles()
+    {
+        // include third party libraries
+        require_once(EXTENSIONS . 'csrf/csrf_class.php'); // protection against CSRF attacks
+        require_once(EXTENSIONS . 'Inspekt/Inspekt.php'); // sanitation
+        require_once(EXTENSIONS . 'ezSQL/ez_sql_core.php'); // database
+        require_once(EXTENSIONS . 'ezSQL/mysql/ez_sql_mysql.php'); // database
+        
+        // include libraries
+        require_once(LIBS . 'Avatar.php');          // for displaying avatars
+        require_once(LIBS . 'IncludeCssJs.php');    // for including and mergeing css and javascript
+        require_once(LIBS . 'InspektExtras.php');   // for custom Inspekt methods
+        require_once(LIBS . 'Language.php');
+        require_once(LIBS . 'PageHandling.php');    // for page handling
+        require_once(LIBS . 'Plugin.php');          // for plugin properties
+        require_once(LIBS . 'PluginFunctions.php'); // for plugin functions
+        require_once(LIBS . 'PluginSettings.php');  // for plugin settings
+        require_once(LIBS . 'Post.php');            // for posts
+        require_once(LIBS . 'UserBase.php');        // for users, settings and permissions
+        require_once(LIBS . 'UserAuth.php');        // for user authentication, login and registering
+        
+        // include functions
+        require_once(FUNCTIONS . 'funcs.strings.php');
+        require_once(FUNCTIONS . 'funcs.arrays.php');
+        require_once(FUNCTIONS . 'funcs.times.php');
+        require_once(FUNCTIONS . 'funcs.files.php');
+        
+    }
+
+    
+    /**
+     * Initialize Database
+     *
+     * @return object
+     */
+    public function initDatabase()
+    {
+        $ezSQL = new ezSQL_mysql(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
+        $ezSQL->query("SET NAMES 'utf8'");
+        
+        return $ezSQL;
+    }
+    
+    
+    /**
+     * Initialize Inspekt
+     *
+     * @return object
+     */
+    public function initInspektCage()
+    {
+        $cage = Inspekt::makeSuperCage(); 
+    
+        // Add Hotaru custom methods
+        $cage->addAccessor('testAlnumLines');
+        $cage->addAccessor('testPage');
+        $cage->addAccessor('testUsername');
+        $cage->addAccessor('testPassword');
+        $cage->addAccessor('getFriendlyUrl');
+        $cage->addAccessor('getMixedString1');
+        $cage->addAccessor('getMixedString2');
+        $cage->addAccessor('getHtmLawed');
+        
+        return $cage;
+    }
+    
+    
+    /**
+     * Returns all site settings
+     *
+     * @return bool
+     */
+    public function readSettings()
+    {
+        $sql = "SELECT * FROM " . TABLE_SETTINGS;
+        $settings = $this->db->get_results($this->db->prepare($sql));
+        
+        if(!$settings) { return false; }
+        
+        // Make Hotaru settings global constants
+        foreach ($settings as $setting)
+        {
+            if (!defined($setting->settings_name)) { 
+                define($setting->settings_name, $setting->settings_value);
+            }
+        }
+        return true;
+    }
+    
+
+    /**
+     * Set up database cache
+     *
+     * Note: Queries are still only cached following $this->db->cache_queries = true;
+     */
+    public function setUpDatabaseCache()
+    {
+        // Setup database cache
+        $this->db->cache_timeout = DB_CACHE_DURATION; // Note: this is hours
+        $this->db->cache_dir = CACHE . 'db_cache';
+        if (DB_CACHE_ON == "true") {
+            $this->db->use_disk_cache = true;
+            return true;
+        } else {
+            $this->db->use_disk_cache = false;
+            return false;
+        }   
+    }
+    
+    
+    /**
+     * Debug timer
+     *
+     * @ return bool 
+     */
+    public function checkDebug()
+    {
+        // Start timer if debugging
+        if (DEBUG == "true") {
+            require_once(FUNCTIONS . 'funcs.times.php');
+            timer_start();
+            ini_set('display_errors', 1); // show errors
+            ini_set('error_log', CACHE . 'debug_logs/error_log.txt');
+            return true;
+        } else {
+            ini_set('display_errors', 0); // hide errors
+        }
+        
+        return false;
+    }
+
+	/**
+     * Get JQuery Globals
+     *
+     *  
+     */
+    public function setUpJsConstants()
+    {
+        // Start timer if debugging
+		$global_js_var = "jQuery('document').ready(function($) {BASEURL = '". BASEURL ."'; ADMIN_THEME = '" . ADMIN_THEME . "'; THEME = '" . THEME . "';});";	
+		$JsConstantsFile = "css_js_cache/JavascriptConstants.js";
+
+		if (!file_exists(CACHE . $JsConstantsFile)) {
+			$JsConstantsPath = CACHE . $JsConstantsFile;
+			$JsConstantsfh = fopen($JsConstantsPath, 'w') or die ("Can't open file");	
+			fwrite($JsConstantsfh, $global_js_var);
+			fclose($JsConstantsfh);		
+		}        
+        return false;
+    }
+        
+}
+?>
Index: /trunk/libs/Comment.php
===================================================================
--- /trunk/libs/Comment.php	(revision 1081)
+++ /trunk/libs/Comment.php	(revision 1081)
@@ -0,0 +1,496 @@
+<?php
+/**
+ * The Comment class contains some useful methods for using comments
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+    
+class Comment
+{
+    protected $id           = 0;
+    protected $parent       = 0;
+    protected $postId       = 0;
+    protected $author       = 0;
+    protected $date         = '';
+    protected $status       = 'approved';
+    protected $votes        = 0;
+    protected $content      = '';
+    protected $type         = 'newcomment';   // or "editcomment"
+    protected $subscribe    = 0;
+    protected $levels       = 0;         // max nesting levels
+    protected $depth        = 0;         // this nesting level
+    protected $email        = '';
+    protected $allowableTags = '';
+    protected $itemsPerPage = 20;
+    protected $pagination   = '';
+    protected $thisForm     = '';
+    protected $allForms     = 'checked';
+    protected $avatars      = '';
+    protected $avatarSize   = 16;
+    protected $voting       = '';
+    protected $order        = 'asc';   // oldest comments first
+
+    
+    /**
+     * Access modifier to set protected properties
+     */
+    public function __set($var, $val)
+    {
+        $this->$var = $val;  
+    }
+    
+    
+    /**
+     * Access modifier to get protected properties
+     */
+    public function __get($var)
+    {
+        return $this->$var;
+    }
+
+    
+    /**
+     * Count comments
+     *
+     * @param bool $link - true used for "comments" link, false for top of actual comments
+     * @return string - text to show in the link, e.g. "3 comments"
+     */
+    function countComments($h, $link = true)
+    {
+        $sql = "SELECT COUNT(comment_id) FROM " . TABLE_COMMENTS . " WHERE comment_post_id = %d AND comment_status = %s";
+        $num_comments = $h->db->get_var($h->db->prepare($sql, $h->post->id, 'approved'));
+        
+        if ($num_comments == 1) {
+            return "1 " . $h->lang['comments_singular_link'];
+        } elseif ($num_comments > 1) {
+            return $num_comments . " " . $h->lang['comments_plural_link'];
+        } else {
+            if (!$link) { 
+                return $h->lang['comments_leave_comment'];  // shows "Leave a comment" above comment form when no comments
+            }
+            else
+            {
+                return $h->lang['comments_none_link']; // Shows "No comments"
+            }
+        }
+    }
+    
+    
+    /**
+     * Read all comment parents
+     *
+     * @param int $post_id - the id of the post this comment is on
+     * @param array|false
+     */
+    function readAllParents($h, $post_id, $order = "ASC")
+    {
+        $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_post_id = %d AND comment_parent = %d ORDER BY comment_date " . $order;
+        $parents = $h->db->get_results($h->db->prepare($sql, $post_id, 0));
+        
+        if($parents) { return $parents; } else { return false; }
+    }
+
+
+    /**
+     * Read all comment children
+     *
+     * @param int $parent - the id of the parent comment
+     * @param array|false
+     */
+    function readAllChildren($h, $parent)
+    {
+        $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_parent = %d ORDER BY comment_date";
+        $children = $h->db->get_results($h->db->prepare($sql, $parent));
+        
+        if($children) { return $children; } else { return false; }
+    }
+    
+    
+    /**
+     * Get comment from database
+     *
+     * @param int $comment_id
+     * @return array|false
+     */
+    function getComment($h, $comment_id = 0)
+    {
+        $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_id = %d";
+        $comment = $h->db->get_row($h->db->prepare($sql, $comment_id));
+
+        if($comment) { return $comment; } else { return false; }
+    }
+    
+    
+    /**
+     * Get all comments from database
+     *
+     * @param int $post_id - you can limit comments to a single post
+     * @return array|false
+     */
+    function getAllComments($h, $post_id = 0, $order = "ASC", $limit = 0, $userid = 0)
+    {
+        // limiting is used in the rssFeed function. Other than that, pagination does limiting for us.
+        if(!$limit) { $limit = ''; } else { $limit = " LIMIT "  .$limit; }
+        
+        if ($post_id) {
+            // get all comments from specified post
+            $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_post_id = %d AND comment_status = %s ORDER BY comment_date " . $order;
+            $comments = $h->db->get_results($h->db->prepare($sql, $post_id, 'approved'));
+        } else {
+            // get all comments
+            if ($userid) { 
+                $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_status = %s AND comment_user_id = %d ORDER BY comment_date " . $order . $limit;
+                $comments = $h->db->get_results($h->db->prepare($sql, 'approved', $userid));
+            } else {
+                $sql = "SELECT * FROM " . TABLE_COMMENTS . " WHERE comment_status = %s ORDER BY comment_date " . $order . $limit;
+                $comments = $h->db->get_results($h->db->prepare($sql, 'approved'));
+            }
+        }
+        
+        if($comments) { return $comments; } else { return false; }
+    }
+    
+    
+    /**
+     * Read comment
+     *
+     * @param array $comment
+     */
+    function readComment($h, $comment = array())
+    {
+        $this->id = $comment->comment_id;
+        $this->parent = $comment->comment_parent;
+        $this->postId = $comment->comment_post_id;
+        $this->author = $comment->comment_user_id;
+        $this->date = $comment->comment_date;
+        $this->status = $comment->comment_status;
+        $this->votes_up = $comment->comment_votes_up;
+        $this->votes_down = $comment->comment_votes_down;
+        $this->content = urldecode($comment->comment_content);
+        $this->subscribe = $comment->comment_subscribe;
+        
+        $h->pluginHook('comment_read_comment');
+        
+        return $this;
+    }
+    
+    
+    /**
+     * Add comment
+     *
+     * @return true
+     */
+    function addComment($h)
+    {
+        $h->pluginHook('comment_pre_add_comment');  // Akismet uses this to change the status
+        
+        $can_comment = $h->currentUser->getPermission('can_comment'); // This was already checked, but Akismet sometimes reverts the status, so we do it again.
+
+        if ($can_comment == 'mod') { $this->status = 'pending'; } // forces all to 'pending' if user's comments are moderated
+        
+        // Get settings from database...
+        $comments_settings = $h->getSerializedSettings('comments');
+
+        $set_pending = $comments_settings['comment_set_pending'];
+        $daily_limit = $comments_settings['comment_daily_limit'];
+        $url_limit = $comments_settings['comment_url_limit'];
+        
+        if ($set_pending == 'some_pending') {
+            $comments_approved = $this->commentsApproved($h, $h->currentUser->id);
+            $x_comments_needed = $comments_settings['comment_x_comments'];
+        }
+        
+        if ($h->currentUser->role == 'member') {
+            if ($daily_limit && ($daily_limit < $this->countDailyComments($h))) { $this->status = 'pending'; } // exceeded daily limit, set to pending
+            if ($url_limit && ($url_limit < $this->countUrls())) { $this->status = 'pending'; } // exceeded url limit, set to pending
+        }
+                    
+        if ($set_pending == 'all_pending') {
+            $this->status = 'pending'; 
+        } elseif (($set_pending == 'some_pending') && ($comments_approved <= $x_comments_needed)) {
+            $this->status = 'pending'; 
+        } 
+                
+        $sql = "INSERT INTO " . TABLE_COMMENTS . " SET comment_post_id = %d, comment_user_id = %d, comment_parent = %d, comment_date = CURRENT_TIMESTAMP, comment_status = %s, comment_content = %s, comment_subscribe = %d, comment_updateby = %d";
+                
+        $h->db->query($h->db->prepare($sql, $this->postId, $this->author, $this->parent, $this->status, urlencode(trim(stripslashes($this->content))), $this->subscribe, $h->currentUser->id));
+        
+        $last_insert_id = $h->db->get_var($h->db->prepare("SELECT LAST_INSERT_ID()"));
+        
+        $this->id = $last_insert_id;
+        $h->vars['last_insert_id'] = $last_insert_id;    // make it available outside this class
+        
+        $h->pluginHook('comment_post_add_comment');
+        
+        return true;
+    }
+    
+
+    /**
+     * Edit comment
+     *
+     * @return true
+     */
+    function editComment($h)
+    {
+        $sql = "UPDATE " . TABLE_COMMENTS . " SET comment_status = %s, comment_content = %s, comment_subscribe = %d, comment_updateby = %d WHERE comment_id = %d";
+        $h->db->query($h->db->prepare($sql, $this->status, urlencode(trim(stripslashes($this->content))), $this->subscribe, $h->currentUser->id, $this->id));
+        
+        $h->comment->id = $this->id; // a small hack to get the id for use in plugins.
+        $h->pluginHook('comment_update_comment');
+        
+        return true;
+    }
+
+
+    /**
+     * Physically delete a comment from the database 
+     *
+     */    
+    public function deleteComment($h)
+    {
+        $sql = "DELETE FROM " . TABLE_COMMENTS . " WHERE comment_id = %d";
+        $h->db->query($h->db->prepare($sql, $this->id));
+        
+        // delete any votes for this comment
+        //$sql = "DELETE FROM " . TABLE_COMMENTVOTES . " WHERE cvote_comment_id = %d";
+        //$h->db->query($h->db->prepare($sql, $this->id));
+        
+        $h->comment->id = $this->id; // a small hack to get the id for use in plugins.
+        $h->pluginHook('comment_delete_comment');
+    }
+    
+    
+    /**
+     * Physically delete all comments by a specified user (and responses)
+     *
+     * @param array $user_id
+     * @return bool
+     */
+    public function deleteComments($h, $user_id) 
+    {
+        $sql = "SELECT comment_id FROM " . DB_PREFIX . "comments WHERE comment_user_id = %d";
+        $results = $h->db->get_results($h->db->prepare($sql, $user_id));
+
+        if ($results) {
+            foreach ($results as $r) {
+                $h->comment->id = $r->comment_id;   // used by other plugins in "comment_delete_comment" function/hook
+                $this->deleteComment($h);    // delete parent comment
+                $this->deleteCommentTree($h, $h->comment->id);  // delete all children of that comment regardless of user
+            }
+        }
+        
+        return true;
+    }
+    
+    
+    /**
+     * Recurse through comment tree, deleting all
+     *
+     * @param int $comment_id - id of current comment
+     * @return bool
+     */
+    public function deleteCommentTree($h, $comment_id)
+    {
+        while ($children = $this->readAllChildren($h, $comment_id)) {
+            foreach ($children as $child) {
+                $this->readComment($h, $child);
+                $this->deleteComment($h);
+                if ($this->deletecommentTree($h, $this->id)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+    }
+    
+    
+    /**
+     * Recurse through comment tree, setting all to 'pending'
+     *
+     * @param int $comment_id - id of current comment
+     * @return bool
+     */
+    public function setPendingCommentTree($h, $comment_id)
+    {
+        while ($children = $this->readAllChildren($h, $comment_id)) {
+            foreach ($children as $child) {
+                $this->readComment($h, $child);
+                $this->status = 'pending';
+                $this->editComment($h);
+                if ($this->setPendingCommentTree($h, $this->id)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+    }
+    
+    
+    /**
+     * Determine if the comment form is open or closed
+     *
+     * @param int $post_id
+     * @return string 'open' or 'closed'
+     */
+    function formStatus($h, $type)
+    {
+        if ($type == 'select') {
+            $sql = "SELECT post_comments FROM " . TABLE_POSTS . " WHERE post_id = %d";
+            $form_status = $h->db->get_var($h->db->prepare($sql, $h->post->id));
+            
+            if ($form_status) { return $form_status; } else { return 'open'; } // default 'open'
+        }
+        
+        if ($type == 'open' || $type == 'closed') {
+            $h->comment->form = $type;
+            $sql = "UPDATE " . TABLE_POSTS . " SET post_comments = %s WHERE post_id = %d";
+            $h->db->query($h->db->prepare($sql, $type, $h->post->id));
+        }
+    }
+    
+    
+    /**
+     * Unsubscribe from a thread
+     *
+     * @param int $post_id
+     * @return true
+     */
+    function unsubscribe($h, $post_id)
+    {
+        $h->readPost($post_id);
+            
+        $sql = "UPDATE " . TABLE_COMMENTS . " SET comment_subscribe = %d WHERE comment_post_id = %d AND comment_user_id = %d";
+        $h->db->query($h->db->prepare($sql, 0, $h->post->id, $h->currentUser->id));
+               
+        // Check if the currentUser is the post author
+        if ($h->post->author == $h->currentUser->id) {
+        // Check if the user subscribed to comments as a submitter
+            if ($h->post->subscribe == 1) { 
+                $sql = "UPDATE " . TABLE_POSTS . " SET post_subscribe = %d WHERE post_id = %d AND post_author = %d";
+                $h->db->query($h->db->prepare($sql, 0, $h->post->id, $h->currentUser->id));
+            } 
+        }
+        return true;
+    }
+    
+    
+    /**
+     * Update thread subscription 
+     *
+     * @param int $post_id
+     * @return true
+     */
+    function updateSubscribe($h, $post_id)
+    {
+        if ($this->comment_subscribe == 1)
+        {
+            $sql = "UPDATE " . TABLE_COMMENTS . " SET comment_subscribe = %d WHERE comment_post_id = %d AND comment_user_id = %d";
+            $h->db->query($h->db->prepare($sql, 1, $h->post->id, $h->currentUser->id));
+        } 
+        else 
+        {
+            $this->unsubscribe($h, $post_id);
+        }
+    }
+    
+    
+    /**
+     * Count how many approved comments a user has had
+     *
+     * @param int $userid 
+     * @return int 
+     */
+    public function commentsApproved($h, $userid)
+    {
+        $sql = "SELECT COUNT(*) FROM " . TABLE_COMMENTS . " WHERE comment_status = %s AND comment_user_id = %d";
+        $count = $h->db->get_var($h->db->prepare($sql, 'approved', $userid));
+        
+        return $count;
+    }
+    
+    
+    /**
+     * Count daily comments for this commenter
+     *
+     * @return int 
+     */
+    public function countDailyComments($h)
+    {
+        $start = date('YmdHis', strtotime("now"));
+        $end = date('YmdHis', strtotime("-1 day"));
+        $sql = "SELECT COUNT(comment_id) FROM " . TABLE_COMMENTS . " WHERE comment_archived = %s AND comment_user_id = %d AND (comment_date >= %s AND comment_date <= %s)";
+        $count = $h->db->get_var($h->db->prepare($sql, 'N', $this->author, $end, $start));
+        
+        return $count;
+    }
+    
+    
+    /**
+     * Count urls in comment
+     *
+     * @return int 
+     * @link http://www.liamdelahunty.com/tips/php_url_count_check_for_comment_spam.php
+     */
+    public function countUrls()
+    {
+        $text = $this->content;
+        
+        //$http = substr_count($text, "http");
+        $href = substr_count($text, "href");
+        $url = substr_count($text, "[url");
+        
+        return $href + $url;
+    }
+    
+    
+    /**
+     * Stats for Admin homepage
+     *
+     * @param string $stat_type
+     * @return int
+     */
+    public function stats($h, $stat_type = '')
+    {
+        switch ($stat_type) {
+            case 'total_comments':
+                $sql = "SELECT count(comment_id) FROM " . TABLE_COMMENTS;
+                $comments = $h->db->get_var($sql);
+                break;
+            case 'approved_comments':
+                $sql = "SELECT count(comment_id) FROM " . TABLE_COMMENTS . " WHERE comment_status = %s";
+                $comments = $h->db->get_var($h->db->prepare($sql, 'approved'));
+                break;
+            case 'pending_comments':
+                $sql = "SELECT count(comment_id) FROM " . TABLE_COMMENTS . " WHERE comment_status = %s";
+                $comments = $h->db->get_var($h->db->prepare($sql, 'pending'));
+                break;
+            default:
+                $comments = '';
+        }
+        
+        return $comments;
+    }
+}
+?>
Index: /trunk/libs/Blocked.php
===================================================================
--- /trunk/libs/Blocked.php	(revision 1081)
+++ /trunk/libs/Blocked.php	(revision 1081)
@@ -0,0 +1,250 @@
+<?php
+/**
+ * Functions for the Blocked list
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Blocked
+{
+     /**
+     * Prepare a list of blocked items for the Admin "Blocked List" page
+     */
+    public function buildBlockedList($h)
+    {
+        $safe = true; // CSRF flag
+        
+        if ($h->cage->post->keyExists('type')) {
+            $safe = $h->csrf();
+            if (!$safe) {
+                $h->message = $h->lang['error_csrf'];
+                $h->messageType = 'red';
+            }
+        }
+        
+        // if new item to block
+        if ($safe && $h->cage->post->getAlpha('type') == 'new')
+        {
+            $type = $h->cage->post->testAlnumLines('blocked_type');
+            $value = $h->cage->post->getMixedString2('value');
+            
+            if (!$value) {
+                $h->message = $h->lang['admin_blocked_list_empty'];
+                $h->messageType = 'red';
+            } else {
+                $this->addToBlockedList($h, $type, $value);
+            }
+        }
+        
+        // if edit item
+        if ($safe && $h->cage->post->getAlpha('type') == 'edit')
+        {
+            $id = $h->cage->post->testInt('id');
+            $type = $h->cage->post->testAlnumLines('blocked_type');
+            $value = $h->cage->post->getMixedString2('value');
+            $this->updateBlockedList($h, $id, $type, $value);
+            $h->message = $h->lang['admin_blocked_list_updated'];
+            $h->messageType = 'green';
+        }
+        
+        // if remove item
+        if ($safe && ($h->cage->get->getAlpha('action') == 'remove'))
+        {
+            $id = $h->cage->get->testInt('id');
+            $this->removeFromBlockedList($h->db, $id);
+            $h->message = $h->lang["admin_blocked_list_removed"];
+            $h->messageType = 'green';
+        }
+        
+        // GET CURRENTLY BLOCKED ITEMS...
+        
+        $where_clause = '';
+        
+        // if search
+        if ($safe && $h->cage->post->getAlpha('type') == 'search') {
+            $search_term = $h->cage->post->getMixedString2('search_value');
+            $where_clause = " WHERE blocked_value LIKE '%" . trim($h->db->escape($search_term)) . "%'";
+        }
+        
+        // if filter
+        if ($safe && $h->cage->post->getAlpha('type') == 'filter') {
+            $filter = $h->cage->post->testAlnumLines('blocked_type');
+            if ($filter == 'all') { $where_clause = ''; } else { $where_clause = " WHERE blocked_type = %s"; }
+        }
+        
+        // SQL
+        $sql = "SELECT * FROM " . TABLE_BLOCKED . $where_clause;
+
+        if (isset($search_term)) { 
+            $blocked_items = $h->db->get_results($sql);
+        } elseif (isset($filter)) { 
+            $blocked_items = $h->db->get_results($h->db->prepare($sql, $filter));
+        } else {
+            $blocked_items = $h->db->get_results($h->db->prepare($sql));
+        }
+        
+        if (!$blocked_items) { return array(); }
+        
+        $pg = $h->cage->get->getInt('pg');
+        $items = 20;
+        $output = "";
+        
+        require_once(EXTENSIONS . 'Paginated/Paginated.php');
+        require_once(EXTENSIONS . 'Paginated/DoubleBarLayout.php');
+        $pagedResults = new Paginated($blocked_items, $items, $pg);
+        
+        $alt = 0;
+        while($block = $pagedResults->fetchPagedRow()) {    //when $story is false loop terminates    
+            $alt++;
+            $output .= "<tr class='table_row_" . $alt % 2 . "'>\n";
+            $output .= "<td>" . $block->blocked_type . "</td>\n";
+            $output .= "<td>" . $block->blocked_value . "</td>\n";
+            $output .= "<td>" . "<a class='table_drop_down' href='#'>\n";
+            $output .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/edit.png'>" . "</a></td>\n";
+            $output .= "<td>" . "<a href='" . BASEURL . "admin_index.php?page=blocked_list&amp;action=remove&amp;id=" . $block->blocked_id . "'>\n";
+            $output .= "<img src='" . BASEURL . "content/admin_themes/" . ADMIN_THEME . "images/delete.png'>" . "</a></td>\n";
+            $output .= "</tr>\n";
+            $output .= "<tr class='table_tr_details' style='display:none;'>\n";
+            $output .= "<td colspan=3 class='table_description'>\n";
+            $output .= "<form name='blocked_list_edit_form' action='" . BASEURL . "admin_index.php' method='post'>\n";
+            $output .= "<table><tr><td><select name='blocked_type'>\n";
+            
+            switch($block->blocked_type) { 
+                case 'url':
+                    $text = $h->lang["admin_theme_blocked_url"];
+                    break;
+                case 'email':
+                    $text = $h->lang["admin_theme_blocked_email"];
+                    break;
+                default:
+                    $text = $h->lang["admin_theme_blocked_ip"];
+                    break;
+            }
+            
+            $output .= "<option value='" . $block->blocked_type . "'>" . $text . "</option>\n";
+            $output .= "<option value='ip'>" . $h->lang["admin_theme_blocked_ip"] . "</option>\n";
+            $output .= "<option value='url'>" . $h->lang["admin_theme_blocked_url"] . "</option>\n";
+            $output .= "<option value='email'>" . $h->lang["admin_theme_blocked_email"] . "</option>\n";
+            $output .= "<option value='user'>" . $h->lang["admin_theme_blocked_username"] . "</option>\n";
+            $output .= "</select></td>\n";
+            $output .= "<td><input type='text' size=30 name='value' value='" . $block->blocked_value . "' /></td>\n";
+            $output .= "<td><input class='submit' type='submit' value='" . $h->lang['admin_blocked_list_update'] . "' /></td>\n";
+            $output .= "</tr></table>\n";
+            $output .= "<input type='hidden' name='id' value='" . $block->blocked_id . "' />\n";
+            $output .= "<input type='hidden' name='page' value='blocked_list' />\n";
+            $output .= "<input type='hidden' name='type' value='edit' />\n";
+            $output .= "<input type='hidden' name='csrf' value='" . $h->csrfToken . "' />";
+            $output .= "</form>\n";
+            $output .= "</td>";
+            $output .= "<td class='table_description_close'><a class='table_hide_details' href='#'>" . $h->lang["admin_theme_plugins_close"] . "</a></td>";
+            $output .= "</tr>";
+        }
+
+        $blocked_array = array('blocked_items' => $output, 'pagedResults' => $pagedResults);
+        
+        return $blocked_array;
+    }
+    
+    
+     /**
+     * Add or update blocked items 
+     *
+     * @param string $type - e.g. url, email, ip
+     * @param string $value - item to block
+     * @param bool $msg - show a success/failure message on Maintenance page
+     * @return bool
+     */
+    public function addToBlockedList($h, $type = '', $value = 0, $msg = true)
+    {
+        $sql = "SELECT blocked_id FROM " . TABLE_BLOCKED . " WHERE blocked_type = %s AND blocked_value = %s"; 
+        $id = $h->db->get_var($h->db->prepare($sql, $type, $value));
+        
+        if ($id) { // already exists
+            if ($msg) { 
+                $h->message = $h->lang['admin_blocked_list_exists']; 
+                $h->messageType = 'red';
+            }
+            return false;
+        } 
+        
+        $sql = "INSERT INTO " . TABLE_BLOCKED . " (blocked_type, blocked_value, blocked_updateby) VALUES (%s, %s, %d)"; 
+        $h->db->query($h->db->prepare($sql, $type, $value, $h->currentUser->id));
+        if ($msg) { 
+            $h->message = $h->lang['admin_blocked_list_added']; 
+            $h->messageType = 'green';
+        }
+        
+        return true;
+    }
+    
+    
+     /**
+     * Add to or update items 
+     *
+     * @return array|false
+     */
+    public function updateBlockedList($h, $id = 0, $type = '', $value = 0)
+    {
+        $sql = "UPDATE " . TABLE_BLOCKED . " SET blocked_type = %s, blocked_value = %s, blocked_updateby = %d WHERE blocked_id = %d"; 
+        $h->db->query($h->db->prepare($sql, $type, $value, $h->currentUser->id, $id));
+    }
+    
+    
+     /**
+     * Remove from blocked list
+     */
+    public function removeFromBlockedList($db, $id = 0)
+    {
+        $sql = "DELETE FROM " . TABLE_BLOCKED . " WHERE blocked_id = %d"; 
+        $db->get_var($db->prepare($sql, $id));
+    }
+    
+    
+     /**
+     * Check if a value is blocked
+     *
+     * Note: Other methods for the Blocked List can be found in the Admin class
+     *
+     * @param string $type - i.e. ip, url, email, user
+     * @param string $value
+     * @param bool $like - used for LIKE sql if true
+     * @return bool
+     */
+    public function isBlocked($db, $type = '', $value = '', $operator = '=')
+    {
+        $exists = 0;
+        
+        // if both type and value provided...
+        if ($type && $value) {
+            $sql = "SELECT blocked_value FROM " . TABLE_BLOCKED . " WHERE blocked_type = %s AND blocked_value " . $operator . " %s"; 
+            $exists = $db->get_var($db->prepare($sql, $type, $value));
+        } 
+        // if only value provided...
+        elseif ($value) 
+        {
+            $sql = "SELECT blocked_value FROM " . TABLE_BLOCKED . " WHERE blocked_value " . $operator . " %s"; 
+            $exists = $db->get_var($db->prepare($sql, $value));
+        }
+        
+        if ($exists) { return true; } else { return false; }
+    }
+}
+?>
Index: /trunk/libs/InspektExtras.php
===================================================================
--- /trunk/libs/InspektExtras.php	(revision 1081)
+++ /trunk/libs/InspektExtras.php	(revision 1081)
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Extends Inspekt with custom Hotaru methods
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+ 
+require_once(EXTENSIONS . 'Inspekt/Inspekt/AccessorAbstract.php');
+ 
+class testAlnumLines extends AccessorAbstract {
+
+   /**
+    * a function to test for chars, digits, underscores and dashes.
+    *
+    * @return bool
+    */
+    protected function inspekt($val)
+    {
+        if (preg_match('/^([a-z0-9_-])+$/i', $val)) {
+            return $val;
+        } else {
+            return false;
+        }
+   }
+}
+
+
+class testPage extends AccessorAbstract {
+
+   /**
+    * a function to test for a valid pagename
+    *
+    * @return bool
+    */
+    protected function inspekt($val)
+    {
+        if (preg_match('/^([a-z0-9\/_-])+$/i', $val)) {
+            return $val;
+        } else {
+            return false;
+        }
+   }
+}
+
+
+class testUsername extends AccessorAbstract {
+
+   /**
+    * a function to test for a valid username
+    *
+    * @return bool
+    */
+    protected function inspekt($val)
+    {
+        if (preg_match('/^([a-z0-9_-]{4,32})+$/i', $val)) {
+            return $val;
+        } else {
+            return false;
+        }
+   }
+}
+
+
+class testPassword extends AccessorAbstract {
+
+   /**
+    * a function to test for a valid password
+    *
+    * @return bool
+    */
+    protected function inspekt($val)
+    {
+        if (preg_match('/^([a-z0-9@*#_-]{8,60})+$/i', $val)) {
+            return $val;
+        } else {
+            return false;
+        }
+   }
+}
+
+
+class getFriendlyUrl extends AccessorAbstract {
+
+   /**
+    * a function to makea url friendly
+    *
+    * @return string
+    */
+    protected function inspekt($val)
+    {
+        return make_url_friendly($val);
+   }
+}
+
+
+class getMixedString1 extends AccessorAbstract {
+
+   /**
+    * a function to sanitize a string with htmlentities
+    *
+    * @return string
+    */
+    protected function inspekt($val)
+    {
+        return sanitize($val, 1);
+   }
+}
+
+
+class getMixedString2 extends AccessorAbstract {
+
+   /**
+    * a function to sanitize a string without htmlentities
+    *
+    * @return string
+    */
+    protected function inspekt($val)
+    {
+        return sanitize($val, 2);
+   }
+}
+
+
+class getHtmLawed extends AccessorAbstract {
+
+   /**
+    * a function to filter HTML
+    *
+    * @return string
+    */
+    protected function inspekt($text)
+    {
+        $config = array('safe' => 1);
+        
+        // Allow plugins to alter the value of $config/
+        // Plugins should return an array, e.g. array('safe' => 1); 
+        require_once(BASE . 'Hotaru.php');
+        $h = new Hotaru();
+        $results = $h->pluginHook('hotaru_inspekt_htmlawed_config');
+        if (is_array($results)) {
+            foreach ($results as $res) {
+                // THIS LOOKS WEIRD. IT NEEDS A RETHINK /Nick
+                $config = $res; // $config takes on the value returned from the last plugin using this hook.
+            }
+        }
+        
+        require_once(EXTENSIONS . 'htmLawed/htmLawed.php');
+        
+        if (!get_magic_quotes_gpc()) {
+            return htmLawed($text, $config);
+        }
+        else 
+        {
+            return htmLawed(stripslashes($text), $config);
+        }
+        return false;
+   }
+}
+
+?>
Index: /trunk/libs/Messages.php
===================================================================
--- /trunk/libs/Messages.php	(revision 1081)
+++ /trunk/libs/Messages.php	(revision 1081)
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Message functions, i.e. for the red and green success and error messages
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class Messages
+{
+    /**
+     * Display a SINGLE success or failure message
+     *
+     * @param object $h
+     * @param string $msg
+     * @param string $msg_type ('green' or 'red')
+     * 
+     *  Usage:
+     *    Longhand:
+     *        $this->hotaru->message = "This is a message";
+     *        $this->hotaru->messageType = "green";
+     *        $this->hotaru->showMessage();
+     *        
+     *    Shorthand:
+     *        $this->hotaru->showMessage("This is a message", "green");
+     */
+    public function showMessage($h, $msg = '', $msg_type = 'green')
+    {
+        if ($msg != '') {
+            echo "<div class='message " . $msg_type . "'>" . $msg . "</div>";
+        } elseif ($h->message != '') {
+            echo "<div class='message " . $h->messageType . "'>" . 
+            $h->message . "</div>";
+        }
+    }
+    
+    
+    /**
+     * Displays ALL success or failure messages
+     *
+     * @param object $h
+     *
+     *  Usage:
+     *        $this->hotaru->messages['This is a message'] = "green";
+     *        $this->hotaru->showMessages();
+     */
+    public function showMessages($h)
+    {
+        if ($h->messages) {
+            foreach ($h->messages as $msg => $msg_type) {
+                echo "<div class='message " . $msg_type . "'>" . 
+                $msg . "</div>";
+            }
+        }
+    }
+}
+?>
Index: /trunk/libs/AdminPages.php
===================================================================
--- /trunk/libs/AdminPages.php	(revision 1081)
+++ /trunk/libs/AdminPages.php	(revision 1081)
@@ -0,0 +1,421 @@
+<?php
+/**
+ * Functions for Admin pages, e.g. settings, maintenance, blocked list...
+ *
+ * PHP version 5
+ *
+ * LICENSE: Hotaru CMS 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 3 of 
+ * the License, or (at your option) any later version. 
+ *
+ * Hotaru CMS 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. 
+ *
+ * You should have received a copy of the GNU General Public License along 
+ * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
+ * 
+ * @category  Content Management System
+ * @package   HotaruCMS
+ * @author    Nick Ramsay <admin@hotarucms.org>
+ * @copyright Copyright (c) 2009, Hotaru CMS
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @link      http://www.hotarucms.org/
+ */
+class AdminPages
+{
+     /**
+     * Admin Pages
+     */
+    public function pages($h, $page = 'admin_login')
+    {
+        $h->vars['admin_sidebar_layout'] = 'vertical';
+        
+        $h->pluginHook('admin_pages');
+        
+        switch ($page) {
+            case "admin_login":
+                $h->sidebars = false;
+                $h->adminLoginLogout('login');
+                break;
+            case "admin_logout":
+                $h->adminLoginLogout('logout');
+                break;
+            case "admin_account":
+                $h->vars['admin_account'] = $this->adminAccount($h);
+                break;
+            case "settings":
+                $h->vars['admin_settings'] = $this->settings($h);
+                break;
+            case "maintenance":
+                $this->maintenanceAction($h);
+                $h->vars['admin_plugin_settings'] = $this->listPluginSettings($h);
+                $h->vars['admin_plugin_tables'] = $this->listDbTables($h);
+                break;
+            case "blocked_list":
+                $h->vars['admin_blocked_list'] = $this->blocked($h);
+                break;
+            case "plugin_management":
+                $h->sidebars = false;
+                $h->vars['admin_sidebar_layout'] = 'horizontal';
+                $this->adminPlugins($h);
+                break;
+            case "plugin_settings":
+                $h->vars['settings_plugin'] = $h->cage->get->testAlnumLines('plugin'); // get plugin name from url
+                if (!$h->vars['settings_plugin']) { 
+                    $h->vars['settings_plugin'] = $h->cage->post->testAlnumLines('plugin');  // get plugin name from form
+                }
+                $h->vars['plugin_settings_csrf_error'] = '';
+                if ($h->cage->post->testAlpha('submitted') == 'true') { 
+                    $h->vars['plugin_settings_csrf_error'] = (!$h->csrf()) ? true : false;
+                }
+                break;
+            case "theme_settings":
+                $h->vars['settings_theme'] = $h->cage->get->testAlnumLines('theme'); // get plugin name from url
+                if (!$h->vars['settings_theme']) { 
+                    $h->vars['settings_theme'] = $h->cage->post->testAlnumLines('theme');  // get plugin name from form
+                }
+                $h->vars['theme_settings_csrf_error'] = '';
+                if ($h->cage->post->testAlpha('submitted') == 'true') { 
+                    $h->vars['theme_settings_csrf_error'] = (!$h->csrf()) ? true : false;
+                }
+                break;
+            default:
+                // we need this because it's not specified in the url:
+                $h->pageName = 'admin_home';
+                break;
+        }
+        
+        // Display the main theme's index.php template
+        $h->displayTemplate('index');
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  ACCOUNT PAGE
+ *
+ * *********************************************************** */
+ 
+    
+    /**
+     * Call the updateAccount method in UserAuth
+     */    
+    public function adminAccount($h)
+    {
+        return $h->currentUser->updateAccount($h);
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  SETTINGS PAGE
+ *
+ * *********************************************************** */
+ 
+    
+    /**
+     * Process the settings form
+     */    
+    public function settings($h)
+    {
+        $loaded_settings = $this->getAllAdminSettings($h->db);    // get all admin settings from the database
+        
+        $error = 0;
+        
+        if ($h->cage->post->noTags('settings_update')  == 'true') {
+        
+            // if either the login or forgot password form is submitted, check the CSRF key
+            if (!$h->csrf()) { $error = 1; }
+        
+            foreach ($loaded_settings as $setting_name) {
+                if ($h->cage->post->keyExists($setting_name->settings_name)) {
+                    $setting_value = $h->cage->post->noTags($setting_name->settings_name);
+                    if (!$error && $setting_value && $setting_value != $setting_name->settings_value) {
+                        $this->adminSettingUpdate($h, $setting_name->settings_name, $setting_value);
+    
+                    } else {
+                        if (!$setting_value) {
+                            // empty value 
+                            $error = 1; 
+                        } else { 
+                            // No change to the value
+                            $error = 0; 
+                        }
+                    }
+                } else {
+                    // error, setting empty.
+                    $error = 1;
+                }
+            }
+            
+            if ($error == 0) {
+                $h->message = $h->lang['admin_settings_update_success'];
+                $h->messageType = 'green';
+            } else {
+                $h->message = $h->lang['admin_settings_update_failure'];
+                $h->messageType = 'red';
+            }
+        }
+        
+        // Activate themes from theme settings pages - called via JavaScript
+        if ($h->cage->post->testAlnumLines('admin') == 'theme_settings' )
+        {
+            $theme = strtolower($h->cage->post->testAlnumLines('theme') . "/" );
+            $this->adminSettingUpdate($h, 'THEME', $theme);
+            $json_array = array('activate'=>'true', 'message'=>$h->lang["admin_settings_theme_activate_success"], 'color'=>'green');
+            
+            // Send back result data
+            echo json_encode($json_array);
+            exit;
+        }
+        
+        $loaded_settings = $this->getAllAdminSettings($h->db);
+        
+        return $loaded_settings;
+    }
+    
+    
+    /**
+     * Returns all setting-value pairs
+     *
+     * @return array|false
+     */
+    public function getAllAdminSettings($db)
+    {
+        $sql = "SELECT * FROM " . TABLE_SETTINGS;
+        $results = $db->get_results($db->prepare($sql));
+        if ($results) { return $results; } else { return false; }
+    }
+    
+    
+    /**
+     * Update an admin setting
+     *
+     * @param string $setting
+     * @param string $value
+     */
+    public function adminSettingUpdate($h, $setting = '', $value = '')
+    {
+        $exists = $this->adminSettingExists($h->db, $setting);
+        
+        if (!$exists) {
+            $sql = "INSERT INTO " . TABLE_SETTINGS . " (settings_name, settings_value, settings_updateby) VALUES (%s, %s, %d)";
+            $h->db->query($h->db->prepare($sql, $setting, $value, $h->currentUser->id));
+        } else {
+            $sql = "UPDATE " . TABLE_SETTINGS . " SET settings_name = %s, settings_value = %s, settings_updateby = %d WHERE (settings_name = %s)";
+            $h->db->query($h->db->prepare($sql, $setting, $value, $h->currentUser->id, $setting));
+        }
+    }
+    
+    
+    /**
+     * Determine if a setting already exists
+     *
+     * Note: The actual value is ignored
+     *
+     * @param string $setting
+     * @return mixed|false
+     */
+    public function adminSettingExists($db, $setting = '')
+    {
+        $sql = "SELECT settings_name FROM " . TABLE_SETTINGS . " WHERE (settings_name = %s)";
+        $returned_setting = $db->get_var($db->prepare($sql, $setting));
+        if ($returned_setting) { return $returned_setting; } else { return false; }
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  MAINTENANCE PAGE
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Check action called in Maintenance template
+     */
+    public function maintenanceAction($h)
+    {
+        require_once(LIBS . 'Maintenance.php');
+        $maintenance = new Maintenance();
+        $maintenance->getSiteAnnouncement($h);
+        
+        $h->vars['debug_files'] = $h->getFiles(CACHE . 'debug_logs');
+        
+        // if no action, return now
+        if (!$action = $h->cage->get->testAlnumLines('action')) { return false; }
+        
+        if ($action == 'announcement') { $maintenance->addSiteAnnouncement($h); }
+        if ($action == 'open') { $h->openCloseSite('open'); }
+        if ($action == 'close') { $h->openCloseSite('close'); }
+        if ($action == 'clear_all_cache') { 
+            $h->clearCache('db_cache', false);
+            $h->clearCache('css_js_cache', false);
+            $h->clearCache('rss_cache', false);
+            $h->clearCache('html_cache', false);
+            $h->message = $h->lang['admin_maintenance_clear_all_cache_success'];
+            $h->messageType = 'green';
+        }
+        if ($action == 'clear_db_cache') { $h->clearCache('db_cache'); }
+        if ($action == 'clear_css_js_cache') { $h->clearCache('css_js_cache'); }
+        if ($action == 'clear_rss_cache') { $h->clearCache('rss_cache'); }
+        if ($action == 'clear_html_cache') { $h->clearCache('html_cache'); }
+        if ($action == 'optimize') { $h->optimizeTables(); }
+        if ($action == 'empty') { $h->emptyTable($h->cage->get->testAlnumLines('table')); }
+        if ($action == 'drop') { $h->dropTable($h->cage->get->testAlnumLines('table')); }
+        if ($action == 'remove_settings') { $h->removeSettings($h->cage->get->testAlnumLines('settings')); }
+        if ($action == 'delete_debugs') { 
+            $h->clearCache('debug_logs');
+            $h->vars['debug_files'] = $h->getFiles(CACHE . 'debug_logs');
+        }
+    }
+    
+    
+    /**
+     * List all plugins with settings
+     *
+     * @return array|false
+     */
+    public function listPluginSettings($h)
+    {
+        $plugin_settings = array();
+        $sql = "SELECT DISTINCT plugin_folder FROM " . DB_PREFIX . "pluginsettings";
+        $results = $h->db->get_results($h->db->prepare($sql));
+    
+        if (!$results) { return false; } 
+        
+        foreach ($results as $item) {
+            array_push($plugin_settings, $item->plugin_folder);
+        }
+        
+        return $plugin_settings;
+    }
+    
+    
+    /**
+     * List all created tables
+     */
+    public function listDbTables($h)
+    {
+        $db_tables = array();
+        
+        $exceptions = array(
+            DB_PREFIX . 'settings',
+            DB_PREFIX . 'users',
+            DB_PREFIX . 'usermeta',
+            DB_PREFIX . 'categories',
+            DB_PREFIX . 'comments',
+            DB_PREFIX . 'commentvotes',
+            DB_PREFIX . 'miscdata',
+            DB_PREFIX . 'postmeta',
+            DB_PREFIX . 'posts',
+            DB_PREFIX . 'postvotes',
+            DB_PREFIX . 'tags',
+            DB_PREFIX . 'useractivity',
+            DB_PREFIX . 'widgets',
+        );
+            
+        $h->db->select(DB_NAME);
+        
+        if (!$h->db->get_col("SHOW TABLES",0)) { return $db_tables; }
+        
+        foreach ( $h->db->get_col("SHOW TABLES",0) as $table_name )
+        {
+            if (!in_array($table_name, $exceptions)) {
+                array_push($db_tables, $table_name);
+            }
+        }
+        
+        return $db_tables;
+    }
+    
+  
+ /* *************************************************************
+ *
+ *  BLOCKED PAGE
+ *
+ * *********************************************************** */
+ 
+ 
+    /**
+     * Determine and respond to actions from the Blocked list
+     */
+    public function blocked($h)
+    {
+        require_once(LIBS . 'Blocked.php');
+        $blocked = new Blocked();
+        $blocked_items = $blocked->buildBlockedList($h);
+
+        return $blocked_items;
+    }
+    
+    
+ /* *************************************************************
+ *
+ *  PLUGIN MANAGEMENT PAGE
+ *
+ * *********************************************************** */
+ 
+ 
+     /**
+     * Call functions based on user actions in Plugin Management
+     */
+    public function adminPlugins($h)
+    {
+        $pfolder = $h->cage->get->testAlnumLines('plugin');
+        $h->plugin->folder = $pfolder;   // assign this plugin to Hotaru
+        
+        $action = $h->cage->get->testAlnumLines('action');
+        $order = $h->cage->get->testAlnumLines('order');
+        
+        require_once(LIBS . 'PluginManagement.php');
+        $plugman = new PluginManagement();
+        
+        switch ($action) {
+            case "activate":
+                $plugman->activateDeactivate($h, 1);
+                break;
+            case "deactivate":
+                $plugman->activateDeactivate($h, 0);
+                break;    
+            case "activate_all":
+                $plugman->activateDeactivateAll($h, 1);
+                break;
+            case "deactivate_all":
+                $plugman->activateDeactivateAll($h, 0);
+                break;    
+            case "uninstall_all":
+                $plugman->uninstallAll($h);
+                break;    
+            case "install":
+                $plugman->install($h);
+                break;
+            case "uninstall":
+                $plugman->uninstall($h);
+                break;    
+            case "orderup":
+                $plugman->pluginOrder($h, $order, "up");
+                break;    
+            case "orderdown":
+                $plugman->pluginOrder($h, $order, "down");
+                break;    
+            default:
+                // nothing to do here...
+                break;
+        }
+        
+        // get and sort all the plugins ready for display:
+        $allplugins = $plugman->getPlugins($h);  // get plugins
+        
+        $installed_plugins = array_filter($allplugins, array($plugman, 'getInstalledPlugins'));
+        $h->vars['installed_plugins'] = sksort($installed_plugins, "order", "int", true);
+        
+        $uninstalled_plugins = array_filter($allplugins, array($plugman, 'getUninstalledPlugins'));
+        $h->vars['uninstalled_plugins'] = sksort($uninstalled_plugins, 'name', 'char', true);
+    
+        return true;
+    }
+}
+?>
Index: /trunk/libs/extensions/http/license.txt
===================================================================
--- /trunk/libs/extensions/http/license.txt	(revision 1081)
+++ /trunk/libs/extensions/http/license.txt	(revision 1081)
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Md Emran Hasan <phpfour@gmail.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
Index: /trunk/libs/extensions/http/class.http.php
===================================================================
--- /trunk/libs/extensions/http/class.http.php	(revision 1081)
+++ /trunk/libs/extensions/http/class.http.php	(revision 1081)
@@ -0,0 +1,1187 @@
+<?php
+
+/**
+ * HTTP Class
+ *
+ * This is a wrapper HTTP class that uses either cURL or fsockopen to 
+ * harvest resources from web. This can be used with scripts that need 
+ * a way to communicate with various APIs who support REST.
+ *
+ * @author      Md Emran Hasan <phpfour@gmail.com>
+ * @package     HTTP Library
+ * @copyright   2007-2008 Md Emran Hasan
+ * @link        http://www.phpfour.com/lib/http
+ * @since       Version 0.1
+ */
+
+class Http
+{
+    /**
+     * Contains the target URL
+     *
+     * @var string
+     */
+    var $target;
+    
+    /**
+     * Contains the target host
+     *
+     * @var string
+     */
+    var $host;
+    
+    /**
+     * Contains the target port
+     *
+     * @var integer
+     */
+    var $port;
+    
+    /**
+     * Contains the target path
+     *
+     * @var string
+     */
+    var $path;
+    
+    /**
+     * Contains the target schema
+     *
+     * @var string
+     */
+    var $schema;
+    
+    /**
+     * Contains the http method (GET or POST)
+     *
+     * @var string
+     */
+    var $method;
+    
+    /**
+     * Contains the parameters for request
+     *
+     * @var array
+     */
+    var $params;
+    
+    /**
+     * Contains the cookies for request
+     *
+     * @var array
+     */
+    var $cookies;
+    
+    /**
+     * Contains the cookies retrieved from response
+     *
+     * @var array
+     */
+    var $_cookies;
+    
+    /**
+     * Number of seconds to timeout
+     *
+     * @var integer
+     */
+    var $timeout;
+    
+    /**
+     * Whether to use cURL or not
+     *
+     * @var boolean
+     */
+    var $useCurl;
+    
+    /**
+     * Contains the referrer URL
+     *
+     * @var string
+     */
+    var $referrer;
+    
+    /**
+     * Contains the User agent string
+     *
+     * @var string
+     */
+    var $userAgent;
+    
+    /**
+     * Contains the cookie path (to be used with cURL)
+     *
+     * @var string
+     */
+    var $cookiePath;
+    
+    /**
+     * Whether to use cookie at all
+     *
+     * @var boolean
+     */
+    var $useCookie;
+    
+    /**
+     * Whether to store cookie for subsequent requests
+     *
+     * @var boolean
+     */
+    var $saveCookie;
+    
+    /**
+     * Contains the Username (for authentication)
+     *
+     * @var string
+     */
+    var $username;
+    
+    /**
+     * Contains the Password (for authentication)
+     *
+     * @var string
+     */
+    var $password;
+    
+    /**
+     * Contains the fetched web source
+     *
+     * @var string
+     */
+    var $result;
+    
+    /**
+     * Contains the last headers 
+     *
+     * @var string
+     */
+    var $headers;
+    
+    /**
+     * Contains the last call's http status code
+     *
+     * @var string
+     */
+    var $status;
+    
+    /**
+     * Whether to follow http redirect or not
+     *
+     * @var boolean
+     */
+    var $redirect;
+    
+    /**
+     * The maximum number of redirect to follow
+     *
+     * @var integer
+     */
+    var $maxRedirect;
+    
+    /**
+     * The current number of redirects
+     *
+     * @var integer
+     */
+    var $curRedirect;
+    
+    /**
+     * Contains any error occurred
+     *
+     * @var string
+     */
+    var $error;
+    
+    /**
+     * Store the next token
+     *
+     * @var string
+     */
+    var $nextToken;
+    
+    /**
+     * Whether to keep debug messages
+     *
+     * @var boolean
+     */
+    var $debug;
+    
+    /**
+     * Stores the debug messages
+     *
+     * @var array
+     * @todo will keep debug messages
+     */
+    var $debugMsg;
+    
+    /**
+     * Constructor for initializing the class with default values.
+     * 
+     * @return void  
+     */
+    function Http()
+    {
+        $this->clear();    
+    }
+    
+    /**
+     * Initialize preferences
+     * 
+     * This function will take an associative array of config values and 
+     * will initialize the class variables using them. 
+     * 
+     * Example use:
+     * 
+     * <pre>
+     * $httpConfig['method']     = 'GET';
+     * $httpConfig['target']     = 'http://www.somedomain.com/index.html';
+     * $httpConfig['referrer']   = 'http://www.somedomain.com';
+     * $httpConfig['user_agent'] = 'My Crawler';
+     * $httpConfig['timeout']    = '30';
+     * $httpConfig['params']     = array('var1' => 'testvalue', 'var2' => 'somevalue');
+     * 
+     * $http = new Http();
+     * $http->initialize($httpConfig);
+     * </pre>
+     *
+     * @param array Config values as associative array
+     * @return void
+     */    
+    function initialize($config = array())
+    {
+        $this->clear();
+        foreach ($config as $key => $val)
+        {
+            if (isset($this->$key))
+            {
+                $method = 'set' . ucfirst(str_replace('_', '', $key));
+                
+                if (method_exists($this, $method))
+                {
+                    $this->$method($val);
+                }
+                else
+                {
+                    $this->$key = $val;
+                }            
+            }
+        }
+    }
+    
+    /**
+     * Clear Everything
+     * 
+     * Clears all the properties of the class and sets the object to
+     * the beginning state. Very handy if you are doing subsequent calls 
+     * with different data.
+     *
+     * @return void
+     */
+    function clear()
+    {
+        // Set the request defaults
+        $this->host         = '';
+        $this->port         = 0;
+        $this->path         = '';
+        $this->target       = '';
+        $this->method       = 'GET';
+        $this->schema       = 'http';
+        $this->params       = array();
+        $this->headers      = array();
+        $this->cookies      = array();
+        $this->_cookies     = array();
+        
+        // Set the config details        
+        $this->debug        = FALSE;
+        $this->error        = '';
+        $this->status       = 0;
+        $this->timeout      = '25';
+        $this->useCurl      = TRUE;
+        $this->referrer     = '';
+        $this->username     = '';
+        $this->password     = '';
+        $this->redirect     = TRUE;
+        
+        // Set the cookie and agent defaults
+        $this->nextToken    = '';
+        $this->useCookie    = TRUE;
+        $this->saveCookie   = TRUE;
+        $this->maxRedirect  = 3;
+        $this->cookiePath   = 'cookie.txt';
+        $this->userAgent    = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.9';
+    }
+    
+    /**
+     * Set target URL
+     *
+     * @param string URL of target resource
+     * @return void
+     */
+    function setTarget($url)
+    {
+        if ($url)
+        {
+            $this->target = $url;
+        }   
+    }
+    
+    /**
+     * Set http method
+     *
+     * @param string HTTP method to use (GET or POST)
+     * @return void
+     */
+    function setMethod($method)
+    {
+        if ($method == 'GET' || $method == 'POST')
+        {
+            $this->method = $method;
+        }   
+    }
+    
+    /**
+     * Set referrer URL
+     *
+     * @param string URL of referrer page
+     * @return void
+     */
+    function setReferrer($referrer)
+    {
+        if ($referrer)
+        {
+            $this->referrer = $referrer;
+        }   
+    }
+    
+    /**
+     * Set User agent string
+     *
+     * @param string Full user agent string
+     * @return void
+     */
+    function setUseragent($agent)
+    {
+        if ($agent)
+        {
+            $this->userAgent = $agent;
+        }   
+    }
+    
+    /**
+     * Set timeout of execution
+     *
+     * @param integer Timeout delay in seconds
+     * @return void
+     */
+    function setTimeout($seconds)
+    {
+        if ($seconds > 0)
+        {
+            $this->timeout = $seconds;
+        }   
+    }
+    
+    /**
+     * Set cookie path (cURL only)
+     *
+     * @param string File location of cookiejar
+     * @return void
+     */
+    function setCookiepath($path)
+    {
+        if ($path)
+        {
+            $this->cookiePath = $path;
+        }   
+    }
+    
+    /**
+     * Set request parameters
+     *
+     * @param array All the parameters for GET or POST
+     * @return void
+     */
+    function setParams($dataArray)
+    {
+        if (is_array($dataArray))
+        {
+            $this->params = array_merge($this->params, $dataArray);
+        }   
+    }
+    
+    /**
+     * Set basic http authentication realm
+     *
+     * @param string Username for authentication
+     * @param string Password for authentication
+     * @return void
+     */
+    function setAuth($username, $password)
+    {
+        if (!empty($username) && !empty($password))
+        {
+            $this->username = $username;
+            $this->password = $password;
+        }
+    }
+    
+    /**
+     * Set maximum number of redirection to follow
+     *
+     * @param integer Maximum number of redirects
+     * @return void
+     */
+    function setMaxredirect($value)
+    {
+        if (!empty($value))
+        {
+            $this->maxRedirect = $value;
+        }
+    }
+    
+    /**
+     * Add request parameters
+     *
+     * @param string Name of the parameter
+     * @param string Value of the parameter
+     * @return void
+     */
+    function addParam($name, $value)
+    {
+        if (!empty($name) && !empty($value))
+        {
+            $this->params[$name] = $value;
+        }   
+    }
+    
+    /**
+     * Add a cookie to the request
+     *
+     * @param string Name of cookie
+     * @param string Value of cookie
+     * @return void
+     */
+    function addCookie($name, $value)
+    {
+        if (!empty($name) && !empty($value))
+        {
+            $this->cookies[$name] = $value;
+        }   
+    }
+    
+    /**
+     * Whether to use cURL or not
+     *
+     * @param boolean Whether to use cURL or not
+     * @return void
+     */
+    function useCurl($value = TRUE)
+    {
+        if (is_bool($value))
+        {
+            $this->useCurl = $value;
+        }   
+    }
+    
+    /**
+     * Whether to use cookies or not
+     *
+     * @param boolean Whether to use cookies or not
+     * @return void
+     */
+    function useCookie($value = TRUE)
+    {
+        if (is_bool($value))
+        {
+            $this->useCookie = $value;
+        }   
+    }
+    
+    /**
+     * Whether to save persistent cookies in subsequent calls
+     *
+     * @param boolean Whether to save persistent cookies or not
+     * @return void
+     */
+    function saveCookie($value = TRUE)
+    {
+        if (is_bool($value))
+        {
+            $this->saveCookie = $value;
+        }   
+    }
+    
+    /**
+     * Whether to follow HTTP redirects
+     *
+     * @param boolean Whether to follow HTTP redirects or not
+     * @return void
+     */
+    function followRedirects($value = TRUE)
+    {
+        if (is_bool($value))
+        {
+            $this->redirect = $value;
+        }   
+    }
+    
+    /**
+     * Get execution result body
+     *
+     * @return string output of execution
+     */
+    function getResult()
+    {
+        return $this->result;
+    }
+    
+    /**
+     * Get execution result headers
+     *
+     * @return array last headers of execution
+     */
+    function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Get execution status code
+     *
+     * @return integer last http status code
+     */
+    function getStatus()
+    {
+        return $this->status;
+    }
+        
+    /**
+     * Get last execution error
+     *
+     * @return string last error message (if any)
+     */
+    function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * Execute a HTTP request
+     * 
+     * Executes the http fetch using all the set properties. Intellegently
+     * switch to fsockopen if cURL is not present. And be smart to follow
+     * redirects (if asked so).
+     * 
+     * @param string URL of the target page (optional)
+     * @param string URL of the referrer page (optional)
+     * @param string The http method (GET or POST) (optional)
+     * @param array Parameter array for GET or POST (optional)
+     * @return string Response body of the target page
+     */    
+    function execute($target = '', $referrer = '', $method = '', $data = array())
+    {
+        // Populate the properties
+        $this->target = ($target) ? $target : $this->target;
+        $this->method = ($method) ? $method : $this->method;
+        
+        $this->referrer = ($referrer) ? $referrer : $this->referrer;
+        
+        // Add the new params
+        if (is_array($data) && count($data) > 0) 
+        {
+            $this->params = array_merge($this->params, $data);
+        }
+        
+        // Process data, if presented
+        if(is_array($this->params) && count($this->params) > 0)
+        {
+            // Get a blank slate
+            $tempString = array();
+            
+            // Convert data array into a query string (ie animal=dog&sport=baseball)
+            foreach ($this->params as $key => $value) 
+            {
+                if(strlen(trim($value))>0)
+                {
+                    $tempString[] = $key . "=" . urlencode($value);
+                }
+            }
+            
+            $queryString = join('&', $tempString);
+        }
+        
+        // If cURL is not installed, we'll force fscokopen
+        $this->useCurl = $this->useCurl && in_array('curl', get_loaded_extensions());
+        
+        // GET method configuration
+        if($this->method == 'GET')
+        {
+            if(isset($queryString))
+            {
+                $this->target = $this->target . "?" . $queryString;
+            }
+        }
+        
+        // Parse target URL
+        $urlParsed = parse_url($this->target);
+        
+        // Handle SSL connection request
+        if ($urlParsed['scheme'] == 'https')
+        {
+            $this->host = 'ssl://' . $urlParsed['host'];
+            $this->port = ($this->port != 0) ? $this->port : 443;
+        }
+        else
+        {
+            $this->host = $urlParsed['host'];
+            $this->port = ($this->port != 0) ? $this->port : 80;
+        }
+        
+        // Finalize the target path
+        $this->path   = (isset($urlParsed['path']) ? $urlParsed['path'] : '/') . (isset($urlParsed['query']) ? '?' . $urlParsed['query'] : '');
+        $this->schema = $urlParsed['scheme'];
+        
+        // Pass the requred cookies
+        $this->_passCookies();
+        
+        // Process cookies, if requested
+        if(is_array($this->cookies) && count($this->cookies) > 0)
+        {
+            // Get a blank slate
+            $tempString   = array();
+            
+            // Convert cookiesa array into a query string (ie animal=dog&sport=baseball)
+            foreach ($this->cookies as $key => $value) 
+            {
+                if(strlen(trim($value)) > 0)
+                {
+                    $tempString[] = $key . "=" . urlencode($value);
+                }
+            }
+            
+            $cookieString = join('&', $tempString);
+        }
+        
+        // Do we need to use cURL
+        if ($this->useCurl)
+        {
+            // Initialize PHP cURL handle
+            $ch = curl_init();
+    
+            // GET method configuration
+            if($this->method == 'GET')
+            {
+                curl_setopt ($ch, CURLOPT_HTTPGET, TRUE); 
+                curl_setopt ($ch, CURLOPT_POST, FALSE); 
+            }
+            // POST method configuration
+            else
+            {
+                if(isset($queryString))
+                {
+                    curl_setopt ($ch, CURLOPT_POSTFIELDS, $queryString);
+                }
+                
+                curl_setopt ($ch, CURLOPT_POST, TRUE); 
+                curl_setopt ($ch, CURLOPT_HTTPGET, FALSE); 
+            }
+            
+            // Basic Authentication configuration
+            if ($this->username && $this->password)
+            {
+                curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);
+            }
+            
+            // Custom cookie configuration
+            if($this->useCookie && isset($cookieString))
+            {
+                curl_setopt ($ch, CURLOPT_COOKIE, $cookieString);
+            }
+            
+            curl_setopt($ch, CURLOPT_HEADER,         TRUE);                 // No need of headers
+            curl_setopt($ch, CURLOPT_NOBODY,         FALSE);                // Return body
+                
+            curl_setopt($ch, CURLOPT_COOKIEJAR,      $this->cookiePath);    // Cookie management.
+            curl_setopt($ch, CURLOPT_TIMEOUT,        $this->timeout);       // Timeout
+            curl_setopt($ch, CURLOPT_USERAGENT,      $this->userAgent);     // Webbot name
+            curl_setopt($ch, CURLOPT_URL,            $this->target);        // Target site
+            curl_setopt($ch, CURLOPT_REFERER,        $this->referrer);      // Referer value
+            
+            curl_setopt($ch, CURLOPT_VERBOSE,        FALSE);                // Minimize logs
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);                // No certificate
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->redirect);      // Follow redirects
+            curl_setopt($ch, CURLOPT_MAXREDIRS,      $this->maxRedirect);   // Limit redirections to four
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                 // Return in string
+            
+            // Get the target contents
+            $content = curl_exec($ch);
+            $contentArray = explode("\r\n\r\n", $content);
+            
+            // Get the request info 
+            $status  = curl_getinfo($ch);
+            
+            // Store the contents
+            $this->result = $contentArray[count($contentArray) - 1]; 
+
+            // Parse the headers
+            $this->_parseHeaders($contentArray[count($contentArray) - 2]);
+                        
+            // Store the error (is any)
+            $this->_setError(curl_error($ch));
+            
+            // Close PHP cURL handle
+            curl_close($ch);
+        }
+        else
+        {
+            // Get a file pointer
+            $filePointer = fsockopen($this->host, $this->port, $errorNumber, $errorString, $this->timeout);
+       
+            // We have an error if pointer is not there
+            if (!$filePointer)
+            {
+                $this->_setError('Failed opening http socket connection: ' . $errorString . ' (' . $errorNumber . ')');
+                return FALSE;
+            }
+
+            // Set http headers with host, user-agent and content type
+            $requestHeader  = $this->method . " " . $this->path . "  HTTP/1.1\r\n";
+            $requestHeader .= "Host: " . $urlParsed['host'] . "\r\n";
+            $requestHeader .= "User-Agent: " . $this->userAgent . "\r\n";
+            $requestHeader .= "Content-Type: application/x-www-form-urlencoded\r\n";
+            
+            // Specify the custom cookies
+            if ($this->useCookie && isset($cookieString) && $cookieString != '')
+            {
+                $requestHeader.= "Cookie: " . $cookieString . "\r\n";
+            }
+
+            // POST method configuration
+            if ($this->method == "POST")
+            {
+                $requestHeader.= "Content-Length: " . strlen($queryString) . "\r\n";
+            }
+            
+            // Specify the referrer
+            if ($this->referrer != '')
+            {
+                $requestHeader.= "Referer: " . $this->referrer . "\r\n";
+            }
+            
+            // Specify http authentication (basic)
+            if ($this->username && $this->password)
+            {
+                $requestHeader.= "Authorization: Basic " . base64_encode($this->username . ':' . $this->password) . "\r\n";
+            }
+       
+            $requestHeader.= "Connection: close\r\n\r\n";
+       
+            // POST method configuration
+            if ($this->method == "POST")
+            {
+                $requestHeader .= $queryString;
+            }           
+
+            // We're ready to launch
+            fwrite($filePointer, $requestHeader);
+       
+            // Clean the slate
+            $responseHeader = '';
+            $responseContent = '';
+
+            // 3...2...1...Launch !
+            do
+            {
+                $responseHeader .= fread($filePointer, 1);
+            }
+            while (!preg_match('/\\r\\n\\r\\n$/', $responseHeader));
+            
+            // Parse the headers
+            $this->_parseHeaders($responseHeader);
+            
+            // Do we have a 301/302 redirect ?
+            if (($this->status == '301' || $this->status == '302') && $this->redirect == TRUE)
+            {
+                if ($this->curRedirect < $this->maxRedirect)
+                {
+                    // Let's find out the new redirect URL
+                    $newUrlParsed = parse_url($this->headers['location']);
+                    
+                    if ($newUrlParsed['host'])
+                    {
+                        $newTarget = $this->headers['location'];    
+                    }
+                    else
+                    {
+                        $newTarget = $this->schema . '://' . $this->host . '/' . $this->headers['location'];
+                    }
+                    
+                    // Reset some of the properties
+                    $this->port   = 0;
+                    $this->status = 0;
+                    $this->params = array();
+                    $this->method = 'GET';
+                    $this->referrer = $this->target;
+                    
+                    // Increase the redirect counter
+                    $this->curRedirect++;
+                    
+                    // Let's go, go, go !
+                    $this->result = $this->execute($newTarget);
+                }
+                else
+                {
+                    $this->_setError('Too many redirects.');
+                    return FALSE;
+                }
+            }
+            else
+            {
+                // Nope...so lets get the rest of the contents (non-chunked)
+                if (isset($this->headers['transfer-encoding']) && $this->headers['transfer-encoding'] != 'chunked')
+                {
+                    while (!feof($filePointer))
+                    {
+                        $responseContent .= fgets($filePointer, 128);
+                    }
+                }
+                else
+                {
+                    // Get the contents (chunked)
+                    while ($chunkLength = hexdec(fgets($filePointer)))
+                    {
+                        $responseContentChunk = '';
+                        $readLength = 0;
+                       
+                        while ($readLength < $chunkLength)
+                        {
+                            $responseContentChunk .= fread($filePointer, $chunkLength - $readLength);
+                            $readLength = strlen($responseContentChunk);
+                        }
+
+                        $responseContent .= $responseContentChunk;
+                        fgets($filePointer);  
+                    }
+                }
+                
+                // Store the target contents
+                $this->result = chop($responseContent);
+            }
+        }
+        
+        // There it is! We have it!! Return to base !!!
+        return $this->result;
+    }
+    
+    /**
+     * Parse Headers (internal)
+     * 
+     * Parse the response headers and store them for finding the resposne 
+     * status, redirection location, cookies, etc. 
+     *
+     * @param string Raw header response
+     * @return void
+     * @access private
+     */
+    function _parseHeaders($responseHeader)
+    {
+        // Break up the headers
+        $headers = explode("\r\n", $responseHeader);
+        
+        // Clear the header array
+        $this->_clearHeaders();
+        
+        // Get resposne status
+        if($this->status == 0)
+        {
+            // Oooops !
+            if(!preg_match('/^http\/[0-9]+\.[0-9]+[ \t]+([0-9]+)[ \t]*(.*)$/i', $headers[0], $matches))
+            {
+                $this->_setError('Unexpected HTTP response status:' . $headers[0]);
+                return FALSE;
+            }
+            
+            // Gotcha!
+            $this->status = $matches[1];
+            array_shift($headers);
+        }
+        
+        // Prepare all the other headers
+        foreach ($headers as $header)
+        {
+            // Get name and value
+            $headerName  = strtolower($this->_tokenize($header, ':'));
+            $headerValue = trim(chop($this->_tokenize("\r\n")));
+            
+            // If its already there, then add as an array. Otherwise, just keep there
+            if(isset($this->headers[$headerName]))
+            {
+                if(gettype($this->headers[$headerName]) == "string")
+                {
+                    $this->headers[$headerName] = array($this->headers[$headerName]);
+                }
+                    
+                $this->headers[$headerName][] = $headerValue;
+            }
+            else
+            {
+                $this->headers[$headerName] = $headerValue;
+            }
+        }
+            
+        // Save cookies if asked 
+        if ($this->saveCookie && isset($this->headers['set-cookie']))
+        {
+            $this->_parseCookie();
+        }
+    }
+    
+    /**
+     * Clear the headers array (internal)
+     *
+     * @return void
+     * @access private
+     */
+    function _clearHeaders()
+    {
+        $this->headers = array();
+    }
+    
+    /**
+     * Parse Cookies (internal)
+     * 
+     * Parse the set-cookie headers from response and add them for inclusion.
+     *
+     * @return void
+     * @access private
+     */
+    function _parseCookie()
+    {
+        // Get the cookie header as array
+        if(gettype($this->headers['set-cookie']) == "array")
+        {
+            $cookieHeaders = $this->headers['set-cookie'];
+        }
+        else
+        {
+            $cookieHeaders = array($this->headers['set-cookie']);
+        }
+
+        // Loop through the cookies
+        for ($cookie = 0; $cookie < count($cookieHeaders); $cookie++)
+        {
+            $cookieName  = trim($this->_tokenize($cookieHeaders[$cookie], "="));
+            $cookieValue = $this->_tokenize(";");
+            
+            $urlParsed   = parse_url($this->target);
+            
+            $domain      = $urlParsed['host'];
+            $secure      = '0';
+            
+            $path        = "/";
+            $expires     = "";
+            
+            while(($name = trim(urldecode($this->_tokenize("=")))) != "")
+            {
+                $value = urldecode($this->_tokenize(";"));
+                
+                switch($name)
+                {
+                    case "path"     : $path     = $value; break;
+                    case "domain"   : $domain   = $value; break;
+                    case "secure"   : $secure   = ($value != '') ? '1' : '0'; break;
+                }
+            }
+            
+            $this->_setCookie($cookieName, $cookieValue, $expires, $path , $domain, $secure);
+        }
+    }
+    
+    /**
+     * Set cookie (internal)
+     * 
+     * Populate the internal _cookies array for future inclusion in 
+     * subsequent requests. This actually validates and then populates 
+     * the object properties with a dimensional entry for cookie.
+     *
+     * @param string Cookie name
+     * @param string Cookie value
+     * @param string Cookie expire date
+     * @param string Cookie path
+     * @param string Cookie domain
+     * @param string Cookie security (0 = non-secure, 1 = secure)
+     * @return void
+     * @access private
+     */
+    function _setCookie($name, $value, $expires = "" , $path = "/" , $domain = "" , $secure = 0)
+    {
+        if(strlen($name) == 0)
+        {
+            return($this->_setError("No valid cookie name was specified."));
+        }
+
+        if(strlen($path) == 0 || strcmp($path[0], "/"))
+        {
+            return($this->_setError("$path is not a valid path for setting cookie $name."));
+        }
+            
+        if($domain == "" || !strpos($domain, ".", $domain[0] == "." ? 1 : 0))
+        {
+            return($this->_setError("$domain is not a valid domain for setting cookie $name."));
+        }
+        
+        $domain = strtolower($domain);
+        
+        if(!strcmp($domain[0], "."))
+        {
+            $domain = substr($domain, 1);
+        }
+            
+        $name  = $this->_encodeCookie($name, true);
+        $value = $this->_encodeCookie($value, false);
+        
+        $secure = intval($secure);
+        
+        $this->_cookies[] = array( "name"      =>  $name,
+                                   "value"     =>  $value,
+                                   "domain"    =>  $domain,
+                                   "path"      =>  $path,
+                                   "expires"   =>  $expires,
+                                   "secure"    =>  $secure
+                                 );
+    }
+    
+    /**
+     * Encode cookie name/value (internal)
+     *
+     * @param string Value of cookie to encode
+     * @param string Name of cookie to encode
+     * @return string encoded string
+     * @access private
+     */
+    function _encodeCookie($value, $name)
+    {
+        return($name ? str_replace("=", "%25", $value) : str_replace(";", "%3B", $value));
+    }
+    
+    /**
+     * Pass Cookies (internal)
+     * 
+     * Get the cookies which are valid for the current request. Checks 
+     * domain and path to decide the return.
+     *
+     * @return void
+     * @access private
+     */
+    function _passCookies()
+    {
+        if (is_array($this->_cookies) && count($this->_cookies) > 0)
+        {
+            $urlParsed = parse_url($this->target);
+            $tempCookies = array();
+            
+            foreach($this->_cookies as $cookie)
+            {
+                if ($this->_domainMatch($urlParsed['host'], $cookie['domain']) && (0 === strpos($urlParsed['path'], $cookie['path']))
+                    && (empty($cookie['secure']) || $urlParsed['protocol'] == 'https')) 
+                {
+                    $tempCookies[$cookie['name']][strlen($cookie['path'])] = $cookie['value'];
+                }
+            }
+            
+            // cookies with longer paths go first
+            foreach ($tempCookies as $name => $values) 
+            {
+                krsort($values);
+                foreach ($values as $value) 
+                {
+                    $this->addCookie($name, $value);
+                }
+            }
+        }
+    }
+    
+    /**
+    * Checks if cookie domain matches a request host (internal)
+    * 
+    * Cookie domain can begin with a dot, it also must contain at least
+    * two dots.
+    * 
+    * @param string Request host
+    * @param string Cookie domain
+    * @return bool Match success
+     * @access private
+    */
+    function _domainMatch($requestHost, $cookieDomain)
+    {
+        if ('.' != $cookieDomain{0}) 
+        {
+            return $requestHost == $cookieDomain;
+        } 
+        elseif (substr_count($cookieDomain, '.') < 2) 
+        {
+            return false;
+        } 
+        else 
+        {
+            return substr('.'. $requestHost, - strlen($cookieDomain)) == $cookieDomain;
+        }
+    }
+    
+    /**
+     * Tokenize String (internal)
+     * 
+     * Tokenize string for various internal usage. Omit the second parameter 
+     * to tokenize the previous string that was provided in the prior call to 
+     * the function.
+     *
+     * @param string The string to tokenize
+     * @param string The seperator to use
+     * @return string Tokenized string
+     * @access private
+     */
+    function _tokenize($string, $separator = '')
+    {
+        if(!strcmp($separator, ''))
+        {
+            $separator = $string;
+            $string = $this->nextToken;
+        }
+        
+        for($character = 0; $character < strlen($separator); $character++)
+        {
+            if(gettype($position = strpos($string, $separator[$character])) == "integer")
+            {
+                $found = (isset($found) ? min($found, $position) : $position);
+            }
+        }
+        
+        if(isset($found))
+        {
+            $this->nextToken = substr($string, $found + 1);
+            return(substr($string, 0, $found));
+        }
+        else
+        {
+            $this->nextToken = '';
+            return($string);
+        }
+    }
+    
+    /**
+     * Set error message (internal)
+     *
+     * @param string Error message
+     * @return string Error message
+     * @access private
+     */
+    function _setError($error)
+    {
+        if ($error != '')
+        {
+            $this->error = $error;
+            return $error;
+        }
+    }
+}
+
+?>
Index: /trunk/libs/extensions/htmLawed/htmLawed_README.txt
===================================================================
--- /trunk/libs/extensions/htmLawed/htmLawed_README.txt	(revision 1081)
+++ /trunk/libs/extensions/htmLawed/htmLawed_README.txt	(revision 1081)
@@ -0,0 +1,1600 @@
+/*
+htmLawed_README.txt, 16 July 2009
+htmLawed 1.1.8.1, 16 July 2009
+Copyright Santosh Patnaik
+GPL v3 license
+A PHP Labware internal utility - http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed
+*/
+
+
+== Content ==========================================================
+
+
+1  About htmLawed
+  1.1  Example uses
+  1.2  Features
+  1.3  History
+  1.4  License & copyright
+  1.5  Terms used here
+2  Usage
+  2.1  Simple
+  2.2  Configuring htmLawed using the '$config' parameter
+  2.3  Extra HTML specifications using the '$spec' parameter
+  2.4  Performance time & memory usage
+  2.5  Some security risks to keep in mind
+  2.6  Use without modifying old 'kses()' code
+  2.7  Tolerance for ill-written HTML
+  2.8  Limitations & work-arounds
+  2.9  Examples
+3  Details
+  3.1  Invalid/dangerous characters
+  3.2  Character references/entities
+  3.3  HTML elements
+    3.3.1  HTML comments and 'CDATA' sections
+    3.3.2  Tag-transformation for better XHTML-Strict
+    3.3.3  Tag balancing and proper nesting
+    3.3.4  Elements requiring child elements
+    3.3.5  Beautify or compact HTML
+  3.4  Attributes
+    3.4.1  Auto-addition of XHTML-required attributes
+    3.4.2  Duplicate/invalid 'id' values
+    3.4.3  URL schemes (protocols) and scripts in attribute values
+    3.4.4  Absolute & relative URLs
+    3.4.5  Lower-cased, standard attribute values
+    3.4.6  Transformation of deprecated attributes
+    3.4.7  Anti-spam & 'href'
+    3.4.8  Inline style properties
+    3.4.9  Hook function for tag content
+  3.5  Simple configuration directive for most valid XHTML
+  3.6  Simple configuration directive for most `safe` HTML
+  3.7  Using a hook function
+  3.8  Obtaining `finalized` parameter values
+  3.9  Retaining non-HTML tags in input with mixed markup
+4  Other
+  4.1  Support
+  4.2  Known issues
+  4.3  Change-log
+  4.4  Testing
+  4.5  Upgrade, & old versions
+  4.6  Comparison with 'HTMLPurifier'
+  4.7  Use through application plug-ins/modules
+  4.8  Use in non-PHP applications
+  4.9  Donate
+  4.10  Acknowledgements
+5  Appendices
+  5.1  Characters discouraged in HTML
+  5.2  Valid attribute-element combinations
+  5.3  CSS 2.1 properties accepting URLs
+  5.4  Microsoft Windows 1252 character replacements
+  5.5  URL format
+  5.6  Brief on htmLawed code
+
+
+== 1  About htmLawed ================================================
+
+
+  htmLawed is a highly customizable single-file PHP script to make text secure, and standard- and admin policy-compliant for use in the body of HTML 4, XHTML 1 or 1.1, or generic XML documents. It is thus a configurable input (X)HTML filter, processor, purifier, sanitizer, beautifier, etc., and an alternative to the HTMLTidy:- http://tidy.sourceforge.net application.
+
+  The `lawing in` of input text is needed to ensure that HTML code in the text is standard-compliant, does not introduce security vulnerabilities, and does not break the aesthetics, design or layout of web-pages. htmLawed tries to do this by, for example, making HTML well-formed with balanced and properly nested tags, neutralizing code that may be used for cross-site scripting ('XSS') attacks, and allowing only specified HTML elements/tags and attributes.
+
+
+-- 1.1  Example uses ------------------------------------------------
+
+
+  *  Filtering of text submitted as comments on blogs to allow only certain HTML elements
+
+  *  Making RSS/Atom newsfeed item-content standard-compliant: often one uses an excerpt from an HTML document for the content, and with unbalanced tags, non-numerical entities, etc., such excerpts may not be XML-compliant
+
+  *  Text processing for stricter XML standard-compliance: e.g., to have lowercased 'x' in hexadecimal numeric entities becomes necessary if an XHTML document with MathML content needs to be served as 'application/xml'
+
+  *  Scraping text or data from web-pages
+
+  *  Pretty-printing HTML code
+
+
+-- 1.2  Features ---------------------------------------------------o
+
+
+  Key: '*' security feature, '^' standard compliance, '~' requires setting right options, '`' different from 'Kses'
+
+  *  make input more *secure* and *standard-compliant*
+  *  use for HTML 4, XHTML 1.0 or 1.1, or even generic *XML* documents  ^~`
+
+  *  *beautify* or *compact* HTML  ^~`
+
+  *  *restrict elements*  ^~`
+  *  proper closure of empty elements like 'img'  ^`
+  *  *transform deprecated elements* like 'u'  ^~`
+  *  HTML *comments* and 'CDATA' sections can be permitted  ^~`
+  *  elements like 'script', 'object' and 'form' can be permitted  ~
+
+  *  *restrict attributes*, including *element-specifically*  ^~`
+  *  remove *invalid attributes*  ^`
+  *  element and attribute names are *lower-cased*  ^
+  *  provide *required attributes*, like 'alt' for 'image'  ^`
+  *  *transform deprecated attributes*  ^~`
+  *  attributes *declared only once*  ^`
+
+  *  *restrict attribute values*, including *element-specifically*  ^~`
+  *  a value is declared for `empty` (`minimized`) attributes like 'checked'  ^
+  *  check for potentially dangerous attribute values  *~
+  *  ensure *unique* 'id' attribute values  ^~`
+  *  *double-quote* attribute values  ^
+  *  lower-case *standard attribute values* like 'password'  ^`
+
+  *  *attribute-specific URL protocol/scheme restriction*  *~`
+  *  disable *dynamic expressions* in 'style' values  *~`
+
+  *  neutralize invalid named character entities  ^`
+  *  *convert* hexadecimal numeric entities to decimal ones, or vice versa  ^~`
+  *  convert named entities to numeric ones for generic XML use  ^~`
+
+  *  remove *null* characters  *
+  *  neutralize potentially dangerous proprietary Netscape *Javascript entities*  *
+  *  replace potentially dangerous *soft-hyphen* character in attribute values with spaces  *
+
+  *  remove common *invalid characters* not allowed in HTML or XML  ^`
+  *  replace *characters from Microsoft applications* like 'Word' that are discouraged in HTML or XML  ^~`
+  *  neutralize entities for characters invalid or discouraged in HTML or XML  ^`
+  *  appropriately neutralize '<', '&', '"', and '>' characters  ^*`
+
+  *  understands improperly spaced tag content (like, spread over more than a line) and properly spaces them  `
+  *  attempts to *balance tags* for well-formedness  ^~`
+  *  understands when *omitable closing tags* like '</p>' (allowed in HTML 4, transitional, e.g.) are missing  ^~`
+  *  attempts to permit only *validly nested tags*  ^~`
+  *  option to *remove or neutralize bad content* ^~`
+  *  attempts to *rectify common errors of plain-text misplacement* (e.g., directly inside 'blockquote') ^~`
+
+  *  fast, *non-OOP* code of ~45 kb incurring peak basal memory usage of ~0.5 MB
+  *  *compatible* with pre-existing code using 'Kses' (the filter used by 'WordPress')
+
+  *  optional *anti-spam* measures such as addition of 'rel="nofollow"' and link-disabling  ~`
+  *  optionally makes *relative URLs absolute*, and vice versa  ~`
+
+  *  optionally mark '&' to identify the entities for '&', '<' and '>' introduced by htmLawed  ~`
+
+  *  allows deployment of powerful *hook functions* to *inject* HTML, *consolidate* 'style' attributes to 'class', finely check attribute values, etc.  ~`
+
+  *  *independent of character encoding* of input and does not affect it
+
+  *  *tolerance for ill-written HTML* to a certain degree
+
+
+-- 1.3  History ----------------------------------------------------o
+
+
+  htmLawed was developed for use with 'LabWiki', a wiki software developed at PHP Labware, as a suitable software could not be found. Existing PHP software like 'Kses' and 'HTMLPurifier' were deemed inadequate, slow, resource-intensive, or dependent on external applications like 'HTML Tidy'.
+
+  htmLawed started as a modification of Ulf Harnhammar's 'Kses' (version 0.2.2) software, and is compatible with code that uses 'Kses'; see section:- #2.6.
+
+
+-- 1.4  License & copyright ----------------------------------------o
+
+
+  htmLawed is free and open-source software licensed under GPL license version 3:- http://www.gnu.org/licenses/gpl-3.0.txt, and copyrighted by Santosh Patnaik, MD, PhD.
+
+
+-- 1.5  Terms used here --------------------------------------------o
+
+
+  *  `administrator` - or admin; person setting up the code to pass input through htmLawed; also, `user`
+  *  `attributes` - name-value pairs like 'href="http://x.com"' in opening tags
+  *  `author` - `writer`
+  *  `character` - atomic unit of text; internally represented by a numeric `code-point` as specified by the `encoding` or `charset` in use
+  *  `entity` - markup like '&gt;' and '&#160;' used to refer to a character
+  *  `element` - HTML element like 'a' and 'img'
+  *  `element content` -  content between the opening and closing tags of an element, like 'click' of '<a href="x">click</a>'
+  *  `HTML` - implies XHTML unless specified otherwise
+  *  `input` - text string given to htmLawed to process
+  *  `processing` - involves filtering, correction, etc., of input
+  *  `safe` - absence or reduction of certain characters and HTML elements and attributes in the input that can otherwise potentially and circumstantially expose web-site users to security vulnerabilities like cross-site scripting attacks (XSS)
+  *  `scheme` - URL protocol like 'http' and 'ftp'
+  *  `specs` - standard specifications
+  *  `style property` - terms like 'border' and 'height' for which declarations are made in values for the 'style' attribute of elements
+  *  `tag` - markers like '<a href="x">' and '</a>' delineating element content; the opening tag can contain attributes
+  *  `tag content` - consists of tag markers '<' and '>', element names like 'div', and possibly attributes
+  *  `user` - administrator
+  *  `writer` - end-user like a blog commenter providing the input that is to be processed; also, `author`
+
+
+== 2  Usage ========================================================oo
+
+
+  htmLawed should work with PHP 4.3 and higher. Either 'include()' the 'htmLawed.php' file or copy-paste the entire code.
+
+  To easily *test* htmLawed using a form-based interface, use the provided demo:- htmLawedTest.php ('htmLawed.php' and 'htmLawedTest.php' should be in the same directory on the web-server).
+
+
+-- 2.1  Simple ------------------------------------------------------
+
+
+  The input text to be processed, '$text', is passed as an argument of type string; 'htmLawed()' returns the processed string:
+
+    $processed = htmLawed($text);
+
+  *Note*: If input is from a '$_GET' or '$_POST' value, and 'magic quotes' are enabled on the PHP setup, run 'stripslashes()' on the input before passing to htmLawed.
+
+  By default, htmLawed will process the text allowing all valid HTML elements/tags, secure URL scheme/CSS style properties, etc. It will allow 'CDATA' sections and HTML comments, balance tags, and ensure proper nesting of elements. Such actions can be configured using two other optional arguments -- '$config' and '$spec':
+
+    $processed = htmLawed($text, $config, $spec);
+
+  These extra parameters are detailed below. Some examples are shown in section:- #2.9.
+
+  *Note*: For maximum protection against 'XSS' and other scripting attacks (e.g., by disallowing Javascript code), consider using the 'safe' parameter; see section:- #3.6.
+
+
+-- 2.2  Configuring htmLawed using the '$config' parameter ---------o
+
+
+  '$config' instructs htmLawed on how to tackle certain tasks. When '$config' is not specified, or not set as an array (e.g., '$config = 1'), htmLawed will take default actions. One or many of the task-action or value-specification pairs can be specified in '$config' as array key-value pairs. If a parameter is not specified, htmLawed will use the default value/action indicated further below.
+
+    $config = array('comment'=>0, 'cdata'=>1);
+    $processed = htmLawed($text, $config);
+
+  Or,
+
+    $processed = htmLawed($text, array('comment'=>0, 'cdata'=>1));
+
+  Below are the possible value-specification combinations. In PHP code, values that are integers should not be quoted and should be used as numeric types (unless meant as string/text).
+
+  Key: '*' default, '^' different default when htmLawed is used in the Kses-compatible mode (see section:- #2.6), '~' different default when 'valid_xhtml' is set to '1' (see section:- #3.5), '"' different default when 'safe' is set to '1' (see section:- #3.6)
+
+  *abs_url*
+  Make URLs absolute or relative; '$config["base_url"]' needs to be set; see section:- #3.4.4
+
+  '-1' - make relative
+  '0' - no action  *
+  '1' - make absolute
+
+  *and_mark*
+  Mark '&' characters in the original input; see section:- #3.2
+
+  *anti_link_spam*
+  Anti-link-spam measure; see section:- #3.4.7
+      
+  '0' - no measure taken  *
+  'array("regex1", "regex2")' - will ensure a 'rel' attribute with 'nofollow' in its value in case the 'href' attribute value matches the regular expression pattern 'regex1', and/or will remove 'href' if its value matches the regular expression pattern 'regex2'. E.g., 'array("/./", "/://\W*(?!(abc\.com|xyz\.org))/")'; see section:- #3.4.7 for more.
+
+  *anti_mail_spam*
+  Anti-mail-spam measure; see section:- #3.4.7
+
+  '0' - no measure taken  *
+  'word' - '@' in mail address in 'href' attribute value is replaced with specified 'word'
+
+  *balance*
+  Balance tags for well-formedness and proper nesting; see section:- #3.3.3
+
+  '0' - no
+  '1' - yes  *
+
+  *base_url*
+  Base URL value that needs to be set if '$config["abs_url"]' is not '0'; see section:- #3.4.4
+
+  *cdata*
+  Handling of 'CDATA' sections; see section:- #3.3.1
+
+  '0' - don't consider 'CDATA' sections as markup and proceed as if plain text  ^"
+  '1' - remove
+  '2' - allow, but neutralize any '<', '>', and '&' inside by converting them to named entities
+  '3' - allow  *
+
+  *clean_ms_char*
+  Replace discouraged characters introduced by Microsoft Word, etc.; see section:- #3.1
+
+  '0' - no  *
+  '1' - yes
+  '2' - yes, but replace special single & double quotes with ordinary ones
+
+  *comment*
+  Handling of HTML comments; see section:- #3.3.1
+
+  '0' - don't consider comments as markup and proceed as if plain text  ^"
+  '1' - remove
+  '2' - allow, but neutralize any '<', '>', and '&' inside by converting to named entities
+  '3' - allow  *
+
+  *css_expression*
+  Allow dynamic CSS expression by not removing the expression from CSS property values in 'style' attributes; see section:- #3.4.8
+
+  '0' - remove  *
+  '1' - allow
+
+  *deny_attribute*
+  Denied HTML attributes; see section:- #3.4
+
+  '0' - none  *
+  'string' - dictated by values in 'string'
+  'on*' (like 'onfocus') attributes not allowed - "
+
+  *elements*
+  Allowed HTML elements; see section:- #3.3
+
+  '* -center -dir -font -isindex -menu -s -strike -u' -  ~
+  'applet, embed, iframe, object, script' not allowed - "
+
+  *hexdec_entity*
+  Allow hexadecimal numeric entities and do not convert to the more widely accepted decimal ones, or convert decimal to hexadecimal ones; see section:- #3.2
+
+  '0' - no
+  '1' - yes  *
+  '2' - convert decimal to hexadecimal ones
+
+  *hook*
+  Name of an optional hook function to alter the input string, '$config' or '$spec' before htmLawed starts its main work; see section:- #3.7
+
+  '0' - no hook function  *
+  'name' - 'name' is name of the hook function ('kses_hook'  ^)
+
+  *hook_tag*
+  Name of an optional hook function to alter tag content finalized by htmLawed; see section:- #3.4.9
+
+  '0' - no hook function  *
+  'name' - 'name' is name of the hook function
+
+  *keep_bad*
+  Neutralize bad tags by converting '<' and '>' to entities, or remove them; see section:- #3.3.3
+
+  '0' - remove  ^
+  '1' - neutralize both tags and element content
+  '2' - remove tags but neutralize element content
+  '3' and '4' - like '1' and '2' but remove if text ('pcdata') is invalid in parent element
+  '5' and '6' * -  like '3' and '4' but line-breaks, tabs and spaces are left
+
+  *lc_std_val*
+  For XHTML compliance, predefined, standard attribute values, like 'get' for the 'method' attribute of 'form', must be lowercased; see section:- #3.4.5
+
+  '0' - no
+  '1' - yes  *
+
+  *make_tag_strict*
+  Transform/remove these non-strict XHTML elements, even if they are allowed by the admin: 'applet' 'center' 'dir' 'embed' 'font' 'isindex' 'menu' 's' 'strike' 'u'; see section:- #3.3.2
+
+  '0' - no  ^
+  '1' - yes, but leave 'applet', 'embed' and 'isindex' elements that currently can't be transformed  *
+  '2' - yes, removing 'applet', 'embed' and 'isindex' elements and their contents (nested elements remain)  ~
+
+  *named_entity*
+  Allow non-universal named HTML entities, or convert to numeric ones; see section:- #3.2
+
+  '0' - convert
+  '1' - allow  *
+
+  *no_deprecated_attr*
+  Allow deprecated attributes or transform them; see section:- #3.4.6
+
+  '0' - allow  ^
+  '1' - transform, but 'name' attributes for 'a' and 'map' are retained  *
+  '2' - transform
+
+  *parent*
+  Name of the parent element, possibly imagined, that will hold the input; see section:- #3.3
+
+  *safe*
+  Magic parameter to make input the most secure against XSS without needing to specify other relevant '$config' parameters; see section:- #3.6
+
+  '0' - no  *
+  '1' - will auto-adjust other relevant '$config' parameters (indicated by '"' in this list)
+
+  *schemes*
+  Array of attribute-specific, comma-separated, lower-cased list of schemes (protocols) allowed in attributes accepting URLs; '*' covers all unspecified attributes; see section:- #3.4.3
+
+  'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https'  *
+  '*: ftp, gopher, http, https, mailto, news, nntp, telnet'  ^
+  'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; style: nil; *:file, http, https'  "
+
+  *show_setting*
+  Name of a PHP variable to assign the `finalized` '$config' and '$spec' values; see section:- #3.8
+
+  *style_pass*
+  Do not look at 'style' attribute values, letting them through without any alteration
+
+  '0' - no *
+  '1' - htmLawed will let through any 'style' value; see section:- #3.4.8
+
+  *tidy*
+  Beautify or compact HTML code; see section:- #3.3.5
+
+  '-1' - compact
+  '0' - no  *
+  '1' or 'string' - beautify (custom format specified by 'string')
+
+  *unique_ids*
+  'id' attribute value checks; see section:- #3.4.2
+
+  '0' - no  ^
+  '1' - remove duplicate and/or invalid ones  *
+  'word' - remove invalid ones and replace duplicate ones with new and unique ones based on the 'word'; the admin-specified 'word', like 'my_', should begin with a letter (a-z) and can contain letters, digits, '.', '_', '-', and ':'.
+
+  *valid_xhtml*
+  Magic parameter to make input the most valid XHTML without needing to specify other relevant '$config' parameters; see section:- #3.5
+
+  '0' - no  *
+  '1' - will auto-adjust other relevant '$config' parameters (indicated by '~' in this list)
+
+  *xml:lang*
+  Auto-adding 'xml:lang' attribute; see section:- #3.4.1
+
+  '0' - no  *
+  '1' - add if 'lang' attribute is present
+  '2' - add if 'lang' attribute is present, and remove 'lang'  ~
+
+
+-- 2.3  Extra HTML specifications using the $spec parameter --------o
+
+
+  The '$spec' argument can be used to disallow an otherwise legal attribute for an element, or to restrict the attribute's values. This can also be helpful as a security measure (e.g., in certain versions of browsers, certain values can cause buffer overflows and denial of service attacks), or in enforcing admin policy compliance. '$spec' is specified as a string of text containing one or more `rules`, with multiple rules separated from each other by a semi-colon (';'). E.g.,
+
+    $spec = 'i=-*; td, tr=style, id, -*; a=id(match="/[a-z][a-z\d.:\-`"]*/i"/minval=2), href(maxlen=100/minlen=34); img=-width,-alt';
+    $processed = htmLawed($text, $config, $spec);
+
+  Or,
+
+    $processed = htmLawed($text, $config, 'i=-*; td, tr=style, id, -*; a=id(match="/[a-z][a-z\d.:\-`"]*/i"/minval=2), href(maxlen=100/minlen=34); img=-width,-alt');
+
+  A rule begins with an HTML *element* name(s) (`rule-element`), for which the rule applies, followed by an equal ('=') sign. A rule-element may represent multiple elements if comma (,)-separated element names are used. E.g., 'th,td,tr='.
+
+  Rest of the rule consists of comma-separated HTML *attribute names*. A minus ('-') character before an attribute means that the attribute is not permitted inside the rule-element. E.g., '-width'. To deny all attributes, '-*' can be used.
+
+  Following shows examples of rule excerpts with rule-element 'a' and the attributes that are being permitted:
+
+  *  'a=' - all
+  *  'a=id' - all
+  *  'a=href, title, -id, -onclick' - all except 'id' and 'onclick'
+  *  'a=*, id, -id' - all except 'id'
+  *  'a=-*' - none
+  *  'a=-*, href, title' - none except 'href' and 'title'
+  *  'a=-*, -id, href, title' - none except 'href' and 'title'
+
+  Rules regarding *attribute values* are optionally specified inside round brackets after attribute names in slash ('/')-separated `parameter = value` pairs. E.g., 'title(maxlen=30/minlen=5)'. None, or one or more of the following parameters may be specified:
+
+  *  'oneof' - one or more choices separated by '|' that the value should match; if only one choice is provided, then the value must match that choice
+
+  *  'noneof' - one or more choices separated by '|' that the value should not match
+
+  *  'maxlen' and 'minlen' - upper and lower limits for the number of characters in the attribute value; specified in numbers
+
+  *  'maxval' and 'minval' - upper and lower limits for the numerical value specified in the attribute value; specified in numbers
+
+  *  'match' and 'nomatch' - pattern that the attribute value should or should not match; specified as PHP/PCRE-compatible regular expressions with delimiters and possibly modifiers
+
+  *  'default' - a value to force on the attribute if the value provided by the writer does not fit any of the specified parameters
+
+  If 'default' is not set and the attribute value does not satisfy any of the specified parameters, then the attribute is removed. The 'default' value can also be used to force all attribute declarations to take the same value (by getting the values declared illegal by setting, e.g., 'maxlen' to '-1').
+
+  Examples with `input` '<input title="WIDTH" value="10em" /><input title="length" value="5" />' are shown below.
+
+  `Rule`: 'input=title(maxlen=60/minlen=6), value'
+  `Output`: '<input value="10em" /><input title="length" value="5" />'
+
+  `Rule`: 'input=title(), value(maxval=8/default=6)'
+  `Output`: '<input title="WIDTH" value="6" /><input title="length" value="5" />'
+
+  `Rule`: 'input=title(nomatch=$w.d$i), value(match=$em$/default=6em)'
+  `Output`: '<input value="10em" /><input title="length" value="6em" />'
+
+  `Rule`: 'input=title(oneof=height|depth/default=depth), value(noneof=5|6)'
+  `Output`: '<input title="depth" value="10em" /><input title="depth" />'
+
+  *Special characters*: The characters ';', ',', '/', '(', ')', '|', '~' and space have special meanings in the rules. Words in the rules that use such characters, or the characters themselves, should be `escaped` by enclosing in pairs of double-quotes ('"'). A back-tick ('`') can be used to escape a literal '"'. An example rule illustrating this is 'input=value(maxlen=30/match="/^\w/"/default="your `"ID`"")'.
+   
+  *Note*: To deny an attribute for all elements for which it is legal, '$config["deny_attribute"]' (see section:- #3.4) can be used instead of '$spec'. Also, attributes can be allowed element-specifically through '$spec' while being denied globally through '$config["deny_attribute"]'. The 'hook_tag' parameter (section:- #3.4.9) can also be used to implement the '$spec' functionality.
+
+
+-- 2.4  Performance time & memory usage ----------------------------o
+
+
+  The time and memory used by htmLawed depends on its configuration and the size of the input, and the amount, nestedness and well-formedness of the HTML markup within it. In particular, tag balancing and beautification each can increase the processing time by about a quarter.
+
+  The htmLawed demo:- htmLawedTest.php can be used to evaluate the performance and effects of different types of input and '$config'.
+
+
+-- 2.5  Some security risks to keep in mind ------------------------o
+
+
+  When setting the parameters/arguments (like those to allow certain HTML elements) for use with htmLawed, one should bear in mind that the setting may let through potentially `dangerous` HTML code. (This may not be a problem if the authors are trusted.)
+
+  For example, following increase security risks:
+
+  *  Allowing 'script', 'applet', 'embed', 'iframe' or 'object' elements, or certain of their attributes like 'allowscriptaccess'
+
+  *  Allowing HTML comments (some Internet Explorer versions are vulnerable with, e.g., '<!--[if gte IE 4]><script>alert("xss");</script><![endif]-->'
+  
+  *  Allowing dynamic CSS expressions (a feature of the IE browser)
+
+  `Unsafe` HTML can be removed by setting '$config' appropriately. E.g., '$config["elements"] = "* -script"' (section:- #3.3), '$config["safe"] = 1' (section:- #3.6), etc.
+
+
+-- 2.6  Use without modifying old 'kses()' code --------------------o
+
+
+  The 'Kses' PHP script is used by many applications (like 'WordPress'). It is possible to have such applications use htmLawed instead, since it is compatible with code that calls the 'kses()' function declared in the 'Kses' file (usually named 'kses.php'). E.g., application code like this will continue to work after replacing 'Kses' with htmLawed:
+
+    $comment_filtered = kses($comment_input, array('a'=>array(), 'b'=>array(), 'i'=>array()));
+
+  For some of the '$config' parameters, htmLawed will use values other than the default ones. These are indicated by '^' in section:- #2.2. To force htmLawed to use other values, function 'kses()' in the htmLawed code should be edited -- a few configurable parameters/variables need to be changed.
+
+  If the application uses a 'Kses' file that has the 'kses()' function declared, then, to have the application use htmLawed instead of 'Kses', simply rename 'htmLawed.php' (to 'kses.php', e.g.) and replace the 'Kses' file (or just replace the code in the 'Kses' file with the htmLawed code). If the 'kses()' function in the 'Kses' file had been renamed by the application developer (e.g., in 'WordPress', it is named 'wp_kses()'), then appropriately rename the 'kses()' function in the htmLawed code.
+
+  If the 'Kses' file used by the application has been highly altered by the application developers, then one may need a different approach. E.g., with 'WordPress', it is best to copy the htmLawed code to 'wp_includes/kses.php', rename the newly added function 'kses()' to 'wp_kses()', and delete the code for the original 'wp_kses()' function.
+
+  If the 'Kses' code has a non-empty hook function (e.g., 'wp_kses_hook()' in case of 'WordPress'), then the code for htmLawed's 'kses_hook()' function should be appropriately edited. However, the requirement of the hook function should be re-evaluated considering that htmLawed has extra capabilities. With 'WordPress', the hook function is an essential one. The following code is suggested for the htmLawed 'kses_hook()' in case of 'WordPress':
+
+    function kses_hook($string, &$cf, &$spec){
+    // kses compatibility
+    $allowed_html = $spec;
+    $allowed_protocols = array();
+    foreach($cf['schemes'] as $v){
+     foreach($v as $k2=>$v2){
+      if(!in_array($k2, $allowed_protocols)){
+       $allowed_protocols[] = $k2;
+      }
+     }
+    }
+    return wp_kses_hook($string, $allowed_html, $allowed_protocols);
+    // eof
+    }
+
+
+-- 2.7  Tolerance for ill-written HTML -----------------------------o
+
+
+  htmLawed can work with ill-written HTML code in the input. However, HTML that is too ill-written may not be `read` as HTML, and be considered mere plain text instead. Following statements indicate the degree of `looseness` that htmLawed can work with, and can be provided in instructions to writers:
+
+  *  Tags must be flanked by '<' and '>' with no '>' inside -- any needed '>' should be put in as '&gt;'. It is possible for tag content (element name and attributes) to be spread over many lines instead of being on one. A space may be present between the tag content and '>', like '<div >' and '<img / >', but not after the '<'.
+
+  *  Element and attribute names need not be lower-cased.
+
+  *  Attribute string of elements may be liberally spaced with tabs, line-breaks, etc.
+
+  *  Attribute values may not be double-quoted, or may be single-quoted.
+
+  *  Left-padding of numeric entities (like, '&#0160;', '&x07ff;') with '0' is okay as long as the number of characters between between the '&' and the ';' does not exceed 8. All entities must end with ';' though. 
+
+  *  Named character entities must be properly cased. E.g., '&Lt;' or '&TILDE;' will not be let through without modification.
+
+  *  HTML comments should not be inside element tags (okay between tags), and should begin with '<!--' and end with '-->'. Characters like '<', '>', and '&' may be allowed inside depending on '$config', but any '-->' inside should be put in as '--&gt;'. Any '--' inside will be automatically converted to '-', and a space will be added before the comment delimiter '-->'.
+
+  *  'CDATA' sections should not be inside element tags, and can be in element content only if plain text is allowed for that element. They should begin with '<[CDATA[' and end with ']]>'. Characters like '<', '>', and '&' may be allowed inside depending on '$config', but any ']]>' inside should be put in as ']]&gt;'.
+
+  *  For attribute values, character entities '&lt;', '&gt;' and '&amp;' should be used instead of characters '<' and '>', and '&' (when '&' is not part of a character entity). This applies even for Javascript code in values of attributes like 'onclick'.
+
+  *  Characters '<', '>', '&' and '"' that are part of actual Javascript, etc., code in 'script' elements should be used as such and not be put in as entities like '&gt;'. Otherwise, though the HTML will be valid, the code may fail to work. Further, if such characters have to be used, then they should be put inside 'CDATA' sections.
+
+  *  Simple instructions like "an opening tag cannot be present between two closing tags" and "nested elements should be closed in the reverse order of how they were opened" can help authors write balanced HTML. If tags are imbalanced, htmLawed will try to balance them, but in the process, depending on '$config["keep_bad"]', some code/text may be lost.
+
+  *  Input authors should be notified of admin-specified allowed elements, attributes, configuration values (like conversion of named entities to numeric ones), etc.
+
+  *  With '$config["unique_ids"]' not '0' and the 'id' attribute being permitted, writers should carefully avoid using duplicate or invalid 'id' values as even though htmLawed will correct/remove the values, the final output may not be the one desired. E.g., when '<a id="home"></a><input id="home" /><label for="home"></label>' is processed into 
+'<a id="home"></a><input id="prefix_home" /><label for="home"></label>'.
+
+  *  Note that even if intended HTML is lost in a highly ill-written input, the processed output will be more secure and standard-compliant.
+
+  *  For URLs, unless '$config["scheme"]' is appropriately set, writers should avoid using escape characters or entities in schemes. E.g., 'htt&#112;' (which many browsers will read as the harmless 'http') may be considered bad by htmLawed.
+
+  *  htmLawed will attempt to put plain text present directly inside 'blockquote', 'form', 'map' and 'noscript' elements (illegal as per the specs) inside auto-generated 'div' elements.
+
+
+-- 2.8  Limitations & work-arounds ---------------------------------o
+
+
+  htmLawed's main objective is to make the input text `more` standard-compliant, secure for web-page readers, and free of HTML elements and attributes considered undesirable by the administrator. Some of its current limitations, regardless of this objective, are noted below along with work-arounds.
+
+  It should be borne in mind that no browser application is 100% standard-compliant, and that some of the standard specs (like asking for normalization of white-spacing within 'textarea' elements) are clearly wrong. Regarding security, note that `unsafe` HTML code is not necessarily legally invalid.
+
+  *  htmLawed is meant for input that goes into the 'body' of HTML documents. HTML's head-level elements are not supported, nor are the frameset elements 'frameset', 'frame' and 'noframes'.
+
+  *  It cannot transform the non-standard 'embed' elements to the standard-compliant 'object' elements. Yet, it can allow 'embed' elements if permitted ('embed' is widely used and supported). Admins can certainly use the 'hook_tag' parameter (section:- #3.4.9) to deploy a custom embed-to-object converter function.
+
+  *  The only non-standard element that may be permitted is 'embed'; others like 'noembed' and 'nobr' cannot be permitted without modifying the htmLawed code.
+
+  *  It cannot handle input that has non-HTML code like 'SVG' and 'MathML'. One way around is to break the input into pieces and passing only those without non-HTML code to htmLawed. Another is described in section:- #3.9. A third way may be to some how take advantage of the '$config["and_mark"]' parameter (see section:- #3.2).
+
+  *  By default, htmLawed won't check many attribute values for standard compliance. E.g., 'width="20m"' with the dimension in non-standard 'm' is let through. Implementing universal and strict attribute value checks can make htmLawed slow and resource-intensive. Admins should look at the 'hook_tag' parameter (section:- #3.4.9) or '$spec' to enforce finer checks.
+
+  *  The attributes, deprecated (which can be transformed too) or not, that it supports are largely those that are in the specs. Only a few of the proprietary attributes are supported.
+
+  *  Except for contained URLs and dynamic expressions (also optional), htmLawed does not check CSS style property values. Admins should look at using the 'hook_tag' parameter (section:- #3.4.9) or '$spec' for finer checks. Perhaps the best option is to disallow 'style' but allow 'class' attributes with the right 'oneof' or 'match' values for 'class', and have the various class style properties in '.css' CSS stylesheet files.
+
+  *  htmLawed does not parse emoticons, decode `BBcode`, or `wikify`, auto-converting text to proper HTML. Similarly, it won't convert line-breaks to 'br' elements. Such functions are beyond its purview. Admins should use other code to pre- or post-process the input for such purposes.
+
+  *  htmLawed cannot be used to have links force-opened in new windows (by auto-adding appropriate 'target' and 'onclick' attributes to 'a'). Admins should look at Javascript-based DOM-modifying solutions for this. Admins may also be able to use a custom hook function to enforce such checks ('hook_tag' parameter; see section:- #3.4.9).
+
+  *  Nesting-based checks are not possible. E.g., one cannot disallow 'p' elements specifically inside 'td' while permitting it elsewhere. Admins may be able to use a custom hook function to enforce such checks ('hook_tag' parameter; see section:- #3.4.9).
+
+  *  Except for optionally converting absolute or relative URLs to the other type, htmLawed will not alter URLs (e.g., to change the value of query strings or to convert 'http' to 'https'. Having absolute URLs may be a standard-requirement, e.g., when HTML is embedded in email messages, whereas altering URLs for other purposes is beyond htmLawed's goals. Admins may be able to use a custom hook function to enforce such checks ('hook_tag' parameter; see section:- #3.4.9).
+
+  *  Pairs of opening and closing tags that do not enclose any content (like '<em></em>') are not removed. This may be against the standard specs for certain elements (e.g., 'table'). However, presence of such standard-incompliant code will not break the display or layout of content. Admins can also use simple regex-based code to filter out such code.
+
+  *  htmLawed does not check for certain element orderings described in the standard specs (e.g., in a 'table', 'tbody' is allowed before 'tfoot'). Admins may be able to use a custom hook function to enforce such checks ('hook_tag' parameter; see section:- #3.4.9).
+
+  *  htmLawed does not check the number of nested elements. E.g., it will allow two 'caption' elements in a 'table' element, illegal as per the specs. Admins may be able to use a custom hook function to enforce such checks ('hook_tag' parameter; see section:- #3.4.9).
+
+  *  htmLawed might convert certain entities to actual characters and remove backslashes and CSS comment-markers ('/*') in 'style' attribute values in order to detect malicious HTML like crafted IE-specific dynamic expressions like '&#101;xpression...'. If this is too harsh, admins can allow CSS expressions through htmLawed core but then use a custom function through the 'hook_tag' parameter (section:- #3.4.9) to more specifically identify CSS expressions in the 'style' attribute values. Also, using '$config["style_pass"]', it is possible to have htmLawed pass 'style' attribute values without even looking at them (section:- #3.4.8).
+
+  *  htmLawed does not correct certain possible attribute-based security vulnerabilities (e.g., '<a href="http://x%22+style=%22background-image:xss">x</a>'). These arise when browsers mis-identify markup in `escaped` text, defeating the very purpose of escaping text (a bad browser will read the given example as '<a href="http://x" style="background-image:xss">x</a>').
+  
+  *  Because of poor Unicode support in PHP, htmLawed does not remove the `high value` HTML-invalid characters with multi-byte code-points. Such characters however are extremely unlikely to be in the input. (see section:- #3.1).
+
+  *  Like any script using PHP's PCRE regex functions, PHP setup-specific low PCRE limit values can cause htmLawed to at least partially fail with very long input texts.
+
+
+-- 2.9  Examples ---------------------------------------------------o
+
+
+  *1.* A blog administrator wants to allow only 'a', 'em', 'strike', 'strong' and 'u' in comments, but needs 'strike' and 'u' transformed to 'span' for better XHTML 1-strict compliance, and, he wants the 'a' links to be to 'http' or 'https' resources:
+
+    $processed = htmLawed($in, array('elements'=>'a, em, strike, strong, u', 'make_tag_strict'=>1, 'safe'=>1, 'schemes'=>'*:http, https'), 'a=href');
+
+  *2.* An author uses a custom-made web application to load content on his web-site. He is the only one using that application and the content he generates has all types of HTML, including scripts. The web application uses htmLawed primarily as a tool to correct errors that creep in while writing HTML and to take care of the occasional `bad` characters in copy-paste text introduced by Microsoft Office. The web application provides a preview before submitted input is added to the content. For the previewing process, htmLawed is set up as follows:
+
+    $processed = htmLawed($in, array('css_expression'=>1, 'keep_bad'=>1, 'make_tag_strict'=>1, 'schemes'=>'*:*', 'valid_xhtml'=>1));
+
+  For the final submission process, 'keep_bad' is set to '6'. A value of '1' for the preview process allows the author to note and correct any HTML mistake without losing any of the typed text.
+
+  *3.* A data-miner is scraping information in a specific table of similar web-pages and is collating the data rows, and uses htmLawed to reduce unnecessary markup and white-spaces:
+
+    $processed = htmLawed($in, array('elements'=>'tr, td', 'tidy'=>-1), 'tr, td =');
+
+
+== 3  Details =====================================================oo
+
+
+-- 3.1  Invalid/dangerous characters --------------------------------
+
+
+  Valid characters (more correctly, their code-points) in HTML or XML are, hexadecimally, '9', 'a', 'd', '20' to 'd7ff', and 'e000' to '10ffff', except 'fffe' and 'ffff' (decimally, '9', '10', '13', '32' to '55295', and '57344' to '1114111', except '65534' and '65535'). htmLawed removes the invalid characters '0' to '8', 'b', 'c', and 'e' to '1f'.
+
+  Because of PHP's poor native support for multi-byte characters, htmLawed cannot check for the remaining invalid code-points. However, for various reasons, it is very unlikely for any of those characters to be in the input.
+
+  Characters that are discouraged (see section:- #5.1) but not invalid are not removed by htmLawed.
+
+  It (function 'hl_tag()') also replaces the potentially dangerous (in some Mozilla [Firefox] and Opera browsers) soft-hyphen character (code-point, hexadecimally, 'ad', or decimally, '173') in attribute values with spaces. Where required, the characters '<', '>', '&', and '"' are converted to entities.
+
+  With '$config["clean_ms_char"]' set as '1' or '2', many of the discouraged characters (decimal code-points '127' to '159' except '133') that many Microsoft applications incorrectly use (as per the 'Windows 1252' ['Cp-1252'] or a similar encoding system), and the character for decimal code-point '133', are converted to appropriate decimal numerical entities (or removed for a few cases)-- see appendix in section:- #5.4. This can help avoid some display issues arising from copying-pasting of content.
+
+  With '$config["clean_ms_char"]' set as '2', characters for the hexadecimal code-points '82', '91', and '92' (for special single-quotes), and '84', '93', and '94' (for special double-quotes) are converted to ordinary single and double quotes respectively and not to entities.
+
+  The character values are replaced with entities/characters and not character values referred to by the entities/characters to keep this task independent of the character-encoding of input text.
+
+  The '$config["clean_ms_char"]' parameter need not be used if authors do not copy-paste Microsoft-created text or if the input text is not believed to use the 'Windows 1252' or a similar encoding. Further, the input form and the web-pages displaying it or its content should have the character encoding appropriately marked-up.
+
+
+-- 3.2  Character references/entities ------------------------------o
+
+
+  Valid character entities take the form '&*;' where '*' is '#x' followed by a hexadecimal number (hexadecimal numeric entity; like '&#xA0;' for non-breaking space), or alphanumeric like 'gt' (external or named entity; like '&nbsp;' for non-breaking space), or '#' followed by a number (decimal numeric entity; like '&#160;' for non-breaking space). Character entities referring to the soft-hyphen character (the '&shy;' or '\xad' character; hexadecimal code-point 'ad' [decimal '173']) in attribute values are always replaced with spaces; soft-hyphens in attribute values introduce vulnerabilities in some older versions of the Opera and Mozilla [Firefox] browsers.
+
+  htmLawed (function 'hl_ent()'):
+
+  *  Neutralizes entities with multiple leading zeroes or missing semi-colons (potentially dangerous)
+
+  *  Lowercases the 'X' (for XML-compliance) and 'A-F' of hexadecimal numeric entities
+
+  *  Neutralizes entities referring to characters that are HTML-invalid (see section:- #3.1)
+
+  *  Neutralizes entities referring to characters that are HTML-discouraged (code-points, hexadecimally, '7f' to '84', '86' to '9f', and 'fdd0' to 'fddf', or decimally, '127' to '132', '134' to '159', and '64991' to '64976'). Entities referring to the remaining discouraged characters (see section:- #5.1 for a full list) are let through.
+
+  *  Neutralizes named entities that are not in the specs.
+
+  *  Optionally converts valid HTML-specific named entities except '&gt;', '&lt;', '&quot;', and '&amp;' to decimal numeric ones (hexadecimal if $config["hexdec_entity"] is '2') for generic XML-compliance. For this, '$config["named_entity"]' should be '1'.
+
+  *  Optionally converts hexadecimal numeric entities to the more widely supported decimal ones. For this, '$config["hexdec_entity"]' should be '0'.
+
+  *  Optionally converts decimal numeric entities to the hexadecimal ones. For this, '$config["hexdec_entity"]' should be '2'.
+
+  `Neutralization` refers to the `entitification` of '&' to '&amp;'.
+
+  *Note*: htmLawed does not convert entities to the actual characters represented by them; one can pass the htmLawed output through PHP's 'html_entity_decode' function:- http://www.php.net/html_entity_decode for that.
+
+  *Note*: If '$config["and_mark"]' is set, and set to a value other than '0', then the '&' characters in the original input are replaced with the control character for the hexadecimal code-point '6' ('\x06'; '&' characters introduced by htmLawed, e.g., after converting '<' to '&lt;', are not affected). This allows one to distinguish, say, an '&gt;' introduced by htmLawed and an '&gt;' put in by the input writer, and can be helpful in further processing of the htmLawed-processed text (e.g., to identify the character sequence 'o(><)o' to generate an emoticon image). When this feature is active, admins should ensure that the htmLawed output is not directly used in web pages or XML documents as the presence of the '\x06' can break documents. Before use in such documents, and preferably before any storage, any remaining '\x06' should be changed back to '&', e.g., with:
+
+    $final = str_replace("\x06", '&', $prelim);
+
+  Also, see section:- #3.9.
+
+
+-- 3.3  HTML elements ----------------------------------------------o
+
+
+  htmLawed can be configured to allow only certain HTML elements (tags) in the input. Disallowed elements (just tag-content, and not element-content), based on '$config["keep_bad"]', are either `neutralized` (converted to plain text by entitification of '<' and '>') or removed.
+
+  E.g., with only 'em' permitted:
+
+  Input:
+
+      <em>My</em> website is <a href="http://a.com>a.com</a>.
+
+  Output, with '$config["keep_bad"] = 0':
+
+      <em>My</em> website is a.com.
+
+  Output, with '$config["keep_bad"]' not '0':
+
+      <em>My</em> website is &lt;a href=""&gt;a.com&lt;/a&gt;.
+
+  See section:- #3.3.3 for differences between the various non-zero '$config["keep_bad"]' values.
+
+  htmLawed by default permits these 86 elements:
+
+    a, abbr, acronym, address, applet, area, b, bdo, big, blockquote, br, button, caption, center, cite, code, col, colgroup, dd, del, dfn, dir, div, dl, dt, em, embed, fieldset, font, form, h1, h2, h3, h4, h5, h6, hr, i, iframe, img, input, ins, isindex, kbd, label, legend, li, map, menu, noscript, object, ol, optgroup, option, p, param, pre, q, rb, rbc, rp, rt, rtc, ruby, s, samp, script, select, small, span, strike, strong, sub, sup, table, tbody, td, textarea, tfoot, th, thead, tr, tt, u, ul, var
+
+  Except for 'embed' (included because of its wide-spread use) and the Ruby elements ('rb', 'rbc', 'rp', 'rt', 'rtc', 'ruby'; part of XHTML 1.1), these are all the elements in the HTML 4/XHTML 1 specs. Strict-specific specs. exclude 'center', 'dir', 'font', 'isindex', 'menu', 's', 'strike', and 'u'.
+
+  With '$config["safe"] = 1', the default set will exclude 'applet', 'embed', 'iframe', 'object' and 'script'; see section:- #3.6.
+
+  When '$config["elements"]', which specifies allowed elements, is `properly` defined, and neither empty nor set to '0' or '*', the default set is not used. To have elements added to or removed from the default set, a '+/-' notation is used. E.g., '*-script-object' implies that only 'script' and 'object' are disallowed, whereas '*+embed' means that 'noembed' is also allowed. Elements can also be specified as comma separated names. E.g., 'a, b, i' means only 'a', 'b' and 'i' are permitted. In this notation, '*', '+' and '-' have no significance and can actually cause a mis-reading.
+
+  Some more examples of '$config["elements"]' values indicating permitted elements (note that empty spaces are liberally allowed for clarity):
+
+  *  'a, blockquote, code, em, strong' -- only 'a', 'blockquote', 'code', 'em', and 'strong'
+  *  '*-script' -- all excluding 'script'
+  *  '* -center -dir -font -isindex -menu -s -strike -u' -- only XHTML-Strict elements
+  *  '*+noembed-script' -- all including 'noembed' excluding 'script'
+
+  Some mis-usages (and the resulting permitted elements) that can be avoided:
+
+  *  '-*' -- none; instead of htmLawed, one might just use, e.g., the 'htmlspecialchars()' PHP function
+  *  '*, -script' -- all except 'script'; admin probably meant '*-script'
+  *  '-*, a, em, strong' -- all; admin probably meant 'a, em, strong'
+  *  '*' -- all; admin need not have set 'elements'
+  *  '*-form+form' -- all; a '+' will always over-ride any '-'
+  *  '*, noembed' -- only 'noembed'; admin probably meant '*+noembed'
+  *  'a, +b, i' -- only 'a' and 'i'; admin probably meant 'a, b, i'
+
+  Basically, when using the '+/-' notation, commas (',') should not be used, and vice versa, and '*' should be used with the former but not the latter.
+
+  *Note*: Even if an element that is not in the default set is allowed through '$config["elements"]', like 'noembed' in the last example, it will eventually be removed during tag balancing unless such balancing is turned off ('$config["balance"]' set to '0'). Currently, the only way around this, which actually is simple, is to edit the various arrays in the function 'hl_bal()' to accommodate the element and its nesting properties.
+
+  *A possibly second way to specify allowed elements* is to set '$config["parent"]' to an element name that supposedly will hold the input, and to set '$config["balance"]' to '1'. During tag balancing (see section:- #3.3.3), all elements that cannot legally nest inside the parent element will be removed. The parent element is auto-reset to 'div' if '$config["parent"]' is empty, 'body', or an element not in htmLawed's default set of 86 elements.
+
+  `Tag transformation` is possible for improving XHTML-Strict compliance -- most of the deprecated elements are removed or converted to valid XHTML-Strict ones; see section:- #3.3.2.
+
+
+.. 3.3.1  Handling of comments and CDATA sections ...................
+
+
+  'CDATA' sections have the format '<![CDATA[...anything but not "]]>"...]]>', and HTML comments, '<!--...anything but not "-->"... -->'. Neither HTML comments nor 'CDATA' sections can reside inside tags. HTML comments can exist anywhere else, but 'CDATA' sections can exist only where plain text is allowed (e.g., immediately inside 'td' element content but not immediately inside 'tr' element content).
+
+  htmLawed (function 'hl_cmtcd()') handles HTML comments or 'CDATA' sections depending on the values of '$config["comment"]' or '$config["cdata"]'. If '0', such markup is not looked for and the text is processed like plain text. If '1', it is removed completely. If '2', it is preserved but any '<', '>' and '&' inside are changed to entities. If '3', they are left as such.
+
+  Note that for the last two cases, HTML comments and 'CDATA' sections will always be removed from tag content (function 'hl_tag()').
+
+  Examples:
+
+  Input:
+    <!-- home link --><a href="home.htm"><![CDATA[x=&y]]>Home</a>
+  Output ('$config["comment"] = 0, $config["cdata"] = 2'):
+    &lt;-- home link --&gt;<a href="home.htm"><![CDATA[x=&amp;y]]>Home</a>
+  Output ('$config["comment"] = 1, $config["cdata"] = 2'):
+    <a href="home.htm"><![CDATA[x=&amp;y]]>Home</a>
+  Output ('$config["comment"] = 2, $config["cdata"] = 2'):
+    <!-- home link --><a href="home.htm"><![CDATA[x=&amp;y]]>Home</a>
+  Output ('$config["comment"] = 2, $config["cdata"] = 1'):
+    <!-- home link --><a href="home.htm">Home</a>
+  Output ('$config["comment"] = 3, $config["cdata"] = 3'):
+    <!-- home link --><a href="home.htm"><![CDATA[x=&y]]>Home</a>
+
+  For standard-compliance, comments are given the form '<!--comment -->', and any '--' in the content is made '-'.
+
+  When '$config["safe"] = 1', CDATA sections and comments are considered plain text unless '$config["comment"]' or '$config["cdata"]' is explicitly specified; see section:- #3.6.
+
+
+.. 3.3.2  Tag-transformation for better XHTML-Strict ................o
+
+
+  If '$config["make_tag_strict"]' is set and not '0', following non-XHTML-Strict elements (and attributes), even if admin-permitted, are mutated as indicated (element content remains intact; function 'hl_tag2()'):
+
+  *  applet - (based on '$config["make_tag_strict"]', unchanged ('1') or removed ('2'))
+  *  center - 'div style="text-align: center;"'
+  *  dir - 'ul'
+  *  embed - (based on '$config["make_tag_strict"]', unchanged ('1') or removed ('2'))
+  *  font (face, size, color) -	'span style="font-family: ; font-size: ; color: ;"' (size transformation reference:- http://style.cleverchimp.com/font_size_intervals/altintervals.html)
+  *  isindex - (based on '$config["make_tag_strict"]', unchanged ('1') or removed ('2'))
+  *  menu - 'ul'
+  *  s - 'span style="text-decoration: line-through;"'
+  *  strike - 'span style="text-decoration: line-through;"'
+  *  u - 'span style="text-decoration: underline;"'
+
+  For an element with a pre-existing 'style' attribute value, the extra style properties are appended.
+
+  Example input:
+
+    <center>
+     The PHP <s>software</s> script used for this <strike>web-page</strike> web-page is <font style="font-weight: bold " face=arial size='+3' color   =  "red  ">htmLawedTest.php</font>, from <u style= 'color:green'>PHP Labware</u>.
+    </center>
+
+  The output:
+
+    <div style="text-align: center;">
+     The PHP <span style="text-decoration: line-through;">software</span> script used for this <span style="text-decoration: line-through;">web-page</span> web-page is <span style="font-weight: bold; font-family: arial; color: red; font-size: 200%;">htmLawedTest.php</span>, from <span style="color:green; text-decoration: underline;">PHP Labware</span>.
+    </div>
+
+
+-- 3.3.3  Tag balancing and proper nesting -------------------------o
+
+
+  If '$config["balance"]' is set to '1', htmLawed (function 'hl_bal()') checks and corrects the input to have properly balanced tags and legal element content (i.e., any element nesting should be valid, and plain text may be present only in the content of elements that allow them).
+
+  Depending on the value of '$config["keep_bad"]' (see section:- #2.2 and section:- #3.3), illegal content may be removed or neutralized to plain text by converting < and > to entities:
+
+  '0' - remove; this option is available only to maintain Kses-compatibility and should not be used otherwise (see section:- #2.6)
+  '1' - neutralize tags and keep element content
+  '2' - remove tags but keep element content
+  '3' and '4' - like '1' and '2', but keep element content only if text ('pcdata') is valid in parent element as per specs
+  '5' and '6' -  like '3' and '4', but line-breaks, tabs and spaces are left
+
+  Example input (disallowing the 'p' element):
+
+    <*> Pseudo-tags <*>
+    <xml>Non-HTML tag xml</xml>
+    <p>
+    Disallowed tag p
+    </p>
+    <ul>Bad<li>OK</li></ul>
+
+  The output with '$config["keep_bad"] = 1':
+
+    &lt;*&gt; Pseudo-tags &lt;*&gt;
+    &lt;xml&gt;Non-HTML tag xml&lt;/xml&gt;
+    &lt;p&gt;
+    Disallowed tag p
+    &lt;/p&gt;
+    <ul>Bad<li>OK</li></ul>
+
+  The output with '$config["keep_bad"] = 3':
+
+    &lt;*&gt; Pseudo-tags &lt;*&gt;
+    &lt;xml&gt;Non-HTML tag xml&lt;/xml&gt;
+    &lt;p&gt;
+    Disallowed tag p
+    &lt;/p&gt;
+    <ul><li>OK</li></ul>
+
+  The output with '$config["keep_bad"] = 6':
+
+    &lt;*&gt; Pseudo-tags &lt;*&gt;
+    Non-HTML tag xml
+    
+    Disallowed tag p
+    
+    <ul><li>OK</li></ul>
+
+  An option like '1' is useful, e.g., when a writer previews his submission, whereas one like '3' is useful before content is finalized and made available to all.
+
+  *Note:* In the example above, unlike '<*>', '<xml>' gets considered as a tag (even though there is no HTML element named 'xml'). In general, text matching the regular expression pattern '<(/?)([a-zA-Z][a-zA-Z1-6]*)([^>]*?)\s?>' is considered a tag (phrase enclosed by the angled brackets '<' and '>', and starting [with an optional slash preceding] with an alphanumeric word that starts with an alphabet...).
+
+  Nesting/content rules for each of the 86 elements in htmLawed's default set (see section:- #3.3) are defined in function 'hl_bal()'. This means that if a non-standard element besides 'embed' is being permitted through '$config["elements"]', the element's tag content will end up getting removed if '$config["balance"]' is set to '1'.
+
+  Plain text and/or certain elements nested inside 'blockquote', 'form', 'map' and 'noscript' need to be in block-level elements. This point is often missed during manual writing of HTML code. htmLawed attempts to address this during balancing. E.g., if the parent container is set as 'form', the input 'B:<input type="text" value="b" />C:<input type="text" value="c" />' is converted to '<div>B:<input type="text" value="b" />C:<input type="text" value="c" /></div>'.
+
+
+-- 3.3.4  Elements requiring child elements ------------------------o
+
+
+  As per specs, the following elements require legal child elements nested inside them:
+
+    blockquote, dir, dl, form, map, menu, noscript, ol, optgroup, rbc, rtc, ruby, select, table, tbody, tfoot, thead, tr, ul
+
+  In some cases, the specs stipulate the number and/or the ordering of the child elements. A 'table' can have 0 or 1 'caption', 'tbody', 'tfoot', and 'thead', but they must be in this order: 'caption', 'thead', 'tfoot', 'tbody'.
+
+  htmLawed currently does not check for conformance to these rules. Note that any non-compliance in this regard will not introduce security vulnerabilities, crash browser applications, or affect the rendering of web-pages.
+
+
+-- 3.3.5  Beautify or compact HTML ---------------------------------o
+
+
+  By default, htmLawed will neither `beautify` HTML code by formatting it with indentations, etc., nor will it make it compact by removing un-needed white-space.(It does always properly white-space tag content.)
+
+  As per the HTML standards, spaces, tabs and line-breaks in web-pages (except those inside 'pre' elements) are all considered equivalent, and referred to as `white-spaces`. Browser applications are supposed to consider contiguous white-spaces as just a single space, and to disregard white-spaces trailing opening tags or preceding closing tags. This white-space `normalization` allows the use of text/code beautifully formatted with indentations and line-spacings for readability. Such `pretty` HTML can, however, increase the size of web-pages, or make the extraction or scraping of plain text cumbersome.
+
+  With the '$config' parameter 'tidy', htmLawed can be used to beautify or compact the input text. Input with just plain text and no HTML markup is also subject to this. Besides 'pre', the 'script' and 'textarea' elements, CDATA sections, and HTML comments are not subjected to the tidying process.
+
+  To `compact`, use '$config["tidy"] = -1'; single instances or runs of white-spaces are replaced with a single space, and white-spaces trailing and leading open and closing tags, respectively, are removed.
+
+  To `beautify`, '$config["tidy"]' is set as '1', or for customized tidying, as a string like '2s2n'. The 's' or 't' character specifies the use of spaces or tabs for indentation. The first and third characters, any of the digits 0-9, specify the number of spaces or tabs per indentation, and any parental lead spacing (extra indenting of the whole block of input text). The 'r' and 'n' characters are used to specify line-break characters: 'n' for '\n' (Unix/Mac OS X line-breaks), 'rn' or 'nr' for '\r\n' (Windows/DOS line-breaks), or 'r' for '\r'.
+
+  The '$config["tidy"]' value of '1' is equivalent to '2s0n'. Other '$config["tidy"]' values are read loosely: a value of '4' is equivalent to '4s0n'; 't2', to '1t2n'; 's', to '2s0n'; '2TR', to '2t0r'; 'T1', to '1t1n'; 'nr3', to '3s0nr', and so on. Except in the indentations and line-spacings, runs of white-spaces are replaced with a single space during beautification.
+
+  Input formatting using '$config["tidy"]' is not recommended when input text has mixed markup (like HTML + PHP).
+
+
+-- 3.4  Attributes ------------------------------------------------oo
+
+
+  htmLawed will only permit attributes described in the HTML specs (including deprecated ones). It also permits some attributes for use with the 'embed' element (the non-standard 'embed' element is supported in htmLawed because of its widespread use), and the the 'xml:space' attribute (valid only in XHTML 1.1). A list of such 111 attributes and the elements they are allowed in is in section:- #5.2.
+
+  When '$config["deny_attribute"]' is not set, or set to '0', or empty ('""'), all the 111 attributes are permitted. Otherwise, '$config["deny_attribute"]' can be set as a list of comma-separated names of the denied attributes. 'on*' can be used to refer to the group of potentially dangerous, script-accepting attributes: 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onreset', 'onselect' and 'onsubmit'.
+
+  Note that attributes specified in '$config["deny_attribute"]' are denied globally, for all elements. To deny attributes for only specific elements, '$spec' (see section:- #2.3) can be used. '$spec' can also be used to element-specifically permit an attribute otherwise denied through '$config["deny_attribute"]'.
+
+  With '$config["safe"] = 1' (section:- #3.6), the 'on*' attributes are automatically disallowed.
+
+  *Note*: To deny all but a few attributes globally, a simpler way to specify '$config["deny_attribute"]' would be to use the notation '* -attribute1 -attribute2 ...'. Thus, a value of '* -title -href' implies that except 'href' and 'title' (where allowed as per standards) all other attributes are to be removed. With this notation, the value for the parameter 'safe' (section:- #3.6) will have no effect on 'deny_attribute'.
+
+  htmLawed (function 'hl_tag()') also:
+
+  *  Lower-cases attribute names
+  *  Removes duplicate attributes (last one stays)
+  *  Gives attributes the form 'name="value"' and single-spaces them, removing unnecessary white-spacing
+  *  Provides `required` attributes (see section:- #3.4.1)
+  *  Double-quotes values and escapes any '"' inside them
+  *  Replaces the possibly dangerous soft-hyphen characters (hexadecimal code-point 'ad') in the values with spaces
+  *  Allows custom function to additionally filter/modify attribute values (see section:- #3.4.9)
+
+
+.. 3.4.1  Auto-addition of XHTML-required attributes ................
+
+
+  If indicated attributes for the following elements are found missing, htmLawed (function 'hl_tag()') will add them (with values same as attribute names unless indicated otherwise below):
+
+  *  area - alt ('area')
+  *  area, img - src, alt ('image')
+  *  bdo - dir ('ltr')
+  *  form - action
+  *  map - name
+  *  optgroup - label
+  *  param - name
+  *  script - type ('text/javascript')
+  *  textarea - rows ('10'), cols ('50')
+
+  Additionally, with '$config["xml:lang"]' set to '1' or '2', if the 'lang' but not the 'xml:lang' attribute is declared, then the latter is added too, with a value copied from that of 'lang'. This is for better standard-compliance. With '$config["xml:lang"]' set to '2', the 'lang' attribute is removed (XHTML 1.1 specs).
+
+  Note that the 'name' attribute for 'map', invalid in XHTML 1.1, is also transformed if required -- see section:- #3.4.6.
+
+
+.. 3.4.2  Duplicate/invalid 'id' values ............................o
+
+
+  If '$config["unique_ids"]' is '1', htmLawed (function 'hl_tag()') removes 'id' attributes with values that are not XHTML-compliant (must begin with a letter and can contain letters, digits, ':', '.', '-' and '_') or duplicate. If '$config["unique_ids"]' is a word, any duplicate but otherwise valid value will be appropriately prefixed with the word to ensure its uniqueness. The word should begin with a letter and should contain only letters, numbers, ':', '.', '_' and '-'.
+
+  Even if multiple inputs need to be filtered (through multiple calls to htmLawed), htmLawed ensures uniqueness of 'id' values as it uses a global variable ('$GLOBALS["hl_Ids"]' array). Further, an admin can restrict the use of certain 'id' values by presetting this variable before htmLawed is called into use. E.g.:
+
+    $GLOBALS['hl_Ids'] = array('top'=>1, 'bottom'=>1, 'myform'=>1); // id values not allowed in input
+    $processed = htmLawed($text); // filter input
+
+
+.. 3.4.3  URL schemes (protocols) and scripts in attribute values ............o
+
+
+  htmLawed edits attributes that take URLs as values if they are found to contain un-permitted schemes. E.g., if the 'afp' scheme is not permitted, then '<a href="afp://domain.org">' becomes '<a href="denied:afp://domain.org">', and if Javascript is not permitted '<a onclick="javascript:xss();">' becomes '<a onclick="denied:javascript:xss();">'.
+
+  By default htmLawed permits these schemes in URLs for the 'href' attribute:
+
+    aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet
+
+  Also, only 'file', 'http' and 'https' are permitted in attributes whose names start with 'o' (like 'onmouseover'), and in these attributes that accept URLs:
+
+    action, cite, classid, codebase, data, href, longdesc, model, pluginspage, pluginurl, src, style, usemap
+
+  These default sets are used when '$config["schemes"]' is not set (see section:- #2.2). To over-ride the defaults, '$config["schemes"]' is defined as a string of semi-colon-separated sub-strings of type 'attribute: comma-separated schemes'. E.g., 'href: mailto, http, https; onclick: javascript; src: http, https'. For unspecified attributes, 'file', 'http' and 'https' are permitted. This can be changed by passing schemes for '*' in '$config["schemes"]'. E.g., 'href: mailto, http, https; *: https, https'.
+
+  '*' can be put in the list of schemes to permit all protocols. E.g., 'style: *; img: http, https' results in protocols not being checked in 'style' attribute values. However, in such cases, any relative-to-absolute URL conversion, or vice versa, (section:- #3.4.4) is not done.
+
+  Thus, `to allow Javascript`, one can set '$config["schemes"]' as 'href: mailto, http, https; *: http, https, javascript', or 'href: mailto, http, https, javascript; *: http, https, javascript', or '*: *', and so on.
+
+  As a side-note, one may find 'style: *' useful as URLs in 'style' attributes can be specified in a variety of ways, and the patterns that htmLawed uses to identify URLs may mistakenly identify non-URL text.
+
+  *Note*: If URL-accepting attributes other than those listed above are being allowed, then the scheme will not be checked unless the attribute name contains the string 'src' (e.g., 'dynsrc') or starts with 'o' (e.g., 'onbeforecopy').
+
+  With '$config["safe"] = 1', all URLs are disallowed in the 'style' attribute values.
+
+
+.. 3.4.4  Absolute & relative URLs in attribute values .............o
+
+
+  htmLawed can make absolute URLs in attributes like 'href' relative ('$config["abs_url"]' is '-1'), and vice versa ('$config["abs_url"]' is '1'). URLs in scripts are not considered for this, and so are URLs like '#section_6' (fragment), '?name=Tim#show' (starting with query string), and ';var=1?name=Tim#show' (starting with parameters). Further, this requires that '$config["base_url"]' be set properly, with the '://' and a trailing slash ('/'), with no query string, etc. E.g., 'file:///D:/page/', 'https://abc.com/x/y/', or 'http://localhost/demo/' are okay, but 'file:///D:/page/?help=1', 'abc.com/x/y/' and 'http://localhost/demo/index.htm' are not.
+
+  For making absolute URLs relative, only those URLs that have the '$config["base_url"]' string at the beginning are converted. E.g., with '$config["base_url"] = "https://abc.com/x/y/"', 'https://abc.com/x/y/a.gif' and 'https://abc.com/x/y/z/b.gif' become 'a.gif' and 'z/b.gif' respectively, while 'https://abc.com/x/c.gif' is not changed.
+
+  When making relative URLs absolute, only values for scheme, network location (host-name) and path values in the base URL are inherited. See section:- #5.5 for more about the URL specification as per RFC 1808:- http://www.ietf.org/rfc/rfc1808.txt.
+
+
+.. 3.4.5  Lower-cased, standard attribute values ....................o
+
+
+  Optionally, for standard-compliance, htmLawed (function 'hl_tag()') lower-cases standard attribute values to give, e.g., 'input type="password"' instead of 'input type="Password"', if '$config["lc_std_val"]' is '1'. Attribute values matching those listed below for any of the elements (plus those for the 'type' attribute of 'button' or 'input') are lower-cased:
+
+    all, baseline, bottom, button, center, char, checkbox, circle, col, colgroup, cols, data, default, file, get, groups, hidden, image, justify, left, ltr, middle, none, object, password, poly, post, preserve, radio, rect, ref, reset, right, row, rowgroup, rows, rtl, submit, text, top
+
+    a, area, bdo, button, col, form, img, input, object, option, optgroup, param, script, select, table, td, tfoot, th, thead, tr, xml:space
+
+  The following `empty` (`minimized`) attributes are always assigned lower-cased values (same as the names):
+
+    checked, compact, declare, defer, disabled, ismap, multiple, nohref, noresize, noshade, nowrap, readonly, selected
+
+
+.. 3.4.6  Transformation of deprecated attributes ..................o
+
+
+  If '$config["no_deprecated_attr"]' is '0', then deprecated attributes (see appendix in section:- #5.2) are removed and, in most cases, their values are transformed to CSS style properties and added to the 'style' attributes (function 'hl_tag()'). Except for 'bordercolor' for 'table', 'tr' and 'td', the scores of proprietary attributes that were never part of any cross-browser standard are not supported.
+
+  *Note*: The attribute 'target' for 'a' is allowed even though it is not in XHTML 1.0 specs. This is because of the attribute's wide-spread use and browser-support, and because the attribute is valid in XHTML 1.1 onwards.
+
+  *  align - for 'img' with value of 'left' or 'right', becomes, e.g., 'float: left'; for 'div' and 'table' with value 'center', becomes 'margin: auto'; all others become, e.g., 'text-align: right'
+
+  *  bgcolor - E.g., 'bgcolor="#ffffff"' becomes 'background-color: #ffffff'
+  *  border - E.g., 'height= "10"' becomes 'height: 10px'
+  *  bordercolor - E.g., 'bordercolor=#999999' becomes 'border-color: #999999;'
+  *  compact - 'font-size: 85%'
+  *  clear - E.g., 'clear="all" becomes 'clear: both'
+
+  *  height - E.g., 'height= "10"' becomes 'height: 10px' and 'height="*"' becomes 'height: auto'
+
+  *  hspace - E.g., 'hspace="10"' becomes 'margin-left: 10px; margin-right: 10px'
+  *  language - 'language="VBScript"' becomes 'type="text/vbscript"'
+  *  name - E.g., 'name="xx"' becomes 'id="xx"'
+  *  noshade - 'border-style: none; border: 0; background-color: gray; color: gray'
+  *  nowrap - 'white-space: nowrap'
+  *  size - E.g., 'size="10"' becomes 'height: 10px'
+  *  start - removed
+  *  type - E.g., 'type="i"' becomes 'list-style-type: lower-roman'
+  *  value - removed
+  *  vspace - E.g., 'vspace="10"' becomes 'margin-top: 10px; margin-bottom: 10px'
+  *  width - like 'height'
+
+  Example input:
+
+    <img src="j.gif" alt="image" name="dad's" /><img src="k.gif" alt="image" id="dad_off" name="dad" />
+    <br clear="left" />
+    <hr noshade size="1" />
+    <img name="img" src="i.gif" align="left" alt="image" hspace="10" vspace="10" width="10em" height="20" border="1" style="padding:5px;" />
+    <table width="50em" align="center" bgcolor="red">
+     <tr>
+      <td width="20%">
+       <div align="center">
+        <h3 align="right">Section</h3>
+        <p align="right">Para</p>
+        <ol type="a" start="e"><li value="x">First item</li></ol>
+       </div>
+      </td>
+      <td width="*">
+       <ol type="1"><li>First item</li></ol>
+      </td>
+     </tr>
+    </table>
+    <br clear="all" />
+
+  And the output with '$config["no_deprecated_attr"] = 1':
+
+    <img src="j.gif" alt="image" /><img src="k.gif" alt="image" id="dad_off" />
+    <br style="clear: left;" />
+    <hr style="border-style: none; border: 0; background-color: gray; color: gray; size: 1px;" />
+    <img src="i.gif" alt="image" width="10em" height="20" style="padding:5px; float: left; margin-left: 10px; margin-right: 10px; margin-top: 10px; margin-bottom: 10px; border: 1px;" id="img" />
+    <table width="50em" style="margin: auto; background-color: red;">
+     <tr>
+      <td style="width: 20%;">
+       <div style="margin: auto;">
+        <h3 style="text-align: right;">Section</h3>
+        <p style="text-align: right;">Para</p>
+        <ol style="list-style-type: lower-latin;"><li>First item</li></ol>
+       </div>
+      </td>
+      <td style="width: auto;">
+       <ol style="list-style-type: decimal;"><li>First item</li></ol>
+      </td>
+     </tr>
+    </table>
+    <br style="clear: both;" />
+
+  For 'lang', deprecated in XHTML 1.1, transformation is taken care of through '$config["xml:lang"]'; see section:- #3.4.1.
+
+  The attribute 'name' is deprecated in 'form', 'iframe', and 'img', and is replaced with 'id' if an 'id' attribute doesn't exist and if the 'name' value is appropriate for 'id'. For such replacements for 'a' and 'map', for which the 'name' attribute is deprecated in XHTML 1.1, '$config["no_deprecated_attr"]' should be set to '2' (when set to '1', for these two elements, the 'name' attribute is retained).
+
+
+-- 3.4.7  Anti-spam & 'href' ---------------------------------------o
+
+
+  htmLawed (function 'hl_tag()') can check the 'href' attribute values (link addresses) as an anti-spam (email or link spam) measure.
+
+  If '$config["anti_mail_spam"]' is not '0', the '@' of email addresses in 'href' values like 'mailto:a@b.com' is replaced with text specified by '$config["anti_mail_spam"]'. The text should be of a form that makes it clear to others that the address needs to be edited before a mail is sent; e.g., '<remove_this_antispam>@' (makes the example address 'a<remove_this_antispam>@b.com').
+
+  For regular links, one can choose to have a 'rel' attribute with 'nofollow' in its value (which tells some search engines to not follow a link). This can discourage link spammers. Additionally, or as an alternative, one can choose to empty the 'href' value altogether (disable the link).
+
+  For use of these options, '$config["anti_link_spam"]' should be set as an array with values 'regex1' and 'regex2', both or one of which can be empty (like 'array("", "regex2")') to indicate that that option is not to be used. Otherwise, 'regex1' or 'regex2' should be PHP- and PCRE-compatible regular expression patterns: 'href' values will be matched against them and those matching the pattern will accordingly be treated.
+
+  Note that the regular expressions should have `delimiters`, and be well-formed and preferably fast. Absolute efficiency/accuracy is often not needed.
+
+  An example, to have a 'rel' attribute with 'nofollow' for all links, and to disable links that do not point to domains 'abc.com' and 'xyz.org':
+
+    $config["anti_link_spam"] = array('`.`', '`://\W*(?!(abc\.com|xyz\.org))`');
+
+
+-- 3.4.8  Inline style properties ----------------------------------o
+
+
+  htmLawed can check URL schemes and dynamic expressions (to guard against Javascript, etc., script-based insecurities) in inline CSS style property values in the 'style' attributes. (CSS properties like 'background-image' that accept URLs in their values are noted in section:- #5.3.) Dynamic CSS expressions that allow scripting in the IE browser, and can be a vulnerability, can be removed from property values by setting '$config["css_expression"]' to '1' (default setting).
+
+  *Note*: Because of the various ways of representing characters in attribute values (URL-escapement, entitification, etc.), htmLawed might alter the values of the 'style' attribute values, and may even falsely identify dynamic CSS expressions and URL schemes in them. If this is an important issue, checking of URLs and dynamic expressions can be turned off ('$config["schemes"] = "...style:*..."', see section:- #3.4.3, and '$config["css_expression"] = 0'). Alternately, admins can use their own custom function for finer handling of 'style' values through the 'hook_tag' parameter (see section:- #3.4.9).
+
+  It is also possible to have htmLawed let through any 'style' value by setting '$config["style_pass"]' to '1'.
+
+  As such, it is better to set up a CSS file with class declarations, disallow the 'style' attribute, set a '$spec' rule (see section:- #2.3) for 'class' for the 'oneof' or 'match' parameter, and ask writers to make use of the 'class' attribute.
+
+
+-- 3.4.9  Hook function for tag content ----------------------------o
+
+
+  It is possible to utilize a custom hook function to alter the tag content htmLawed has finalized (i.e., after it has checked/corrected for required attributes, transformed attributes, lower-cased attribute names, etc.).
+
+  When '$config' parameter 'hook_tag' is set to the name of a function, htmLawed (function 'hl_tag()') will pass on the element name, and the `finalized` attribute name-value pairs as array elements to the function. The function is expected to return the full opening tag string like '<element_name attribute_1_name="attribute_1_value"...>' (for empty elements like 'img' and 'input', the element-closing slash '/' should also be included).
+
+  This is a *powerful functionality* that can be exploited for various objectives: consolidate-and-convert inline 'style' attributes to 'class', convert 'embed' elements to 'object', permit only one 'caption' element in a 'table' element, disallow embedding of certain types of media, *inject HTML*, use CSSTidy:- http://csstidy.sourceforge.net to sanitize 'style' attribute values, etc.
+
+  As an example, the custom hook code below can be used to force a series of specifically ordered 'id' attributes on all elements, and a specific 'param' element inside all 'object' elements:
+
+    function my_tag_function($element, $attribute_array){
+      static $id = 0;
+      // Remove any duplicate element
+      if($element == 'param' && isset($attribute_array['allowscriptaccess'])){
+        return '';
+      }
+
+      $new_element = '';
+
+      // Force a serialized ID number
+      $attribute_array['id'] = 'my_'. $id;
+      ++$id;
+
+      // Inject param for allowscriptaccess
+      if($element == 'object'){
+        $new_element = '<param id='my_'. $id; allowscriptaccess="never" />';
+        ++$id;
+      }
+
+      $string = '';
+      foreach($attribute_array as $k=>$v){
+        $string .= " {$k}=\"{$v}\"";
+      }
+      return "<{$element}{$string}". (isset($in_array($element, $empty_elements) ? ' /' : ''). '>'. $new_element;
+    }
+
+  The 'hook_tag' parameter is different from the 'hook' parameter (section:- #3.7).
+
+  Snippets of hook function code developed by others may be available on the htmLawed:- http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed website.
+
+
+-- 3.5  Simple configuration directive for most valid XHTML -------oo
+
+
+  If '$config["valid_xhtml"]' is set to '1', some relevant '$config' parameters (indicated by '~' in section:- #2.2) are auto-adjusted. This allows one to pass the '$config' argument with a simpler value. If a value for a parameter auto-set through 'valid_xhtml' is still manually provided, then that value will over-ride the auto-set value.
+
+
+-- 3.6  Simple configuration directive for most `safe` HTML --------o
+
+
+  `Safe` HTML refers to HTML that is restricted to reduce the vulnerability for scripting attacks (such as XSS) based on HTML code which otherwise may still be legal and compliant with the HTML standard specs. When elements such as 'script' and 'object', and attributes such as 'onmouseover' and 'style' are allowed in the input text, an input writer can introduce malevolent HTML code. Note that what is considered 'safe' depends on the nature of the web application and the trust-level accorded to its users.
+
+  htmLawed allows an admin to use '$config["safe"]' to auto-adjust multiple '$config' parameters (such as 'elements' which declares the allowed element-set), which otherwise would have to be manually set. The relevant parameters are indicated by '"' in section:- #2.2). Thus, one can pass the '$config' argument with a simpler value.
+
+  With the value of '1', htmLawed considers 'CDATA' sections and HTML comments as plain text, and prohibits the 'applet', 'embed', 'iframe', 'object' and 'script' elements, and the 'on*' attributes like 'onclick'. ( There are '$config' parameters like 'css_expression' that are not affected by the value set for 'safe' but whose default values still contribute towards a more `safe` output.) Further, URLs with schemes (see section:- #3.4.3) are neutralized so that, e.g., 'style="moz-binding:url(http://danger)"' becomes 'style="moz-binding:url(denied:http://danger)"' while 'style="moz-binding:url(ok)"' remains intact.
+
+  Admins, however, may still want to completely deny the 'style' attribute, e.g., with code like
+
+    $processed = htmLawed($text, array('safe'=>1, 'deny_attribute'=>'style'));
+
+  If a value for a parameter auto-set through 'safe' is still manually provided, then that value can over-ride the auto-set value. E.g., with '$config["safe"] = 1' and '$config["elements"] = "*+script"', 'script', but not 'applet', is allowed.
+
+  A page illustrating the efficacy of htmLawed's anti-XSS abilities with 'safe' set to '1' against XSS vectors listed by RSnake:- http://ha.ckers.org/xss.html may be available here:- http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/rsnake/RSnakeXSSTest.htm.
+
+
+-- 3.7  Using a hook function --------------------------------------o
+
+
+  If '$config["hook"]' is not set to '0', then htmLawed will allow preliminarily processed input to be altered by a hook function named by '$config["hook"]' before starting the main work (but after handling of characters, entities, HTML comments and 'CDATA' sections -- see code for function 'htmLawed()').
+
+  The hook function also allows one to alter the `finalized` values of '$config' and '$spec'.
+
+  Note that the 'hook' parameter is different from the 'hook_tag' parameter (section:- #3.4.9).
+
+  Snippets of hook function code developed by others may be available on the htmLawed:- http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed website.
+
+
+-- 3.8  Obtaining `finalized` parameter values ---------------------o
+
+
+  htmLawed can assign the `finalized` '$config' and '$spec' values to a variable named by '$config["show_setting"]'. The variable, made global by htmLawed, is set as an array with three keys: 'config', with the '$config' value, 'spec', with the '$spec' value, and 'time', with a value that is the Unix time (the output of PHP's 'microtime()' function) when the value was assigned. Admins should use a PHP-compliant variable name (e.g., one that does not begin with a numerical digit) that does not conflict with variable names in their non-htmLawed code.
+
+  The values, which are also post-hook function (if any), can be used to auto-generate information (on, e.g., the elements that are permitted) for input writers.
+
+
+-- 3.9  Retaining non-HTML tags in input with mixed markup ---------o
+
+
+  htmLawed does not remove certain characters that though invalid are nevertheless discouraged in HTML documents as per the specs (see section:- #5.1). This can be utilized to deal with input that contains mixed markup. Input that may have HTML markup as well as some other markup that is based on the '<', '>' and '&' characters is considered to have mixed markup. The non-HTML markup can be rather proprietary (like markup for emoticons/smileys), or standard (like MathML or SVG). Or it can be programming code meant for execution/evaluation (such as embedded PHP code).
+
+  To deal with such mixed markup, the input text can be pre-processed to hide the non-HTML markup by specifically replacing the '<', '>' and '&' characters with some of the HTML-discouraged characters (see section:- #3.1.2). Post-htmLawed processing, the replacements are reverted.
+
+  An example (mixed HTML and PHP code in input text):
+
+    $text = preg_replace('`<\?php(.+?)\?>`sm', "\x83?php\\1?\x84", $text);
+    $processed = htmLawed($text);
+    $processed = preg_replace('`\x83\?php(.+?)\?\x84`sm', '<?php$1?>', $processed);
+
+  This code will not work if '$config["clean_ms_char"]' is set to '1' (section:- #3.1), in which case one should instead deploy a hook function (section:- #3.7). (htmLawed internally uses certain control characters, code-points '1' to '7', and use of these characters as markers in the logic of hook functions may cause issues.)
+
+  Admins may also be able to use '$config["and_mark"]' to deal with such mixed markup; see section:- #3.2.
+ 
+
+== 4  Other =======================================================oo
+
+
+-- 4.1  Support -----------------------------------------------------
+
+
+  A careful re-reading of this documentation will very likely answer your questions.
+
+  Software updates and forum-based community-support may be found at http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed. For general PHP issues (not htmLawed-specific), support may be found through internet searches and at http://php.net.
+
+
+-- 4.2  Known issues -----------------------------------------------o
+
+
+  See section:- #2.8.
+
+  Readers are advised to cross-check information given in this document.
+
+
+-- 4.3  Change-log -------------------------------------------------o
+
+
+  (The release date for the downloadable package of files containing documentation, demo script, test-cases, etc., besides the 'htmLawed.php' file may be updated independently if the secondary files are revised.)
+
+  `Version number - Release date. Notes`
+
+  1.1.8.1 - 16 July 2009. Minor code-change to fix a PHP error notice
+
+  1.1.8 - 23 April 2009. Parameter 'deny_attribute' now accepts the wild-card '*', making it simpler to specify its value when all but a few attributes are being denied; fixed a bug in interpreting '$spec'
+
+  1.1.7 - 11-12 March 2009. Attributes globally denied through 'deny_attribute' can be allowed element-specifically through '$spec'; '$config["style_pass"]' allowing letting through any 'style' value introduced; altered logic to catch certain types of dynamic crafted CSS expressions
+
+  1.1.3-6 - 28-31 January - 4 February 2009. Altered logic to catch certain types of dynamic crafted CSS expressions
+
+  1.1.2 - 22 January 2009. Fixed bug in parsing of 'font' attributes during tag transformation
+  
+  1.1.1 - 27 September 2008. Better nesting correction when omitable closing tags are absent
+
+  1.1 - 29 June 2008. '$config["hook_tag"]' and '$config["format"]' introduced for custom tag/attribute check/modification/injection and output compaction/beautification; fixed a regex-in-$spec parsing bug
+
+  1.0.9 - 11 June 2008. Fixed bug in invalid HTML code-point entity check
+
+  1.0.8 - 15 May 2008. 'bordercolor' attribute for 'table', 'td' and 'tr'
+
+  1.0.7 - 1 May 2008. Support for 'wmode' attribute for 'embed'; '$config["show_setting"]' introduced; improved '$config["elements"]' evaluation
+
+  1.0.6 - 20 April 2008. '$config["and_mark"]' introduced
+
+  1.0.5 - 12 March 2008. 'style' URL schemes essentially disallowed when $config 'safe' is on; improved regex for CSS expression search
+
+  1.0.4 - 10 March 2008. Improved corrections for 'blockquote', 'form', 'map' and 'noscript'
+
+  1.0.3 - 3 March 2008. Character entities for soft-hyphens are now replaced with spaces (instead of being removed); a bug allowing 'td' directly inside 'table' fixed; 'safe' '$config' parameter added
+
+  1.0.2 - 13 February 2008. Improved implementation of '$config["keep_bad"]'
+
+  1.0.1 - 7 November 2007. Improved regex for identifying URLs, protocols and dynamic expressions ('hl_tag()' and 'hl_prot()'); no error display with 'hl_regex()'
+
+  1.0 - 2 November 2007. First release
+
+
+-- 4.4  Testing ----------------------------------------------------o
+
+
+  To test htmLawed using a form interface, a demo:- htmLawedTest.php web-page is provided with the htmLawed distribution ('htmLawed.php' and 'htmLawedTest.php' should be in the same directory on the web-server). A file with test-cases:- htmLawed_TESTCASE.txt is also provided.
+
+
+-- 4.5  Upgrade, & old versions ------------------------------------o
+
+
+  Upgrading is as simple as replacing the previous version of 'htmLawed.php' (assuming it was not modified for customized features). As htmLawed output is almost always used in static documents, upgrading should not affect old, finalized content.
+
+  Old versions of htmLawed may be available online. E.g., for version 1.0, check http://www.bioinformatics.org/phplabware/downloads/htmLawed1.zip, for 1.1.1, htmLawed111.zip, and for 1.1.10, htmLawed1110.zip.
+
+
+-- 4.6  Comparison with 'HTMLPurifier' -----------------------------o
+
+
+  The HTMLPurifier PHP library by Edward Yang is a very good HTML filtering script that uses object oriented PHP code. Compared to htmLawed, it:
+
+  *  does not support PHP versions older than 5.0 (HTMLPurifier dropped PHP 4 support after version 2)
+
+  *  is 15-20 times bigger (scores of files totalling more than 750 kb)
+
+  *  consumes 10-15 times more RAM memory (just including the HTMLPurifier files without calling the filter requires a few MBs of memory)
+
+  *  is expectedly slower
+
+  *  does not allow admins to fully allow all valid HTML (because of incomplete HTML support, it always considers elements like 'script' illegal)
+
+  *  lacks many of the extra features of htmLawed (like entity conversions and code compaction/beautification)
+
+  *  has poor documentation
+
+  However, HTMLPurifier has finer checks for character encodings and attribute values, and can log warnings and errors. Visit the HTMLPurifier website:- http://htmlpurifier.org for updated information.
+
+
+-- 4.7  Use through application plug-ins/modules -------------------o
+
+
+  Plug-ins/modules to implement htmLawed in applications such as Drupal and DokuWiki may have been developed. Please check the application websites and the forum on the htmLawed site:- http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed.
+
+
+-- 4.8  Use in non-PHP applications --------------------------------o
+
+
+  Non-PHP applications written in Python, Ruby, etc., may be able to use htmLawed through system calls to the PHP engine. Such code may have been documented on the internet. Also check the forum on the htmLawed site:- http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed.
+
+
+-- 4.9  Donate -----------------------------------------------------o
+
+
+  A donation in any currency and amount to appreciate or support this software can be sent by PayPal:- http://paypal.com to this email address: drpatnaik at yahoo dot com.
+
+
+-- 4.10  Acknowledgements ------------------------------------------o
+
+
+  Bryan Blakey, Ulf Harnhammer, Gareth Heyes, Lukasz Pilorz, Shelley Powers, Edward Yang, and many anonymous users.
+
+  Thank you!
+
+
+== 5  Appendices ==================================================oo
+
+
+-- 5.1  Characters discouraged in XHTML -----------------------------
+
+
+  Characters represented by the following hexadecimal code-points are `not` invalid, even though some validators may issue messages stating otherwise.
+
+  '7f' to '84', '86' to '9f', 'fdd0' to 'fddf', '1fffe', '1ffff', '2fffe', '2ffff', '3fffe', '3ffff', '4fffe', '4ffff', '5fffe', '5ffff', '6fffe', '6ffff', '7fffe', '7ffff', '8fffe', '8ffff', '9fffe', '9ffff', 'afffe', 'affff', 'bfffe', 'bffff', 'cfffe', 'cffff', 'dfffe', 'dffff', 'efffe', 'effff', 'ffffe', 'fffff', '10fffe' and '10ffff'
+
+
+-- 5.2  Valid attribute-element combinations -----------------------o
+
+
+  Valid attribute-element combinations as per W3C specs.
+
+  *  includes deprecated attributes (marked '^'), attributes for the non-standard 'embed' element (marked '*'), and the proprietary 'bordercolor' (marked '~')
+  *  only non-frameset, HTML body elements
+  *  'name' for 'a' and 'map', and 'lang' are invalid in XHTML 1.1
+  *  'target' is valid for 'a' in XHTML 1.1 and higher
+  *  'xml:space' is only for XHTML 1.1
+
+  abbr - td, th
+  accept - form, input
+  accept-charset - form
+  accesskey - a, area, button, input, label, legend, textarea
+  action - form
+  align - caption^, embed, applet, iframe, img^, input^, object^, legend^, table^, hr^, div^, h1^, h2^, h3^, h4^, h5^, h6^, p^, col, colgroup, tbody, td, tfoot, th, thead, tr
+  alt - applet, area, img, input
+  archive - applet, object
+  axis - td, th
+  bgcolor - embed, table^, tr^, td^, th^
+  border - table, img^, object^
+  bordercolor~ - table, td, tr
+  cellpadding - table
+  cellspacing - table
+  char - col, colgroup, tbody, td, tfoot, th, thead, tr
+  charoff - col, colgroup, tbody, td, tfoot, th, thead, tr
+  charset - a, script
+  checked - input
+  cite - blockquote, q, del, ins
+  classid - object
+  clear - br^
+  code - applet
+  codebase - object, applet
+  codetype - object
+  color - font
+  cols - textarea
+  colspan - td, th
+  compact - dir, dl^, menu, ol^, ul^
+  coords - area, a
+  data - object
+  datetime - del, ins
+  declare - object
+  defer - script
+  dir - bdo
+  disabled - button, input, optgroup, option, select, textarea
+  enctype - form
+  face - font
+  for - label
+  frame - table
+  frameborder - iframe
+  headers - td, th
+  height - embed, iframe, td^, th^, img, object, applet
+  href - a, area
+  hreflang - a
+  hspace - applet, img^, object^
+  ismap - img, input
+  label - option, optgroup
+  language - script^
+  longdesc - img, iframe
+  marginheight - iframe
+  marginwidth - iframe
+  maxlength - input
+  method - form
+  model* - embed
+  multiple - select
+  name - button, embed, textarea, applet^, select, form^, iframe^, img^, a^, input, object, map^, param
+  nohref - area
+  noshade - hr^
+  nowrap - td^, th^
+  object - applet
+  onblur - a, area, button, input, label, select, textarea
+  onchange - input, select, textarea
+  onfocus - a, area, button, input, label, select, textarea
+  onreset - form
+  onselect - input, textarea
+  onsubmit - form
+  pluginspage* - embed
+  pluginurl* - embed
+  prompt - isindex
+  readonly - textarea, input
+  rel - a
+  rev - a
+  rows - textarea
+  rowspan - td, th
+  rules - table
+  scope - td, th
+  scrolling - iframe
+  selected - option
+  shape - area, a
+  size - hr^, font, input, select
+  span - col, colgroup
+  src - embed, script, input, iframe, img
+  standby - object
+  start - ol^
+  summary - table
+  tabindex - a, area, button, input, object, select, textarea
+  target - a^, area, form
+  type - a, embed, object, param, script, input, li^, ol^, ul^, button
+  usemap - img, input, object
+  valign - col, colgroup, tbody, td, tfoot, th, thead, tr
+  value - input, option, param, button, li^
+  valuetype - param
+  vspace - applet, img^, object^
+  width - embed, hr^, iframe, img, object, table, td^, th^, applet, col, colgroup, pre^
+  wmode - embed
+  xml:space - pre, script, style
+
+  These are allowed in all but the shown elements:
+
+  class - param, script
+  dir - applet, bdo, br, iframe, param, script
+  id - script
+  lang - applet, br, iframe, param, script
+  onclick - applet, bdo, br, font, iframe, isindex, param, script
+  ondblclick - applet, bdo, br, font, iframe, isindex, param, script
+  onkeydown - applet, bdo, br, font, iframe, isindex, param, script
+  onkeypress - applet, bdo, br, font, iframe, isindex, param, script
+  onkeyup - applet, bdo, br, font, iframe, isindex, param, script
+  onmousedown - applet, bdo, br, font, iframe, isindex, param, script
+  onmousemove - applet, bdo, br, font, iframe, isindex, param, script
+  onmouseout - applet, bdo, br, font, iframe, isindex, param, script
+  onmouseover - applet, bdo, br, font, iframe, isindex, param, script
+  onmouseup - applet, bdo, br, font, iframe, isindex, param, script
+  style - param, script
+  title - param, script
+  xml:lang - applet, br, iframe, param, script
+
+
+-- 5.3  CSS 2.1 properties accepting URLs ------------------------o
+
+
+  background
+  background-image
+  content
+  cue-after
+  cue-before
+  cursor
+  list-style
+  list-style-image
+  play-during
+
+
+-- 5.4  Microsoft Windows 1252 character replacements --------------o
+
+
+  Key: 'd' double, 'l' left, 'q' quote, 'r' right, 's.' single
+
+  Code-point (decimal) - hexadecimal value - replacement entity - represented character
+
+  127 - 7f - (removed) - (not used)
+  128 - 80 - &#8364; - euro
+  129 - 81 - (removed) - (not used)
+  130 - 82 - &#8218; - baseline s. q
+  131 - 83 - &#402; - florin
+  132 - 84 - &#8222; - baseline d q
+  133 - 85 - &#8230; - ellipsis
+  134 - 86 - &#8224; - dagger
+  135 - 87 - &#8225; - d dagger
+  136 - 88 - &#710; - circumflex accent
+  137 - 89 - &#8240; - permile
+  138 - 8a - &#352; - S Hacek
+  139 - 8b - &#8249; - l s. guillemet
+  140 - 8c - &#338; - OE ligature
+  141 - 8d - (removed) - (not used)
+  142 - 8e - &#381; - Z dieresis
+  143 - 8f - (removed) - (not used)
+  144 - 90 - (removed) - (not used)
+  145 - 91 - &#8216; - l s. q
+  146 - 92 - &#8217; - r s. q
+  147 - 93 - &#8220; - l d q
+  148 - 94 - &#8221; - r d q
+  149 - 95 - &#8226; - bullet
+  150 - 96 - &#8211; - en dash
+  151 - 97 - &#8212; - em dash
+  152 - 98 - &#732; - tilde accent
+  153 - 99 - &#8482; - trademark
+  154 - 9a - &#353; - s Hacek
+  155 - 9b - &#8250; - r s. guillemet
+  156 - 9c - &#339; - oe ligature
+  157 - 9d - (removed) - (not used)
+  158 - 9e - &#382; - z dieresis
+  159 - 9f - &#376; - Y dieresis
+
+
+-- 5.5  URL format -------------------------------------------------o
+
+
+  An `absolute` URL has a 'protocol' or 'scheme', a 'network location' or 'hostname', and, optional 'path', 'parameters', 'query' and 'fragment' segments. Thus, an absolute URL has this generic structure:
+
+    (scheme) : (//network location) /(path) ;(parameters) ?(query) #(fragment)
+
+  The schemes can only contain letters, digits, '+', '.' and '-'. Hostname is the portion after the '//' and up to the first '/' (if any; else, up to the end) when ':' is followed by a '//' (e.g., 'abc.com' in 'ftp://abc.com/def'); otherwise, it consists of everything after the ':' (e.g., 'def@abc.com' in mailto:def@abc.com').
+
+  `Relative` URLs do not have explicit schemes and network locations; such values are inherited from a `base` URL.
+
+
+-- 5.6  Brief on htmLawed code -------------------------------------o
+
+
+  Much of the code's logic and reasoning can be understood from the documentation above.
+
+  The *output* of htmLawed is a text string containing the processed input. There is no custom error tracking.
+
+  *Function arguments* for htmLawed are:
+
+  *  '$in' - 1st argument; a text string; the *input text* to be processed. Any extraneous slashes added by PHP when `magic quotes` are enabled should be removed beforehand using PHP's 'stripslashes()' function.
+
+  *  '$config' - 2nd argument; an associative array; optional (named '$C' in htmLawed code). The array has keys with names like 'balance' and 'keep_bad', and the values, which can be boolean, string, or array, depending on the key, are read to accordingly set the *configurable parameters* (indicated by the keys). All configurable parameters receive some default value if the value to be used is not specified by the user through '$config'. `Finalized` '$config' is thus a filtered and possibly larger array.
+
+  *  '$spec' - 3rd argument; a text string; optional. The string has rules, written in an htmLawed-designated format, *specifying* element-specific attribute and attribute value restrictions. Function 'hl_spec()' is used to convert the string to an associative-array for internal use. `Finalized` '$spec' is thus an array.
+
+  `Finalized` '$config' and '$spec' are made *global variables* while htmLawed is at work. Values of any pre-existing global variables with same names are noted, and their values are restored after htmLawed finishes processing the input (to capture the `finalized` values, the 'show_settings' parameter of '$config' should be used). Depending on '$config', another global variable 'hl_Ids', to track 'id' attribute values for uniqueness, may be set. Unlike the other two variables, this one is not reset (or unset) post-processing.
+
+  Except for the main function 'htmLawed()' and the functions 'kses()' and 'kses_hook()', htmLawed's functions are *name-spaced* using the 'hl_' prefix. The *functions* and their roles are:
+
+  *  'hl_attrval' - checking attribute values against $spec
+  *  'hl_bal' - tag balancing
+  *  'hl_cmtcd' - handling CDATA sections and HTML comments
+  *  'hl_ent' - entity handling
+  *  'hl_prot' - checking a URL scheme/protocol
+  *  'hl_regex' - checking syntax of a regular expression
+  *  'hl_spec' - converting user-supplied $spec value to one used by htmLawed internally
+  *  'hl_tag' - handling tags
+  *  'hl_tag2' - transforming tags
+  *  'hl_tidy' - compact/beautify HTML 
+  *  'hl_version' - reporting htmLawed version
+  *  'htmLawed' - main function
+  *  'kses' - main function of 'kses'
+  *  'kses_hook' - hook function of 'kses'
+
+  The last two are for compatibility with pre-existing code using the 'kses' script. htmLawed's 'kses()' basically passes on the filtering task to 'htmLawed()' function after deciphering '$config' and '$spec' from the argument values supplied to it. 'kses_hook()' is an empty function and is meant for being filled with custom code if the 'kses' script users were using one.
+
+  'htmLawed()' finalizes '$spec' (with the help of 'hl_spec()') and '$config', and globalizes them. Finalization of '$config' involves setting default values if an inappropriate or invalid one is supplied. This includes calling 'hl_regex()' to check well-formedness of regular expression patterns if such expressions are user-supplied through '$config'. 'htmLawed()' then removes invalid characters like nulls and 'x01' and appropriately handles entities using 'hl_ent()'. HTML comments and CDATA sections are identified and treated as per '$config' with the help of 'hl_cmtcd()'. When retained, the '<' and '>' characters identifying them, and the '<', '>' and '&' characters inside them, are replaced with control characters (code-points '1' to '5') till any tag balancing is completed.
+
+  After this `initial processing` 'htmLawed()' identifies tags using regex and processes them with the help of 'hl_tag()' --  a large function that analyzes tag content, filtering it as per HTML standards, '$config' and '$spec'. Among other things, 'hl_tag()' transforms deprecated elements using 'hl_tag2()', removes attributes from closing tags, checks attribute values as per '$spec' rules using 'hl_attrval()', and checks URL protocols using 'hl_prot()'. 'htmLawed()' performs tag balancing and nesting checks with a call to 'hl_bal()', and optionally compacts/beautifies the output with proper white-spacing with a call to 'hl_tidy()'. The latter temporarily replaces white-space, and '<', '>' and '&' characters inside 'pre', 'script' and 'textarea' elements, and HTML comments and CDATA sections with control characters (code-points '1' to '5', and '7').
+
+  htmLawed permits the use of custom code or *hook functions* at two stages. The first, called inside 'htmLawed()', allows the input text as well as the finalized $config and $spec values to be altered right after the initial processing (see section:- #3.7). The second is called by 'hl_tag()' once the tag content is finalized (see section:- #3.4.9).
+
+  Being dictated by the external and stable HTML standard, htmLawed's objective is very clear-cut and less concerned with tweakability. The code is only minimally annotated with comments -- it is not meant to instruct; PHP developers familiar with the HTML specs will see the logic, and others can always refer to the htmLawed documentation. The compact structuring of the statements is meant to aid in quickly grasping the logic, at least when viewed with code syntax highlighted.
+
+___________________________________________________________________oo
+
+
+@@description: htmLawed PHP software is a free, open-source, customizable HTML input purifier and filter
+@@encoding: utf-8
+@@keywords: htmLawed, HTM, HTML, HTML Tidy, converter, filter, formatter, purifier, sanitizer, XSS, input, PHP, software, code, script, security, cross-site scripting, hack, sanitize, remove, standards, tags, attributes, elements
+@@language: en
+@@title: htmLawed documentation
Index: /trunk/libs/extensions/htmLawed/htmLawed.php
===================================================================
--- /trunk/libs/extensions/htmLawed/htmLawed.php	(revision 1081)
+++ /trunk/libs/extensions/htmLawed/htmLawed.php	(revision 1081)
@@ -0,0 +1,715 @@
+<?php
+
+/*
+htmLawed 1.1.8.1, 16 July 2009
+Copyright Santosh Patnaik
+GPL v3 license
+A PHP Labware internal utility; www.bioinformatics.org/phplabware/internal_utilities/htmLawed
+
+See htmLawed_README.txt/htm
+*/
+
+function htmLawed($t, $C=1, $S=array()){
+$C = is_array($C) ? $C : array();
+if(!empty($C['valid_xhtml'])){
+ $C['elements'] = empty($C['elements']) ? '*-center-dir-font-isindex-menu-s-strike-u' : $C['elements'];
+ $C['make_tag_strict'] = isset($C['make_tag_strict']) ? $C['make_tag_strict'] : 2;
+ $C['xml:lang'] = isset($C['xml:lang']) ? $C['xml:lang'] : 2;
+}
+// config eles
+$e = array('a'=>1, 'abbr'=>1, 'acronym'=>1, 'address'=>1, 'applet'=>1, 'area'=>1, 'b'=>1, 'bdo'=>1, 'big'=>1, 'blockquote'=>1, 'br'=>1, 'button'=>1, 'caption'=>1, 'center'=>1, 'cite'=>1, 'code'=>1, 'col'=>1, 'colgroup'=>1, 'dd'=>1, 'del'=>1, 'dfn'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'dt'=>1, 'em'=>1, 'embed'=>1, 'fieldset'=>1, 'font'=>1, 'form'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'i'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'ins'=>1, 'isindex'=>1, 'kbd'=>1, 'label'=>1, 'legend'=>1, 'li'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'object'=>1, 'ol'=>1, 'optgroup'=>1, 'option'=>1, 'p'=>1, 'param'=>1, 'pre'=>1, 'q'=>1, 'rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1, 'ruby'=>1, 's'=>1, 'samp'=>1, 'script'=>1, 'select'=>1, 'small'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'sub'=>1, 'sup'=>1, 'table'=>1, 'tbody'=>1, 'td'=>1, 'textarea'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1, 'tt'=>1, 'u'=>1, 'ul'=>1, 'var'=>1); // 86/deprecated+embed+ruby
+if(!empty($C['safe'])){
+ unset($e['applet'], $e['embed'], $e['iframe'], $e['object'], $e['script']);
+}
+$x = !empty($C['elements']) ? str_replace(array("\n", "\r", "\t", ' '), '', $C['elements']) : '*';
+if($x == '-*'){$e = array();}
+elseif(strpos($x, '*') === false){$e = array_flip(explode(',', $x));}
+else{
+ if(isset($x[1])){
+  preg_match_all('`(?:^|-|\+)[^\-+]+?(?=-|\+|$)`', $x, $m, PREG_SET_ORDER);
+  for($i=count($m); --$i>=0;){$m[$i] = $m[$i][0];}
+  foreach($m as $v){
+   if($v[0] == '+'){$e[substr($v, 1)] = 1;}
+   if($v[0] == '-' && isset($e[($v = substr($v, 1))]) && !in_array('+'. $v, $m)){unset($e[$v]);}
+  }
+ }
+}
+$C['elements'] =& $e;
+// config attrs
+$x = !empty($C['deny_attribute']) ? str_replace(array("\n", "\r", "\t", ' '), '', $C['deny_attribute']) : '';
+$x = array_flip((isset($x[0]) && $x[0] == '*') ? explode('-', $x) : explode(',', $x. (!empty($C['safe']) ? ',on*' : '')));
+if(isset($x['on*'])){
+ unset($x['on*']);
+ $x += array('onblur'=>1, 'onchange'=>1, 'onclick'=>1, 'ondblclick'=>1, 'onfocus'=>1, 'onkeydown'=>1, 'onkeypress'=>1, 'onkeyup'=>1, 'onmousedown'=>1, 'onmousemove'=>1, 'onmouseout'=>1, 'onmouseover'=>1, 'onmouseup'=>1, 'onreset'=>1, 'onselect'=>1, 'onsubmit'=>1);
+}
+$C['deny_attribute'] = $x;
+// config URL
+$x = (isset($C['schemes'][2]) && strpos($C['schemes'], ':')) ? strtolower($C['schemes']) : 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https';
+$C['schemes'] = array();
+foreach(explode(';', str_replace(array(' ', "\t", "\r", "\n"), '', $x)) as $v){
+ $x = $x2 = null; list($x, $x2) = explode(':', $v, 2);
+ if($x2){$C['schemes'][$x] = array_flip(explode(',', $x2));}
+}
+if(!isset($C['schemes']['*'])){$C['schemes']['*'] = array('file'=>1, 'http'=>1, 'https'=>1,);}
+if(!empty($C['safe']) && empty($C['schemes']['style'])){$C['schemes']['style'] = array('nil'=>1);}
+$C['abs_url'] = isset($C['abs_url']) ? $C['abs_url'] : 0;
+if(!isset($C['base_url']) or !preg_match('`^[a-zA-Z\d.+\-]+://[^/]+/(.+?/)?$`', $C['base_url'])){
+ $C['base_url'] = $C['abs_url'] = 0;
+}
+// config rest
+$C['and_mark'] = empty($C['and_mark']) ? 0 : 1;
+$C['anti_link_spam'] = (isset($C['anti_link_spam']) && is_array($C['anti_link_spam']) && count($C['anti_link_spam']) == 2 && (empty($C['anti_link_spam'][0]) or hl_regex($C['anti_link_spam'][0])) && (empty($C['anti_link_spam'][1]) or hl_regex($C['anti_link_spam'][1]))) ? $C['anti_link_spam'] : 0;
+$C['anti_mail_spam'] = isset($C['anti_mail_spam']) ? $C['anti_mail_spam'] : 0;
+$C['balance'] = isset($C['balance']) ? (bool)$C['balance'] : 1;
+$C['cdata'] = isset($C['cdata']) ? $C['cdata'] : (empty($C['safe']) ? 3 : 0);
+$C['clean_ms_char'] = empty($C['clean_ms_char']) ? 0 : $C['clean_ms_char'];
+$C['comment'] = isset($C['comment']) ? $C['comment'] : (empty($C['safe']) ? 3 : 0);
+$C['css_expression'] = empty($C['css_expression']) ? 0 : 1;
+$C['hexdec_entity'] = isset($C['hexdec_entity']) ? $C['hexdec_entity'] : 1;
+$C['hook'] = (!empty($C['hook']) && function_exists($C['hook'])) ? $C['hook'] : 0;
+$C['hook_tag'] = (!empty($C['hook_tag']) && function_exists($C['hook_tag'])) ? $C['hook_tag'] : 0;
+$C['keep_bad'] = isset($C['keep_bad']) ? $C['keep_bad'] : 6;
+$C['lc_std_val'] = isset($C['lc_std_val']) ? (bool)$C['lc_std_val'] : 1;
+$C['make_tag_strict'] = isset($C['make_tag_strict']) ? $C['make_tag_strict'] : 1;
+$C['named_entity'] = isset($C['named_entity']) ? (bool)$C['named_entity'] : 1;
+$C['no_deprecated_attr'] = isset($C['no_deprecated_attr']) ? $C['no_deprecated_attr'] : 1;
+$C['parent'] = isset($C['parent'][0]) ? strtolower($C['parent']) : 'body';
+$C['show_setting'] = !empty($C['show_setting']) ? $C['show_setting'] : 0;
+$C['style_pass'] = empty($C['style_pass']) ? 0 : 1;
+$C['tidy'] = empty($C['tidy']) ? 0 : $C['tidy'];
+$C['unique_ids'] = isset($C['unique_ids']) ? $C['unique_ids'] : 1;
+$C['xml:lang'] = isset($C['xml:lang']) ? $C['xml:lang'] : 0;
+
+if(isset($GLOBALS['C'])){$reC = $GLOBALS['C'];}
+$GLOBALS['C'] = $C;
+$S = is_array($S) ? $S : hl_spec($S);
+if(isset($GLOBALS['S'])){$reS = $GLOBALS['S'];}
+$GLOBALS['S'] = $S;
+
+$t = preg_replace('`[\x00-\x08\x0b-\x0c\x0e-\x1f]`', '', $t);
+if($C['clean_ms_char']){
+ $x = array("\x7f"=>'', "\x80"=>'&#8364;', "\x81"=>'', "\x83"=>'&#402;', "\x85"=>'&#8230;', "\x86"=>'&#8224;', "\x87"=>'&#8225;', "\x88"=>'&#710;', "\x89"=>'&#8240;', "\x8a"=>'&#352;', "\x8b"=>'&#8249;', "\x8c"=>'&#338;', "\x8d"=>'', "\x8e"=>'&#381;', "\x8f"=>'', "\x90"=>'', "\x95"=>'&#8226;', "\x96"=>'&#8211;', "\x97"=>'&#8212;', "\x98"=>'&#732;', "\x99"=>'&#8482;', "\x9a"=>'&#353;', "\x9b"=>'&#8250;', "\x9c"=>'&#339;', "\x9d"=>'', "\x9e"=>'&#382;', "\x9f"=>'&#376;');
+ $x = $x + ($C['clean_ms_char'] == 1 ? array("\x82"=>'&#8218;', "\x84"=>'&#8222;', "\x91"=>'&#8216;', "\x92"=>'&#8217;', "\x93"=>'&#8220;', "\x94"=>'&#8221;') : array("\x82"=>'\'', "\x84"=>'"', "\x91"=>'\'', "\x92"=>'\'', "\x93"=>'"', "\x94"=>'"'));
+ $t = strtr($t, $x);
+}
+if($C['cdata'] or $C['comment']){$t = preg_replace_callback('`<!(?:(?:--.*?--)|(?:\[CDATA\[.*?\]\]))>`sm', 'hl_cmtcd', $t);}
+$t = preg_replace_callback('`&amp;([A-Za-z][A-Za-z0-9]{1,30}|#(?:[0-9]{1,8}|[Xx][0-9A-Fa-f]{1,7}));`', 'hl_ent', str_replace('&', '&amp;', $t));
+if($C['unique_ids'] && !isset($GLOBALS['hl_Ids'])){$GLOBALS['hl_Ids'] = array();}
+if($C['hook']){$t = $C['hook']($t, $C, $S);}
+if($C['show_setting'] && preg_match('`^[a-z][a-z0-9_]*$`i', $C['show_setting'])){
+ $GLOBALS[$C['show_setting']] = array('config'=>$C, 'spec'=>$S, 'time'=>microtime());
+}
+// main
+$t = preg_replace_callback('`<(?:(?:\s|$)|(?:[^>]*(?:>|$)))|>`m', 'hl_tag', $t);
+$t = $C['balance'] ? hl_bal($t, $C['keep_bad'], $C['parent']) : $t;
+$t = (($C['cdata'] or $C['comment']) && strpos($t, "\x01") !== false) ? str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05"), array('', '', '&', '<', '>'), $t) : $t;
+$t = $C['tidy'] ? hl_tidy($t, $C['tidy'], $C['parent']) : $t;
+unset($C, $e);
+if(isset($reC)){$GLOBALS['C'] = $reC;}
+if(isset($reS)){$GLOBALS['S'] = $reS;}
+return $t;
+// eof
+}
+
+function hl_attrval($t, $p){
+// check attr val against $S
+$o = 1; $l = strlen($t);
+foreach($p as $k=>$v){
+ switch($k){
+  case 'maxlen':if($l > $v){$o = 0;}
+  break; case 'minlen': if($l < $v){$o = 0;}
+  break; case 'maxval': if((float)($t) > $v){$o = 0;}
+  break; case 'minval': if((float)($t) < $v){$o = 0;}
+  break; case 'match': if(!preg_match($v, $t)){$o = 0;}
+  break; case 'nomatch': if(preg_match($v, $t)){$o = 0;}
+  break; case 'oneof':
+   $m = 0;
+   foreach(explode('|', $v) as $n){if($t == $n){$m = 1; break;}}
+   $o = $m;
+  break; case 'noneof':
+   $m = 1;
+   foreach(explode('|', $v) as $n){if($t == $n){$m = 0; break;}}
+   $o = $m;
+  break; default:
+  break;
+ }
+ if(!$o){break;}
+}
+return ($o ? $t : (isset($p['default']) ? $p['default'] : 0));
+// eof
+}
+
+function hl_bal($t, $do=1, $in='div'){
+// balance tags
+// by content
+$cB = array('blockquote'=>1, 'form'=>1, 'map'=>1, 'noscript'=>1); // Block
+$cE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); // Empty
+$cF = array('button'=>1, 'del'=>1, 'div'=>1, 'dd'=>1, 'fieldset'=>1, 'iframe'=>1, 'ins'=>1, 'li'=>1, 'noscript'=>1, 'object'=>1, 'td'=>1, 'th'=>1); // Flow; later context-wise dynamic move of ins & del to $cI
+$cI = array('a'=>1, 'abbr'=>1, 'acronym'=>1, 'address'=>1, 'b'=>1, 'bdo'=>1, 'big'=>1, 'caption'=>1, 'cite'=>1, 'code'=>1, 'dfn'=>1, 'dt'=>1, 'em'=>1, 'font'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'i'=>1, 'kbd'=>1, 'label'=>1, 'legend'=>1, 'p'=>1, 'pre'=>1, 'q'=>1, 'rb'=>1, 'rt'=>1, 's'=>1, 'samp'=>1, 'small'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'sub'=>1, 'sup'=>1, 'tt'=>1, 'u'=>1, 'var'=>1); // Inline
+$cN = array('a'=>array('a'=>1), 'button'=>array('a'=>1, 'button'=>1, 'fieldset'=>1, 'form'=>1, 'iframe'=>1, 'input'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'fieldset'=>array('fieldset'=>1), 'form'=>array('form'=>1), 'label'=>array('label'=>1), 'noscript'=>array('script'=>1), 'pre'=>array('big'=>1, 'font'=>1, 'img'=>1, 'object'=>1, 'script'=>1, 'small'=>1, 'sub'=>1, 'sup'=>1), 'rb'=>array('ruby'=>1), 'rt'=>array('ruby'=>1)); // Illegal
+$cN2 = array_keys($cN);
+$cR = array('blockquote'=>1, 'dir'=>1, 'dl'=>1, 'form'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'select'=>1, 'table'=>1, 'tbody'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1);
+$cS = array('colgroup'=>array('col'=>1), 'dir'=>array('li'), 'dl'=>array('dd'=>1, 'dt'=>1), 'menu'=>array('li'=>1), 'ol'=>array('li'=>1), 'optgroup'=>array('option'=>1), 'option'=>array('#pcdata'=>1), 'rbc'=>array('rb'=>1), 'rp'=>array('#pcdata'=>1), 'rtc'=>array('rt'=>1), 'ruby'=>array('rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1), 'select'=>array('optgroup'=>1, 'option'=>1), 'script'=>array('#pcdata'=>1), 'table'=>array('caption'=>1, 'col'=>1, 'colgroup'=>1, 'tfoot'=>1, 'tbody'=>1, 'tr'=>1, 'thead'=>1), 'tbody'=>array('tr'=>1), 'tfoot'=>array('tr'=>1), 'textarea'=>array('#pcdata'=>1), 'thead'=>array('tr'=>1), 'tr'=>array('td'=>1, 'th'=>1), 'ul'=>array('li'=>1)); // Specific - immediate parent-child
+$cO = array('address'=>array('p'=>1), 'applet'=>array('param'=>1), 'blockquote'=>array('script'=>1), 'fieldset'=>array('legend'=>1, '#pcdata'=>1), 'form'=>array('script'=>1), 'map'=>array('area'=>1), 'object'=>array('param'=>1, 'embed'=>1)); // Other
+$cT = array('colgroup'=>1, 'dd'=>1, 'dt'=>1, 'li'=>1, 'option'=>1, 'p'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1); // Omitable closing
+// block/inline type; ins & del both type; #pcdata: text
+$eB = array('address'=>1, 'blockquote'=>1, 'center'=>1, 'del'=>1, 'dir'=>1, 'dl'=>1, 'div'=>1, 'fieldset'=>1, 'form'=>1, 'ins'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'isindex'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'p'=>1, 'pre'=>1, 'table'=>1, 'ul'=>1);
+$eI = array('#pcdata'=>1, 'a'=>1, 'abbr'=>1, 'acronym'=>1, 'applet'=>1, 'b'=>1, 'bdo'=>1, 'big'=>1, 'br'=>1, 'button'=>1, 'cite'=>1, 'code'=>1, 'del'=>1, 'dfn'=>1, 'em'=>1, 'embed'=>1, 'font'=>1, 'i'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'ins'=>1, 'kbd'=>1, 'label'=>1, 'map'=>1, 'object'=>1, 'param'=>1, 'q'=>1, 'ruby'=>1, 's'=>1, 'samp'=>1, 'select'=>1, 'script'=>1, 'small'=>1, 'span'=>1, 'strike'=>1, 'strong'=>1, 'sub'=>1, 'sup'=>1, 'textarea'=>1, 'tt'=>1, 'u'=>1, 'var'=>1);
+$eN = array('a'=>1, 'big'=>1, 'button'=>1, 'fieldset'=>1, 'font'=>1, 'form'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'label'=>1, 'object'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'small'=>1, 'sub'=>1, 'sup'=>1, 'textarea'=>1); // Exclude from specific ele; $cN values
+$eO = array('area'=>1, 'caption'=>1, 'col'=>1, 'colgroup'=>1, 'dd'=>1, 'dt'=>1, 'legend'=>1, 'li'=>1, 'optgroup'=>1, 'option'=>1, 'rb'=>1, 'rbc'=>1, 'rp'=>1, 'rt'=>1, 'rtc'=>1, 'script'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'thead'=>1, 'th'=>1, 'tr'=>1); // Missing in $eB & $eI
+$eF = $eB + $eI;
+
+// $in sets allowed child
+$in = ((isset($eF[$in]) && $in != '#pcdata') or isset($eO[$in])) ? $in : 'div';
+if(isset($cE[$in])){
+ return (!$do ? '' : str_replace(array('<', '>'), array('&lt;', '&gt;'), $t));
+}
+if(isset($cS[$in])){$inOk = $cS[$in];}
+elseif(isset($cI[$in])){$inOk = $eI; $cI['del'] = 1; $cI['ins'] = 1;}
+elseif(isset($cF[$in])){$inOk = $eF; unset($cI['del'], $cI['ins']);}
+elseif(isset($cB[$in])){$inOk = $eB; unset($cI['del'], $cI['ins']);}
+if(isset($cO[$in])){$inOk = $inOk + $cO[$in];}
+if(isset($cN[$in])){$inOk = array_diff_assoc($inOk, $cN[$in]);}
+
+$t = explode('<', $t);
+$ok = $q = array(); // $q seq list of open non-empty ele
+ob_start();
+
+for($i=-1, $ci=count($t); ++$i<$ci;){
+ // allowed $ok in parent $p
+ if($ql = count($q)){
+  $p = array_pop($q);
+  $q[] = $p;
+  if(isset($cS[$p])){$ok = $cS[$p];}
+  elseif(isset($cI[$p])){$ok = $eI; $cI['del'] = 1; $cI['ins'] = 1;}
+  elseif(isset($cF[$p])){$ok = $eF; unset($cI['del'], $cI['ins']);}
+  elseif(isset($cB[$p])){$ok = $eB; unset($cI['del'], $cI['ins']);}
+  if(isset($cO[$p])){$ok = $ok + $cO[$p];}
+  if(isset($cN[$p])){$ok = array_diff_assoc($ok, $cN[$p]);}
+ }else{$ok = $inOk; unset($cI['del'], $cI['ins']);}
+ // bad tags, & ele content
+ if(isset($e) && ($do == 1 or (isset($ok['#pcdata']) && ($do == 3 or $do == 5)))){
+  echo '&lt;', $s, $e, $a, '&gt;';
+ }
+ if(isset($x[0])){
+  if($do < 3 or isset($ok['#pcdata'])){echo $x;}
+  elseif(strpos($x, "\x02\x04")){
+   foreach(preg_split('`(\x01\x02[^\x01\x02]+\x02\x01)`', $x, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $v){
+    echo (substr($v, 0, 2) == "\x01\x02" ? $v : ($do > 4 ? preg_replace('`\S`', '', $v) : ''));
+   }
+  }elseif($do > 4){echo preg_replace('`\S`', '', $x);}
+ }
+ // get markup
+ if(!preg_match('`^(/?)([a-zA-Z1-6]+)([^>]*)>(.*)`sm', $t[$i], $r)){$x = $t[$i]; continue;}
+ $s = null; $e = null; $a = null; $x = null; list($all, $s, $e, $a, $x) = $r;
+ // close tag
+ if($s){
+  if(isset($cE[$e]) or !in_array($e, $q)){continue;} // Empty/unopen
+  if($p == $e){array_pop($q); echo '</', $e, '>'; unset($e); continue;} // Last open
+  $add = ''; // Nesting - close open tags that need to be
+  for($j=-1, $cj=count($q); ++$j<$cj;){  
+   if(($d = array_pop($q)) == $e){break;}
+   else{$add .= "</{$d}>";}
+  }
+  echo $add, '</', $e, '>'; unset($e); continue;
+ }
+ // open tag
+ // $cB ele needs $eB ele as child
+ if(isset($cB[$e]) && strlen(trim($x))){
+  $t[$i] = "{$e}{$a}>";
+  array_splice($t, $i+1, 0, 'div>'. $x); unset($e, $x); ++$ci; --$i; continue;
+ }
+ if((($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql)) && !isset($eB[$e]) && !isset($ok[$e])){
+  array_splice($t, $i, 0, 'div>'); unset($e, $x); ++$ci; --$i; continue;
+ }
+ // if no open ele, $in = parent; mostly immediate parent-child relation should hold
+ if(!$ql or !isset($eN[$e]) or !array_intersect($q, $cN2)){
+  if(!isset($ok[$e])){
+   if($ql && isset($cT[$p])){echo '</', array_pop($q), '>'; unset($e, $x); --$i;}
+   continue;
+  }
+  if(!isset($cE[$e])){$q[] = $e;}
+  echo '<', $e, $a, '>'; unset($e); continue;
+ }
+ // specific parent-child
+ if(isset($cS[$p][$e])){
+  if(!isset($cE[$e])){$q[] = $e;}
+  echo '<', $e, $a, '>'; unset($e); continue;
+ }
+ // nesting
+ $add = '';
+ $q2 = array();
+ for($k=-1, $kc=count($q); ++$k<$kc;){
+  $d = $q[$k];
+  $ok2 = array();
+  if(isset($cS[$d])){$q2[] = $d; continue;}
+  $ok2 = isset($cI[$d]) ? $eI : $eF;
+  if(isset($cO[$d])){$ok2 = $ok2 + $cO[$d];}
+  if(isset($cN[$d])){$ok2 = array_diff_assoc($ok2, $cN[$d]);}
+  if(!isset($ok2[$e])){
+   if(!$k && !isset($inOk[$e])){continue 2;}
+   $add = "</{$d}>";
+   for(;++$k<$kc;){$add = "</{$q[$k]}>{$add}";}
+   break;
+  }
+  else{$q2[] = $d;}
+ }
+ $q = $q2;
+ if(!isset($cE[$e])){$q[] = $e;}
+ echo $add, '<', $e, $a, '>'; unset($e); continue;
+}
+
+// end
+if($ql = count($q)){
+ $p = array_pop($q);
+ $q[] = $p;
+ if(isset($cS[$p])){$ok = $cS[$p];}
+ elseif(isset($cI[$p])){$ok = $eI; $cI['del'] = 1; $cI['ins'] = 1;}
+ elseif(isset($cF[$p])){$ok = $eF; unset($cI['del'], $cI['ins']);}
+ elseif(isset($cB[$p])){$ok = $eB; unset($cI['del'], $cI['ins']);}
+ if(isset($cO[$p])){$ok = $ok + $cO[$p];}
+ if(isset($cN[$p])){$ok = array_diff_assoc($ok, $cN[$p]);}
+}else{$ok = $inOk; unset($cI['del'], $cI['ins']);}
+if(isset($e) && ($do == 1 or (isset($ok['#pcdata']) && ($do == 3 or $do == 5)))){
+ echo '&lt;', $s, $e, $a, '&gt;';
+}
+if(isset($x[0])){
+ if(strlen(trim($x)) && (($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql))){
+  echo '<div>', $x, '</div>';
+ }
+ elseif($do < 3 or isset($ok['#pcdata'])){echo $x;}
+ elseif(strpos($x, "\x02\x04")){
+  foreach(preg_split('`(\x01\x02[^\x01\x02]+\x02\x01)`', $x, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $v){
+   echo (substr($v, 0, 2) == "\x01\x02" ? $v : ($do > 4 ? preg_replace('`\S`', '', $v) : ''));
+  }
+ }elseif($do > 4){echo preg_replace('`\S`', '', $x);}
+}
+while(!empty($q) && ($e = array_pop($q))){echo '</', $e, '>';}
+$o = ob_get_contents();
+ob_end_clean();
+return $o;
+// eof
+}
+
+function hl_cmtcd($t){
+// comment/CDATA sec handler
+$t = $t[0];
+global $C;
+if($t[3] == '-'){
+ if(!$C['comment']){return $t;}
+ if($C['comment'] == 1){return '';}
+ if(substr(($t = preg_replace('`--+`', '-', substr($t, 4, -3))), -1) != ' '){$t .= ' ';}
+ $t = $C['comment'] == 2 ? str_replace(array('&', '<', '>'), array('&amp;', '&lt;', '&gt;'), $t) : $t;
+ $t = "\x01\x02\x04!--$t--\x05\x02\x01";
+}else{ // CDATA
+ if(!$C['cdata']){return $t;}
+ if($C['cdata'] == 1){return '';}
+ $t = substr($t, 1, -1);
+ $t = $C['cdata'] == 2 ? str_replace(array('&', '<', '>'), array('&amp;', '&lt;', '&gt;'), $t) : $t;
+ $t = "\x01\x01\x04$t\x05\x01\x01";
+}
+return str_replace(array('&', '<', '>'), array("\x03", "\x04", "\x05"), $t);
+// eof
+}
+
+function hl_ent($t){
+// entitity handler
+global $C;
+$t = $t[1];
+static $U = array('quot'=>1,'amp'=>1,'lt'=>1,'gt'=>1);
+static $N = array('fnof'=>'402', 'Alpha'=>'913', 'Beta'=>'914', 'Gamma'=>'915', 'Delta'=>'916', 'Epsilon'=>'917', 'Zeta'=>'918', 'Eta'=>'919', 'Theta'=>'920', 'Iota'=>'921', 'Kappa'=>'922', 'Lambda'=>'923', 'Mu'=>'924', 'Nu'=>'925', 'Xi'=>'926', 'Omicron'=>'927', 'Pi'=>'928', 'Rho'=>'929', 'Sigma'=>'931', 'Tau'=>'932', 'Upsilon'=>'933', 'Phi'=>'934', 'Chi'=>'935', 'Psi'=>'936', 'Omega'=>'937', 'alpha'=>'945', 'beta'=>'946', 'gamma'=>'947', 'delta'=>'948', 'epsilon'=>'949', 'zeta'=>'950', 'eta'=>'951', 'theta'=>'952', 'iota'=>'953', 'kappa'=>'954', 'lambda'=>'955', 'mu'=>'956', 'nu'=>'957', 'xi'=>'958', 'omicron'=>'959', 'pi'=>'960', 'rho'=>'961', 'sigmaf'=>'962', 'sigma'=>'963', 'tau'=>'964', 'upsilon'=>'965', 'phi'=>'966', 'chi'=>'967', 'psi'=>'968', 'omega'=>'969', 'thetasym'=>'977', 'upsih'=>'978', 'piv'=>'982', 'bull'=>'8226', 'hellip'=>'8230', 'prime'=>'8242', 'Prime'=>'8243', 'oline'=>'8254', 'frasl'=>'8260', 'weierp'=>'8472', 'image'=>'8465', 'real'=>'8476', 'trade'=>'8482', 'alefsym'=>'8501', 'larr'=>'8592', 'uarr'=>'8593', 'rarr'=>'8594', 'darr'=>'8595', 'harr'=>'8596', 'crarr'=>'8629', 'lArr'=>'8656', 'uArr'=>'8657', 'rArr'=>'8658', 'dArr'=>'8659', 'hArr'=>'8660', 'forall'=>'8704', 'part'=>'8706', 'exist'=>'8707', 'empty'=>'8709', 'nabla'=>'8711', 'isin'=>'8712', 'notin'=>'8713', 'ni'=>'8715', 'prod'=>'8719', 'sum'=>'8721', 'minus'=>'8722', 'lowast'=>'8727', 'radic'=>'8730', 'prop'=>'8733', 'infin'=>'8734', 'ang'=>'8736', 'and'=>'8743', 'or'=>'8744', 'cap'=>'8745', 'cup'=>'8746', 'int'=>'8747', 'there4'=>'8756', 'sim'=>'8764', 'cong'=>'8773', 'asymp'=>'8776', 'ne'=>'8800', 'equiv'=>'8801', 'le'=>'8804', 'ge'=>'8805', 'sub'=>'8834', 'sup'=>'8835', 'nsub'=>'8836', 'sube'=>'8838', 'supe'=>'8839', 'oplus'=>'8853', 'otimes'=>'8855', 'perp'=>'8869', 'sdot'=>'8901', 'lceil'=>'8968', 'rceil'=>'8969', 'lfloor'=>'8970', 'rfloor'=>'8971', 'lang'=>'9001', 'rang'=>'9002', 'loz'=>'9674', 'spades'=>'9824', 'clubs'=>'9827', 'hearts'=>'9829', 'diams'=>'9830', 'apos'=>'39',  'OElig'=>'338', 'oelig'=>'339', 'Scaron'=>'352', 'scaron'=>'353', 'Yuml'=>'376', 'circ'=>'710', 'tilde'=>'732', 'ensp'=>'8194', 'emsp'=>'8195', 'thinsp'=>'8201', 'zwnj'=>'8204', 'zwj'=>'8205', 'lrm'=>'8206', 'rlm'=>'8207', 'ndash'=>'8211', 'mdash'=>'8212', 'lsquo'=>'8216', 'rsquo'=>'8217', 'sbquo'=>'8218', 'ldquo'=>'8220', 'rdquo'=>'8221', 'bdquo'=>'8222', 'dagger'=>'8224', 'Dagger'=>'8225', 'permil'=>'8240', 'lsaquo'=>'8249', 'rsaquo'=>'8250', 'euro'=>'8364', 'nbsp'=>'160', 'iexcl'=>'161', 'cent'=>'162', 'pound'=>'163', 'curren'=>'164', 'yen'=>'165', 'brvbar'=>'166', 'sect'=>'167', 'uml'=>'168', 'copy'=>'169', 'ordf'=>'170', 'laquo'=>'171', 'not'=>'172', 'shy'=>'173', 'reg'=>'174', 'macr'=>'175', 'deg'=>'176', 'plusmn'=>'177', 'sup2'=>'178', 'sup3'=>'179', 'acute'=>'180', 'micro'=>'181', 'para'=>'182', 'middot'=>'183', 'cedil'=>'184', 'sup1'=>'185', 'ordm'=>'186', 'raquo'=>'187', 'frac14'=>'188', 'frac12'=>'189', 'frac34'=>'190', 'iquest'=>'191', 'Agrave'=>'192', 'Aacute'=>'193', 'Acirc'=>'194', 'Atilde'=>'195', 'Auml'=>'196', 'Aring'=>'197', 'AElig'=>'198', 'Ccedil'=>'199', 'Egrave'=>'200', 'Eacute'=>'201', 'Ecirc'=>'202', 'Euml'=>'203', 'Igrave'=>'204', 'Iacute'=>'205', 'Icirc'=>'206', 'Iuml'=>'207', 'ETH'=>'208', 'Ntilde'=>'209', 'Ograve'=>'210', 'Oacute'=>'211', 'Ocirc'=>'212', 'Otilde'=>'213', 'Ouml'=>'214', 'times'=>'215', 'Oslash'=>'216', 'Ugrave'=>'217', 'Uacute'=>'218', 'Ucirc'=>'219', 'Uuml'=>'220', 'Yacute'=>'221', 'THORN'=>'222', 'szlig'=>'223', 'agrave'=>'224', 'aacute'=>'225', 'acirc'=>'226', 'atilde'=>'227', 'auml'=>'228', 'aring'=>'229', 'aelig'=>'230', 'ccedil'=>'231', 'egrave'=>'232', 'eacute'=>'233', 'ecirc'=>'234', 'euml'=>'235', 'igrave'=>'236', 'iacute'=>'237', 'icirc'=>'238', 'iuml'=>'239', 'eth'=>'240', 'ntilde'=>'241', 'ograve'=>'242', 'oacute'=>'243', 'ocirc'=>'244', 'otilde'=>'245', 'ouml'=>'246', 'divide'=>'247', 'oslash'=>'248', 'ugrave'=>'249', 'uacute'=>'250', 'ucirc'=>'251', 'uuml'=>'252', 'yacute'=>'253', 'thorn'=>'254', 'yuml'=>'255');
+if($t[0] != '#'){
+ return ($C['and_mark'] ? "\x06" : '&'). (isset($U[$t]) ? $t : (isset($N[$t]) ? (!$C['named_entity'] ? '#'. ($C['hexdec_entity'] > 1 ? 'x'. dechex($N[$t]) : $N[$t]) : $t) : 'amp;'. $t)). ';';
+}
+if(($n = ctype_digit($t = substr($t, 1)) ? intval($t) : hexdec(substr($t, 1))) < 9 or ($n > 13 && $n < 32) or $n == 11 or $n == 12 or ($n > 126 && $n < 160 && $n != 133) or ($n > 55295 && ($n < 57344 or ($n > 64975 && $n < 64992) or $n == 65534 or $n == 65535 or $n > 1114111))){
+ return ($C['and_mark'] ? "\x06" : '&'). "amp;#{$t};";
+}
+return ($C['and_mark'] ? "\x06" : '&'). '#'. (((ctype_digit($t) && $C['hexdec_entity'] < 2) or !$C['hexdec_entity']) ? $n : 'x'. dechex($n)). ';';
+// eof
+}
+
+function hl_prot($p, $c=null){
+// check URL scheme
+global $C;
+$b = $a = '';
+if($c == null){$c = 'style'; $b = $p[1]; $a = $p[3]; $p = trim($p[2]);}
+$c = isset($C['schemes'][$c]) ? $C['schemes'][$c] : $C['schemes']['*'];
+if(isset($c['*']) or !strcspn($p, '#?;')){return "{$b}{$p}{$a}";} // All ok, frag, query, param
+if(preg_match('`^([a-z\d\-+.&#; ]+?)(:|&#(58|x3a);|%3a|\\\\0{0,4}3a).`i', $p, $m) && !isset($c[strtolower($m[1])])){ // Denied prot
+ return "{$b}denied:{$p}{$a}";
+}
+if($C['abs_url']){
+ if($C['abs_url'] == -1 && strpos($p, $C['base_url']) === 0){ // Make url rel
+  $p = substr($p, strlen($C['base_url']));
+ }elseif(empty($m[1])){ // Make URL abs
+  if(substr($p, 0, 2) == '//'){$p = substr($C['base_url'], 0, strpos($C['base_url'], ':')+1). $p;}
+  elseif($p[0] == '/'){$p = preg_replace('`(^.+?://[^/]+)(.*)`', '$1', $C['base_url']). $p;}
+  elseif(strcspn($p, './')){$p = $C['base_url']. $p;}
+  else{
+   preg_match('`^([a-zA-Z\d\-+.]+://[^/]+)(.*)`', $C['base_url'], $m);
+   $p = preg_replace('`(?<=/)\./`', '', $m[2]. $p);
+   while(preg_match('`(?<=/)([^/]{3,}|[^/.]+?|\.[^/.]|[^/.]\.)/\.\./`', $p)){
+    $p = preg_replace('`(?<=/)([^/]{3,}|[^/.]+?|\.[^/.]|[^/.]\.)/\.\./`', '', $p);
+   }
+   $p = $m[1]. $p;
+  }
+ }
+}
+return "{$b}{$p}{$a}";
+// eof
+}
+
+function hl_regex($p){
+// ?regex
+if(empty($p)){return 0;}
+if($t = ini_get('track_errors')){$o = isset($php_errormsg) ? $php_errormsg : null;}
+else{ini_set('track_errors', 1);}
+unset($php_errormsg);
+if(($d = ini_get('display_errors'))){ini_set('display_errors', 0);}
+preg_match($p, '');
+if($d){ini_set('display_errors', 1);}
+$r = isset($php_errormsg) ? 0 : 1;
+if($t){$php_errormsg = isset($o) ? $o : null;}
+else{ini_set('track_errors', 0);}
+return $r;
+// eof
+}
+
+function hl_spec($t){
+// final $spec
+$s = array();
+$t = str_replace(array("\t", "\r", "\n", ' '), '', preg_replace('/"(?>(`.|[^"])*)"/sme', 'substr(str_replace(array(";", "|", "~", " ", ",", "/", "(", ")", \'`"\'), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\""), "$0"), 1, -1)', trim($t))); 
+for($i = count(($t = explode(';', $t))); --$i>=0;){
+ $w = $t[$i];
+ if(empty($w) or ($e = strpos($w, '=')) === false or !strlen(($a =  substr($w, $e+1)))){continue;}
+ $y = $n = array();
+ foreach(explode(',', $a) as $v){
+  if(!preg_match('`^([a-z:\-\*]+)(?:\((.*?)\))?`i', $v, $m)){continue;}
+  if(($x = strtolower($m[1])) == '-*'){$n['*'] = 1; continue;}
+  if($x[0] == '-'){$n[substr($x, 1)] = 1; continue;}
+  if(!isset($m[2])){$y[$x] = 1; continue;}
+  foreach(explode('/', $m[2]) as $m){
+   if(empty($m) or ($p = strpos($m, '=')) == 0 or $p < 5){$y[$x] = 1; continue;}
+   $y[$x][strtolower(substr($m, 0, $p))] = str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08"), array(";", "|", "~", " ", ",", "/", "(", ")"), substr($m, $p+1));
+  }
+  if(isset($y[$x]['match']) && !hl_regex($y[$x]['match'])){unset($y[$x]['match']);}
+  if(isset($y[$x]['nomatch']) && !hl_regex($y[$x]['nomatch'])){unset($y[$x]['nomatch']);}
+ }
+ if(!count($y) && !count($n)){continue;}
+ foreach(explode(',', substr($w, 0, $e)) as $v){
+  if(!strlen(($v = strtolower($v)))){continue;}
+  if(count($y)){$s[$v] = $y;}
+  if(count($n)){$s[$v]['n'] = $n;}
+ }
+}
+return $s;
+// eof
+}
+
+function hl_tag($t){
+// tag/attribute handler
+global $C;
+$t = $t[0];
+// invalid < >
+if($t == '< '){return '&lt; ';}
+if($t == '>'){return '&gt;';}
+if(!preg_match('`^<(/?)([a-zA-Z][a-zA-Z1-6]*)([^>]*?)\s?>$`m', $t, $m)){
+ return str_replace(array('<', '>'), array('&lt;', '&gt;'), $t);
+}elseif(!isset($C['elements'][($e = strtolower($m[2]))])){
+ return (($C['keep_bad']%2) ? str_replace(array('<', '>'), array('&lt;', '&gt;'), $t) : '');
+}
+// attr string
+$a = str_replace(array("\xad", "\n", "\r", "\t"), ' ', trim($m[3]));
+if(strpos($a, '&') !== false){
+ str_replace(array('&#xad;', '&#173;', '&shy;'), ' ', $a);
+}
+// tag transform
+static $eD = array('applet'=>1, 'center'=>1, 'dir'=>1, 'embed'=>1, 'font'=>1, 'isindex'=>1, 'menu'=>1, 's'=>1, 'strike'=>1, 'u'=>1); // Deprecated
+if($C['make_tag_strict'] && isset($eD[$e])){
+ $trt = hl_tag2($e, $a, $C['make_tag_strict']);
+ if(!$e){return (($C['keep_bad']%2) ? str_replace(array('<', '>'), array('&lt;', '&gt;'), $t) : '');}
+}
+// close tag
+static $eE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); // Empty ele
+if(!empty($m[1])){
+ return (!isset($eE[$e]) ? "</$e>" : (($C['keep_bad'])%2 ? str_replace(array('<', '>'), array('&lt;', '&gt;'), $t) : ''));
+}
+
+// open tag & attr
+static $aN = array('abbr'=>array('td'=>1, 'th'=>1), 'accept-charset'=>array('form'=>1), 'accept'=>array('form'=>1, 'input'=>1), 'accesskey'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'legend'=>1, 'textarea'=>1), 'action'=>array('form'=>1), 'align'=>array('caption'=>1, 'embed'=>1, 'applet'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'legend'=>1, 'table'=>1, 'hr'=>1, 'div'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'p'=>1, 'col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'alt'=>array('applet'=>1, 'area'=>1, 'img'=>1, 'input'=>1), 'archive'=>array('applet'=>1, 'object'=>1), 'axis'=>array('td'=>1, 'th'=>1), 'bgcolor'=>array('embed'=>1, 'table'=>1, 'tr'=>1, 'td'=>1, 'th'=>1), 'border'=>array('table'=>1, 'img'=>1, 'object'=>1), 'bordercolor'=>array('table'=>1, 'td'=>1, 'tr'=>1), 'cellpadding'=>array('table'=>1), 'cellspacing'=>array('table'=>1), 'char'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'charoff'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'charset'=>array('a'=>1, 'script'=>1), 'checked'=>array('input'=>1), 'cite'=>array('blockquote'=>1, 'q'=>1, 'del'=>1, 'ins'=>1), 'classid'=>array('object'=>1), 'clear'=>array('br'=>1), 'code'=>array('applet'=>1), 'codebase'=>array('object'=>1, 'applet'=>1), 'codetype'=>array('object'=>1), 'color'=>array('font'=>1), 'cols'=>array('textarea'=>1), 'colspan'=>array('td'=>1, 'th'=>1), 'compact'=>array('dir'=>1, 'dl'=>1, 'menu'=>1, 'ol'=>1, 'ul'=>1), 'coords'=>array('area'=>1, 'a'=>1), 'data'=>array('object'=>1), 'datetime'=>array('del'=>1, 'ins'=>1), 'declare'=>array('object'=>1), 'defer'=>array('script'=>1), 'dir'=>array('bdo'=>1), 'disabled'=>array('button'=>1, 'input'=>1, 'optgroup'=>1, 'option'=>1, 'select'=>1, 'textarea'=>1), 'enctype'=>array('form'=>1), 'face'=>array('font'=>1), 'for'=>array('label'=>1), 'frame'=>array('table'=>1), 'frameborder'=>array('iframe'=>1), 'headers'=>array('td'=>1, 'th'=>1), 'height'=>array('embed'=>1, 'iframe'=>1, 'td'=>1, 'th'=>1, 'img'=>1, 'object'=>1, 'applet'=>1), 'href'=>array('a'=>1, 'area'=>1), 'hreflang'=>array('a'=>1), 'hspace'=>array('applet'=>1, 'img'=>1, 'object'=>1), 'ismap'=>array('img'=>1, 'input'=>1), 'label'=>array('option'=>1, 'optgroup'=>1), 'language'=>array('script'=>1), 'longdesc'=>array('img'=>1, 'iframe'=>1), 'marginheight'=>array('iframe'=>1), 'marginwidth'=>array('iframe'=>1), 'maxlength'=>array('input'=>1), 'method'=>array('form'=>1), 'model'=>array('embed'=>1), 'multiple'=>array('select'=>1), 'name'=>array('button'=>1, 'embed'=>1, 'textarea'=>1, 'applet'=>1, 'select'=>1, 'form'=>1, 'iframe'=>1, 'img'=>1, 'a'=>1, 'input'=>1, 'object'=>1, 'map'=>1, 'param'=>1), 'nohref'=>array('area'=>1), 'noshade'=>array('hr'=>1), 'nowrap'=>array('td'=>1, 'th'=>1), 'object'=>array('applet'=>1), 'onblur'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'onchange'=>array('input'=>1, 'select'=>1, 'textarea'=>1), 'onfocus'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'label'=>1, 'select'=>1, 'textarea'=>1), 'onreset'=>array('form'=>1), 'onselect'=>array('input'=>1, 'textarea'=>1), 'onsubmit'=>array('form'=>1), 'pluginspage'=>array('embed'=>1), 'pluginurl'=>array('embed'=>1), 'prompt'=>array('isindex'=>1), 'readonly'=>array('textarea'=>1, 'input'=>1), 'rel'=>array('a'=>1), 'rev'=>array('a'=>1), 'rows'=>array('textarea'=>1), 'rowspan'=>array('td'=>1, 'th'=>1), 'rules'=>array('table'=>1), 'scope'=>array('td'=>1, 'th'=>1), 'scrolling'=>array('iframe'=>1), 'selected'=>array('option'=>1), 'shape'=>array('area'=>1, 'a'=>1), 'size'=>array('hr'=>1, 'font'=>1, 'input'=>1, 'select'=>1), 'span'=>array('col'=>1, 'colgroup'=>1), 'src'=>array('embed'=>1, 'script'=>1, 'input'=>1, 'iframe'=>1, 'img'=>1), 'standby'=>array('object'=>1), 'start'=>array('ol'=>1), 'summary'=>array('table'=>1), 'tabindex'=>array('a'=>1, 'area'=>1, 'button'=>1, 'input'=>1, 'object'=>1, 'select'=>1, 'textarea'=>1), 'target'=>array('a'=>1, 'area'=>1, 'form'=>1), 'type'=>array('a'=>1, 'embed'=>1, 'object'=>1, 'param'=>1, 'script'=>1, 'input'=>1, 'li'=>1, 'ol'=>1, 'ul'=>1, 'button'=>1), 'usemap'=>array('img'=>1, 'input'=>1, 'object'=>1), 'valign'=>array('col'=>1, 'colgroup'=>1, 'tbody'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1), 'value'=>array('input'=>1, 'option'=>1, 'param'=>1, 'button'=>1, 'li'=>1), 'valuetype'=>array('param'=>1), 'vspace'=>array('applet'=>1, 'img'=>1, 'object'=>1), 'width'=>array('embed'=>1, 'hr'=>1, 'iframe'=>1, 'img'=>1, 'object'=>1, 'table'=>1, 'td'=>1, 'th'=>1, 'applet'=>1, 'col'=>1, 'colgroup'=>1, 'pre'=>1), 'wmode'=>array('embed'=>1), 'xml:space'=>array('pre'=>1, 'script'=>1, 'style'=>1)); // Ele-specific
+static $aNE = array('checked'=>1, 'compact'=>1, 'declare'=>1, 'defer'=>1, 'disabled'=>1, 'ismap'=>1, 'multiple'=>1, 'nohref'=>1, 'noresize'=>1, 'noshade'=>1, 'nowrap'=>1, 'readonly'=>1, 'selected'=>1); // Empty
+static $aNP = array('action'=>1, 'cite'=>1, 'classid'=>1, 'codebase'=>1, 'data'=>1, 'href'=>1, 'longdesc'=>1, 'model'=>1, 'pluginspage'=>1, 'pluginurl'=>1, 'usemap'=>1); // Need scheme check; excludes style, on* & src
+static $aNU = array('class'=>array('param'=>1, 'script'=>1), 'dir'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'id'=>array('script'=>1), 'lang'=>array('applet'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'xml:lang'=>array('applet'=>1, 'br'=>1, 'iframe'=>1, 'param'=>1, 'script'=>1), 'onclick'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'ondblclick'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeydown'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeypress'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onkeyup'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmousedown'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmousemove'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseout'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseover'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'onmouseup'=>array('applet'=>1, 'bdo'=>1, 'br'=>1, 'font'=>1, 'iframe'=>1, 'isindex'=>1, 'param'=>1, 'script'=>1), 'style'=>array('param'=>1, 'script'=>1), 'title'=>array('param'=>1, 'script'=>1)); // Univ & exceptions
+
+if($C['lc_std_val']){
+ // predef attr vals for $eAL & $aNE ele
+ static $aNL = array('all'=>1, 'baseline'=>1, 'bottom'=>1, 'button'=>1, 'center'=>1, 'char'=>1, 'checkbox'=>1, 'circle'=>1, 'col'=>1, 'colgroup'=>1, 'cols'=>1, 'data'=>1, 'default'=>1, 'file'=>1, 'get'=>1, 'groups'=>1, 'hidden'=>1, 'image'=>1, 'justify'=>1, 'left'=>1, 'ltr'=>1, 'middle'=>1, 'none'=>1, 'object'=>1, 'password'=>1, 'poly'=>1, 'post'=>1, 'preserve'=>1, 'radio'=>1, 'rect'=>1, 'ref'=>1, 'reset'=>1, 'right'=>1, 'row'=>1, 'rowgroup'=>1, 'rows'=>1, 'rtl'=>1, 'submit'=>1, 'text'=>1, 'top'=>1);
+ static $eAL = array('a'=>1, 'area'=>1, 'bdo'=>1, 'button'=>1, 'col'=>1, 'form'=>1, 'img'=>1, 'input'=>1, 'object'=>1, 'optgroup'=>1, 'option'=>1, 'param'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'td'=>1, 'tfoot'=>1, 'th'=>1, 'thead'=>1, 'tr'=>1, 'xml:space'=>1);
+ $lcase = isset($eAL[$e]) ? 1 : 0;
+}
+
+$depTr = 0;
+if($C['no_deprecated_attr']){
+ // dep attr:applicable ele
+ static $aND = array('align'=>array('caption'=>1, 'div'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'legend'=>1, 'object'=>1, 'p'=>1, 'table'=>1), 'bgcolor'=>array('table'=>1, 'td'=>1, 'th'=>1, 'tr'=>1), 'border'=>array('img'=>1, 'object'=>1), 'bordercolor'=>array('table'=>1, 'td'=>1, 'tr'=>1), 'clear'=>array('br'=>1), 'compact'=>array('dl'=>1, 'ol'=>1, 'ul'=>1), 'height'=>array('td'=>1, 'th'=>1), 'hspace'=>array('img'=>1, 'object'=>1), 'language'=>array('script'=>1), 'name'=>array('a'=>1, 'form'=>1, 'iframe'=>1, 'img'=>1, 'map'=>1), 'noshade'=>array('hr'=>1), 'nowrap'=>array('td'=>1, 'th'=>1), 'size'=>array('hr'=>1), 'start'=>array('ol'=>1), 'type'=>array('li'=>1, 'ol'=>1, 'ul'=>1), 'value'=>array('li'=>1), 'vspace'=>array('img'=>1, 'object'=>1), 'width'=>array('hr'=>1, 'pre'=>1, 'td'=>1, 'th'=>1));
+ static $eAD = array('a'=>1, 'br'=>1, 'caption'=>1, 'div'=>1, 'dl'=>1, 'form'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'hr'=>1, 'iframe'=>1, 'img'=>1, 'input'=>1, 'legend'=>1, 'li'=>1, 'map'=>1, 'object'=>1, 'ol'=>1, 'p'=>1, 'pre'=>1, 'script'=>1, 'table'=>1, 'td'=>1, 'th'=>1, 'tr'=>1, 'ul'=>1);
+ $depTr = isset($eAD[$e]) ? 1 : 0;
+}
+
+// attr name-vals
+if(strpos($a, "\x01") !== false){$a = preg_replace('`\x01[^\x01]*\x01`', '', $a);} // No comment/CDATA sec
+$mode = 0; $a = trim($a, ' /'); $aA = array();
+while(strlen($a)){
+ $w = 0;
+ switch($mode){
+  case 0: // Name
+   if(preg_match('`^[a-zA-Z][\-a-zA-Z:]+`', $a, $m)){
+    $nm = strtolower($m[0]);
+    $w = $mode = 1; $a = ltrim(substr_replace($a, '', 0, strlen($m[0])));
+   }
+  break; case 1:
+   if($a[0] == '='){ // =
+    $w = 1; $mode = 2; $a = ltrim($a, '= ');
+   }else{ // No val
+    $w = 1; $mode = 0; $a = ltrim($a);
+    $aA[$nm] = '';
+   }
+  break; case 2: // Val
+   if(preg_match('`^"[^"]*"`', $a, $m) or preg_match("`^'[^']*'`", $a, $m) or preg_match("`^\s*[^\s\"']+`", $a, $m)){
+    $m = $m[0]; $w = 1; $mode = 0; $a = ltrim(substr_replace($a, '', 0, strlen($m)));
+    $aA[$nm] = trim(($m[0] == '"' or $m[0] == '\'') ? substr($m, 1, -1) : $m);
+   }
+  break;
+ }
+ if($w == 0){ // Parse errs, deal with space, " & '
+  $a = preg_replace('`^(?:"[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*`', '', $a);
+  $mode = 0;
+ }
+}
+if($mode == 1){$aA[$nm] = '';}
+
+// clean attrs
+global $S;
+$rl = isset($S[$e]) ? $S[$e] : array();
+$a = array(); $nfr = 0;
+foreach($aA as $k=>$v){
+ if(((isset($C['deny_attribute']['*']) ? isset($C['deny_attribute'][$k]) : !isset($C['deny_attribute'][$k])) or isset($rl[$k])) && ((!isset($rl['n'][$k]) && !isset($rl['n']['*'])) or isset($rl[$k])) && (isset($aN[$k][$e]) or (isset($aNU[$k]) && !isset($aNU[$k][$e])))){
+  if(isset($aNE[$k])){$v = $k;}
+  elseif(!empty($lcase) && (($e != 'button' or $e != 'input') or $k == 'type')){ // Rather loose but ?not cause issues
+   $v = (isset($aNL[($v2 = strtolower($v))])) ? $v2 : $v;
+  }
+  if($k == 'style' && !$C['style_pass']){
+   if(false !== strpos($v, '&#')){
+    static $sC = array('&#x20;'=>' ', '&#32;'=>' ', '&#x45;'=>'e', '&#69;'=>'e', '&#x65;'=>'e', '&#101;'=>'e', '&#x58;'=>'x', '&#88;'=>'x', '&#x78;'=>'x', '&#120;'=>'x', '&#x50;'=>'p', '&#80;'=>'p', '&#x70;'=>'p', '&#112;'=>'p', '&#x53;'=>'s', '&#83;'=>'s', '&#x73;'=>'s', '&#115;'=>'s', '&#x49;'=>'i', '&#73;'=>'i', '&#x69;'=>'i', '&#105;'=>'i', '&#x4f;'=>'o', '&#79;'=>'o', '&#x6f;'=>'o', '&#111;'=>'o', '&#x4e;'=>'n', '&#78;'=>'n', '&#x6e;'=>'n', '&#110;'=>'n', '&#x55;'=>'u', '&#85;'=>'u', '&#x75;'=>'u', '&#117;'=>'u', '&#x52;'=>'r', '&#82;'=>'r', '&#x72;'=>'r', '&#114;'=>'r', '&#x4c;'=>'l', '&#76;'=>'l', '&#x6c;'=>'l', '&#108;'=>'l', '&#x28;'=>'(', '&#40;'=>'(', '&#x29;'=>')', '&#41;'=>')', '&#x20;'=>':', '&#32;'=>':', '&#x22;'=>'"', '&#34;'=>'"', '&#x27;'=>"'", '&#39;'=>"'", '&#x2f;'=>'/', '&#47;'=>'/', '&#x2a;'=>'*', '&#42;'=>'*', '&#x5c;'=>'\\', '&#92;'=>'\\');
+    $v = strtr($v, $sC);
+   }
+   $v = preg_replace_callback('`(url(?:\()(?: )*(?:\'|"|&(?:quot|apos);)?)(.+)((?:\'|"|&(?:quot|apos);)?(?: )*(?:\)))`iS', 'hl_prot', $v);
+   $v = !$C['css_expression'] ? preg_replace('`expression`i', ' ', preg_replace('`\\\\\S|(/|(%2f))(\*|(%2a))`i', ' ', $v)) : $v;
+  }elseif(isset($aNP[$k]) or strpos($k, 'src') !== false or $k[0] == 'o'){
+   $v = hl_prot($v, $k);
+   if($k == 'href'){ // X-spam
+    if($C['anti_mail_spam'] && strpos($v, 'mailto:') === 0){
+     $v = str_replace('@', htmlspecialchars($C['anti_mail_spam']), $v);
+    }elseif($C['anti_link_spam']){
+     $r1 = $C['anti_link_spam'][1];
+     if(!empty($r1) && preg_match($r1, $v)){continue;}
+     $r0 = $C['anti_link_spam'][0];
+     if(!empty($r0) && preg_match($r0, $v)){
+      if(isset($a['rel'])){
+       if(!preg_match('`\bnofollow\b`i', $a['rel'])){$a['rel'] .= ' nofollow';}
+      }elseif(isset($aA['rel'])){
+       if(!preg_match('`\bnofollow\b`i', $aA['rel'])){$nfr = 1;}
+      }else{$a['rel'] = 'nofollow';}
+     }
+    }
+   }
+  }
+  if(isset($rl[$k]) && is_array($rl[$k]) && ($v = hl_attrval($v, $rl[$k])) === 0){continue;}
+  $a[$k] = str_replace('"', '&quot;', $v);
+ }
+}
+if($nfr){$a['rel'] = isset($a['rel']) ? $a['rel']. ' nofollow' : 'nofollow';}
+
+// rqd attr
+static $eAR = array('area'=>array('alt'=>'area'), 'bdo'=>array('dir'=>'ltr'), 'form'=>array('action'=>''), 'img'=>array('src'=>'', 'alt'=>'image'), 'map'=>array('name'=>''), 'optgroup'=>array('label'=>''), 'param'=>array('name'=>''), 'script'=>array('type'=>'text/javascript'), 'textarea'=>array('rows'=>'10', 'cols'=>'50'));
+if(isset($eAR[$e])){
+ foreach($eAR[$e] as $k=>$v){
+  if(!isset($a[$k])){$a[$k] = isset($v[0]) ? $v : $k;}
+ }
+}
+
+// depr attrs
+if($depTr){
+ $c = array();
+ foreach($a as $k=>$v){
+  if($k == 'style' or !isset($aND[$k][$e])){continue;}
+  if($k == 'align'){
+   unset($a['align']);
+   if($e == 'img' && ($v == 'left' or $v == 'right')){$c[] = 'float: '. $v;}
+   elseif(($e == 'div' or $e == 'table') && $v == 'center'){$c[] = 'margin: auto';}
+   else{$c[] = 'text-align: '. $v;}
+  }elseif($k == 'bgcolor'){
+   unset($a['bgcolor']);
+   $c[] = 'background-color: '. $v;
+  }elseif($k == 'border'){
+   unset($a['border']); $c[] = "border: {$v}px";
+  }elseif($k == 'bordercolor'){
+   unset($a['bordercolor']); $c[] = 'border-color: '. $v;
+  }elseif($k == 'clear'){
+   unset($a['clear']); $c[] = 'clear: '. ($v != 'all' ? $v : 'both');
+  }elseif($k == 'compact'){
+   unset($a['compact']); $c[] = 'font-size: 85%';
+  }elseif($k == 'height' or $k == 'width'){
+   unset($a[$k]); $c[] = $k. ': '. ($v[0] != '*' ? $v. (ctype_digit($v) ? 'px' : '') : 'auto');
+  }elseif($k == 'hspace'){
+   unset($a['hspace']); $c[] = "margin-left: {$v}px; margin-right: {$v}px";
+  }elseif($k == 'language' && !isset($a['type'])){
+   unset($a['language']);
+   $a['type'] = 'text/'. strtolower($v);
+  }elseif($k == 'name'){
+   if($C['no_deprecated_attr'] == 2 or ($e != 'a' && $e != 'map')){unset($a['name']);}
+   if(!isset($a['id']) && preg_match('`[a-zA-Z][a-zA-Z\d.:_\-]*`', $v)){$a['id'] = $v;}
+  }elseif($k == 'noshade'){
+   unset($a['noshade']); $c[] = 'border-style: none; border: 0; background-color: gray; color: gray';
+  }elseif($k == 'nowrap'){
+   unset($a['nowrap']); $c[] = 'white-space: nowrap';
+  }elseif($k == 'size'){
+   unset($a['size']); $c[] = 'size: '. $v. 'px';
+  }elseif($k == 'start' or $k == 'value'){
+   unset($a[$k]);
+  }elseif($k == 'type'){
+   unset($a['type']);
+   static $ol_type = array('i'=>'lower-roman', 'I'=>'upper-roman', 'a'=>'lower-latin', 'A'=>'upper-latin', '1'=>'decimal');
+   $c[] = 'list-style-type: '. (isset($ol_type[$v]) ? $ol_type[$v] : 'decimal');
+  }elseif($k == 'vspace'){
+   unset($a['vspace']); $c[] = "margin-top: {$v}px; margin-bottom: {$v}px";
+  }
+ }
+ if(count($c)){
+  $c = implode('; ', $c);
+  $a['style'] = isset($a['style']) ? rtrim($a['style'], ' ;'). '; '. $c. ';': $c. ';';
+ }
+}
+// unique ID
+if($C['unique_ids'] && isset($a['id'])){
+ if(!preg_match('`^[A-Za-z][A-Za-z0-9_\-.:]*$`', ($id = $a['id'])) or (isset($GLOBALS['hl_Ids'][$id]) && $C['unique_ids'] == 1)){unset($a['id']);
+ }else{
+  while(isset($GLOBALS['hl_Ids'][$id])){$id = $C['unique_ids']. $id;}
+  $GLOBALS['hl_Ids'][($a['id'] = $id)] = 1;
+ }
+}
+// xml:lang
+if($C['xml:lang'] && isset($a['lang'])){
+ $a['xml:lang'] = isset($a['xml:lang']) ? $a['xml:lang'] : $a['lang'];
+ if($C['xml:lang'] == 2){unset($a['lang']);}
+}
+// for transformed tag
+if(!empty($trt)){
+ $a['style'] = isset($a['style']) ? rtrim($a['style'], ' ;'). '; '. $trt : $trt;
+}
+// return with empty ele /
+if(empty($C['hook_tag'])){
+ $aA = '';
+ foreach($a as $k=>$v){$aA .= " {$k}=\"{$v}\"";}
+ return "<{$e}{$aA}". (isset($eE[$e]) ? ' /' : ''). '>';
+}
+else{return $C['hook_tag']($e, $a);}
+// eof
+}
+
+function hl_tag2(&$e, &$a, $t=1){
+// transform tag
+if($e == 'center'){$e = 'div'; return 'text-align: center;';}
+if($e == 'dir' or $e == 'menu'){$e = 'ul'; return '';}
+if($e == 's' or $e == 'strike'){$e = 'span'; return 'text-decoration: line-through;';}
+if($e == 'u'){$e = 'span'; return 'text-decoration: underline;';}
+static $fs = array('0'=>'xx-small', '1'=>'xx-small', '2'=>'small', '3'=>'medium', '4'=>'large', '5'=>'x-large', '6'=>'xx-large', '7'=>'300%', '-1'=>'smaller', '-2'=>'60%', '+1'=>'larger', '+2'=>'150%', '+3'=>'200%', '+4'=>'300%');
+if($e == 'font'){
+ $a2 = '';
+ if(preg_match('`face\s*=\s*(\'|")([^=]+?)\\1`i', $a, $m) or preg_match('`face\s*=\s*([^"])(\S+)`i', $a, $m)){
+  $a2 .= ' font-family: '. str_replace('"', '\'', trim($m[2])). ';';
+ }
+ if(preg_match('`color\s*=\s*(\'|")?(.+?)(\\1|\s|$)`i', $a, $m)){
+  $a2 .= ' color: '. trim($m[2]). ';';
+ }
+ if(preg_match('`size\s*=\s*(\'|")?(.+?)(\\1|\s|$)`i', $a, $m) && isset($fs[($m = trim($m[2]))])){
+  $a2 .= ' font-size: '. $fs[$m]. ';';
+ }
+ $e = 'span'; return ltrim($a2);
+}
+if($t == 2){$e = 0; return 0;}
+return '';
+// eof
+}
+
+function hl_tidy($t, $w, $p){
+// Tidy/compact HTM
+if(strpos(' pre,script,textarea', "$p,")){return $t;}
+$t = str_replace(' </', '</', preg_replace(array('`(<\w[^>]*(?<!/)>)\s+`', '`\s+`', '`(<\w[^>]*(?<!/)>) `'), array(' $1', ' ', '$1'), preg_replace_callback(array('`(<(!\[CDATA\[))(.+?)(\]\]>)`sm', '`(<(!--))(.+?)(-->)`sm', '`(<(pre|script|textarea).*?>)(.+?)(</\2>)`sm'), create_function('$m', 'return $m[1]. str_replace(array("<", ">", "\n", "\r", "\t", " "), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), $m[3]). $m[4];'), $t)));
+if(($w = strtolower($w)) == -1){
+ return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array('<', '>', "\n", "\r", "\t", ' '), $t);
+}
+$s = strpos(" $w", 't') ? "\t" : ' ';
+$s = preg_match('`\d`', $w, $m) ? str_repeat($s, $m[0]) : str_repeat($s, ($s == "\t" ? 1 : 2));
+$n = preg_match('`[ts]([1-9])`', $w, $m) ? $m[1] : 0;
+$a = array('br'=>1);
+$b = array('button'=>1, 'input'=>1, 'option'=>1);
+$c = array('caption'=>1, 'dd'=>1, 'dt'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'isindex'=>1, 'label'=>1, 'legend'=>1, 'li'=>1, 'object'=>1, 'p'=>1, 'pre'=>1, 'td'=>1, 'textarea'=>1, 'th'=>1);
+$d = array('address'=>1, 'blockquote'=>1, 'center'=>1, 'colgroup'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'fieldset'=>1, 'form'=>1, 'hr'=>1, 'iframe'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1);
+ob_start();
+if(isset($d[$p])){echo str_repeat($s, ++$n);}
+$t = explode('<', $t);
+echo ltrim(array_shift($t));
+for($i=-1, $j=count($t); ++$i<$j;){
+ $r = ''; list($e, $r) = explode('>', $t[$i]);
+ $x = $e[0] == '/' ? 0 : (substr($e, -1) == '/' ? 1 : ($e[0] != '!' ? 2 : -1));
+ $y = !$x ? ltrim($e, '/') : ($x > 0 ? substr($e, 0, strcspn($e, ' ')) : 0);
+ $e = "<$e>"; 
+ if(isset($d[$y])){
+  if(!$x){echo "\n", str_repeat($s, --$n), "$e\n", str_repeat($s, $n);}
+  else{echo "\n", str_repeat($s, $n), "$e\n", str_repeat($s, ($x != 1 ? ++$n : $n));}
+  echo ltrim($r); continue;
+ }
+ $f = "\n". str_repeat($s, $n);
+ if(isset($c[$y])){
+  if(!$x){echo $e, $f, ltrim($r);}
+  else{echo $f, $e, $r;}
+ }elseif(isset($b[$y])){echo $f, $e, $r;
+ }elseif(isset($a[$y])){echo $e, $f, ltrim($r);
+ }elseif(!$y){echo $f, $e, $f, ltrim($r);
+ }else{echo $e, $r;}
+}
+$t = preg_replace('`[\n]\s*?[\n]+`', "\n", ob_get_contents());
+ob_end_clean();
+if(($l = strpos(" $w", 'r') ? (strpos(" $w", 'n') ? "\r\n" : "\r") : 0)){
+ $t = str_replace("\n", $l, $t);
+}
+return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array('<', '>', "\n", "\r", "\t", ' '), $t);
+// eof
+}
+
+function hl_version(){
+// rel
+return '1.1.8.1';
+// eof
+}
+
+function kses($t, $h, $p=array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'gopher', 'mailto')){
+// kses compat
+foreach($h as $k=>$v){
+ $h[$k]['n']['*'] = 1;
+}
+$C['cdata'] = $C['comment'] = $C['make_tag_strict'] = $C['no_deprecated_attr'] = $C['unique_ids'] = 0;
+$C['keep_bad'] = 1;
+$C['elements'] = count($h) ? strtolower(implode(',', array_keys($h))) : '-*';
+$C['hook'] = 'kses_hook';
+$C['schemes'] = '*:'. implode(',', $p);
+return htmLawed($t, $C, $h);
+// eof
+}
+
+function kses_hook($t, &$C, &$S){
+// kses compat
+return $t;
+// eof
+}
Index: /trunk/libs/extensions/SimplePie/simplepie.inc
===================================================================
--- /trunk/libs/extensions/SimplePie/simplepie.inc	(revision 1081)
+++ /trunk/libs/extensions/SimplePie/simplepie.inc	(revision 1081)
@@ -0,0 +1,14597 @@
+﻿<?php
+/**
+ * SimplePie
+ *
+ * A PHP-Based RSS and Atom Feed Framework.
+ * Takes the hard work out of managing a complete RSS/Atom solution.
+ *
+ * Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 	* Redistributions of source code must retain the above copyright notice, this list of
+ * 	  conditions and the following disclaimer.
+ *
+ * 	* Redistributions in binary form must reproduce the above copyright notice, this list
+ * 	  of conditions and the following disclaimer in the documentation and/or other materials
+ * 	  provided with the distribution.
+ *
+ * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
+ * 	  to endorse or promote products derived from this software without specific prior
+ * 	  written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
+ * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package SimplePie
+ * @version 1.3-dev
+ * @copyright 2004-2009 Ryan Parman, Geoffrey Sneddon, Ryan McCue
+ * @author Ryan Parman
+ * @author Geoffrey Sneddon
+ * @author Ryan McCue
+ * @link http://simplepie.org/ SimplePie
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @todo phpDoc comments
+ */
+
+/**
+ * SimplePie Name
+ */
+define('SIMPLEPIE_NAME', 'SimplePie');
+
+/**
+ * SimplePie Version
+ */
+define('SIMPLEPIE_VERSION', '1.3-dev');
+
+/**
+ * SimplePie Build
+ * @todo Hardcode for release (there's no need to have to call SimplePie_Misc::parse_date() only every load of simplepie.inc)
+ */
+define('SIMPLEPIE_BUILD', gmdate('YmdHis', SimplePie_Misc::parse_date(substr('$Date$', 7, 25)) ? SimplePie_Misc::parse_date(substr('$Date$', 7, 25)) : filemtime(__FILE__)));
+
+/**
+ * SimplePie Website URL
+ */
+define('SIMPLEPIE_URL', 'http://simplepie.org');
+
+/**
+ * SimplePie Useragent
+ * @see SimplePie::set_useragent()
+ */
+define('SIMPLEPIE_USERAGENT', SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION . ' (Feed Parser; ' . SIMPLEPIE_URL . '; Allow like Gecko) Build/' . SIMPLEPIE_BUILD);
+
+/**
+ * SimplePie Linkback
+ */
+define('SIMPLEPIE_LINKBACK', '<a href="' . SIMPLEPIE_URL . '" title="' . SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . '">' . SIMPLEPIE_NAME . '</a>');
+
+/**
+ * No Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_NONE', 0);
+
+/**
+ * Feed Link Element Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1);
+
+/**
+ * Local Feed Extension Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2);
+
+/**
+ * Local Feed Body Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4);
+
+/**
+ * Remote Feed Extension Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8);
+
+/**
+ * Remote Feed Body Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16);
+
+/**
+ * All Feed Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_ALL', 31);
+
+/**
+ * No known feed type
+ */
+define('SIMPLEPIE_TYPE_NONE', 0);
+
+/**
+ * RSS 0.90
+ */
+define('SIMPLEPIE_TYPE_RSS_090', 1);
+
+/**
+ * RSS 0.91 (Netscape)
+ */
+define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2);
+
+/**
+ * RSS 0.91 (Userland)
+ */
+define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4);
+
+/**
+ * RSS 0.91 (both Netscape and Userland)
+ */
+define('SIMPLEPIE_TYPE_RSS_091', 6);
+
+/**
+ * RSS 0.92
+ */
+define('SIMPLEPIE_TYPE_RSS_092', 8);
+
+/**
+ * RSS 0.93
+ */
+define('SIMPLEPIE_TYPE_RSS_093', 16);
+
+/**
+ * RSS 0.94
+ */
+define('SIMPLEPIE_TYPE_RSS_094', 32);
+
+/**
+ * RSS 1.0
+ */
+define('SIMPLEPIE_TYPE_RSS_10', 64);
+
+/**
+ * RSS 2.0
+ */
+define('SIMPLEPIE_TYPE_RSS_20', 128);
+
+/**
+ * RDF-based RSS
+ */
+define('SIMPLEPIE_TYPE_RSS_RDF', 65);
+
+/**
+ * Non-RDF-based RSS (truly intended as syndication format)
+ */
+define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190);
+
+/**
+ * All RSS
+ */
+define('SIMPLEPIE_TYPE_RSS_ALL', 255);
+
+/**
+ * Atom 0.3
+ */
+define('SIMPLEPIE_TYPE_ATOM_03', 256);
+
+/**
+ * Atom 1.0
+ */
+define('SIMPLEPIE_TYPE_ATOM_10', 512);
+
+/**
+ * All Atom
+ */
+define('SIMPLEPIE_TYPE_ATOM_ALL', 768);
+
+/**
+ * All feed types
+ */
+define('SIMPLEPIE_TYPE_ALL', 1023);
+
+/**
+ * No construct
+ */
+define('SIMPLEPIE_CONSTRUCT_NONE', 0);
+
+/**
+ * Text construct
+ */
+define('SIMPLEPIE_CONSTRUCT_TEXT', 1);
+
+/**
+ * HTML construct
+ */
+define('SIMPLEPIE_CONSTRUCT_HTML', 2);
+
+/**
+ * XHTML construct
+ */
+define('SIMPLEPIE_CONSTRUCT_XHTML', 4);
+
+/**
+ * base64-encoded construct
+ */
+define('SIMPLEPIE_CONSTRUCT_BASE64', 8);
+
+/**
+ * IRI construct
+ */
+define('SIMPLEPIE_CONSTRUCT_IRI', 16);
+
+/**
+ * A construct that might be HTML
+ */
+define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32);
+
+/**
+ * All constructs
+ */
+define('SIMPLEPIE_CONSTRUCT_ALL', 63);
+
+/**
+ * Don't change case
+ */
+define('SIMPLEPIE_SAME_CASE', 1);
+
+/**
+ * Change to lowercase
+ */
+define('SIMPLEPIE_LOWERCASE', 2);
+
+/**
+ * Change to uppercase
+ */
+define('SIMPLEPIE_UPPERCASE', 4);
+
+/**
+ * PCRE for HTML attributes
+ */
+define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*');
+
+/**
+ * PCRE for XML attributes
+ */
+define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*');
+
+/**
+ * XML Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace');
+
+/**
+ * Atom 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom');
+
+/**
+ * Atom 0.3 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#');
+
+/**
+ * RDF Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
+
+/**
+ * RSS 0.90 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/');
+
+/**
+ * RSS 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/');
+
+/**
+ * RSS 1.0 Content Module Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/');
+
+/**
+ * RSS 2.0 Namespace
+ * (Stupid, I know, but I'm certain it will confuse people less with support.)
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_20', '');
+
+/**
+ * DC 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/');
+
+/**
+ * DC 1.1 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/');
+
+/**
+ * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#');
+
+/**
+ * GeoRSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss');
+
+/**
+ * Media RSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/');
+
+/**
+ * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec.
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss');
+
+/**
+ * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5.
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2', 'http://video.search.yahoo.com/mrss');
+
+/**
+ * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace.
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3', 'http://video.search.yahoo.com/mrss/');
+
+/**
+ * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace.
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4', 'http://www.rssboard.org/media-rss');
+
+/**
+ * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL.
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5', 'http://www.rssboard.org/media-rss/');
+
+/**
+ * iTunes RSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+
+/**
+ * XHTML Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml');
+
+/**
+ * IANA Link Relations Registry
+ */
+define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/');
+
+/**
+ * Whether we're running on PHP5
+ */
+define('SIMPLEPIE_PHP5', version_compare(PHP_VERSION, '5.0.0', '>='));
+
+/**
+ * No file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_NONE', 0);
+
+/**
+ * Remote file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1);
+
+/**
+ * Local file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2);
+
+/**
+ * fsockopen() file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4);
+
+/**
+ * cURL file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_CURL', 8);
+
+/**
+ * file_get_contents() file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16);
+
+/**
+ * SimplePie
+ *
+ * @package SimplePie
+ */
+class SimplePie
+{
+	/**
+	 * @var array Raw data
+	 * @access private
+	 */
+	public $data = array();
+
+	/**
+	 * @var mixed Error string
+	 * @access private
+	 */
+	public $error;
+
+	/**
+	 * @var object Instance of SimplePie_Sanitize (or other class)
+	 * @see SimplePie::set_sanitize_class()
+	 * @access private
+	 */
+	public $sanitize;
+
+	/**
+	 * @var string SimplePie Useragent
+	 * @see SimplePie::set_useragent()
+	 * @access private
+	 */
+	public $useragent = SIMPLEPIE_USERAGENT;
+
+	/**
+	 * @var string Feed URL
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	public $feed_url;
+
+	/**
+	 * @var object Instance of SimplePie_File to use as a feed
+	 * @see SimplePie::set_file()
+	 * @access private
+	 */
+	public $file;
+
+	/**
+	 * @var string Raw feed data
+	 * @see SimplePie::set_raw_data()
+	 * @access private
+	 */
+	public $raw_data;
+
+	/**
+	 * @var int Timeout for fetching remote files
+	 * @see SimplePie::set_timeout()
+	 * @access private
+	 */
+	public $timeout = 10;
+
+	/**
+	 * @var bool Forces fsockopen() to be used for remote files instead
+	 * of cURL, even if a new enough version is installed
+	 * @see SimplePie::force_fsockopen()
+	 * @access private
+	 */
+	public $force_fsockopen = false;
+
+	/**
+	 * @var bool Force the given data/URL to be treated as a feed no matter what
+	 * it appears like
+	 * @see SimplePie::force_feed()
+	 * @access private
+	 */
+	public $force_feed = false;
+
+	/**
+	 * @var bool Enable/Disable XML dump
+	 * @see SimplePie::enable_xml_dump()
+	 * @access private
+	 */
+	public $xml_dump = false;
+
+	/**
+	 * @var bool Enable/Disable Caching
+	 * @see SimplePie::enable_cache()
+	 * @access private
+	 */
+	public $cache = true;
+
+	/**
+	 * @var int Cache duration (in seconds)
+	 * @see SimplePie::set_cache_duration()
+	 * @access private
+	 */
+	public $cache_duration = 3600;
+
+	/**
+	 * @var int Auto-discovery cache duration (in seconds)
+	 * @see SimplePie::set_autodiscovery_cache_duration()
+	 * @access private
+	 */
+	public $autodiscovery_cache_duration = 604800; // 7 Days.
+
+	/**
+	 * @var string Cache location (relative to executing script)
+	 * @see SimplePie::set_cache_location()
+	 * @access private
+	 */
+	public $cache_location = './cache';
+
+	/**
+	 * @var string Function that creates the cache filename
+	 * @see SimplePie::set_cache_name_function()
+	 * @access private
+	 */
+	public $cache_name_function = 'md5';
+
+	/**
+	 * @var bool Reorder feed by date descending
+	 * @see SimplePie::enable_order_by_date()
+	 * @access private
+	 */
+	public $order_by_date = true;
+
+	/**
+	 * @var mixed Force input encoding to be set to the follow value
+	 * (false, or anything type-cast to false, disables this feature)
+	 * @see SimplePie::set_input_encoding()
+	 * @access private
+	 */
+	public $input_encoding = false;
+
+	/**
+	 * @var int Feed Autodiscovery Level
+	 * @see SimplePie::set_autodiscovery_level()
+	 * @access private
+	 */
+	public $autodiscovery = SIMPLEPIE_LOCATOR_ALL;
+
+	/**
+	 * @var string Class used for caching feeds
+	 * @see SimplePie::set_cache_class()
+	 * @access private
+	 */
+	public $cache_class = 'SimplePie_Cache';
+
+	/**
+	 * @var string Class used for locating feeds
+	 * @see SimplePie::set_locator_class()
+	 * @access private
+	 */
+	public $locator_class = 'SimplePie_Locator';
+
+	/**
+	 * @var string Class used for parsing feeds
+	 * @see SimplePie::set_parser_class()
+	 * @access private
+	 */
+	public $parser_class = 'SimplePie_Parser';
+
+	/**
+	 * @var string Class used for fetching feeds
+	 * @see SimplePie::set_file_class()
+	 * @access private
+	 */
+	public $file_class = 'SimplePie_File';
+
+	/**
+	 * @var string Class used for items
+	 * @see SimplePie::set_item_class()
+	 * @access private
+	 */
+	public $item_class = 'SimplePie_Item';
+
+	/**
+	 * @var string Class used for authors
+	 * @see SimplePie::set_author_class()
+	 * @access private
+	 */
+	public $author_class = 'SimplePie_Author';
+
+	/**
+	 * @var string Class used for categories
+	 * @see SimplePie::set_category_class()
+	 * @access private
+	 */
+	public $category_class = 'SimplePie_Category';
+
+	/**
+	 * @var string Class used for enclosures
+	 * @see SimplePie::set_enclosures_class()
+	 * @access private
+	 */
+	public $enclosure_class = 'SimplePie_Enclosure';
+
+	/**
+	 * @var string Class used for Media RSS <media:text> captions
+	 * @see SimplePie::set_caption_class()
+	 * @access private
+	 */
+	public $caption_class = 'SimplePie_Caption';
+
+	/**
+	 * @var string Class used for Media RSS <media:copyright>
+	 * @see SimplePie::set_copyright_class()
+	 * @access private
+	 */
+	public $copyright_class = 'SimplePie_Copyright';
+
+	/**
+	 * @var string Class used for Media RSS <media:credit>
+	 * @see SimplePie::set_credit_class()
+	 * @access private
+	 */
+	public $credit_class = 'SimplePie_Credit';
+
+	/**
+	 * @var string Class used for Media RSS <media:rating>
+	 * @see SimplePie::set_rating_class()
+	 * @access private
+	 */
+	public $rating_class = 'SimplePie_Rating';
+
+	/**
+	 * @var string Class used for Media RSS <media:restriction>
+	 * @see SimplePie::set_restriction_class()
+	 * @access private
+	 */
+	public $restriction_class = 'SimplePie_Restriction';
+
+	/**
+	 * @var string Class used for content-type sniffing
+	 * @see SimplePie::set_content_type_sniffer_class()
+	 * @access private
+	 */
+	public $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer';
+
+	/**
+	 * @var string Class used for item sources.
+	 * @see SimplePie::set_source_class()
+	 * @access private
+	 */
+	public $source_class = 'SimplePie_Source';
+
+	/**
+	 * @var mixed Set javascript query string parameter (false, or
+	 * anything type-cast to false, disables this feature)
+	 * @see SimplePie::set_javascript()
+	 * @access private
+	 */
+	public $javascript = 'js';
+
+	/**
+	 * @var int Maximum number of feeds to check with autodiscovery
+	 * @see SimplePie::set_max_checked_feeds()
+	 * @access private
+	 */
+	public $max_checked_feeds = 10;
+
+	/**
+	 * @var array All the feeds found during the autodiscovery process
+	 * @see SimplePie::get_all_discovered_feeds()
+	 * @access private
+	 */
+	public $all_discovered_feeds = array();
+
+	/**
+	 * @var string Web-accessible path to the handler_image.php file.
+	 * @see SimplePie::set_image_handler()
+	 * @access private
+	 */
+	public $image_handler = '';
+
+	/**
+	 * @var array Stores the URLs when multiple feeds are being initialized.
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	public $multifeed_url = array();
+
+	/**
+	 * @var array Stores SimplePie objects when multiple feeds initialized.
+	 * @access private
+	 */
+	public $multifeed_objects = array();
+
+	/**
+	 * @var array Stores the get_object_vars() array for use with multifeeds.
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	public $config_settings = null;
+
+	/**
+	 * @var integer Stores the number of items to return per-feed with multifeeds.
+	 * @see SimplePie::set_item_limit()
+	 * @access private
+	 */
+	public $item_limit = 0;
+
+	/**
+	 * @var array Stores the default attributes to be stripped by strip_attributes().
+	 * @see SimplePie::strip_attributes()
+	 * @access private
+	 */
+	public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
+
+	/**
+	 * @var array Stores the default tags to be stripped by strip_htmltags().
+	 * @see SimplePie::strip_htmltags()
+	 * @access private
+	 */
+	public $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');
+
+	/**
+	 * The SimplePie class contains feed level data and options
+	 *
+	 * There are two ways that you can create a new SimplePie object. The first
+	 * is by passing a feed URL as a parameter to the SimplePie constructor
+	 * (as well as optionally setting the cache location and cache expiry). This
+	 * will initialise the whole feed with all of the default settings, and you
+	 * can begin accessing methods and properties immediately.
+	 *
+	 * The second way is to create the SimplePie object with no parameters
+	 * at all. This will enable you to set configuration options. After setting
+	 * them, you must initialise the feed using $feed->init(). At that point the
+	 * object's methods and properties will be available to you. This format is
+	 * what is used throughout this documentation.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 */
+	public function __construct()
+	{
+		if (version_compare(PHP_VERSION, '5.0', '<'))
+		{
+			trigger_error('PHP 4.x is no longer supported. Please upgrade to PHP 5.2 or newer.');
+			die();
+		}
+
+		// Other objects, instances created here so we can set options on them
+		$this->sanitize = new SimplePie_Sanitize();
+
+		if (func_num_args() > 0)
+		{
+			trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.');
+		}
+	}
+
+	/**
+	 * Used for converting object to a string
+	 */
+	public function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	/**
+	 * Remove items that link back to this before destroying this object
+	 */
+	public function __destruct()
+	{
+		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
+		{
+			if (!empty($this->data['items']))
+			{
+				foreach ($this->data['items'] as $item)
+				{
+					$item->__destruct();
+				}
+				unset($item, $this->data['items']);
+			}
+			if (!empty($this->data['ordered_items']))
+			{
+				foreach ($this->data['ordered_items'] as $item)
+				{
+					$item->__destruct();
+				}
+				unset($item, $this->data['ordered_items']);
+			}
+		}
+	}
+
+	/**
+	 * Force the given data/URL to be treated as a feed no matter what it
+	 * appears like
+	 *
+	 * @access public
+	 * @since 1.1
+	 * @param bool $enable Force the given data/URL to be treated as a feed
+	 */
+	public function force_feed($enable = false)
+	{
+		$this->force_feed = (bool) $enable;
+	}
+
+	/**
+	 * This is the URL of the feed you want to parse.
+	 *
+	 * This allows you to enter the URL of the feed you want to parse, or the
+	 * website you want to try to use auto-discovery on. This takes priority
+	 * over any set raw data.
+	 *
+	 * You can set multiple feeds to mash together by passing an array instead
+	 * of a string for the $url. Remember that with each additional feed comes
+	 * additional processing and resources.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param mixed $url This is the URL (or array of URLs) that you want to parse.
+	 * @see SimplePie::set_raw_data()
+	 */
+	public function set_feed_url($url)
+	{
+		if (is_array($url))
+		{
+			$this->multifeed_url = array();
+			foreach ($url as $value)
+			{
+				$this->multifeed_url[] = SimplePie_Misc::fix_protocol($value, 1);
+			}
+		}
+		else
+		{
+			$this->feed_url = SimplePie_Misc::fix_protocol($url, 1);
+		}
+	}
+
+	/**
+	 * Provides an instance of SimplePie_File to use as a feed
+	 *
+	 * @access public
+	 * @param object &$file Instance of SimplePie_File (or subclass)
+	 * @return bool True on success, false on failure
+	 */
+	public function set_file(&$file)
+	{
+		//if (is_a($file, 'SimplePie_File'))
+		if ($file instanceof SimplePie_File)
+		{
+			$this->feed_url = $file->url;
+			$this->file =& $file;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to use a string of RSS/Atom data instead of a remote feed.
+	 *
+	 * If you have a feed available as a string in PHP, you can tell SimplePie
+	 * to parse that data string instead of a remote feed. Any set feed URL
+	 * takes precedence.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param string $data RSS or Atom data as a string.
+	 * @see SimplePie::set_feed_url()
+	 */
+	public function set_raw_data($data)
+	{
+		$this->raw_data = $data;
+	}
+
+	/**
+	 * Allows you to override the default timeout for fetching remote feeds.
+	 *
+	 * This allows you to change the maximum time the feed's server to respond
+	 * and send the feed back.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed.
+	 */
+	public function set_timeout($timeout = 10)
+	{
+		$this->timeout = (int) $timeout;
+	}
+
+	/**
+	 * Forces SimplePie to use fsockopen() instead of the preferred cURL
+	 * functions.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param bool $enable Force fsockopen() to be used
+	 */
+	public function force_fsockopen($enable = false)
+	{
+		$this->force_fsockopen = (bool) $enable;
+	}
+
+	/**
+	 * Enables/disables caching in SimplePie.
+	 *
+	 * This option allows you to disable caching all-together in SimplePie.
+	 * However, disabling the cache can lead to longer load times.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param bool $enable Enable caching
+	 */
+	public function enable_cache($enable = true)
+	{
+		$this->cache = (bool) $enable;
+	}
+
+	/**
+	 * Set the length of time (in seconds) that the contents of a feed
+	 * will be cached.
+	 *
+	 * @access public
+	 * @param int $seconds The feed content cache duration.
+	 */
+	public function set_cache_duration($seconds = 3600)
+	{
+		$this->cache_duration = (int) $seconds;
+	}
+
+	/**
+	 * Set the length of time (in seconds) that the autodiscovered feed
+	 * URL will be cached.
+	 *
+	 * @access public
+	 * @param int $seconds The autodiscovered feed URL cache duration.
+	 */
+	public function set_autodiscovery_cache_duration($seconds = 604800)
+	{
+		$this->autodiscovery_cache_duration = (int) $seconds;
+	}
+
+	/**
+	 * Set the file system location where the cached files should be stored.
+	 *
+	 * @access public
+	 * @param string $location The file system location.
+	 */
+	public function set_cache_location($location = './cache')
+	{
+		$this->cache_location = (string) $location;
+	}
+
+	/**
+	 * Determines whether feed items should be sorted into reverse chronological order.
+	 *
+	 * @access public
+	 * @param bool $enable Sort as reverse chronological order.
+	 */
+	public function enable_order_by_date($enable = true)
+	{
+		$this->order_by_date = (bool) $enable;
+	}
+
+	/**
+	 * Allows you to override the character encoding reported by the feed.
+	 *
+	 * @access public
+	 * @param string $encoding Character encoding.
+	 */
+	public function set_input_encoding($encoding = false)
+	{
+		if ($encoding)
+		{
+			$this->input_encoding = (string) $encoding;
+		}
+		else
+		{
+			$this->input_encoding = false;
+		}
+	}
+
+	/**
+	 * Set how much feed autodiscovery to do
+	 *
+	 * @access public
+	 * @see SIMPLEPIE_LOCATOR_NONE
+	 * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY
+	 * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION
+	 * @see SIMPLEPIE_LOCATOR_LOCAL_BODY
+	 * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION
+	 * @see SIMPLEPIE_LOCATOR_REMOTE_BODY
+	 * @see SIMPLEPIE_LOCATOR_ALL
+	 * @param int $level Feed Autodiscovery Level (level can be a
+	 * combination of the above constants, see bitwise OR operator)
+	 */
+	public function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL)
+	{
+		$this->autodiscovery = (int) $level;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for caching.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_cache_class($class = 'SimplePie_Cache')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Cache'))
+		{
+			$this->cache_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for auto-discovery.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_locator_class($class = 'SimplePie_Locator')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Locator'))
+		{
+			$this->locator_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for XML parsing.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_parser_class($class = 'SimplePie_Parser')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Parser'))
+		{
+			$this->parser_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for remote file fetching.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_file_class($class = 'SimplePie_File')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_File'))
+		{
+			$this->file_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for data sanitization.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_sanitize_class($class = 'SimplePie_Sanitize')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Sanitize'))
+		{
+			$this->sanitize = new $class();
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling feed items.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_item_class($class = 'SimplePie_Item')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Item'))
+		{
+			$this->item_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling author data.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_author_class($class = 'SimplePie_Author')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Author'))
+		{
+			$this->author_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling category data.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_category_class($class = 'SimplePie_Category')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Category'))
+		{
+			$this->category_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for feed enclosures.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_enclosure_class($class = 'SimplePie_Enclosure')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Enclosure'))
+		{
+			$this->enclosure_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:text> captions
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_caption_class($class = 'SimplePie_Caption')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Caption'))
+		{
+			$this->caption_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:copyright>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_copyright_class($class = 'SimplePie_Copyright')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Copyright'))
+		{
+			$this->copyright_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:credit>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_credit_class($class = 'SimplePie_Credit')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Credit'))
+		{
+			$this->credit_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:rating>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_rating_class($class = 'SimplePie_Rating')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Rating'))
+		{
+			$this->rating_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:restriction>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_restriction_class($class = 'SimplePie_Restriction')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Restriction'))
+		{
+			$this->restriction_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for content-type sniffing.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Content_Type_Sniffer'))
+		{
+			$this->content_type_sniffer_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses item sources.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	public function set_source_class($class = 'SimplePie_Source')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Source'))
+		{
+			$this->source_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to override the default user agent string.
+	 *
+	 * @access public
+	 * @param string $ua New user agent string.
+	 */
+	public function set_useragent($ua = SIMPLEPIE_USERAGENT)
+	{
+		$this->useragent = (string) $ua;
+	}
+
+	/**
+	 * Set callback function to create cache filename with
+	 *
+	 * @access public
+	 * @param mixed $function Callback function
+	 */
+	public function set_cache_name_function($function = 'md5')
+	{
+		if (is_callable($function))
+		{
+			$this->cache_name_function = $function;
+		}
+	}
+
+	/**
+	 * Set javascript query string parameter
+	 *
+	 * @access public
+	 * @param mixed $get Javascript query string parameter
+	 */
+	public function set_javascript($get = 'js')
+	{
+		if ($get)
+		{
+			$this->javascript = (string) $get;
+		}
+		else
+		{
+			$this->javascript = false;
+		}
+	}
+
+	/**
+	 * Set options to make SP as fast as possible.  Forgoes a
+	 * substantial amount of data sanitization in favor of speed.
+	 *
+	 * @access public
+	 * @param bool $set Whether to set them or not
+	 */
+	public function set_stupidly_fast($set = false)
+	{
+		if ($set)
+		{
+			$this->enable_order_by_date(false);
+			$this->remove_div(false);
+			$this->strip_comments(false);
+			$this->strip_htmltags(false);
+			$this->strip_attributes(false);
+			$this->set_image_handler(false);
+		}
+	}
+
+	/**
+	 * Set maximum number of feeds to check with autodiscovery
+	 *
+	 * @access public
+	 * @param int $max Maximum number of feeds to check
+	 */
+	public function set_max_checked_feeds($max = 10)
+	{
+		$this->max_checked_feeds = (int) $max;
+	}
+
+	public function remove_div($enable = true)
+	{
+		$this->sanitize->remove_div($enable);
+	}
+
+	public function strip_htmltags($tags = '', $encode = null)
+	{
+		if ($tags === '')
+		{
+			$tags = $this->strip_htmltags;
+		}
+		$this->sanitize->strip_htmltags($tags);
+		if ($encode !== null)
+		{
+			$this->sanitize->encode_instead_of_strip($tags);
+		}
+	}
+
+	public function encode_instead_of_strip($enable = true)
+	{
+		$this->sanitize->encode_instead_of_strip($enable);
+	}
+
+	public function strip_attributes($attribs = '')
+	{
+		if ($attribs === '')
+		{
+			$attribs = $this->strip_attributes;
+		}
+		$this->sanitize->strip_attributes($attribs);
+	}
+
+	public function set_output_encoding($encoding = 'UTF-8')
+	{
+		$this->sanitize->set_output_encoding($encoding);
+	}
+
+	public function strip_comments($strip = false)
+	{
+		$this->sanitize->strip_comments($strip);
+	}
+
+	/**
+	 * Set element/attribute key/value pairs of HTML attributes
+	 * containing URLs that need to be resolved relative to the feed
+	 *
+	 * @access public
+	 * @since 1.0
+	 * @param array $element_attribute Element/attribute key/value pairs
+	 */
+	public function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'))
+	{
+		$this->sanitize->set_url_replacements($element_attribute);
+	}
+
+	/**
+	 * Set the handler to enable the display of cached images.
+	 *
+	 * @access public
+	 * @param str $page Web-accessible path to the handler_image.php file.
+	 * @param str $qs The query string that the value should be passed to.
+	 */
+	public function set_image_handler($page = false, $qs = 'i')
+	{
+		if ($page !== false)
+		{
+			$this->sanitize->set_image_handler($page . '?' . $qs . '=');
+		}
+		else
+		{
+			$this->image_handler = '';
+		}
+	}
+
+	/**
+	 * Set the limit for items returned per-feed with multifeeds.
+	 *
+	 * @access public
+	 * @param integer $limit The maximum number of items to return.
+	 */
+	public function set_item_limit($limit = 0)
+	{
+		$this->item_limit = (int) $limit;
+	}
+
+	public function init()
+	{
+		// Check absolute bare minimum requirements.
+		if ((function_exists('version_compare') && version_compare(PHP_VERSION, '4.3.0', '<')) || !extension_loaded('xml') || !extension_loaded('pcre'))
+		{
+			return false;
+		}
+		// Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader.
+		elseif (!extension_loaded('xmlreader'))
+		{
+			static $xml_is_sane = null;
+			if ($xml_is_sane === null)
+			{
+				$parser_check = xml_parser_create();
+				xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
+				xml_parser_free($parser_check);
+				$xml_is_sane = isset($values[0]['value']);
+			}
+			if (!$xml_is_sane)
+			{
+				return false;
+			}
+		}
+
+		if (isset($_GET[$this->javascript]))
+		{
+			SimplePie_Misc::output_javascript();
+			exit;
+		}
+
+		// Pass whatever was set with config options over to the sanitizer.
+		$this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->cache_class);
+		$this->sanitize->pass_file_data($this->file_class, $this->timeout, $this->useragent, $this->force_fsockopen);
+
+		if ($this->feed_url !== null || $this->raw_data !== null)
+		{
+			$this->error = null;
+			$this->data = array();
+			$this->multifeed_objects = array();
+			$cache = false;
+
+			if ($this->feed_url !== null)
+			{
+				$parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url);
+				// Decide whether to enable caching
+				if ($this->cache && $parsed_feed_url['scheme'] !== '')
+				{
+					$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc');
+				}
+				// If it's enabled and we don't want an XML dump, use the cache
+				if ($cache && !$this->xml_dump)
+				{
+					// Load the Cache
+					$this->data = $cache->load();
+					if (!empty($this->data))
+					{
+						// If the cache is for an outdated build of SimplePie
+						if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD)
+						{
+							$cache->unlink();
+							$this->data = array();
+						}
+						// If we've hit a collision just rerun it with caching disabled
+						elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url)
+						{
+							$cache = false;
+							$this->data = array();
+						}
+						// If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL.
+						elseif (isset($this->data['feed_url']))
+						{
+							// If the autodiscovery cache is still valid use it.
+							if ($cache->mtime() + $this->autodiscovery_cache_duration > time())
+							{
+								// Do not need to do feed autodiscovery yet.
+								if ($this->data['feed_url'] === $this->data['url'])
+								{
+									$cache->unlink();
+									$this->data = array();
+								}
+								else
+								{
+									$this->set_feed_url($this->data['feed_url']);
+									return $this->init();
+								}
+							}
+						}
+						// Check if the cache has been updated
+						elseif ($cache->mtime() + $this->cache_duration < time())
+						{
+							// If we have last-modified and/or etag set
+							if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))
+							{
+								$headers = array();
+								if (isset($this->data['headers']['last-modified']))
+								{
+									$headers['if-modified-since'] = $this->data['headers']['last-modified'];
+								}
+								if (isset($this->data['headers']['etag']))
+								{
+									$headers['if-none-match'] = '"' . $this->data['headers']['etag'] . '"';
+								}
+
+								$file = new $this->file_class($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen);
+
+								if ($file->success)
+								{
+									if ($file->status_code === 304)
+									{
+										$cache->touch();
+										return true;
+									}
+									else
+									{
+										$headers = $file->headers;
+									}
+								}
+								else
+								{
+									unset($file);
+								}
+							}
+						}
+						// If the cache is still valid, just return true
+						else
+						{
+							return true;
+						}
+					}
+					// If the cache is empty, delete it
+					else
+					{
+						$cache->unlink();
+						$this->data = array();
+					}
+				}
+				// If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it.
+				if (!isset($file))
+				{
+					//if (is_a($this->file, 'SimplePie_File') && $this->file->url === $this->feed_url)
+					if (($this->file instanceof SimplePie_File) && ($this->file->url === $this->feed_url))
+					{
+						$file =& $this->file;
+					}
+					else
+					{
+						$file = new $this->file_class($this->feed_url, $this->timeout, 5, null, $this->useragent, $this->force_fsockopen);
+					}
+				}
+				// If the file connection has an error, set SimplePie::error to that and quit
+				if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
+				{
+					$this->error = $file->error;
+					if (!empty($this->data))
+					{
+						return true;
+					}
+					else
+					{
+						return false;
+					}
+				}
+
+				if (!$this->force_feed)
+				{
+					// Check if the supplied URL is a feed, if it isn't, look for it.
+					$locate = new $this->locator_class($file, $this->timeout, $this->useragent, $this->file_class, $this->max_checked_feeds, $this->content_type_sniffer_class);
+
+					if (!$locate->is_feed($file))
+					{
+						// We need to unset this so that if SimplePie::set_file() has been called that object is untouched
+						unset($file);
+						if ($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds))
+						{
+							if ($cache)
+							{
+								$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
+								if (!$cache->save($this))
+								{
+									trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
+								}
+								$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc');
+							}
+							$this->feed_url = $file->url;
+						}
+						else
+						{
+							$this->error = "A feed could not be found at $this->feed_url. A feed with an invalid mime type may fall victim to this error, or " . SIMPLEPIE_NAME . " was unable to auto-discover it.. Use force_feed() if you are certain this URL is a real feed.";
+							SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+							return false;
+						}
+					}
+					$locate = null;
+				}
+
+				$headers = $file->headers;
+				$data = $file->body;
+				$sniffer = new $this->content_type_sniffer_class($file);
+				$sniffed = $sniffer->get_type();
+			}
+			else
+			{
+				$data = $this->raw_data;
+			}
+
+			// Set up array of possible encodings
+			$encodings = array();
+
+			// First check to see if input has been overridden.
+			if ($this->input_encoding !== false)
+			{
+				$encodings[] = $this->input_encoding;
+			}
+
+			$application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity');
+			$text_types = array('text/xml', 'text/xml-external-parsed-entity');
+
+			// RFC 3023 (only applies to sniffed content)
+			if (isset($sniffed))
+			{
+				if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml')
+				{
+					if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
+					{
+						$encodings[] = strtoupper($charset[1]);
+					}
+					$encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data));
+					$encodings[] = 'UTF-8';
+				}
+				elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml')
+				{
+					if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
+					{
+						$encodings[] = $charset[1];
+					}
+					$encodings[] = 'US-ASCII';
+				}
+				// Text MIME-type default
+				elseif (substr($sniffed, 0, 5) === 'text/')
+				{
+					$encodings[] = 'US-ASCII';
+				}
+			}
+
+			// Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1
+			$encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data));
+			$encodings[] = 'UTF-8';
+			$encodings[] = 'ISO-8859-1';
+
+			// There's no point in trying an encoding twice
+			$encodings = array_unique($encodings);
+
+			// If we want the XML, just output that with the most likely encoding and quit
+			if ($this->xml_dump)
+			{
+				header('Content-type: text/xml; charset=' . $encodings[0]);
+				echo $data;
+				exit;
+			}
+
+			// Loop through each possible encoding, till we return something, or run out of possibilities
+			foreach ($encodings as $encoding)
+			{
+				// Change the encoding to UTF-8 (as we always use UTF-8 internally)
+				if ($utf8_data = SimplePie_Misc::change_encoding($data, $encoding, 'UTF-8'))
+				{
+					// Create new parser
+					$parser = new $this->parser_class();
+
+					// If it's parsed fine
+					if ($parser->parse($utf8_data, 'UTF-8'))
+					{
+						$this->data = $parser->get_data();
+						if ($this->get_type() & ~SIMPLEPIE_TYPE_NONE)
+						{
+							if (isset($headers))
+							{
+								$this->data['headers'] = $headers;
+							}
+							$this->data['build'] = SIMPLEPIE_BUILD;
+
+							// Cache the file if caching is enabled
+							if ($cache && !$cache->save($this))
+							{
+								trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
+							}
+							return true;
+						}
+						else
+						{
+							$this->error = "A feed could not be found at $this->feed_url. This does not appear to be a valid RSS or Atom feed.";
+							SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+							return false;
+						}
+					}
+				}
+			}
+
+			if (isset($parser))
+			{
+				// We have an error, just set SimplePie_Misc::error to it and quit
+				$this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column());
+			}
+			else
+			{
+				$this->error = 'The data could not be converted to UTF-8. You MUST have either the iconv or mbstring extension installed. Upgrading to PHP 5.x (which includes iconv) is highly recommended.';
+			}
+
+			SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+
+			return false;
+		}
+		elseif (!empty($this->multifeed_url))
+		{
+			$i = 0;
+			$success = 0;
+			$this->multifeed_objects = array();
+			foreach ($this->multifeed_url as $url)
+			{
+				if (SIMPLEPIE_PHP5)
+				{
+					// This keyword needs to defy coding standards for PHP4 compatibility
+					$this->multifeed_objects[$i] = clone($this);
+				}
+				else
+				{
+					$this->multifeed_objects[$i] = $this;
+				}
+				$this->multifeed_objects[$i]->set_feed_url($url);
+				$success |= $this->multifeed_objects[$i]->init();
+				$i++;
+			}
+			return (bool) $success;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Return the error message for the occured error
+	 *
+	 * @access public
+	 * @return string Error message
+	 */
+	public function error()
+	{
+		return $this->error;
+	}
+
+	public function get_encoding()
+	{
+		return $this->sanitize->output_encoding;
+	}
+
+	public function handle_content_type($mime = 'text/html')
+	{
+		if (!headers_sent())
+		{
+			$header = "Content-type: $mime;";
+			if ($this->get_encoding())
+			{
+				$header .= ' charset=' . $this->get_encoding();
+			}
+			else
+			{
+				$header .= ' charset=UTF-8';
+			}
+			header($header);
+		}
+	}
+
+	public function get_type()
+	{
+		if (!isset($this->data['type']))
+		{
+			$this->data['type'] = SIMPLEPIE_TYPE_ALL;
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10;
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03;
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF']))
+			{
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput']))
+				{
+					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_10;
+				}
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput']))
+				{
+					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_090;
+				}
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL;
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
+				{
+					switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
+					{
+						case '0.91':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091;
+							if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
+							{
+								switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
+								{
+									case '0':
+										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE;
+										break;
+
+									case '24':
+										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND;
+										break;
+								}
+							}
+							break;
+
+						case '0.92':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_092;
+							break;
+
+						case '0.93':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_093;
+							break;
+
+						case '0.94':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_094;
+							break;
+
+						case '2.0':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_20;
+							break;
+					}
+				}
+			}
+			else
+			{
+				$this->data['type'] = SIMPLEPIE_TYPE_NONE;
+			}
+		}
+		return $this->data['type'];
+	}
+
+	/**
+	 * @todo If we have a perm redirect we should return the new URL
+	 * @todo When we make the above change, let's support <itunes:new-feed-url> as well
+	 * @todo Also, |atom:link|@rel=self
+	 */
+	public function subscribe_url()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_feed_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_ATOM_10)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_ATOM_03)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_RDF)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag];
+			}
+		}
+		return null;
+	}
+
+	public function get_channel_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_ATOM_ALL)
+		{
+			if ($return = $this->get_feed_tags($namespace, $tag))
+			{
+				return $return;
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_10)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_090)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		return null;
+	}
+
+	public function get_image_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_RSS_10)
+		{
+			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_090)
+		{
+			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		return null;
+	}
+
+	public function get_base($element = array())
+	{
+		if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base']))
+		{
+			return $element['xml_base'];
+		}
+		elseif ($this->get_link() !== null)
+		{
+			return $this->get_link();
+		}
+		else
+		{
+			return $this->subscribe_url();
+		}
+	}
+
+	public function sanitize($data, $type, $base = '')
+	{
+		return $this->sanitize->sanitize($data, $type, $base);
+	}
+
+	public function get_title()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->author_class($name, $url, $email);
+			}
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if (isset($links[$key]))
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Added for parity between the parent-level and the item/entry-level.
+	 */
+	public function get_permalink()
+	{
+		return $this->get_link(0);
+	}
+
+	public function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					}
+				}
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+					}
+				}
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_all_discovered_feeds()
+	{
+		return $this->all_discovered_feeds;
+	}
+
+	public function get_description()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_copyright()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_language()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['headers']['content-language']))
+		{
+			return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_latitude()
+	{
+
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_longitude()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_title()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_url()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
+		{
+			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_link()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_width()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width'))
+		{
+			return round($return[0]['data']);
+		}
+		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return 88.0;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_height()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height'))
+		{
+			return round($return[0]['data']);
+		}
+		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return 31.0;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_item_quantity($max = 0)
+	{
+		$max = (int) $max;
+		$qty = count($this->get_items());
+		if ($max === 0)
+		{
+			return $qty;
+		}
+		else
+		{
+			return ($qty > $max) ? $max : $qty;
+		}
+	}
+
+	public function get_item($key = 0)
+	{
+		$items = $this->get_items();
+		if (isset($items[$key]))
+		{
+			return $items[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_items($start = 0, $end = 0)
+	{
+		if (!isset($this->data['items']))
+		{
+			if (!empty($this->multifeed_objects))
+			{
+				$this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit);
+			}
+			else
+			{
+				$this->data['items'] = array();
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+			}
+		}
+
+		if (!empty($this->data['items']))
+		{
+			// If we want to order it by date, check if all items have a date, and then sort it
+			if ($this->order_by_date && empty($this->multifeed_objects))
+			{
+				if (!isset($this->data['ordered_items']))
+				{
+					$do_sort = true;
+					foreach ($this->data['items'] as $item)
+					{
+						if (!$item->get_date('U'))
+						{
+							$do_sort = false;
+							break;
+						}
+					}
+					$item = null;
+					$this->data['ordered_items'] = $this->data['items'];
+					if ($do_sort)
+					{
+						usort($this->data['ordered_items'], array(&$this, 'sort_items'));
+					}
+				}
+				$items = $this->data['ordered_items'];
+			}
+			else
+			{
+				$items = $this->data['items'];
+			}
+
+			// Slice the data as desired
+			if ($end === 0)
+			{
+				return array_slice($items, $start);
+			}
+			else
+			{
+				return array_slice($items, $start, $end);
+			}
+		}
+		else
+		{
+			return array();
+		}
+	}
+
+	/**
+	 * @static
+	 */
+	public function sort_items($a, $b)
+	{
+		return $a->get_date('U') <= $b->get_date('U');
+	}
+
+	/**
+	 * @static
+	 */
+	public function merge_items($urls, $start = 0, $end = 0, $limit = 0)
+	{
+		if (is_array($urls) && sizeof($urls) > 0)
+		{
+			$items = array();
+			foreach ($urls as $arg)
+			{
+				//if (is_a($arg, 'SimplePie'))
+				if ($arg instanceof SimplePie)
+				{
+					$items = array_merge($items, $arg->get_items(0, $limit));
+				}
+				else
+				{
+					trigger_error('Arguments must be SimplePie objects', E_USER_WARNING);
+				}
+			}
+
+			$do_sort = true;
+			foreach ($items as $item)
+			{
+				if (!$item->get_date('U'))
+				{
+					$do_sort = false;
+					break;
+				}
+			}
+			$item = null;
+			if ($do_sort)
+			{
+				usort($items, array('SimplePie', 'sort_items'));
+			}
+
+			if ($end === 0)
+			{
+				return array_slice($items, $start);
+			}
+			else
+			{
+				return array_slice($items, $start, $end);
+			}
+		}
+		else
+		{
+			trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING);
+			return array();
+		}
+	}
+}
+
+class SimplePie_Item
+{
+	var $feed;
+	var $data = array();
+
+	public function __construct($feed, $data)
+	{
+		$this->feed = $feed;
+		$this->data = $data;
+	}
+
+	public function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	/**
+	 * Remove items that link back to this before destroying this object
+	 */
+	public function __destruct()
+	{
+		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
+		{
+			unset($this->feed);
+		}
+	}
+
+	public function get_item_tags($namespace, $tag)
+	{
+		if (isset($this->data['child'][$namespace][$tag]))
+		{
+			return $this->data['child'][$namespace][$tag];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_base($element = array())
+	{
+		return $this->feed->get_base($element);
+	}
+
+	public function sanitize($data, $type, $base = '')
+	{
+		return $this->feed->sanitize($data, $type, $base);
+	}
+
+	public function get_feed()
+	{
+		return $this->feed;
+	}
+
+	public function get_id($hash = false)
+	{
+		if (!$hash)
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif (($return = $this->get_permalink()) !== null)
+			{
+				return $return;
+			}
+			elseif (($return = $this->get_title()) !== null)
+			{
+				return $return;
+			}
+		}
+		if ($this->get_permalink() !== null || $this->get_title() !== null)
+		{
+			return md5($this->get_permalink() . $this->get_title());
+		}
+		else
+		{
+			return md5(serialize($this->data));
+		}
+	}
+
+	public function get_title()
+	{
+		if (!isset($this->data['title']))
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$this->data['title'] = null;
+			}
+		}
+		return $this->data['title'];
+	}
+
+	public function get_description($description_only = false)
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (!$description_only)
+		{
+			return $this->get_content(true);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_content($content_only = false)
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_content_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif (!$content_only)
+		{
+			return $this->get_description(true);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->feed->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->feed->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->feed->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->feed->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->feed->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->feed->author_class($name, $url, $email);
+			}
+		}
+		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author'))
+		{
+			$authors[] = new $this->feed->author_class(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		elseif (($source = $this->get_source()) && ($authors = $source->get_authors()))
+		{
+			return $authors;
+		}
+		elseif ($authors = $this->feed->get_authors())
+		{
+			return $authors;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_copyright()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_date($date_format = 'j F Y, g:i a')
+	{
+		if (!isset($this->data['date']))
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+
+			if (!empty($this->data['date']['raw']))
+			{
+				$parser = SimplePie_Parse_Date::get();
+				$this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']);
+			}
+			else
+			{
+				$this->data['date'] = null;
+			}
+		}
+		if ($this->data['date'])
+		{
+			$date_format = (string) $date_format;
+			switch ($date_format)
+			{
+				case '':
+					return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT);
+
+				case 'U':
+					return $this->data['date']['parsed'];
+
+				default:
+					return date($date_format, $this->data['date']['parsed']);
+			}
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_local_date($date_format = '%c')
+	{
+		if (!$date_format)
+		{
+			return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (($date = $this->get_date('U')) !== null)
+		{
+			return strftime($date_format, $date);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_permalink()
+	{
+		$link = $this->get_link();
+		$enclosure = $this->get_enclosure(0);
+		if ($link !== null)
+		{
+			return $link;
+		}
+		elseif ($enclosure !== null)
+		{
+			return $enclosure->get_link();
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if ($links[$key] !== null)
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']))
+				{
+					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+				}
+			}
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']))
+				{
+					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+				}
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
+			{
+				if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true')
+				{
+					$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+				}
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * @todo Add ability to prefer one type of content over another (in a media group).
+	 */
+	public function get_enclosure($key = 0, $prefer = null)
+	{
+		$enclosures = $this->get_enclosures();
+		if (isset($enclosures[$key]))
+		{
+			return $enclosures[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Grabs all available enclosures (podcasts, etc.)
+	 *
+	 * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS.
+	 *
+	 * At this point, we're pretty much assuming that all enclosures for an item are the same content.  Anything else is too complicated to properly support.
+	 *
+	 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4).
+	 * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists).
+	 */
+	public function get_enclosures()
+	{
+		if (!isset($this->data['enclosures']))
+		{
+			$this->data['enclosures'] = array();
+
+			// Elements
+			$captions_parent = null;
+			$categories_parent = null;
+			$copyrights_parent = null;
+			$credits_parent = null;
+			$description_parent = null;
+			$duration_parent = null;
+			$hashes_parent = null;
+			$keywords_parent = null;
+			$player_parent = null;
+			$ratings_parent = null;
+			$restrictions_parent = null;
+			$thumbnails_parent = null;
+			$title_parent = null;
+
+			// Let's do the channel and item-level ones first, and just re-use them if we need to.
+			$parent = $this->get_feed();
+
+			// CAPTIONS
+			if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
+			{
+				foreach ($captions as $caption)
+				{
+					$caption_type = null;
+					$caption_lang = null;
+					$caption_startTime = null;
+					$caption_endTime = null;
+					$caption_text = null;
+					if (isset($caption['attribs']['']['type']))
+					{
+						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['lang']))
+					{
+						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['start']))
+					{
+						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['end']))
+					{
+						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['data']))
+					{
+						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+				}
+			}
+			elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
+			{
+				foreach ($captions as $caption)
+				{
+					$caption_type = null;
+					$caption_lang = null;
+					$caption_startTime = null;
+					$caption_endTime = null;
+					$caption_text = null;
+					if (isset($caption['attribs']['']['type']))
+					{
+						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['lang']))
+					{
+						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['start']))
+					{
+						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['end']))
+					{
+						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['data']))
+					{
+						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+				}
+			}
+			if (is_array($captions_parent))
+			{
+				$captions_parent = array_values(SimplePie_Misc::array_unique($captions_parent));
+			}
+
+			// CATEGORIES
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
+			{
+				$term = null;
+				$scheme = null;
+				$label = null;
+				if (isset($category['data']))
+				{
+					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($category['attribs']['']['scheme']))
+				{
+					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				else
+				{
+					$scheme = 'http://search.yahoo.com/mrss/category_schema';
+				}
+				if (isset($category['attribs']['']['label']))
+				{
+					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+			}
+			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
+			{
+				$term = null;
+				$scheme = null;
+				$label = null;
+				if (isset($category['data']))
+				{
+					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($category['attribs']['']['scheme']))
+				{
+					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				else
+				{
+					$scheme = 'http://search.yahoo.com/mrss/category_schema';
+				}
+				if (isset($category['attribs']['']['label']))
+				{
+					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+			}
+			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category)
+			{
+				$term = null;
+				$scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
+				$label = null;
+				if (isset($category['attribs']['']['text']))
+				{
+					$label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+
+				if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category']))
+				{
+					foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory)
+					{
+						if (isset($subcategory['attribs']['']['text']))
+						{
+							$label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+					}
+				}
+			}
+			if (is_array($categories_parent))
+			{
+				$categories_parent = array_values(SimplePie_Misc::array_unique($categories_parent));
+			}
+
+			// COPYRIGHT
+			if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
+			{
+				$copyright_url = null;
+				$copyright_label = null;
+				if (isset($copyright[0]['attribs']['']['url']))
+				{
+					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($copyright[0]['data']))
+				{
+					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label);
+			}
+			elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
+			{
+				$copyright_url = null;
+				$copyright_label = null;
+				if (isset($copyright[0]['attribs']['']['url']))
+				{
+					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($copyright[0]['data']))
+				{
+					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label);
+			}
+
+			// CREDITS
+			if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
+			{
+				foreach ($credits as $credit)
+				{
+					$credit_role = null;
+					$credit_scheme = null;
+					$credit_name = null;
+					if (isset($credit['attribs']['']['role']))
+					{
+						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($credit['attribs']['']['scheme']))
+					{
+						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$credit_scheme = 'urn:ebu';
+					}
+					if (isset($credit['data']))
+					{
+						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+				}
+			}
+			elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
+			{
+				foreach ($credits as $credit)
+				{
+					$credit_role = null;
+					$credit_scheme = null;
+					$credit_name = null;
+					if (isset($credit['attribs']['']['role']))
+					{
+						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($credit['attribs']['']['scheme']))
+					{
+						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$credit_scheme = 'urn:ebu';
+					}
+					if (isset($credit['data']))
+					{
+						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+				}
+			}
+			if (is_array($credits_parent))
+			{
+				$credits_parent = array_values(SimplePie_Misc::array_unique($credits_parent));
+			}
+
+			// DESCRIPTION
+			if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
+			{
+				if (isset($description_parent[0]['data']))
+				{
+					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+			elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
+			{
+				if (isset($description_parent[0]['data']))
+				{
+					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+
+			// DURATION
+			if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration'))
+			{
+				$seconds = null;
+				$minutes = null;
+				$hours = null;
+				if (isset($duration_parent[0]['data']))
+				{
+					$temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					if (sizeof($temp) > 0)
+					{
+						(int) $seconds = array_pop($temp);
+					}
+					if (sizeof($temp) > 0)
+					{
+						(int) $minutes = array_pop($temp);
+						$seconds += $minutes * 60;
+					}
+					if (sizeof($temp) > 0)
+					{
+						(int) $hours = array_pop($temp);
+						$seconds += $hours * 3600;
+					}
+					unset($temp);
+					$duration_parent = $seconds;
+				}
+			}
+
+			// HASHES
+			if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
+			{
+				foreach ($hashes_iterator as $hash)
+				{
+					$value = null;
+					$algo = null;
+					if (isset($hash['data']))
+					{
+						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($hash['attribs']['']['algo']))
+					{
+						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$algo = 'md5';
+					}
+					$hashes_parent[] = $algo.':'.$value;
+				}
+			}
+			elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
+			{
+				foreach ($hashes_iterator as $hash)
+				{
+					$value = null;
+					$algo = null;
+					if (isset($hash['data']))
+					{
+						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($hash['attribs']['']['algo']))
+					{
+						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$algo = 'md5';
+					}
+					$hashes_parent[] = $algo.':'.$value;
+				}
+			}
+			if (is_array($hashes_parent))
+			{
+				$hashes_parent = array_values(SimplePie_Misc::array_unique($hashes_parent));
+			}
+
+			// KEYWORDS
+			if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			if (is_array($keywords_parent))
+			{
+				$keywords_parent = array_values(SimplePie_Misc::array_unique($keywords_parent));
+			}
+
+			// PLAYER
+			if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
+			{
+				if (isset($player_parent[0]['attribs']['']['url']))
+				{
+					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+				}
+			}
+			elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
+			{
+				if (isset($player_parent[0]['attribs']['']['url']))
+				{
+					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+				}
+			}
+
+			// RATINGS
+			if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = null;
+					$rating_value = null;
+					if (isset($rating['attribs']['']['scheme']))
+					{
+						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$rating_scheme = 'urn:simple';
+					}
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = 'urn:itunes';
+					$rating_value = null;
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = null;
+					$rating_value = null;
+					if (isset($rating['attribs']['']['scheme']))
+					{
+						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$rating_scheme = 'urn:simple';
+					}
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = 'urn:itunes';
+					$rating_value = null;
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			if (is_array($ratings_parent))
+			{
+				$ratings_parent = array_values(SimplePie_Misc::array_unique($ratings_parent));
+			}
+
+			// RESTRICTIONS
+			if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = null;
+					$restriction_type = null;
+					$restriction_value = null;
+					if (isset($restriction['attribs']['']['relationship']))
+					{
+						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['attribs']['']['type']))
+					{
+						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['data']))
+					{
+						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = 'allow';
+					$restriction_type = null;
+					$restriction_value = 'itunes';
+					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
+					{
+						$restriction_relationship = 'deny';
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = null;
+					$restriction_type = null;
+					$restriction_value = null;
+					if (isset($restriction['attribs']['']['relationship']))
+					{
+						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['attribs']['']['type']))
+					{
+						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['data']))
+					{
+						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = 'allow';
+					$restriction_type = null;
+					$restriction_value = 'itunes';
+					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
+					{
+						$restriction_relationship = 'deny';
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			if (is_array($restrictions_parent))
+			{
+				$restrictions_parent = array_values(SimplePie_Misc::array_unique($restrictions_parent));
+			}
+
+			// THUMBNAILS
+			if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
+			{
+				foreach ($thumbnails as $thumbnail)
+				{
+					if (isset($thumbnail['attribs']['']['url']))
+					{
+						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+					}
+				}
+			}
+			elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
+			{
+				foreach ($thumbnails as $thumbnail)
+				{
+					if (isset($thumbnail['attribs']['']['url']))
+					{
+						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+					}
+				}
+			}
+
+			// TITLES
+			if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
+			{
+				if (isset($title_parent[0]['data']))
+				{
+					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+			elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
+			{
+				if (isset($title_parent[0]['data']))
+				{
+					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+
+			// Clear the memory
+			unset($parent);
+
+			// Attributes
+			$bitrate = null;
+			$channels = null;
+			$duration = null;
+			$expression = null;
+			$framerate = null;
+			$height = null;
+			$javascript = null;
+			$lang = null;
+			$length = null;
+			$medium = null;
+			$samplingrate = null;
+			$type = null;
+			$url = null;
+			$width = null;
+
+			// Elements
+			$captions = null;
+			$categories = null;
+			$copyrights = null;
+			$credits = null;
+			$description = null;
+			$hashes = null;
+			$keywords = null;
+			$player = null;
+			$ratings = null;
+			$restrictions = null;
+			$thumbnails = null;
+			$title = null;
+
+			// If we have media:group tags, loop through them.
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group)
+			{
+				if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']))
+				{
+					// If we have media:content tags, loop through them.
+					foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
+					{
+						if (isset($content['attribs']['']['url']))
+						{
+							// Attributes
+							$bitrate = null;
+							$channels = null;
+							$duration = null;
+							$expression = null;
+							$framerate = null;
+							$height = null;
+							$javascript = null;
+							$lang = null;
+							$length = null;
+							$medium = null;
+							$samplingrate = null;
+							$type = null;
+							$url = null;
+							$width = null;
+
+							// Elements
+							$captions = null;
+							$categories = null;
+							$copyrights = null;
+							$credits = null;
+							$description = null;
+							$hashes = null;
+							$keywords = null;
+							$player = null;
+							$ratings = null;
+							$restrictions = null;
+							$thumbnails = null;
+							$title = null;
+
+							// Start checking the attributes of media:content
+							if (isset($content['attribs']['']['bitrate']))
+							{
+								$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['channels']))
+							{
+								$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['duration']))
+							{
+								$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							else
+							{
+								$duration = $duration_parent;
+							}
+							if (isset($content['attribs']['']['expression']))
+							{
+								$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['framerate']))
+							{
+								$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['height']))
+							{
+								$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['lang']))
+							{
+								$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['fileSize']))
+							{
+								$length = ceil($content['attribs']['']['fileSize']);
+							}
+							if (isset($content['attribs']['']['medium']))
+							{
+								$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['samplingrate']))
+							{
+								$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['type']))
+							{
+								$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['attribs']['']['width']))
+							{
+								$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+
+							// Checking the other optional media: elements. Priority: media:content, media:group, item, channel
+
+							// CAPTIONS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+								{
+									$caption_type = null;
+									$caption_lang = null;
+									$caption_startTime = null;
+									$caption_endTime = null;
+									$caption_text = null;
+									if (isset($caption['attribs']['']['type']))
+									{
+										$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['lang']))
+									{
+										$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['start']))
+									{
+										$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['end']))
+									{
+										$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['data']))
+									{
+										$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+								}
+								if (is_array($captions))
+								{
+									$captions = array_values(SimplePie_Misc::array_unique($captions));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+								{
+									$caption_type = null;
+									$caption_lang = null;
+									$caption_startTime = null;
+									$caption_endTime = null;
+									$caption_text = null;
+									if (isset($caption['attribs']['']['type']))
+									{
+										$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['lang']))
+									{
+										$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['start']))
+									{
+										$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['attribs']['']['end']))
+									{
+										$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($caption['data']))
+									{
+										$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+								}
+								if (is_array($captions))
+								{
+									$captions = array_values(SimplePie_Misc::array_unique($captions));
+								}
+							}
+							else
+							{
+								$captions = $captions_parent;
+							}
+
+							// CATEGORIES
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+							{
+								foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+								{
+									$term = null;
+									$scheme = null;
+									$label = null;
+									if (isset($category['data']))
+									{
+										$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($category['attribs']['']['scheme']))
+									{
+										$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$scheme = 'http://search.yahoo.com/mrss/category_schema';
+									}
+									if (isset($category['attribs']['']['label']))
+									{
+										$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$categories[] = new $this->feed->category_class($term, $scheme, $label);
+								}
+							}
+							if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+							{
+								foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+								{
+									$term = null;
+									$scheme = null;
+									$label = null;
+									if (isset($category['data']))
+									{
+										$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($category['attribs']['']['scheme']))
+									{
+										$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$scheme = 'http://search.yahoo.com/mrss/category_schema';
+									}
+									if (isset($category['attribs']['']['label']))
+									{
+										$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$categories[] = new $this->feed->category_class($term, $scheme, $label);
+								}
+							}
+							if (is_array($categories) && is_array($categories_parent))
+							{
+								$categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent)));
+							}
+							elseif (is_array($categories))
+							{
+								$categories = array_values(SimplePie_Misc::array_unique($categories));
+							}
+							elseif (is_array($categories_parent))
+							{
+								$categories = array_values(SimplePie_Misc::array_unique($categories_parent));
+							}
+
+							// COPYRIGHTS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+							{
+								$copyright_url = null;
+								$copyright_label = null;
+								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+								{
+									$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+								{
+									$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+							{
+								$copyright_url = null;
+								$copyright_label = null;
+								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+								{
+									$copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+								{
+									$copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+							}
+							else
+							{
+								$copyrights = $copyrights_parent;
+							}
+
+							// CREDITS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+								{
+									$credit_role = null;
+									$credit_scheme = null;
+									$credit_name = null;
+									if (isset($credit['attribs']['']['role']))
+									{
+										$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($credit['attribs']['']['scheme']))
+									{
+										$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$credit_scheme = 'urn:ebu';
+									}
+									if (isset($credit['data']))
+									{
+										$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+								}
+								if (is_array($credits))
+								{
+									$credits = array_values(SimplePie_Misc::array_unique($credits));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+								{
+									$credit_role = null;
+									$credit_scheme = null;
+									$credit_name = null;
+									if (isset($credit['attribs']['']['role']))
+									{
+										$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($credit['attribs']['']['scheme']))
+									{
+										$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$credit_scheme = 'urn:ebu';
+									}
+									if (isset($credit['data']))
+									{
+										$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+								}
+								if (is_array($credits))
+								{
+									$credits = array_values(SimplePie_Misc::array_unique($credits));
+								}
+							}
+							else
+							{
+								$credits = $credits_parent;
+							}
+
+							// DESCRIPTION
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+							{
+								$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+							{
+								$description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							else
+							{
+								$description = $description_parent;
+							}
+
+							// HASHES
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+								{
+									$value = null;
+									$algo = null;
+									if (isset($hash['data']))
+									{
+										$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($hash['attribs']['']['algo']))
+									{
+										$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$algo = 'md5';
+									}
+									$hashes[] = $algo.':'.$value;
+								}
+								if (is_array($hashes))
+								{
+									$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+								{
+									$value = null;
+									$algo = null;
+									if (isset($hash['data']))
+									{
+										$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($hash['attribs']['']['algo']))
+									{
+										$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$algo = 'md5';
+									}
+									$hashes[] = $algo.':'.$value;
+								}
+								if (is_array($hashes))
+								{
+									$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+								}
+							}
+							else
+							{
+								$hashes = $hashes_parent;
+							}
+
+							// KEYWORDS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+							{
+								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+								{
+									$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+									foreach ($temp as $word)
+									{
+										$keywords[] = trim($word);
+									}
+									unset($temp);
+								}
+								if (is_array($keywords))
+								{
+									$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+							{
+								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+								{
+									$temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+									foreach ($temp as $word)
+									{
+										$keywords[] = trim($word);
+									}
+									unset($temp);
+								}
+								if (is_array($keywords))
+								{
+									$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+								}
+							}
+							else
+							{
+								$keywords = $keywords_parent;
+							}
+
+							// PLAYER
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+							{
+								$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+							{
+								$player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							else
+							{
+								$player = $player_parent;
+							}
+
+							// RATINGS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+								{
+									$rating_scheme = null;
+									$rating_value = null;
+									if (isset($rating['attribs']['']['scheme']))
+									{
+										$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$rating_scheme = 'urn:simple';
+									}
+									if (isset($rating['data']))
+									{
+										$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+								}
+								if (is_array($ratings))
+								{
+									$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+								{
+									$rating_scheme = null;
+									$rating_value = null;
+									if (isset($rating['attribs']['']['scheme']))
+									{
+										$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									else
+									{
+										$rating_scheme = 'urn:simple';
+									}
+									if (isset($rating['data']))
+									{
+										$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+								}
+								if (is_array($ratings))
+								{
+									$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+								}
+							}
+							else
+							{
+								$ratings = $ratings_parent;
+							}
+
+							// RESTRICTIONS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+								{
+									$restriction_relationship = null;
+									$restriction_type = null;
+									$restriction_value = null;
+									if (isset($restriction['attribs']['']['relationship']))
+									{
+										$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($restriction['attribs']['']['type']))
+									{
+										$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($restriction['data']))
+									{
+										$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+								}
+								if (is_array($restrictions))
+								{
+									$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+								{
+									$restriction_relationship = null;
+									$restriction_type = null;
+									$restriction_value = null;
+									if (isset($restriction['attribs']['']['relationship']))
+									{
+										$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($restriction['attribs']['']['type']))
+									{
+										$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									if (isset($restriction['data']))
+									{
+										$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+									}
+									$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+								}
+								if (is_array($restrictions))
+								{
+									$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+								}
+							}
+							else
+							{
+								$restrictions = $restrictions_parent;
+							}
+
+							// THUMBNAILS
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+							{
+								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+								{
+									$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+								}
+								if (is_array($thumbnails))
+								{
+									$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+								}
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+							{
+								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+								{
+									$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+								}
+								if (is_array($thumbnails))
+								{
+									$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+								}
+							}
+							else
+							{
+								$thumbnails = $thumbnails_parent;
+							}
+
+							// TITLES
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+							{
+								$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+							{
+								$title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							else
+							{
+								$title = $title_parent;
+							}
+
+							$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width);
+						}
+					}
+				}
+			}
+
+			// If we have standalone media:content tags, loop through them.
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']))
+			{
+				foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
+				{
+					if (isset($content['attribs']['']['url']))
+					{
+						// Attributes
+						$bitrate = null;
+						$channels = null;
+						$duration = null;
+						$expression = null;
+						$framerate = null;
+						$height = null;
+						$javascript = null;
+						$lang = null;
+						$length = null;
+						$medium = null;
+						$samplingrate = null;
+						$type = null;
+						$url = null;
+						$width = null;
+
+						// Elements
+						$captions = null;
+						$categories = null;
+						$copyrights = null;
+						$credits = null;
+						$description = null;
+						$hashes = null;
+						$keywords = null;
+						$player = null;
+						$ratings = null;
+						$restrictions = null;
+						$thumbnails = null;
+						$title = null;
+
+						// Start checking the attributes of media:content
+						if (isset($content['attribs']['']['bitrate']))
+						{
+							$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['channels']))
+						{
+							$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['duration']))
+						{
+							$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$duration = $duration_parent;
+						}
+						if (isset($content['attribs']['']['expression']))
+						{
+							$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['framerate']))
+						{
+							$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['height']))
+						{
+							$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['lang']))
+						{
+							$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['fileSize']))
+						{
+							$length = ceil($content['attribs']['']['fileSize']);
+						}
+						if (isset($content['attribs']['']['medium']))
+						{
+							$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['samplingrate']))
+						{
+							$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['type']))
+						{
+							$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['width']))
+						{
+							$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+
+						// Checking the other optional media: elements. Priority: media:content, media:group, item, channel
+
+						// CAPTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+							{
+								$caption_type = null;
+								$caption_lang = null;
+								$caption_startTime = null;
+								$caption_endTime = null;
+								$caption_text = null;
+								if (isset($caption['attribs']['']['type']))
+								{
+									$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['lang']))
+								{
+									$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['start']))
+								{
+									$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['end']))
+								{
+									$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['data']))
+								{
+									$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+							}
+							if (is_array($captions))
+							{
+								$captions = array_values(SimplePie_Misc::array_unique($captions));
+							}
+						}
+						else
+						{
+							$captions = $captions_parent;
+						}
+
+						// CATEGORIES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+						{
+							foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+							{
+								$term = null;
+								$scheme = null;
+								$label = null;
+								if (isset($category['data']))
+								{
+									$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($category['attribs']['']['scheme']))
+								{
+									$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$scheme = 'http://search.yahoo.com/mrss/category_schema';
+								}
+								if (isset($category['attribs']['']['label']))
+								{
+									$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$categories[] = new $this->feed->category_class($term, $scheme, $label);
+							}
+						}
+						if (is_array($categories) && is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent)));
+						}
+						elseif (is_array($categories))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories));
+						}
+						elseif (is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories_parent));
+						}
+						else
+						{
+							$categories = null;
+						}
+
+						// COPYRIGHTS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+						{
+							$copyright_url = null;
+							$copyright_label = null;
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+							{
+								$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+							{
+								$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+						}
+						else
+						{
+							$copyrights = $copyrights_parent;
+						}
+
+						// CREDITS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+							{
+								$credit_role = null;
+								$credit_scheme = null;
+								$credit_name = null;
+								if (isset($credit['attribs']['']['role']))
+								{
+									$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($credit['attribs']['']['scheme']))
+								{
+									$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$credit_scheme = 'urn:ebu';
+								}
+								if (isset($credit['data']))
+								{
+									$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+							}
+							if (is_array($credits))
+							{
+								$credits = array_values(SimplePie_Misc::array_unique($credits));
+							}
+						}
+						else
+						{
+							$credits = $credits_parent;
+						}
+
+						// DESCRIPTION
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+						{
+							$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$description = $description_parent;
+						}
+
+						// HASHES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+							{
+								$value = null;
+								$algo = null;
+								if (isset($hash['data']))
+								{
+									$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($hash['attribs']['']['algo']))
+								{
+									$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$algo = 'md5';
+								}
+								$hashes[] = $algo.':'.$value;
+							}
+							if (is_array($hashes))
+							{
+								$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+							}
+						}
+						else
+						{
+							$hashes = $hashes_parent;
+						}
+
+						// KEYWORDS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+						{
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+							{
+								$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+								foreach ($temp as $word)
+								{
+									$keywords[] = trim($word);
+								}
+								unset($temp);
+							}
+							if (is_array($keywords))
+							{
+								$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+							}
+						}
+						else
+						{
+							$keywords = $keywords_parent;
+						}
+
+						// PLAYER
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+						{
+							$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+						}
+						else
+						{
+							$player = $player_parent;
+						}
+
+						// RATINGS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+							{
+								$rating_scheme = null;
+								$rating_value = null;
+								if (isset($rating['attribs']['']['scheme']))
+								{
+									$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$rating_scheme = 'urn:simple';
+								}
+								if (isset($rating['data']))
+								{
+									$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+							}
+							if (is_array($ratings))
+							{
+								$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+							}
+						}
+						else
+						{
+							$ratings = $ratings_parent;
+						}
+
+						// RESTRICTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+							{
+								$restriction_relationship = null;
+								$restriction_type = null;
+								$restriction_value = null;
+								if (isset($restriction['attribs']['']['relationship']))
+								{
+									$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['attribs']['']['type']))
+								{
+									$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['data']))
+								{
+									$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+							}
+							if (is_array($restrictions))
+							{
+								$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+							}
+						}
+						else
+						{
+							$restrictions = $restrictions_parent;
+						}
+
+						// THUMBNAILS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+							{
+								$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							if (is_array($thumbnails))
+							{
+								$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+							}
+						}
+						else
+						{
+							$thumbnails = $thumbnails_parent;
+						}
+
+						// TITLES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+						{
+							$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$title = $title_parent;
+						}
+
+						$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width);
+					}
+				}
+			}
+
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					if (isset($link['attribs']['']['type']))
+					{
+						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($link['attribs']['']['length']))
+					{
+						$length = ceil($link['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					if (isset($link['attribs']['']['type']))
+					{
+						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($link['attribs']['']['length']))
+					{
+						$length = ceil($link['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure'))
+			{
+				if (isset($enclosure[0]['attribs']['']['url']))
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0]));
+					if (isset($enclosure[0]['attribs']['']['type']))
+					{
+						$type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($enclosure[0]['attribs']['']['length']))
+					{
+						$length = ceil($enclosure[0]['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width))
+			{
+				// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+				$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+			}
+
+			$this->data['enclosures'] = array_values(SimplePie_Misc::array_unique($this->data['enclosures']));
+		}
+		if (!empty($this->data['enclosures']))
+		{
+			return $this->data['enclosures'];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_latitude()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_longitude()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_source()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source'))
+		{
+			return new $this->feed->source_class($this, $return[0]);
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Source
+{
+	var $item;
+	var $data = array();
+
+	public function __construct($item, $data)
+	{
+		$this->item = $item;
+		$this->data = $data;
+	}
+
+	public function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	public function get_source_tags($namespace, $tag)
+	{
+		if (isset($this->data['child'][$namespace][$tag]))
+		{
+			return $this->data['child'][$namespace][$tag];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_base($element = array())
+	{
+		return $this->item->get_base($element);
+	}
+
+	public function sanitize($data, $type, $base = '')
+	{
+		return $this->item->sanitize($data, $type, $base);
+	}
+
+	public function get_item()
+	{
+		return $this->item;
+	}
+
+	public function get_title()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->item->feed->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->item->feed->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->item->feed->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->item->feed->author_class($name, $url, $email);
+			}
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->item->feed->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->item->feed->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if (isset($links[$key]))
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Added for parity between the parent-level and the item/entry-level.
+	 */
+	public function get_permalink()
+	{
+		return $this->get_link(0);
+	}
+
+	public function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					}
+				}
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+					}
+				}
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_description()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_copyright()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_language()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['xml_lang']))
+		{
+			return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_latitude()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_longitude()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_image_url()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
+		{
+			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Author
+{
+	var $name;
+	var $link;
+	var $email;
+
+	// Constructor, used to input the data
+	public function __construct($name = null, $link = null, $email = null)
+	{
+		$this->name = $name;
+		$this->link = $link;
+		$this->email = $email;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_name()
+	{
+		if ($this->name !== null)
+		{
+			return $this->name;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_link()
+	{
+		if ($this->link !== null)
+		{
+			return $this->link;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_email()
+	{
+		if ($this->email !== null)
+		{
+			return $this->email;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Category
+{
+	var $term;
+	var $scheme;
+	var $label;
+
+	// Constructor, used to input the data
+	public function __construct($term = null, $scheme = null, $label = null)
+	{
+		$this->term = $term;
+		$this->scheme = $scheme;
+		$this->label = $label;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_term()
+	{
+		if ($this->term !== null)
+		{
+			return $this->term;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_label()
+	{
+		if ($this->label !== null)
+		{
+			return $this->label;
+		}
+		else
+		{
+			return $this->get_term();
+		}
+	}
+}
+
+class SimplePie_Enclosure
+{
+	var $bitrate;
+	var $captions;
+	var $categories;
+	var $channels;
+	var $copyright;
+	var $credits;
+	var $description;
+	var $duration;
+	var $expression;
+	var $framerate;
+	var $handler;
+	var $hashes;
+	var $height;
+	var $javascript;
+	var $keywords;
+	var $lang;
+	var $length;
+	var $link;
+	var $medium;
+	var $player;
+	var $ratings;
+	var $restrictions;
+	var $samplingrate;
+	var $thumbnails;
+	var $title;
+	var $type;
+	var $width;
+
+	// Constructor, used to input the data
+	public function __construct($link = null, $type = null, $length = null, $javascript = null, $bitrate = null, $captions = null, $categories = null, $channels = null, $copyright = null, $credits = null, $description = null, $duration = null, $expression = null, $framerate = null, $hashes = null, $height = null, $keywords = null, $lang = null, $medium = null, $player = null, $ratings = null, $restrictions = null, $samplingrate = null, $thumbnails = null, $title = null, $width = null)
+	{
+		$this->bitrate = $bitrate;
+		$this->captions = $captions;
+		$this->categories = $categories;
+		$this->channels = $channels;
+		$this->copyright = $copyright;
+		$this->credits = $credits;
+		$this->description = $description;
+		$this->duration = $duration;
+		$this->expression = $expression;
+		$this->framerate = $framerate;
+		$this->hashes = $hashes;
+		$this->height = $height;
+		$this->javascript = $javascript;
+		$this->keywords = $keywords;
+		$this->lang = $lang;
+		$this->length = $length;
+		$this->link = $link;
+		$this->medium = $medium;
+		$this->player = $player;
+		$this->ratings = $ratings;
+		$this->restrictions = $restrictions;
+		$this->samplingrate = $samplingrate;
+		$this->thumbnails = $thumbnails;
+		$this->title = $title;
+		$this->type = $type;
+		$this->width = $width;
+
+		if (class_exists('idna_convert'))
+		{
+			$idn = new idna_convert();
+			$parsed = SimplePie_Misc::parse_url($link);
+			$this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
+		}
+		$this->handler = $this->get_handler(); // Needs to load last
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_bitrate()
+	{
+		if ($this->bitrate !== null)
+		{
+			return $this->bitrate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_caption($key = 0)
+	{
+		$captions = $this->get_captions();
+		if (isset($captions[$key]))
+		{
+			return $captions[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_captions()
+	{
+		if ($this->captions !== null)
+		{
+			return $this->captions;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_categories()
+	{
+		if ($this->categories !== null)
+		{
+			return $this->categories;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_channels()
+	{
+		if ($this->channels !== null)
+		{
+			return $this->channels;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_copyright()
+	{
+		if ($this->copyright !== null)
+		{
+			return $this->copyright;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_credit($key = 0)
+	{
+		$credits = $this->get_credits();
+		if (isset($credits[$key]))
+		{
+			return $credits[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_credits()
+	{
+		if ($this->credits !== null)
+		{
+			return $this->credits;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_description()
+	{
+		if ($this->description !== null)
+		{
+			return $this->description;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_duration($convert = false)
+	{
+		if ($this->duration !== null)
+		{
+			if ($convert)
+			{
+				$time = SimplePie_Misc::time_hms($this->duration);
+				return $time;
+			}
+			else
+			{
+				return $this->duration;
+			}
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_expression()
+	{
+		if ($this->expression !== null)
+		{
+			return $this->expression;
+		}
+		else
+		{
+			return 'full';
+		}
+	}
+
+	public function get_extension()
+	{
+		if ($this->link !== null)
+		{
+			$url = SimplePie_Misc::parse_url($this->link);
+			if ($url['path'] !== '')
+			{
+				return pathinfo($url['path'], PATHINFO_EXTENSION);
+			}
+		}
+		return null;
+	}
+
+	public function get_framerate()
+	{
+		if ($this->framerate !== null)
+		{
+			return $this->framerate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_handler()
+	{
+		return $this->get_real_type(true);
+	}
+
+	public function get_hash($key = 0)
+	{
+		$hashes = $this->get_hashes();
+		if (isset($hashes[$key]))
+		{
+			return $hashes[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_hashes()
+	{
+		if ($this->hashes !== null)
+		{
+			return $this->hashes;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_height()
+	{
+		if ($this->height !== null)
+		{
+			return $this->height;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_language()
+	{
+		if ($this->lang !== null)
+		{
+			return $this->lang;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_keyword($key = 0)
+	{
+		$keywords = $this->get_keywords();
+		if (isset($keywords[$key]))
+		{
+			return $keywords[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_keywords()
+	{
+		if ($this->keywords !== null)
+		{
+			return $this->keywords;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_length()
+	{
+		if ($this->length !== null)
+		{
+			return $this->length;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_link()
+	{
+		if ($this->link !== null)
+		{
+			return urldecode($this->link);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_medium()
+	{
+		if ($this->medium !== null)
+		{
+			return $this->medium;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_player()
+	{
+		if ($this->player !== null)
+		{
+			return $this->player;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_rating($key = 0)
+	{
+		$ratings = $this->get_ratings();
+		if (isset($ratings[$key]))
+		{
+			return $ratings[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_ratings()
+	{
+		if ($this->ratings !== null)
+		{
+			return $this->ratings;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_restriction($key = 0)
+	{
+		$restrictions = $this->get_restrictions();
+		if (isset($restrictions[$key]))
+		{
+			return $restrictions[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_restrictions()
+	{
+		if ($this->restrictions !== null)
+		{
+			return $this->restrictions;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_sampling_rate()
+	{
+		if ($this->samplingrate !== null)
+		{
+			return $this->samplingrate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_size()
+	{
+		$length = $this->get_length();
+		if ($length !== null)
+		{
+			return round($length/1048576, 2);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_thumbnail($key = 0)
+	{
+		$thumbnails = $this->get_thumbnails();
+		if (isset($thumbnails[$key]))
+		{
+			return $thumbnails[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_thumbnails()
+	{
+		if ($this->thumbnails !== null)
+		{
+			return $this->thumbnails;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_title()
+	{
+		if ($this->title !== null)
+		{
+			return $this->title;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_width()
+	{
+		if ($this->width !== null)
+		{
+			return $this->width;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function native_embed($options='')
+	{
+		return $this->embed($options, true);
+	}
+
+	/**
+	 * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'.
+	 */
+	public function embed($options = '', $native = false)
+	{
+		// Set up defaults
+		$audio = '';
+		$video = '';
+		$alt = '';
+		$altclass = '';
+		$loop = 'false';
+		$width = 'auto';
+		$height = 'auto';
+		$bgcolor = '#ffffff';
+		$mediaplayer = '';
+		$widescreen = false;
+		$handler = $this->get_handler();
+		$type = $this->get_real_type();
+
+		// Process options and reassign values as necessary
+		if (is_array($options))
+		{
+			extract($options);
+		}
+		else
+		{
+			$options = explode(',', $options);
+			foreach($options as $option)
+			{
+				$opt = explode(':', $option, 2);
+				if (isset($opt[0], $opt[1]))
+				{
+					$opt[0] = trim($opt[0]);
+					$opt[1] = trim($opt[1]);
+					switch ($opt[0])
+					{
+						case 'audio':
+							$audio = $opt[1];
+							break;
+
+						case 'video':
+							$video = $opt[1];
+							break;
+
+						case 'alt':
+							$alt = $opt[1];
+							break;
+
+						case 'altclass':
+							$altclass = $opt[1];
+							break;
+
+						case 'loop':
+							$loop = $opt[1];
+							break;
+
+						case 'width':
+							$width = $opt[1];
+							break;
+
+						case 'height':
+							$height = $opt[1];
+							break;
+
+						case 'bgcolor':
+							$bgcolor = $opt[1];
+							break;
+
+						case 'mediaplayer':
+							$mediaplayer = $opt[1];
+							break;
+
+						case 'widescreen':
+							$widescreen = $opt[1];
+							break;
+					}
+				}
+			}
+		}
+
+		$mime = explode('/', $type, 2);
+		$mime = $mime[0];
+
+		// Process values for 'auto'
+		if ($width === 'auto')
+		{
+			if ($mime === 'video')
+			{
+				if ($height === 'auto')
+				{
+					$width = 480;
+				}
+				elseif ($widescreen)
+				{
+					$width = round((intval($height)/9)*16);
+				}
+				else
+				{
+					$width = round((intval($height)/3)*4);
+				}
+			}
+			else
+			{
+				$width = '100%';
+			}
+		}
+
+		if ($height === 'auto')
+		{
+			if ($mime === 'audio')
+			{
+				$height = 0;
+			}
+			elseif ($mime === 'video')
+			{
+				if ($width === 'auto')
+				{
+					if ($widescreen)
+					{
+						$height = 270;
+					}
+					else
+					{
+						$height = 360;
+					}
+				}
+				elseif ($widescreen)
+				{
+					$height = round((intval($width)/16)*9);
+				}
+				else
+				{
+					$height = round((intval($width)/4)*3);
+				}
+			}
+			else
+			{
+				$height = 376;
+			}
+		}
+		elseif ($mime === 'audio')
+		{
+			$height = 0;
+		}
+
+		// Set proper placeholder value
+		if ($mime === 'audio')
+		{
+			$placeholder = $audio;
+		}
+		elseif ($mime === 'video')
+		{
+			$placeholder = $video;
+		}
+
+		$embed = '';
+
+		// Make sure the JS library is included
+		if (!$native)
+		{
+			static $javascript_outputted = null;
+			if (!$javascript_outputted && $this->javascript)
+			{
+				$embed .= '<script type="text/javascript" src="?' . htmlspecialchars($this->javascript) . '"></script>';
+				$javascript_outputted = true;
+			}
+		}
+
+		// Odeo Feed MP3's
+		if ($handler === 'odeo')
+		{
+			if ($native)
+			{
+				$embed .= '<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://adobe.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url=' . $this->get_link() . '"></embed>';
+			}
+			else
+			{
+				$embed .= '<script type="text/javascript">embed_odeo("' . $this->get_link() . '");</script>';
+			}
+		}
+
+		// Flash
+		elseif ($handler === 'flash')
+		{
+			if ($native)
+			{
+				$embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>";
+			}
+		}
+
+		// Flash Media Player file types.
+		// Preferred handler for MP3 file types.
+		elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== ''))
+		{
+			$height += 20;
+			if ($native)
+			{
+				$embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>";
+			}
+		}
+
+		// QuickTime 7 file types.  Need to test with QuickTime 6.
+		// Only handle MP3's if the Flash Media Player is not present.
+		elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === ''))
+		{
+			$height += 16;
+			if ($native)
+			{
+				if ($placeholder !== '')
+				{
+					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
+				}
+				else
+				{
+					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
+				}
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>";
+			}
+		}
+
+		// Windows Media
+		elseif ($handler === 'wmedia')
+		{
+			$height += 45;
+			if ($native)
+			{
+				$embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>";
+			}
+		}
+
+		// Everything else
+		else $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>';
+
+		return $embed;
+	}
+
+	public function get_real_type($find_handler = false)
+	{
+		// If it's Odeo, let's get it out of the way.
+		if (substr(strtolower($this->get_link()), 0, 15) === 'http://odeo.com')
+		{
+			return 'odeo';
+		}
+
+		// Mime-types by handler.
+		$types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash
+		$types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player
+		$types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime
+		$types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media
+		$types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3
+
+		if ($this->get_type() !== null)
+		{
+			$type = strtolower($this->type);
+		}
+		else
+		{
+			$type = null;
+		}
+
+		// If we encounter an unsupported mime-type, check the file extension and guess intelligently.
+		if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3)))
+		{
+			switch (strtolower($this->get_extension()))
+			{
+				// Audio mime-types
+				case 'aac':
+				case 'adts':
+					$type = 'audio/acc';
+					break;
+
+				case 'aif':
+				case 'aifc':
+				case 'aiff':
+				case 'cdda':
+					$type = 'audio/aiff';
+					break;
+
+				case 'bwf':
+					$type = 'audio/wav';
+					break;
+
+				case 'kar':
+				case 'mid':
+				case 'midi':
+				case 'smf':
+					$type = 'audio/midi';
+					break;
+
+				case 'm4a':
+					$type = 'audio/x-m4a';
+					break;
+
+				case 'mp3':
+				case 'swa':
+					$type = 'audio/mp3';
+					break;
+
+				case 'wav':
+					$type = 'audio/wav';
+					break;
+
+				case 'wax':
+					$type = 'audio/x-ms-wax';
+					break;
+
+				case 'wma':
+					$type = 'audio/x-ms-wma';
+					break;
+
+				// Video mime-types
+				case '3gp':
+				case '3gpp':
+					$type = 'video/3gpp';
+					break;
+
+				case '3g2':
+				case '3gp2':
+					$type = 'video/3gpp2';
+					break;
+
+				case 'asf':
+					$type = 'video/x-ms-asf';
+					break;
+
+				case 'flv':
+					$type = 'video/x-flv';
+					break;
+
+				case 'm1a':
+				case 'm1s':
+				case 'm1v':
+				case 'm15':
+				case 'm75':
+				case 'mp2':
+				case 'mpa':
+				case 'mpeg':
+				case 'mpg':
+				case 'mpm':
+				case 'mpv':
+					$type = 'video/mpeg';
+					break;
+
+				case 'm4v':
+					$type = 'video/x-m4v';
+					break;
+
+				case 'mov':
+				case 'qt':
+					$type = 'video/quicktime';
+					break;
+
+				case 'mp4':
+				case 'mpg4':
+					$type = 'video/mp4';
+					break;
+
+				case 'sdv':
+					$type = 'video/sd-video';
+					break;
+
+				case 'wm':
+					$type = 'video/x-ms-wm';
+					break;
+
+				case 'wmv':
+					$type = 'video/x-ms-wmv';
+					break;
+
+				case 'wvx':
+					$type = 'video/x-ms-wvx';
+					break;
+
+				// Flash mime-types
+				case 'spl':
+					$type = 'application/futuresplash';
+					break;
+
+				case 'swf':
+					$type = 'application/x-shockwave-flash';
+					break;
+			}
+		}
+
+		if ($find_handler)
+		{
+			if (in_array($type, $types_flash))
+			{
+				return 'flash';
+			}
+			elseif (in_array($type, $types_fmedia))
+			{
+				return 'fmedia';
+			}
+			elseif (in_array($type, $types_quicktime))
+			{
+				return 'quicktime';
+			}
+			elseif (in_array($type, $types_wmedia))
+			{
+				return 'wmedia';
+			}
+			elseif (in_array($type, $types_mp3))
+			{
+				return 'mp3';
+			}
+			else
+			{
+				return null;
+			}
+		}
+		else
+		{
+			return $type;
+		}
+	}
+}
+
+class SimplePie_Caption
+{
+	var $type;
+	var $lang;
+	var $startTime;
+	var $endTime;
+	var $text;
+
+	// Constructor, used to input the data
+	public function __construct($type = null, $lang = null, $startTime = null, $endTime = null, $text = null)
+	{
+		$this->type = $type;
+		$this->lang = $lang;
+		$this->startTime = $startTime;
+		$this->endTime = $endTime;
+		$this->text = $text;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_endtime()
+	{
+		if ($this->endTime !== null)
+		{
+			return $this->endTime;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_language()
+	{
+		if ($this->lang !== null)
+		{
+			return $this->lang;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_starttime()
+	{
+		if ($this->startTime !== null)
+		{
+			return $this->startTime;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_text()
+	{
+		if ($this->text !== null)
+		{
+			return $this->text;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Credit
+{
+	var $role;
+	var $scheme;
+	var $name;
+
+	// Constructor, used to input the data
+	public function __construct($role = null, $scheme = null, $name = null)
+	{
+		$this->role = $role;
+		$this->scheme = $scheme;
+		$this->name = $name;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_role()
+	{
+		if ($this->role !== null)
+		{
+			return $this->role;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_name()
+	{
+		if ($this->name !== null)
+		{
+			return $this->name;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Copyright
+{
+	var $url;
+	var $label;
+
+	// Constructor, used to input the data
+	public function __construct($url = null, $label = null)
+	{
+		$this->url = $url;
+		$this->label = $label;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_url()
+	{
+		if ($this->url !== null)
+		{
+			return $this->url;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_attribution()
+	{
+		if ($this->label !== null)
+		{
+			return $this->label;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Rating
+{
+	var $scheme;
+	var $value;
+
+	// Constructor, used to input the data
+	public function __construct($scheme = null, $value = null)
+	{
+		$this->scheme = $scheme;
+		$this->value = $value;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_value()
+	{
+		if ($this->value !== null)
+		{
+			return $this->value;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Restriction
+{
+	var $relationship;
+	var $type;
+	var $value;
+
+	// Constructor, used to input the data
+	public function __construct($relationship = null, $type = null, $value = null)
+	{
+		$this->relationship = $relationship;
+		$this->type = $type;
+		$this->value = $value;
+	}
+
+	public function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	public function get_relationship()
+	{
+		if ($this->relationship !== null)
+		{
+			return $this->relationship;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public function get_value()
+	{
+		if ($this->value !== null)
+		{
+			return $this->value;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+/**
+ * @todo Move to properly supporting RFC2616 (HTTP/1.1)
+ */
+class SimplePie_File
+{
+	var $url;
+	var $useragent;
+	var $success = true;
+	var $headers = array();
+	var $body;
+	var $status_code;
+	var $redirects = 0;
+	var $error;
+	var $method = SIMPLEPIE_FILE_SOURCE_NONE;
+
+	public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false)
+	{
+		if (class_exists('idna_convert'))
+		{
+			$idn = new idna_convert();
+			$parsed = SimplePie_Misc::parse_url($url);
+			$url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
+		}
+		$this->url = $url;
+		$this->useragent = $useragent;
+		if (preg_match('/^http(s)?:\/\//i', $url))
+		{
+			if ($useragent === null)
+			{
+				$useragent = ini_get('user_agent');
+				$this->useragent = $useragent;
+			}
+			if (!is_array($headers))
+			{
+				$headers = array();
+			}
+			if (!$force_fsockopen && function_exists('curl_exec'))
+			{
+				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL;
+				$fp = curl_init();
+				$headers2 = array();
+				foreach ($headers as $key => $value)
+				{
+					$headers2[] = "$key: $value";
+				}
+				if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>='))
+				{
+					curl_setopt($fp, CURLOPT_ENCODING, '');
+				}
+				curl_setopt($fp, CURLOPT_URL, $url);
+				curl_setopt($fp, CURLOPT_HEADER, 1);
+				curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1);
+				curl_setopt($fp, CURLOPT_TIMEOUT, $timeout);
+				curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout);
+				curl_setopt($fp, CURLOPT_REFERER, $url);
+				curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
+				curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
+				if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
+				{
+					curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
+					curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects);
+				}
+
+				$this->headers = curl_exec($fp);
+				if (curl_errno($fp) === 23 || curl_errno($fp) === 61)
+				{
+					curl_setopt($fp, CURLOPT_ENCODING, 'none');
+					$this->headers = curl_exec($fp);
+				}
+				if (curl_errno($fp))
+				{
+					$this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp);
+					$this->success = false;
+				}
+				else
+				{
+					$info = curl_getinfo($fp);
+					curl_close($fp);
+					$this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1);
+					$this->headers = array_pop($this->headers);
+					$parser = new SimplePie_HTTP_Parser($this->headers);
+					if ($parser->parse())
+					{
+						$this->headers = $parser->headers;
+						$this->body = $parser->body;
+						$this->status_code = $parser->status_code;
+						if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
+						{
+							$this->redirects++;
+							$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
+							return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+						}
+					}
+				}
+			}
+			else
+			{
+				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN;
+				$url_parts = parse_url($url);
+				if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https')
+				{
+					$url_parts['host'] = "ssl://$url_parts[host]";
+					$url_parts['port'] = 443;
+				}
+				if (!isset($url_parts['port']))
+				{
+					$url_parts['port'] = 80;
+				}
+				$fp = @fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout);
+				if (!$fp)
+				{
+					$this->error = 'fsockopen error: ' . $errstr;
+					$this->success = false;
+				}
+				else
+				{
+					stream_set_timeout($fp, $timeout);
+					if (isset($url_parts['path']))
+					{
+						if (isset($url_parts['query']))
+						{
+							$get = "$url_parts[path]?$url_parts[query]";
+						}
+						else
+						{
+							$get = $url_parts['path'];
+						}
+					}
+					else
+					{
+						$get = '/';
+					}
+					$out = "GET $get HTTP/1.0\r\n";
+					$out .= "Host: $url_parts[host]\r\n";
+					$out .= "User-Agent: $useragent\r\n";
+					if (extension_loaded('zlib'))
+					{
+						$out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n";
+					}
+
+					if (isset($url_parts['user']) && isset($url_parts['pass']))
+					{
+						$out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n";
+					}
+					foreach ($headers as $key => $value)
+					{
+						$out .= "$key: $value\r\n";
+					}
+					$out .= "Connection: Close\r\n\r\n";
+					fwrite($fp, $out);
+
+					$info = stream_get_meta_data($fp);
+
+					$this->headers = '';
+					while (!$info['eof'] && !$info['timed_out'])
+					{
+						$this->headers .= fread($fp, 1160);
+						$info = stream_get_meta_data($fp);
+					}
+					if (!$info['timed_out'])
+					{
+						$parser = new SimplePie_HTTP_Parser($this->headers);
+						if ($parser->parse())
+						{
+							$this->headers = $parser->headers;
+							$this->body = $parser->body;
+							$this->status_code = $parser->status_code;
+							if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
+							{
+								$this->redirects++;
+								$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
+								return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+							}
+							if (isset($this->headers['content-encoding']))
+							{
+								// Hey, we act dumb elsewhere, so let's do that here too
+								switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20")))
+								{
+									case 'gzip':
+									case 'x-gzip':
+										$decoder = new SimplePie_gzdecode($this->body);
+										if (!$decoder->parse())
+										{
+											$this->error = 'Unable to decode HTTP "gzip" stream';
+											$this->success = false;
+										}
+										else
+										{
+											$this->body = $decoder->data;
+										}
+										break;
+
+									case 'deflate':
+										if (($body = gzuncompress($this->body)) === false)
+										{
+											if (($body = gzinflate($this->body)) === false)
+											{
+												$this->error = 'Unable to decode HTTP "deflate" stream';
+												$this->success = false;
+											}
+										}
+										$this->body = $body;
+										break;
+
+									default:
+										$this->error = 'Unknown content coding';
+										$this->success = false;
+								}
+							}
+						}
+					}
+					else
+					{
+						$this->error = 'fsocket timed out';
+						$this->success = false;
+					}
+					fclose($fp);
+				}
+			}
+		}
+		else
+		{
+			$this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS;
+			if (!$this->body = file_get_contents($url))
+			{
+				$this->error = 'file_get_contents could not read the file';
+				$this->success = false;
+			}
+		}
+	}
+}
+
+/**
+ * HTTP Response Parser
+ *
+ * @package SimplePie
+ */
+class SimplePie_HTTP_Parser
+{
+	/**
+	 * HTTP Version
+	 *
+	 * @access public
+	 * @var float
+	 */
+	var $http_version = 0.0;
+
+	/**
+	 * Status code
+	 *
+	 * @access public
+	 * @var int
+	 */
+	var $status_code = 0;
+
+	/**
+	 * Reason phrase
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $reason = '';
+
+	/**
+	 * Key/value pairs of the headers
+	 *
+	 * @access public
+	 * @var array
+	 */
+	var $headers = array();
+
+	/**
+	 * Body of the response
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $body = '';
+
+	/**
+	 * Current state of the state machine
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $state = 'http_version';
+
+	/**
+	 * Input data
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Input data length (to avoid calling strlen() everytime this is needed)
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $data_length = 0;
+
+	/**
+	 * Current position of the pointer
+	 *
+	 * @var int
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Name of the hedaer currently being parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $name = '';
+
+	/**
+	 * Value of the hedaer currently being parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $value = '';
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	public function __construct($data)
+	{
+		$this->data = $data;
+		$this->data_length = strlen($this->data);
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return bool true on success, false on failure
+	 */
+	public function parse()
+	{
+		while ($this->state && $this->state !== 'emit' && $this->has_data())
+		{
+			$state = $this->state;
+			$this->$state();
+		}
+		$this->data = '';
+		if ($this->state === 'emit' || $this->state === 'body')
+		{
+			return true;
+		}
+		else
+		{
+			$this->http_version = '';
+			$this->status_code = '';
+			$this->reason = '';
+			$this->headers = array();
+			$this->body = '';
+			return false;
+		}
+	}
+
+	/**
+	 * Check whether there is data beyond the pointer
+	 *
+	 * @access private
+	 * @return bool true if there is further data, false if not
+	 */
+	public function has_data()
+	{
+		return (bool) ($this->position < $this->data_length);
+	}
+
+	/**
+	 * See if the next character is LWS
+	 *
+	 * @access private
+	 * @return bool true if the next character is LWS, false if not
+	 */
+	public function is_linear_whitespace()
+	{
+		return (bool) ($this->data[$this->position] === "\x09"
+			|| $this->data[$this->position] === "\x20"
+			|| ($this->data[$this->position] === "\x0A"
+				&& isset($this->data[$this->position + 1])
+				&& ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20")));
+	}
+
+	/**
+	 * Parse the HTTP version
+	 *
+	 * @access private
+	 */
+	public function http_version()
+	{
+		if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/')
+		{
+			$len = strspn($this->data, '0123456789.', 5);
+			$this->http_version = substr($this->data, 5, $len);
+			$this->position += 5 + $len;
+			if (substr_count($this->http_version, '.') <= 1)
+			{
+				$this->http_version = (float) $this->http_version;
+				$this->position += strspn($this->data, "\x09\x20", $this->position);
+				$this->state = 'status';
+			}
+			else
+			{
+				$this->state = false;
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse the status code
+	 *
+	 * @access private
+	 */
+	public function status()
+	{
+		if ($len = strspn($this->data, '0123456789', $this->position))
+		{
+			$this->status_code = (int) substr($this->data, $this->position, $len);
+			$this->position += $len;
+			$this->state = 'reason';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse the reason phrase
+	 *
+	 * @access private
+	 */
+	public function reason()
+	{
+		$len = strcspn($this->data, "\x0A", $this->position);
+		$this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20");
+		$this->position += $len + 1;
+		$this->state = 'new_line';
+	}
+
+	/**
+	 * Deal with a new line, shifting data around as needed
+	 *
+	 * @access private
+	 */
+	public function new_line()
+	{
+		$this->value = trim($this->value, "\x0D\x20");
+		if ($this->name !== '' && $this->value !== '')
+		{
+			$this->name = strtolower($this->name);
+			if (isset($this->headers[$this->name]))
+			{
+				$this->headers[$this->name] .= ', ' . $this->value;
+			}
+			else
+			{
+				$this->headers[$this->name] = $this->value;
+			}
+		}
+		$this->name = '';
+		$this->value = '';
+		if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A")
+		{
+			$this->position += 2;
+			$this->state = 'body';
+		}
+		elseif ($this->data[$this->position] === "\x0A")
+		{
+			$this->position++;
+			$this->state = 'body';
+		}
+		else
+		{
+			$this->state = 'name';
+		}
+	}
+
+	/**
+	 * Parse a header name
+	 *
+	 * @access private
+	 */
+	public function name()
+	{
+		$len = strcspn($this->data, "\x0A:", $this->position);
+		if (isset($this->data[$this->position + $len]))
+		{
+			if ($this->data[$this->position + $len] === "\x0A")
+			{
+				$this->position += $len;
+				$this->state = 'new_line';
+			}
+			else
+			{
+				$this->name = substr($this->data, $this->position, $len);
+				$this->position += $len + 1;
+				$this->state = 'value';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse LWS, replacing consecutive LWS characters with a single space
+	 *
+	 * @access private
+	 */
+	public function linear_whitespace()
+	{
+		do
+		{
+			if (substr($this->data, $this->position, 2) === "\x0D\x0A")
+			{
+				$this->position += 2;
+			}
+			elseif ($this->data[$this->position] === "\x0A")
+			{
+				$this->position++;
+			}
+			$this->position += strspn($this->data, "\x09\x20", $this->position);
+		} while ($this->has_data() && $this->is_linear_whitespace());
+		$this->value .= "\x20";
+	}
+
+	/**
+	 * See what state to move to while within non-quoted header values
+	 *
+	 * @access private
+	 */
+	public function value()
+	{
+		if ($this->is_linear_whitespace())
+		{
+			$this->linear_whitespace();
+		}
+		else
+		{
+			switch ($this->data[$this->position])
+			{
+				case '"':
+					$this->position++;
+					$this->state = 'quote';
+					break;
+
+				case "\x0A":
+					$this->position++;
+					$this->state = 'new_line';
+					break;
+
+				default:
+					$this->state = 'value_char';
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Parse a header value while outside quotes
+	 *
+	 * @access private
+	 */
+	public function value_char()
+	{
+		$len = strcspn($this->data, "\x09\x20\x0A\"", $this->position);
+		$this->value .= substr($this->data, $this->position, $len);
+		$this->position += $len;
+		$this->state = 'value';
+	}
+
+	/**
+	 * See what state to move to while within quoted header values
+	 *
+	 * @access private
+	 */
+	public function quote()
+	{
+		if ($this->is_linear_whitespace())
+		{
+			$this->linear_whitespace();
+		}
+		else
+		{
+			switch ($this->data[$this->position])
+			{
+				case '"':
+					$this->position++;
+					$this->state = 'value';
+					break;
+
+				case "\x0A":
+					$this->position++;
+					$this->state = 'new_line';
+					break;
+
+				case '\\':
+					$this->position++;
+					$this->state = 'quote_escaped';
+					break;
+
+				default:
+					$this->state = 'quote_char';
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Parse a header value while within quotes
+	 *
+	 * @access private
+	 */
+	public function quote_char()
+	{
+		$len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position);
+		$this->value .= substr($this->data, $this->position, $len);
+		$this->position += $len;
+		$this->state = 'value';
+	}
+
+	/**
+	 * Parse an escaped character within quotes
+	 *
+	 * @access private
+	 */
+	public function quote_escaped()
+	{
+		$this->value .= $this->data[$this->position];
+		$this->position++;
+		$this->state = 'quote';
+	}
+
+	/**
+	 * Parse the body
+	 *
+	 * @access private
+	 */
+	public function body()
+	{
+		$this->body = substr($this->data, $this->position);
+		$this->state = 'emit';
+	}
+}
+
+/**
+ * gzdecode
+ *
+ * @package SimplePie
+ */
+class SimplePie_gzdecode
+{
+	/**
+	 * Compressed data
+	 *
+	 * @access private
+	 * @see gzdecode::$data
+	 */
+	var $compressed_data;
+
+	/**
+	 * Size of compressed data
+	 *
+	 * @access private
+	 */
+	var $compressed_size;
+
+	/**
+	 * Minimum size of a valid gzip string
+	 *
+	 * @access private
+	 */
+	var $min_compressed_size = 18;
+
+	/**
+	 * Current position of pointer
+	 *
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Flags (FLG)
+	 *
+	 * @access private
+	 */
+	var $flags;
+
+	/**
+	 * Uncompressed data
+	 *
+	 * @access public
+	 * @see gzdecode::$compressed_data
+	 */
+	var $data;
+
+	/**
+	 * Modified time
+	 *
+	 * @access public
+	 */
+	var $MTIME;
+
+	/**
+	 * Extra Flags
+	 *
+	 * @access public
+	 */
+	var $XFL;
+
+	/**
+	 * Operating System
+	 *
+	 * @access public
+	 */
+	var $OS;
+
+	/**
+	 * Subfield ID 1
+	 *
+	 * @access public
+	 * @see gzdecode::$extra_field
+	 * @see gzdecode::$SI2
+	 */
+	var $SI1;
+
+	/**
+	 * Subfield ID 2
+	 *
+	 * @access public
+	 * @see gzdecode::$extra_field
+	 * @see gzdecode::$SI1
+	 */
+	var $SI2;
+
+	/**
+	 * Extra field content
+	 *
+	 * @access public
+	 * @see gzdecode::$SI1
+	 * @see gzdecode::$SI2
+	 */
+	var $extra_field;
+
+	/**
+	 * Original filename
+	 *
+	 * @access public
+	 */
+	var $filename;
+
+	/**
+	 * Human readable comment
+	 *
+	 * @access public
+	 */
+	var $comment;
+
+	/**
+	 * Don't allow anything to be set
+	 *
+	 * @access public
+	 */
+	public function __set($name, $value)
+	{
+		trigger_error("Cannot write property $name", E_USER_ERROR);
+	}
+
+	/**
+	 * Set the compressed string and related properties
+	 *
+	 * @access public
+	 */
+	public function __construct($data)
+	{
+		$this->compressed_data = $data;
+		$this->compressed_size = strlen($data);
+	}
+
+	/**
+	 * Decode the GZIP stream
+	 *
+	 * @access public
+	 */
+	public function parse()
+	{
+		if ($this->compressed_size >= $this->min_compressed_size)
+		{
+			// Check ID1, ID2, and CM
+			if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08")
+			{
+				return false;
+			}
+
+			// Get the FLG (FLaGs)
+			$this->flags = ord($this->compressed_data[3]);
+
+			// FLG bits above (1 << 4) are reserved
+			if ($this->flags > 0x1F)
+			{
+				return false;
+			}
+
+			// Advance the pointer after the above
+			$this->position += 4;
+
+			// MTIME
+			$mtime = substr($this->compressed_data, $this->position, 4);
+			// Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness
+			if (current(unpack('S', "\x00\x01")) === 1)
+			{
+				$mtime = strrev($mtime);
+			}
+			$this->MTIME = current(unpack('l', $mtime));
+			$this->position += 4;
+
+			// Get the XFL (eXtra FLags)
+			$this->XFL = ord($this->compressed_data[$this->position++]);
+
+			// Get the OS (Operating System)
+			$this->OS = ord($this->compressed_data[$this->position++]);
+
+			// Parse the FEXTRA
+			if ($this->flags & 4)
+			{
+				// Read subfield IDs
+				$this->SI1 = $this->compressed_data[$this->position++];
+				$this->SI2 = $this->compressed_data[$this->position++];
+
+				// SI2 set to zero is reserved for future use
+				if ($this->SI2 === "\x00")
+				{
+					return false;
+				}
+
+				// Get the length of the extra field
+				$len = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+				$position += 2;
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 4;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the extra field to the given data
+					$this->extra_field = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FNAME
+			if ($this->flags & 8)
+			{
+				// Get the length of the filename
+				$len = strcspn($this->compressed_data, "\x00", $this->position);
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 1;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the original filename to the given string
+					$this->filename = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len + 1;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FCOMMENT
+			if ($this->flags & 16)
+			{
+				// Get the length of the comment
+				$len = strcspn($this->compressed_data, "\x00", $this->position);
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 1;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the original comment to the given string
+					$this->comment = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len + 1;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FHCRC
+			if ($this->flags & 2)
+			{
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 2;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Read the CRC
+					$crc = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+
+					// Check the CRC matches
+					if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc)
+					{
+						$this->position += 2;
+					}
+					else
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Decompress the actual data
+			if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false)
+			{
+				return false;
+			}
+			else
+			{
+				$this->position = $this->compressed_size - 8;
+			}
+
+			// Check CRC of data
+			$crc = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+			$this->position += 4;
+			/*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc))
+			{
+				return false;
+			}*/
+
+			// Check ISIZE of data
+			$isize = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+			$this->position += 4;
+			if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize))
+			{
+				return false;
+			}
+
+			// Wow, against all odds, we've actually got a valid gzip string
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+class SimplePie_Cache
+{
+	/**
+	 * Don't call the constructor. Please.
+	 *
+	 * @access private
+	 */
+	private function __construct()
+	{
+		trigger_error('Please call SimplePie_Cache::create() instead of the constructor', E_USER_ERROR);
+	}
+
+	/**
+	 * Create a new SimplePie_Cache object
+	 *
+	 * @static
+	 * @access public
+	 */
+	public static function create($location, $filename, $extension)
+	{
+		$location_iri = new SimplePie_IRI($location);
+		switch ($location_iri->get_scheme())
+		{
+			case 'mysql':
+				if (extension_loaded('mysql'))
+				{
+					return new SimplePie_Cache_MySQL($location_iri, $filename, $extension);
+				}
+				break;
+
+			default:
+				return new SimplePie_Cache_File($location, $filename, $extension);
+		}
+	}
+}
+
+class SimplePie_Cache_File
+{
+	var $location;
+	var $filename;
+	var $extension;
+	var $name;
+
+	public function __construct($location, $filename, $extension)
+	{
+		$this->location = $location;
+		$this->filename = $filename;
+		$this->extension = $extension;
+		$this->name = "$this->location/$this->filename.$this->extension";
+	}
+
+	public function save($data)
+	{
+		if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location))
+		{
+			//if (is_a($data, 'SimplePie'))
+			if ($data instanceof SimplePie)
+			{
+				$data = $data->data;
+			}
+
+			$data = serialize($data);
+
+			if (function_exists('file_put_contents'))
+			{
+				return (bool) file_put_contents($this->name, $data);
+			}
+			else
+			{
+				$fp = fopen($this->name, 'wb');
+				if ($fp)
+				{
+					fwrite($fp, $data);
+					fclose($fp);
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	public function load()
+	{
+		if (file_exists($this->name) && is_readable($this->name))
+		{
+			return unserialize(file_get_contents($this->name));
+		}
+		return false;
+	}
+
+	public function mtime()
+	{
+		if (file_exists($this->name))
+		{
+			return filemtime($this->name);
+		}
+		return false;
+	}
+
+	public function touch()
+	{
+		if (file_exists($this->name))
+		{
+			return touch($this->name);
+		}
+		return false;
+	}
+
+	public function unlink()
+	{
+		if (file_exists($this->name))
+		{
+			return unlink($this->name);
+		}
+		return false;
+	}
+}
+
+class SimplePie_Cache_DB
+{
+	public function prepare_simplepie_object_for_cache($data)
+	{
+		$items = $data->get_items();
+		$items_by_id = array();
+
+		if (!empty($items))
+		{
+			foreach ($items as $item)
+			{
+				$items_by_id[$item->get_id()] = $item;
+			}
+
+			if (count($items_by_id) !== count($items))
+			{
+				$items_by_id = array();
+				foreach ($items as $item)
+				{
+					$items_by_id[$item->get_id(true)] = $item;
+				}
+			}
+
+			if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0];
+			}
+			else
+			{
+				$channel = null;
+			}
+
+			if ($channel !== null)
+			{
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']);
+				}
+			}
+			if (isset($data->data['items']))
+			{
+				unset($data->data['items']);
+			}
+			if (isset($data->data['ordered_items']))
+			{
+				unset($data->data['ordered_items']);
+			}
+		}
+		return array(serialize($data->data), $items_by_id);
+	}
+}
+
+class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
+{
+	var $mysql;
+	var $options;
+	var $id;
+
+	public function __construct($mysql_location, $name, $extension)
+	{
+		$host = $mysql_location->get_host();
+		if (SimplePie_Misc::stripos($host, 'unix(') === 0 && substr($host, -1) === ')')
+		{
+			$server = ':' . substr($host, 5, -1);
+		}
+		else
+		{
+			$server = $host;
+			if ($mysql_location->get_port() !== null)
+			{
+				$server .= ':' . $mysql_location->get_port();
+			}
+		}
+
+		if (strpos($mysql_location->get_userinfo(), ':') !== false)
+		{
+			list($username, $password) = explode(':', $mysql_location->get_userinfo(), 2);
+		}
+		else
+		{
+			$username = $mysql_location->get_userinfo();
+			$password = null;
+		}
+
+		if ($this->mysql = mysql_connect($server, $username, $password))
+		{
+			$this->id = $name . $extension;
+			$this->options = SimplePie_Misc::parse_str($mysql_location->get_query());
+			if (!isset($this->options['prefix'][0]))
+			{
+				$this->options['prefix'][0] = '';
+			}
+
+			if (mysql_select_db(ltrim($mysql_location->get_path(), '/'))
+				&& mysql_query('SET NAMES utf8')
+				&& ($query = mysql_unbuffered_query('SHOW TABLES')))
+			{
+				$db = array();
+				while ($row = mysql_fetch_row($query))
+				{
+					$db[] = $row[0];
+				}
+
+				if (!in_array($this->options['prefix'][0] . 'cache_data', $db))
+				{
+					if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'))
+					{
+						$this->mysql = null;
+					}
+				}
+
+				if (!in_array($this->options['prefix'][0] . 'items', $db))
+				{
+					if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'))
+					{
+						$this->mysql = null;
+					}
+				}
+			}
+			else
+			{
+				$this->mysql = null;
+			}
+		}
+	}
+
+	public function save($data)
+	{
+		if ($this->mysql)
+		{
+			$feed_id = "'" . mysql_real_escape_string($this->id) . "'";
+
+			//if (is_a($data, 'SimplePie'))
+			if ($data instanceof SimplePie)
+			{
+				if (SIMPLEPIE_PHP5)
+				{
+					// This keyword needs to defy coding standards for PHP4 compatibility
+					$data = clone($data);
+				}
+
+				$prepared = $this->prepare_simplepie_object_for_cache($data);
+
+				if ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql))
+				{
+					if (mysql_num_rows($query))
+					{
+						$items = count($prepared[1]);
+						if ($items)
+						{
+							$sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = ' . $items . ', `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id;
+						}
+						else
+						{
+							$sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id;
+						}
+
+						if (!mysql_query($sql, $this->mysql))
+						{
+							return false;
+						}
+					}
+					elseif (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(' . $feed_id . ', ' . count($prepared[1]) . ', \'' . mysql_real_escape_string($prepared[0]) . '\', ' . time() . ')', $this->mysql))
+					{
+						return false;
+					}
+
+					$ids = array_keys($prepared[1]);
+					if (!empty($ids))
+					{
+						foreach ($ids as $id)
+						{
+							$database_ids[] = mysql_real_escape_string($id);
+						}
+
+						if ($query = mysql_unbuffered_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'items` WHERE `id` = \'' . implode('\' OR `id` = \'', $database_ids) . '\' AND `feed_id` = ' . $feed_id, $this->mysql))
+						{
+							$existing_ids = array();
+							while ($row = mysql_fetch_row($query))
+							{
+								$existing_ids[] = $row[0];
+							}
+
+							$new_ids = array_diff($ids, $existing_ids);
+
+							foreach ($new_ids as $new_id)
+							{
+								if (!($date = $prepared[1][$new_id]->get_date('U')))
+								{
+									$date = time();
+								}
+
+								if (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(' . $feed_id . ', \'' . mysql_real_escape_string($new_id) . '\', \'' . mysql_real_escape_string(serialize($prepared[1][$new_id]->data)) . '\', ' . $date . ')', $this->mysql))
+								{
+									return false;
+								}
+							}
+							return true;
+						}
+					}
+					else
+					{
+						return true;
+					}
+				}
+			}
+			elseif ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql))
+			{
+				if (mysql_num_rows($query))
+				{
+					if (mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = 0, `data` = \'' . mysql_real_escape_string(serialize($data)) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id, $this->mysql))
+					{
+						return true;
+					}
+				}
+				elseif (mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(\'' . mysql_real_escape_string($this->id) . '\', 0, \'' . mysql_real_escape_string(serialize($data)) . '\', ' . time() . ')', $this->mysql))
+				{
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	public function load()
+	{
+		if ($this->mysql && ($query = mysql_query('SELECT `items`, `data` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query)))
+		{
+			$data = unserialize($row[1]);
+
+			if (isset($this->options['items'][0]))
+			{
+				$items = (int) $this->options['items'][0];
+			}
+			else
+			{
+				$items = (int) $row[0];
+			}
+
+			if ($items !== 0)
+			{
+				if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
+				}
+				else
+				{
+					$feed = null;
+				}
+
+				if ($feed !== null)
+				{
+					$sql = 'SELECT `data` FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . '\' ORDER BY `posted` DESC';
+					if ($items > 0)
+					{
+						$sql .= ' LIMIT ' . $items;
+					}
+
+					if ($query = mysql_unbuffered_query($sql, $this->mysql))
+					{
+						while ($row = mysql_fetch_row($query))
+						{
+							$feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row[0]);
+						}
+					}
+					else
+					{
+						return false;
+					}
+				}
+			}
+			return $data;
+		}
+		return false;
+	}
+
+	public function mtime()
+	{
+		if ($this->mysql && ($query = mysql_query('SELECT `mtime` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query)))
+		{
+			return $row[0];
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	public function touch()
+	{
+		if ($this->mysql && ($query = mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `mtime` = ' . time() . ' WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && mysql_affected_rows($this->mysql))
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	public function unlink()
+	{
+		if ($this->mysql && ($query = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($query2 = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)))
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+class SimplePie_Misc
+{
+	public static function time_hms($seconds)
+	{
+		$time = '';
+
+		$hours = floor($seconds / 3600);
+		$remainder = $seconds % 3600;
+		if ($hours > 0)
+		{
+			$time .= $hours.':';
+		}
+
+		$minutes = floor($remainder / 60);
+		$seconds = $remainder % 60;
+		if ($minutes < 10 && $hours > 0)
+		{
+			$minutes = '0' . $minutes;
+		}
+		if ($seconds < 10)
+		{
+			$seconds = '0' . $seconds;
+		}
+
+		$time .= $minutes.':';
+		$time .= $seconds;
+
+		return $time;
+	}
+
+	public static function absolutize_url($relative, $base)
+	{
+		$iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative);
+		return $iri->get_iri();
+	}
+
+	public static function remove_dot_segments($input)
+	{
+		$output = '';
+		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
+		{
+			// A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
+			if (strpos($input, '../') === 0)
+			{
+				$input = substr($input, 3);
+			}
+			elseif (strpos($input, './') === 0)
+			{
+				$input = substr($input, 2);
+			}
+			// B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
+			elseif (strpos($input, '/./') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 3);
+			}
+			elseif ($input === '/.')
+			{
+				$input = '/';
+			}
+			// C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
+			elseif (strpos($input, '/../') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 4);
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			elseif ($input === '/..')
+			{
+				$input = '/';
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			// D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
+			elseif ($input === '.' || $input === '..')
+			{
+				$input = '';
+			}
+			// E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer
+			elseif (($pos = strpos($input, '/', 1)) !== false)
+			{
+				$output .= substr($input, 0, $pos);
+				$input = substr_replace($input, '', 0, $pos);
+			}
+			else
+			{
+				$output .= $input;
+				$input = '';
+			}
+		}
+		return $output . $input;
+	}
+
+	public static function get_element($realname, $string)
+	{
+		$return = array();
+		$name = preg_quote($realname, '/');
+		if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
+		{
+			for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++)
+			{
+				$return[$i]['tag'] = $realname;
+				$return[$i]['full'] = $matches[$i][0][0];
+				$return[$i]['offset'] = $matches[$i][0][1];
+				if (strlen($matches[$i][3][0]) <= 2)
+				{
+					$return[$i]['self_closing'] = true;
+				}
+				else
+				{
+					$return[$i]['self_closing'] = false;
+					$return[$i]['content'] = $matches[$i][4][0];
+				}
+				$return[$i]['attribs'] = array();
+				if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER))
+				{
+					for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++)
+					{
+						if (count($attribs[$j]) === 2)
+						{
+							$attribs[$j][2] = $attribs[$j][1];
+						}
+						$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
+					}
+				}
+			}
+		}
+		return $return;
+	}
+
+	public static function element_implode($element)
+	{
+		$full = "<$element[tag]";
+		foreach ($element['attribs'] as $key => $value)
+		{
+			$key = strtolower($key);
+			$full .= " $key=\"" . htmlspecialchars($value['data']) . '"';
+		}
+		if ($element['self_closing'])
+		{
+			$full .= ' />';
+		}
+		else
+		{
+			$full .= ">$element[content]</$element[tag]>";
+		}
+		return $full;
+	}
+
+	public static function error($message, $level, $file, $line)
+	{
+		if ((ini_get('error_reporting') & $level) > 0)
+		{
+			switch ($level)
+			{
+				case E_USER_ERROR:
+					$note = 'PHP Error';
+					break;
+				case E_USER_WARNING:
+					$note = 'PHP Warning';
+					break;
+				case E_USER_NOTICE:
+					$note = 'PHP Notice';
+					break;
+				default:
+					$note = 'Unknown Error';
+					break;
+			}
+
+			$log_error = true;
+			if (!function_exists('error_log'))
+			{
+				$log_error = false;
+			}
+
+			$log_file = @ini_get('error_log');
+			if (!empty($log_file) && ('syslog' != $log_file) && !@is_writable($log_file))
+			{
+				$log_error = false;
+			}
+
+			if ($log_error)
+			{
+				@error_log("$note: $message in $file on line $line", 0);
+			}
+		}
+
+		return $message;
+	}
+
+	public static function fix_protocol($url, $http = 1)
+	{
+		$url = SimplePie_Misc::normalize_url($url);
+		$parsed = SimplePie_Misc::parse_url($url);
+		if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https')
+		{
+			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http);
+		}
+
+		if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url))
+		{
+			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http);
+		}
+
+		if ($http === 2 && $parsed['scheme'] !== '')
+		{
+			return "feed:$url";
+		}
+		elseif ($http === 3 && strtolower($parsed['scheme']) === 'http')
+		{
+			return substr_replace($url, 'podcast', 0, 4);
+		}
+		elseif ($http === 4 && strtolower($parsed['scheme']) === 'http')
+		{
+			return substr_replace($url, 'itpc', 0, 4);
+		}
+		else
+		{
+			return $url;
+		}
+	}
+
+	public static function parse_url($url)
+	{
+		$iri = new SimplePie_IRI($url);
+		return array(
+			'scheme' => (string) $iri->get_scheme(),
+			'authority' => (string) $iri->get_authority(),
+			'path' => (string) $iri->get_path(),
+			'query' => (string) $iri->get_query(),
+			'fragment' => (string) $iri->get_fragment()
+		);
+	}
+
+	public static function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '')
+	{
+		$iri = new SimplePie_IRI('');
+		$iri->set_scheme($scheme);
+		$iri->set_authority($authority);
+		$iri->set_path($path);
+		$iri->set_query($query);
+		$iri->set_fragment($fragment);
+		return $iri->get_iri();
+	}
+
+	public static function normalize_url($url)
+	{
+		$iri = new SimplePie_IRI($url);
+		return $iri->get_iri();
+	}
+
+	public static function percent_encoding_normalization($match)
+	{
+		$integer = hexdec($match[1]);
+		if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E)
+		{
+			return chr($integer);
+		}
+		else
+		{
+			return strtoupper($match[0]);
+		}
+	}
+
+	/**
+	 * Converts a Windows-1252 encoded string to a UTF-8 encoded string
+	 *
+	 * @static
+	 * @access public
+	 * @param string $string Windows-1252 encoded string
+	 * @return string UTF-8 encoded string
+	 */
+	public static function windows_1252_to_utf8($string)
+	{
+		static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF");
+
+		return strtr($string, $convert_table);
+	}
+
+	public static function change_encoding($data, $input, $output)
+	{
+		$input = SimplePie_Misc::encoding($input);
+		$output = SimplePie_Misc::encoding($output);
+
+		// We fail to fail on non US-ASCII bytes
+		if ($input === 'US-ASCII')
+		{
+			static $non_ascii_octects = '';
+			if (!$non_ascii_octects)
+			{
+				for ($i = 0x80; $i <= 0xFF; $i++)
+				{
+					$non_ascii_octects .= chr($i);
+				}
+			}
+			$data = substr($data, 0, strcspn($data, $non_ascii_octects));
+		}
+
+		// This is first, as behaviour of this is completely predictable
+		if ($input === 'Windows-1252' && $output === 'UTF-8')
+		{
+			return SimplePie_Misc::windows_1252_to_utf8($data);
+		}
+		// This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported).
+		elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("\x80", 'UTF-16BE', $input) !== "\x00\x80" && ($return = @mb_convert_encoding($data, $output, $input)))
+		{
+			return $return;
+		}
+		// This is last, as behaviour of this varies with OS userland and PHP version
+		elseif (function_exists('iconv') && ($return = @iconv($input, $output, $data)))
+		{
+			return $return;
+		}
+		// If we can't do anything, just fail
+		else
+		{
+			return false;
+		}
+	}
+
+	public static function encoding($charset)
+	{
+		// Normalization from UTS #22
+		switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset)))
+		{
+			case 'adobestandardencoding':
+			case 'csadobestandardencoding':
+				return 'Adobe-Standard-Encoding';
+
+			case 'adobesymbolencoding':
+			case 'cshppsmath':
+				return 'Adobe-Symbol-Encoding';
+
+			case 'ami1251':
+			case 'amiga1251':
+				return 'Amiga-1251';
+
+			case 'ansix31101983':
+			case 'csat5001983':
+			case 'csiso99naplps':
+			case 'isoir99':
+			case 'naplps':
+				return 'ANSI_X3.110-1983';
+
+			case 'arabic7':
+			case 'asmo449':
+			case 'csiso89asmo449':
+			case 'iso9036':
+			case 'isoir89':
+				return 'ASMO_449';
+
+			case 'big5':
+			case 'csbig5':
+			case 'xxbig5':
+				return 'Big5';
+
+			case 'big5hkscs':
+				return 'Big5-HKSCS';
+
+			case 'bocu1':
+			case 'csbocu1':
+				return 'BOCU-1';
+
+			case 'brf':
+			case 'csbrf':
+				return 'BRF';
+
+			case 'bs4730':
+			case 'csiso4unitedkingdom':
+			case 'gb':
+			case 'iso646gb':
+			case 'isoir4':
+			case 'uk':
+				return 'BS_4730';
+
+			case 'bsviewdata':
+			case 'csiso47bsviewdata':
+			case 'isoir47':
+				return 'BS_viewdata';
+
+			case 'cesu8':
+			case 'cscesu8':
+				return 'CESU-8';
+
+			case 'ca':
+			case 'csa71':
+			case 'csaz243419851':
+			case 'csiso121canadian1':
+			case 'iso646ca':
+			case 'isoir121':
+				return 'CSA_Z243.4-1985-1';
+
+			case 'csa72':
+			case 'csaz243419852':
+			case 'csiso122canadian2':
+			case 'iso646ca2':
+			case 'isoir122':
+				return 'CSA_Z243.4-1985-2';
+
+			case 'csaz24341985gr':
+			case 'csiso123csaz24341985gr':
+			case 'isoir123':
+				return 'CSA_Z243.4-1985-gr';
+
+			case 'csiso139csn369103':
+			case 'csn369103':
+			case 'isoir139':
+				return 'CSN_369103';
+
+			case 'csdecmcs':
+			case 'dec':
+			case 'decmcs':
+				return 'DEC-MCS';
+
+			case 'csiso21german':
+			case 'de':
+			case 'din66003':
+			case 'iso646de':
+			case 'isoir21':
+				return 'DIN_66003';
+
+			case 'csdkus':
+			case 'dkus':
+				return 'dk-us';
+
+			case 'csiso646danish':
+			case 'dk':
+			case 'ds2089':
+			case 'iso646dk':
+				return 'DS_2089';
+
+			case 'csibmebcdicatde':
+			case 'ebcdicatde':
+				return 'EBCDIC-AT-DE';
+
+			case 'csebcdicatdea':
+			case 'ebcdicatdea':
+				return 'EBCDIC-AT-DE-A';
+
+			case 'csebcdiccafr':
+			case 'ebcdiccafr':
+				return 'EBCDIC-CA-FR';
+
+			case 'csebcdicdkno':
+			case 'ebcdicdkno':
+				return 'EBCDIC-DK-NO';
+
+			case 'csebcdicdknoa':
+			case 'ebcdicdknoa':
+				return 'EBCDIC-DK-NO-A';
+
+			case 'csebcdices':
+			case 'ebcdices':
+				return 'EBCDIC-ES';
+
+			case 'csebcdicesa':
+			case 'ebcdicesa':
+				return 'EBCDIC-ES-A';
+
+			case 'csebcdicess':
+			case 'ebcdicess':
+				return 'EBCDIC-ES-S';
+
+			case 'csebcdicfise':
+			case 'ebcdicfise':
+				return 'EBCDIC-FI-SE';
+
+			case 'csebcdicfisea':
+			case 'ebcdicfisea':
+				return 'EBCDIC-FI-SE-A';
+
+			case 'csebcdicfr':
+			case 'ebcdicfr':
+				return 'EBCDIC-FR';
+
+			case 'csebcdicit':
+			case 'ebcdicit':
+				return 'EBCDIC-IT';
+
+			case 'csebcdicpt':
+			case 'ebcdicpt':
+				return 'EBCDIC-PT';
+
+			case 'csebcdicuk':
+			case 'ebcdicuk':
+				return 'EBCDIC-UK';
+
+			case 'csebcdicus':
+			case 'ebcdicus':
+				return 'EBCDIC-US';
+
+			case 'csiso111ecmacyrillic':
+			case 'ecmacyrillic':
+			case 'isoir111':
+			case 'koi8e':
+				return 'ECMA-cyrillic';
+
+			case 'csiso17spanish':
+			case 'es':
+			case 'iso646es':
+			case 'isoir17':
+				return 'ES';
+
+			case 'csiso85spanish2':
+			case 'es2':
+			case 'iso646es2':
+			case 'isoir85':
+				return 'ES2';
+
+			case 'cseucfixwidjapanese':
+			case 'extendedunixcodefixedwidthforjapanese':
+				return 'Extended_UNIX_Code_Fixed_Width_for_Japanese';
+
+			case 'cseucpkdfmtjapanese':
+			case 'eucjp':
+			case 'extendedunixcodepackedformatforjapanese':
+				return 'Extended_UNIX_Code_Packed_Format_for_Japanese';
+
+			case 'gb18030':
+				return 'GB18030';
+
+			case 'chinese':
+			case 'cp936':
+			case 'csgb2312':
+			case 'csiso58gb231280':
+			case 'gb2312':
+			case 'gb231280':
+			case 'gbk':
+			case 'isoir58':
+			case 'ms936':
+			case 'windows936':
+				return 'GBK';
+
+			case 'cn':
+			case 'csiso57gb1988':
+			case 'gb198880':
+			case 'iso646cn':
+			case 'isoir57':
+				return 'GB_1988-80';
+
+			case 'csiso153gost1976874':
+			case 'gost1976874':
+			case 'isoir153':
+			case 'stsev35888':
+				return 'GOST_19768-74';
+
+			case 'csiso150':
+			case 'csiso150greekccitt':
+			case 'greekccitt':
+			case 'isoir150':
+				return 'greek-ccitt';
+
+			case 'csiso88greek7':
+			case 'greek7':
+			case 'isoir88':
+				return 'greek7';
+
+			case 'csiso18greek7old':
+			case 'greek7old':
+			case 'isoir18':
+				return 'greek7-old';
+
+			case 'cshpdesktop':
+			case 'hpdesktop':
+				return 'HP-DeskTop';
+
+			case 'cshplegal':
+			case 'hplegal':
+				return 'HP-Legal';
+
+			case 'cshpmath8':
+			case 'hpmath8':
+				return 'HP-Math8';
+
+			case 'cshppifont':
+			case 'hppifont':
+				return 'HP-Pi-font';
+
+			case 'cshproman8':
+			case 'hproman8':
+			case 'r8':
+			case 'roman8':
+				return 'hp-roman8';
+
+			case 'hzgb2312':
+				return 'HZ-GB-2312';
+
+			case 'csibmsymbols':
+			case 'ibmsymbols':
+				return 'IBM-Symbols';
+
+			case 'csibmthai':
+			case 'ibmthai':
+				return 'IBM-Thai';
+
+			case 'ccsid858':
+			case 'cp858':
+			case 'ibm858':
+			case 'pcmultilingual850euro':
+				return 'IBM00858';
+
+			case 'ccsid924':
+			case 'cp924':
+			case 'ebcdiclatin9euro':
+			case 'ibm924':
+				return 'IBM00924';
+
+			case 'ccsid1140':
+			case 'cp1140':
+			case 'ebcdicus37euro':
+			case 'ibm1140':
+				return 'IBM01140';
+
+			case 'ccsid1141':
+			case 'cp1141':
+			case 'ebcdicde273euro':
+			case 'ibm1141':
+				return 'IBM01141';
+
+			case 'ccsid1142':
+			case 'cp1142':
+			case 'ebcdicdk277euro':
+			case 'ebcdicno277euro':
+			case 'ibm1142':
+				return 'IBM01142';
+
+			case 'ccsid1143':
+			case 'cp1143':
+			case 'ebcdicfi278euro':
+			case 'ebcdicse278euro':
+			case 'ibm1143':
+				return 'IBM01143';
+
+			case 'ccsid1144':
+			case 'cp1144':
+			case 'ebcdicit280euro':
+			case 'ibm1144':
+				return 'IBM01144';
+
+			case 'ccsid1145':
+			case 'cp1145':
+			case 'ebcdices284euro':
+			case 'ibm1145':
+				return 'IBM01145';
+
+			case 'ccsid1146':
+			case 'cp1146':
+			case 'ebcdicgb285euro':
+			case 'ibm1146':
+				return 'IBM01146';
+
+			case 'ccsid1147':
+			case 'cp1147':
+			case 'ebcdicfr297euro':
+			case 'ibm1147':
+				return 'IBM01147';
+
+			case 'ccsid1148':
+			case 'cp1148':
+			case 'ebcdicinternational500euro':
+			case 'ibm1148':
+				return 'IBM01148';
+
+			case 'ccsid1149':
+			case 'cp1149':
+			case 'ebcdicis871euro':
+			case 'ibm1149':
+				return 'IBM01149';
+
+			case 'cp37':
+			case 'csibm37':
+			case 'ebcdiccpca':
+			case 'ebcdiccpnl':
+			case 'ebcdiccpus':
+			case 'ebcdiccpwt':
+			case 'ibm37':
+				return 'IBM037';
+
+			case 'cp38':
+			case 'csibm38':
+			case 'ebcdicint':
+			case 'ibm38':
+				return 'IBM038';
+
+			case 'cp273':
+			case 'csibm273':
+			case 'ibm273':
+				return 'IBM273';
+
+			case 'cp274':
+			case 'csibm274':
+			case 'ebcdicbe':
+			case 'ibm274':
+				return 'IBM274';
+
+			case 'cp275':
+			case 'csibm275':
+			case 'ebcdicbr':
+			case 'ibm275':
+				return 'IBM275';
+
+			case 'csibm277':
+			case 'ebcdiccpdk':
+			case 'ebcdiccpno':
+			case 'ibm277':
+				return 'IBM277';
+
+			case 'cp278':
+			case 'csibm278':
+			case 'ebcdiccpfi':
+			case 'ebcdiccpse':
+			case 'ibm278':
+				return 'IBM278';
+
+			case 'cp280':
+			case 'csibm280':
+			case 'ebcdiccpit':
+			case 'ibm280':
+				return 'IBM280';
+
+			case 'cp281':
+			case 'csibm281':
+			case 'ebcdicjpe':
+			case 'ibm281':
+				return 'IBM281';
+
+			case 'cp284':
+			case 'csibm284':
+			case 'ebcdiccpes':
+			case 'ibm284':
+				return 'IBM284';
+
+			case 'cp285':
+			case 'csibm285':
+			case 'ebcdiccpgb':
+			case 'ibm285':
+				return 'IBM285';
+
+			case 'cp290':
+			case 'csibm290':
+			case 'ebcdicjpkana':
+			case 'ibm290':
+				return 'IBM290';
+
+			case 'cp297':
+			case 'csibm297':
+			case 'ebcdiccpfr':
+			case 'ibm297':
+				return 'IBM297';
+
+			case 'cp420':
+			case 'csibm420':
+			case 'ebcdiccpar1':
+			case 'ibm420':
+				return 'IBM420';
+
+			case 'cp423':
+			case 'csibm423':
+			case 'ebcdiccpgr':
+			case 'ibm423':
+				return 'IBM423';
+
+			case 'cp424':
+			case 'csibm424':
+			case 'ebcdiccphe':
+			case 'ibm424':
+				return 'IBM424';
+
+			case '437':
+			case 'cp437':
+			case 'cspc8codepage437':
+			case 'ibm437':
+				return 'IBM437';
+
+			case 'cp500':
+			case 'csibm500':
+			case 'ebcdiccpbe':
+			case 'ebcdiccpch':
+			case 'ibm500':
+				return 'IBM500';
+
+			case 'cp775':
+			case 'cspc775baltic':
+			case 'ibm775':
+				return 'IBM775';
+
+			case '850':
+			case 'cp850':
+			case 'cspc850multilingual':
+			case 'ibm850':
+				return 'IBM850';
+
+			case '851':
+			case 'cp851':
+			case 'csibm851':
+			case 'ibm851':
+				return 'IBM851';
+
+			case '852':
+			case 'cp852':
+			case 'cspcp852':
+			case 'ibm852':
+				return 'IBM852';
+
+			case '855':
+			case 'cp855':
+			case 'csibm855':
+			case 'ibm855':
+				return 'IBM855';
+
+			case '857':
+			case 'cp857':
+			case 'csibm857':
+			case 'ibm857':
+				return 'IBM857';
+
+			case '860':
+			case 'cp860':
+			case 'csibm860':
+			case 'ibm860':
+				return 'IBM860';
+
+			case '861':
+			case 'cp861':
+			case 'cpis':
+			case 'csibm861':
+			case 'ibm861':
+				return 'IBM861';
+
+			case '862':
+			case 'cp862':
+			case 'cspc862latinhebrew':
+			case 'ibm862':
+				return 'IBM862';
+
+			case '863':
+			case 'cp863':
+			case 'csibm863':
+			case 'ibm863':
+				return 'IBM863';
+
+			case 'cp864':
+			case 'csibm864':
+			case 'ibm864':
+				return 'IBM864';
+
+			case '865':
+			case 'cp865':
+			case 'csibm865':
+			case 'ibm865':
+				return 'IBM865';
+
+			case '866':
+			case 'cp866':
+			case 'csibm866':
+			case 'ibm866':
+				return 'IBM866';
+
+			case 'cp868':
+			case 'cpar':
+			case 'csibm868':
+			case 'ibm868':
+				return 'IBM868';
+
+			case '869':
+			case 'cp869':
+			case 'cpgr':
+			case 'csibm869':
+			case 'ibm869':
+				return 'IBM869';
+
+			case 'cp870':
+			case 'csibm870':
+			case 'ebcdiccproece':
+			case 'ebcdiccpyu':
+			case 'ibm870':
+				return 'IBM870';
+
+			case 'cp871':
+			case 'csibm871':
+			case 'ebcdiccpis':
+			case 'ibm871':
+				return 'IBM871';
+
+			case 'cp880':
+			case 'csibm880':
+			case 'ebcdiccyrillic':
+			case 'ibm880':
+				return 'IBM880';
+
+			case 'cp891':
+			case 'csibm891':
+			case 'ibm891':
+				return 'IBM891';
+
+			case 'cp903':
+			case 'csibm903':
+			case 'ibm903':
+				return 'IBM903';
+
+			case '904':
+			case 'cp904':
+			case 'csibbm904':
+			case 'ibm904':
+				return 'IBM904';
+
+			case 'cp905':
+			case 'csibm905':
+			case 'ebcdiccptr':
+			case 'ibm905':
+				return 'IBM905';
+
+			case 'cp918':
+			case 'csibm918':
+			case 'ebcdiccpar2':
+			case 'ibm918':
+				return 'IBM918';
+
+			case 'cp1026':
+			case 'csibm1026':
+			case 'ibm1026':
+				return 'IBM1026';
+
+			case 'ibm1047':
+				return 'IBM1047';
+
+			case 'csiso143iecp271':
+			case 'iecp271':
+			case 'isoir143':
+				return 'IEC_P27-1';
+
+			case 'csiso49inis':
+			case 'inis':
+			case 'isoir49':
+				return 'INIS';
+
+			case 'csiso50inis8':
+			case 'inis8':
+			case 'isoir50':
+				return 'INIS-8';
+
+			case 'csiso51iniscyrillic':
+			case 'iniscyrillic':
+			case 'isoir51':
+				return 'INIS-cyrillic';
+
+			case 'csinvariant':
+			case 'invariant':
+				return 'INVARIANT';
+
+			case 'iso2022cn':
+				return 'ISO-2022-CN';
+
+			case 'iso2022cnext':
+				return 'ISO-2022-CN-EXT';
+
+			case 'csiso2022jp':
+			case 'iso2022jp':
+				return 'ISO-2022-JP';
+
+			case 'csiso2022jp2':
+			case 'iso2022jp2':
+				return 'ISO-2022-JP-2';
+
+			case 'csiso2022kr':
+			case 'iso2022kr':
+				return 'ISO-2022-KR';
+
+			case 'cswindows30latin1':
+			case 'iso88591windows30latin1':
+				return 'ISO-8859-1-Windows-3.0-Latin-1';
+
+			case 'cswindows31latin1':
+			case 'iso88591windows31latin1':
+				return 'ISO-8859-1-Windows-3.1-Latin-1';
+
+			case 'csisolatin2':
+			case 'iso88592':
+			case 'iso885921987':
+			case 'isoir101':
+			case 'l2':
+			case 'latin2':
+				return 'ISO-8859-2';
+
+			case 'cswindows31latin2':
+			case 'iso88592windowslatin2':
+				return 'ISO-8859-2-Windows-Latin-2';
+
+			case 'csisolatin3':
+			case 'iso88593':
+			case 'iso885931988':
+			case 'isoir109':
+			case 'l3':
+			case 'latin3':
+				return 'ISO-8859-3';
+
+			case 'csisolatin4':
+			case 'iso88594':
+			case 'iso885941988':
+			case 'isoir110':
+			case 'l4':
+			case 'latin4':
+				return 'ISO-8859-4';
+
+			case 'csisolatincyrillic':
+			case 'cyrillic':
+			case 'iso88595':
+			case 'iso885951988':
+			case 'isoir144':
+				return 'ISO-8859-5';
+
+			case 'arabic':
+			case 'asmo708':
+			case 'csisolatinarabic':
+			case 'ecma114':
+			case 'iso88596':
+			case 'iso885961987':
+			case 'isoir127':
+				return 'ISO-8859-6';
+
+			case 'csiso88596e':
+			case 'iso88596e':
+				return 'ISO-8859-6-E';
+
+			case 'csiso88596i':
+			case 'iso88596i':
+				return 'ISO-8859-6-I';
+
+			case 'csisolatingreek':
+			case 'ecma118':
+			case 'elot928':
+			case 'greek':
+			case 'greek8':
+			case 'iso88597':
+			case 'iso885971987':
+			case 'isoir126':
+				return 'ISO-8859-7';
+
+			case 'csisolatinhebrew':
+			case 'hebrew':
+			case 'iso88598':
+			case 'iso885981988':
+			case 'isoir138':
+				return 'ISO-8859-8';
+
+			case 'csiso88598e':
+			case 'iso88598e':
+				return 'ISO-8859-8-E';
+
+			case 'csiso88598i':
+			case 'iso88598i':
+				return 'ISO-8859-8-I';
+
+			case 'cswindows31latin5':
+			case 'iso88599windowslatin5':
+				return 'ISO-8859-9-Windows-Latin-5';
+
+			case 'csisolatin6':
+			case 'iso885910':
+			case 'iso8859101992':
+			case 'isoir157':
+			case 'l6':
+			case 'latin6':
+				return 'ISO-8859-10';
+
+			case 'iso885913':
+				return 'ISO-8859-13';
+
+			case 'iso885914':
+			case 'iso8859141998':
+			case 'isoceltic':
+			case 'isoir199':
+			case 'l8':
+			case 'latin8':
+				return 'ISO-8859-14';
+
+			case 'iso885915':
+			case 'latin9':
+				return 'ISO-8859-15';
+
+			case 'iso885916':
+			case 'iso8859162001':
+			case 'isoir226':
+			case 'l10':
+			case 'latin10':
+				return 'ISO-8859-16';
+
+			case 'iso10646j1':
+				return 'ISO-10646-J-1';
+
+			case 'csunicode':
+			case 'iso10646ucs2':
+				return 'ISO-10646-UCS-2';
+
+			case 'csucs4':
+			case 'iso10646ucs4':
+				return 'ISO-10646-UCS-4';
+
+			case 'csunicodeascii':
+			case 'iso10646ucsbasic':
+				return 'ISO-10646-UCS-Basic';
+
+			case 'csunicodelatin1':
+			case 'iso10646':
+			case 'iso10646unicodelatin1':
+				return 'ISO-10646-Unicode-Latin1';
+
+			case 'csiso10646utf1':
+			case 'iso10646utf1':
+				return 'ISO-10646-UTF-1';
+
+			case 'csiso115481':
+			case 'iso115481':
+			case 'isotr115481':
+				return 'ISO-11548-1';
+
+			case 'csiso90':
+			case 'isoir90':
+				return 'iso-ir-90';
+
+			case 'csunicodeibm1261':
+			case 'isounicodeibm1261':
+				return 'ISO-Unicode-IBM-1261';
+
+			case 'csunicodeibm1264':
+			case 'isounicodeibm1264':
+				return 'ISO-Unicode-IBM-1264';
+
+			case 'csunicodeibm1265':
+			case 'isounicodeibm1265':
+				return 'ISO-Unicode-IBM-1265';
+
+			case 'csunicodeibm1268':
+			case 'isounicodeibm1268':
+				return 'ISO-Unicode-IBM-1268';
+
+			case 'csunicodeibm1276':
+			case 'isounicodeibm1276':
+				return 'ISO-Unicode-IBM-1276';
+
+			case 'csiso646basic1983':
+			case 'iso646basic1983':
+			case 'ref':
+				return 'ISO_646.basic:1983';
+
+			case 'csiso2intlrefversion':
+			case 'irv':
+			case 'iso646irv1983':
+			case 'isoir2':
+				return 'ISO_646.irv:1983';
+
+			case 'csiso2033':
+			case 'e13b':
+			case 'iso20331983':
+			case 'isoir98':
+				return 'ISO_2033-1983';
+
+			case 'csiso5427cyrillic':
+			case 'iso5427':
+			case 'isoir37':
+				return 'ISO_5427';
+
+			case 'iso5427cyrillic1981':
+			case 'iso54271981':
+			case 'isoir54':
+				return 'ISO_5427:1981';
+
+			case 'csiso5428greek':
+			case 'iso54281980':
+			case 'isoir55':
+				return 'ISO_5428:1980';
+
+			case 'csiso6937add':
+			case 'iso6937225':
+			case 'isoir152':
+				return 'ISO_6937-2-25';
+
+			case 'csisotextcomm':
+			case 'iso69372add':
+			case 'isoir142':
+				return 'ISO_6937-2-add';
+
+			case 'csiso8859supp':
+			case 'iso8859supp':
+			case 'isoir154':
+			case 'latin125':
+				return 'ISO_8859-supp';
+
+			case 'csiso10367box':
+			case 'iso10367box':
+			case 'isoir155':
+				return 'ISO_10367-box';
+
+			case 'csiso15italian':
+			case 'iso646it':
+			case 'isoir15':
+			case 'it':
+				return 'IT';
+
+			case 'csiso13jisc6220jp':
+			case 'isoir13':
+			case 'jisc62201969':
+			case 'jisc62201969jp':
+			case 'katakana':
+			case 'x2017':
+				return 'JIS_C6220-1969-jp';
+
+			case 'csiso14jisc6220ro':
+			case 'iso646jp':
+			case 'isoir14':
+			case 'jisc62201969ro':
+			case 'jp':
+				return 'JIS_C6220-1969-ro';
+
+			case 'csiso42jisc62261978':
+			case 'isoir42':
+			case 'jisc62261978':
+				return 'JIS_C6226-1978';
+
+			case 'csiso87jisx208':
+			case 'isoir87':
+			case 'jisc62261983':
+			case 'jisx2081983':
+			case 'x208':
+				return 'JIS_C6226-1983';
+
+			case 'csiso91jisc62291984a':
+			case 'isoir91':
+			case 'jisc62291984a':
+			case 'jpocra':
+				return 'JIS_C6229-1984-a';
+
+			case 'csiso92jisc62991984b':
+			case 'iso646jpocrb':
+			case 'isoir92':
+			case 'jisc62291984b':
+			case 'jpocrb':
+				return 'JIS_C6229-1984-b';
+
+			case 'csiso93jis62291984badd':
+			case 'isoir93':
+			case 'jisc62291984badd':
+			case 'jpocrbadd':
+				return 'JIS_C6229-1984-b-add';
+
+			case 'csiso94jis62291984hand':
+			case 'isoir94':
+			case 'jisc62291984hand':
+			case 'jpocrhand':
+				return 'JIS_C6229-1984-hand';
+
+			case 'csiso95jis62291984handadd':
+			case 'isoir95':
+			case 'jisc62291984handadd':
+			case 'jpocrhandadd':
+				return 'JIS_C6229-1984-hand-add';
+
+			case 'csiso96jisc62291984kana':
+			case 'isoir96':
+			case 'jisc62291984kana':
+				return 'JIS_C6229-1984-kana';
+
+			case 'csjisencoding':
+			case 'jisencoding':
+				return 'JIS_Encoding';
+
+			case 'cshalfwidthkatakana':
+			case 'jisx201':
+			case 'x201':
+				return 'JIS_X0201';
+
+			case 'csiso159jisx2121990':
+			case 'isoir159':
+			case 'jisx2121990':
+			case 'x212':
+				return 'JIS_X0212-1990';
+
+			case 'csiso141jusib1002':
+			case 'iso646yu':
+			case 'isoir141':
+			case 'js':
+			case 'jusib1002':
+			case 'yu':
+				return 'JUS_I.B1.002';
+
+			case 'csiso147macedonian':
+			case 'isoir147':
+			case 'jusib1003mac':
+			case 'macedonian':
+				return 'JUS_I.B1.003-mac';
+
+			case 'csiso146serbian':
+			case 'isoir146':
+			case 'jusib1003serb':
+			case 'serbian':
+				return 'JUS_I.B1.003-serb';
+
+			case 'koi7switched':
+				return 'KOI7-switched';
+
+			case 'cskoi8r':
+			case 'koi8r':
+				return 'KOI8-R';
+
+			case 'koi8u':
+				return 'KOI8-U';
+
+			case 'csksc5636':
+			case 'iso646kr':
+			case 'ksc5636':
+				return 'KSC5636';
+
+			case 'cskz1048':
+			case 'kz1048':
+			case 'rk1048':
+			case 'strk10482002':
+				return 'KZ-1048';
+
+			case 'csiso19latingreek':
+			case 'isoir19':
+			case 'latingreek':
+				return 'latin-greek';
+
+			case 'csiso27latingreek1':
+			case 'isoir27':
+			case 'latingreek1':
+				return 'Latin-greek-1';
+
+			case 'csiso158lap':
+			case 'isoir158':
+			case 'lap':
+			case 'latinlap':
+				return 'latin-lap';
+
+			case 'csmacintosh':
+			case 'mac':
+			case 'macintosh':
+				return 'macintosh';
+
+			case 'csmicrosoftpublishing':
+			case 'microsoftpublishing':
+				return 'Microsoft-Publishing';
+
+			case 'csmnem':
+			case 'mnem':
+				return 'MNEM';
+
+			case 'csmnemonic':
+			case 'mnemonic':
+				return 'MNEMONIC';
+
+			case 'csiso86hungarian':
+			case 'hu':
+			case 'iso646hu':
+			case 'isoir86':
+			case 'msz77953':
+				return 'MSZ_7795.3';
+
+			case 'csnatsdano':
+			case 'isoir91':
+			case 'natsdano':
+				return 'NATS-DANO';
+
+			case 'csnatsdanoadd':
+			case 'isoir92':
+			case 'natsdanoadd':
+				return 'NATS-DANO-ADD';
+
+			case 'csnatssefi':
+			case 'isoir81':
+			case 'natssefi':
+				return 'NATS-SEFI';
+
+			case 'csnatssefiadd':
+			case 'isoir82':
+			case 'natssefiadd':
+				return 'NATS-SEFI-ADD';
+
+			case 'csiso151cuba':
+			case 'cuba':
+			case 'iso646cu':
+			case 'isoir151':
+			case 'ncnc1081':
+				return 'NC_NC00-10:81';
+
+			case 'csiso69french':
+			case 'fr':
+			case 'iso646fr':
+			case 'isoir69':
+			case 'nfz62010':
+				return 'NF_Z_62-010';
+
+			case 'csiso25french':
+			case 'iso646fr1':
+			case 'isoir25':
+			case 'nfz620101973':
+				return 'NF_Z_62-010_(1973)';
+
+			case 'csiso60danishnorwegian':
+			case 'csiso60norwegian1':
+			case 'iso646no':
+			case 'isoir60':
+			case 'no':
+			case 'ns45511':
+				return 'NS_4551-1';
+
+			case 'csiso61norwegian2':
+			case 'iso646no2':
+			case 'isoir61':
+			case 'no2':
+			case 'ns45512':
+				return 'NS_4551-2';
+
+			case 'osdebcdicdf3irv':
+				return 'OSD_EBCDIC_DF03_IRV';
+
+			case 'osdebcdicdf41':
+				return 'OSD_EBCDIC_DF04_1';
+
+			case 'osdebcdicdf415':
+				return 'OSD_EBCDIC_DF04_15';
+
+			case 'cspc8danishnorwegian':
+			case 'pc8danishnorwegian':
+				return 'PC8-Danish-Norwegian';
+
+			case 'cspc8turkish':
+			case 'pc8turkish':
+				return 'PC8-Turkish';
+
+			case 'csiso16portuguese':
+			case 'iso646pt':
+			case 'isoir16':
+			case 'pt':
+				return 'PT';
+
+			case 'csiso84portuguese2':
+			case 'iso646pt2':
+			case 'isoir84':
+			case 'pt2':
+				return 'PT2';
+
+			case 'cp154':
+			case 'csptcp154':
+			case 'cyrillicasian':
+			case 'pt154':
+			case 'ptcp154':
+				return 'PTCP154';
+
+			case 'scsu':
+				return 'SCSU';
+
+			case 'csiso10swedish':
+			case 'fi':
+			case 'iso646fi':
+			case 'iso646se':
+			case 'isoir10':
+			case 'se':
+			case 'sen850200b':
+				return 'SEN_850200_B';
+
+			case 'csiso11swedishfornames':
+			case 'iso646se2':
+			case 'isoir11':
+			case 'se2':
+			case 'sen850200c':
+				return 'SEN_850200_C';
+
+			case 'csshiftjis':
+			case 'mskanji':
+			case 'shiftjis':
+				return 'Shift_JIS';
+
+			case 'csiso102t617bit':
+			case 'isoir102':
+			case 't617bit':
+				return 'T.61-7bit';
+
+			case 'csiso103t618bit':
+			case 'isoir103':
+			case 't61':
+			case 't618bit':
+				return 'T.61-8bit';
+
+			case 'csiso128t101g2':
+			case 'isoir128':
+			case 't101g2':
+				return 'T.101-G2';
+
+			case 'cstscii':
+			case 'tscii':
+				return 'TSCII';
+
+			case 'csunicode11':
+			case 'unicode11':
+				return 'UNICODE-1-1';
+
+			case 'csunicode11utf7':
+			case 'unicode11utf7':
+				return 'UNICODE-1-1-UTF-7';
+
+			case 'csunknown8bit':
+			case 'unknown8bit':
+				return 'UNKNOWN-8BIT';
+
+			case 'ansix341968':
+			case 'ansix341986':
+			case 'ascii':
+			case 'cp367':
+			case 'csascii':
+			case 'ibm367':
+			case 'iso646irv1991':
+			case 'iso646us':
+			case 'isoir6':
+			case 'us':
+			case 'usascii':
+				return 'US-ASCII';
+
+			case 'csusdk':
+			case 'usdk':
+				return 'us-dk';
+
+			case 'utf7':
+				return 'UTF-7';
+
+			case 'utf8':
+				return 'UTF-8';
+
+			case 'utf16':
+				return 'UTF-16';
+
+			case 'utf16be':
+				return 'UTF-16BE';
+
+			case 'utf16le':
+				return 'UTF-16LE';
+
+			case 'utf32':
+				return 'UTF-32';
+
+			case 'utf32be':
+				return 'UTF-32BE';
+
+			case 'utf32le':
+				return 'UTF-32LE';
+
+			case 'csventurainternational':
+			case 'venturainternational':
+				return 'Ventura-International';
+
+			case 'csventuramath':
+			case 'venturamath':
+				return 'Ventura-Math';
+
+			case 'csventuraus':
+			case 'venturaus':
+				return 'Ventura-US';
+
+			case 'csiso70videotexsupp1':
+			case 'isoir70':
+			case 'videotexsuppl':
+				return 'videotex-suppl';
+
+			case 'csviqr':
+			case 'viqr':
+				return 'VIQR';
+
+			case 'csviscii':
+			case 'viscii':
+				return 'VISCII';
+
+			case 'cswindows31j':
+			case 'windows31j':
+				return 'Windows-31J';
+
+			case 'iso885911':
+			case 'tis620':
+				return 'windows-874';
+
+			case 'cseuckr':
+			case 'csksc56011987':
+			case 'euckr':
+			case 'isoir149':
+			case 'korean':
+			case 'ksc5601':
+			case 'ksc56011987':
+			case 'ksc56011989':
+			case 'windows949':
+				return 'windows-949';
+
+			case 'windows1250':
+				return 'windows-1250';
+
+			case 'windows1251':
+				return 'windows-1251';
+
+			case 'cp819':
+			case 'csisolatin1':
+			case 'ibm819':
+			case 'iso88591':
+			case 'iso885911987':
+			case 'isoir100':
+			case 'l1':
+			case 'latin1':
+			case 'windows1252':
+				return 'windows-1252';
+
+			case 'windows1253':
+				return 'windows-1253';
+
+			case 'csisolatin5':
+			case 'iso88599':
+			case 'iso885991989':
+			case 'isoir148':
+			case 'l5':
+			case 'latin5':
+			case 'windows1254':
+				return 'windows-1254';
+
+			case 'windows1255':
+				return 'windows-1255';
+
+			case 'windows1256':
+				return 'windows-1256';
+
+			case 'windows1257':
+				return 'windows-1257';
+
+			case 'windows1258':
+				return 'windows-1258';
+
+			default:
+				return $charset;
+		}
+	}
+
+	public static function get_curl_version()
+	{
+		if (is_array($curl = curl_version()))
+		{
+			$curl = $curl['version'];
+		}
+		elseif (substr($curl, 0, 5) === 'curl/')
+		{
+			$curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5));
+		}
+		elseif (substr($curl, 0, 8) === 'libcurl/')
+		{
+			$curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8));
+		}
+		else
+		{
+			$curl = 0;
+		}
+		return $curl;
+	}
+
+	public static function is_subclass_of($class1, $class2)
+	{
+		if (func_num_args() !== 2)
+		{
+			trigger_error('Wrong parameter count for SimplePie_Misc::is_subclass_of()', E_USER_WARNING);
+		}
+		elseif (version_compare(PHP_VERSION, '5.0.3', '>=') || is_object($class1))
+		{
+			return is_subclass_of($class1, $class2);
+		}
+		elseif (is_string($class1) && is_string($class2))
+		{
+			if (class_exists($class1))
+			{
+				if (class_exists($class2))
+				{
+					$class2 = strtolower($class2);
+					while ($class1 = strtolower(get_parent_class($class1)))
+					{
+						if ($class1 === $class2)
+						{
+							return true;
+						}
+					}
+				}
+			}
+			else
+			{
+				trigger_error('Unknown class passed as parameter', E_USER_WARNNG);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Strip HTML comments
+	 *
+	 * @access public
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	public static function strip_comments($data)
+	{
+		$output = '';
+		while (($start = strpos($data, '<!--')) !== false)
+		{
+			$output .= substr($data, 0, $start);
+			if (($end = strpos($data, '-->', $start)) !== false)
+			{
+				$data = substr_replace($data, '', 0, $end + 3);
+			}
+			else
+			{
+				$data = '';
+			}
+		}
+		return $output . $data;
+	}
+
+	public static function parse_date($dt)
+	{
+		$parser = SimplePie_Parse_Date::get();
+		return $parser->parse($dt);
+	}
+
+	/**
+	 * Decode HTML entities
+	 *
+	 * @static
+	 * @access public
+	 * @param string $data Input data
+	 * @return string Output data
+	 */
+	public static function entities_decode($data)
+	{
+		$decoder = new SimplePie_Decode_HTML_Entities($data);
+		return $decoder->parse();
+	}
+
+	/**
+	 * Remove RFC822 comments
+	 *
+	 * @access public
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	public static function uncomment_rfc822($string)
+	{
+		$string = (string) $string;
+		$position = 0;
+		$length = strlen($string);
+		$depth = 0;
+
+		$output = '';
+
+		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
+		{
+			$output .= substr($string, $position, $pos - $position);
+			$position = $pos + 1;
+			if ($string[$pos - 1] !== '\\')
+			{
+				$depth++;
+				while ($depth && $position < $length)
+				{
+					$position += strcspn($string, '()', $position);
+					if ($string[$position - 1] === '\\')
+					{
+						$position++;
+						continue;
+					}
+					elseif (isset($string[$position]))
+					{
+						switch ($string[$position])
+						{
+							case '(':
+								$depth++;
+								break;
+
+							case ')':
+								$depth--;
+								break;
+						}
+						$position++;
+					}
+					else
+					{
+						break;
+					}
+				}
+			}
+			else
+			{
+				$output .= '(';
+			}
+		}
+		$output .= substr($string, $position);
+
+		return $output;
+	}
+
+	public static function parse_mime($mime)
+	{
+		if (($pos = strpos($mime, ';')) === false)
+		{
+			return trim($mime);
+		}
+		else
+		{
+			return trim(substr($mime, 0, $pos));
+		}
+	}
+
+	public static function htmlspecialchars_decode($string, $quote_style)
+	{
+		if (function_exists('htmlspecialchars_decode'))
+		{
+			return htmlspecialchars_decode($string, $quote_style);
+		}
+		else
+		{
+			return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
+		}
+	}
+
+	public static function atom_03_construct_type($attribs)
+	{
+		if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64'))
+		{
+			$mode = SIMPLEPIE_CONSTRUCT_BASE64;
+		}
+		else
+		{
+			$mode = SIMPLEPIE_CONSTRUCT_NONE;
+		}
+		if (isset($attribs['']['type']))
+		{
+			switch (strtolower(trim($attribs['']['type'])))
+			{
+				case 'text':
+				case 'text/plain':
+					return SIMPLEPIE_CONSTRUCT_TEXT | $mode;
+
+				case 'html':
+				case 'text/html':
+					return SIMPLEPIE_CONSTRUCT_HTML | $mode;
+
+				case 'xhtml':
+				case 'application/xhtml+xml':
+					return SIMPLEPIE_CONSTRUCT_XHTML | $mode;
+
+				default:
+					return SIMPLEPIE_CONSTRUCT_NONE | $mode;
+			}
+		}
+		else
+		{
+			return SIMPLEPIE_CONSTRUCT_TEXT | $mode;
+		}
+	}
+
+	public static function atom_10_construct_type($attribs)
+	{
+		if (isset($attribs['']['type']))
+		{
+			switch (strtolower(trim($attribs['']['type'])))
+			{
+				case 'text':
+					return SIMPLEPIE_CONSTRUCT_TEXT;
+
+				case 'html':
+					return SIMPLEPIE_CONSTRUCT_HTML;
+
+				case 'xhtml':
+					return SIMPLEPIE_CONSTRUCT_XHTML;
+
+				default:
+					return SIMPLEPIE_CONSTRUCT_NONE;
+			}
+		}
+		return SIMPLEPIE_CONSTRUCT_TEXT;
+	}
+
+	public static function atom_10_content_construct_type($attribs)
+	{
+		if (isset($attribs['']['type']))
+		{
+			$type = strtolower(trim($attribs['']['type']));
+			switch ($type)
+			{
+				case 'text':
+					return SIMPLEPIE_CONSTRUCT_TEXT;
+
+				case 'html':
+					return SIMPLEPIE_CONSTRUCT_HTML;
+
+				case 'xhtml':
+					return SIMPLEPIE_CONSTRUCT_XHTML;
+			}
+			if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/')
+			{
+				return SIMPLEPIE_CONSTRUCT_NONE;
+			}
+			else
+			{
+				return SIMPLEPIE_CONSTRUCT_BASE64;
+			}
+		}
+		else
+		{
+			return SIMPLEPIE_CONSTRUCT_TEXT;
+		}
+	}
+
+	public static function is_isegment_nz_nc($string)
+	{
+		return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string);
+	}
+
+	public static function space_seperated_tokens($string)
+	{
+		$space_characters = "\x20\x09\x0A\x0B\x0C\x0D";
+		$string_length = strlen($string);
+
+		$position = strspn($string, $space_characters);
+		$tokens = array();
+
+		while ($position < $string_length)
+		{
+			$len = strcspn($string, $space_characters, $position);
+			$tokens[] = substr($string, $position, $len);
+			$position += $len;
+			$position += strspn($string, $space_characters, $position);
+		}
+
+		return $tokens;
+	}
+
+	public static function array_unique($array)
+	{
+		if (version_compare(PHP_VERSION, '5.2', '>='))
+		{
+			return array_unique($array);
+		}
+		else
+		{
+			$array = (array) $array;
+			$new_array = array();
+			$new_array_strings = array();
+			foreach ($array as $key => $value)
+			{
+				if (is_object($value))
+				{
+					if (method_exists($value, '__toString'))
+					{
+						$cmp = $value->__toString();
+					}
+					else
+					{
+						trigger_error('Object of class ' . get_class($value) . ' could not be converted to string', E_USER_ERROR);
+					}
+				}
+				elseif (is_array($value))
+				{
+					$cmp = (string) reset($value);
+				}
+				else
+				{
+					$cmp = (string) $value;
+				}
+				if (!in_array($cmp, $new_array_strings))
+				{
+					$new_array[$key] = $value;
+					$new_array_strings[] = $cmp;
+				}
+			}
+			return $new_array;
+		}
+	}
+
+	/**
+	 * Converts a unicode codepoint to a UTF-8 character
+	 *
+	 * @static
+	 * @access public
+	 * @param int $codepoint Unicode codepoint
+	 * @return string UTF-8 character
+	 */
+	public static function codepoint_to_utf8($codepoint)
+	{
+		$codepoint = (int) $codepoint;
+		if ($codepoint < 0)
+		{
+			return false;
+		}
+		else if ($codepoint <= 0x7f)
+		{
+			return chr($codepoint);
+		}
+		else if ($codepoint <= 0x7ff)
+		{
+			return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else if ($codepoint <= 0xffff)
+		{
+			return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else if ($codepoint <= 0x10ffff)
+		{
+			return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else
+		{
+			// U+FFFD REPLACEMENT CHARACTER
+			return "\xEF\xBF\xBD";
+		}
+	}
+
+	/**
+	 * Re-implementation of PHP 5's stripos()
+	 *
+	 * Returns the numeric position of the first occurrence of needle in the
+	 * haystack string.
+	 *
+	 * @static
+	 * @access string
+	 * @param object $haystack
+	 * @param string $needle Note that the needle may be a string of one or more
+	 *     characters. If needle is not a string, it is converted to an integer
+	 *     and applied as the ordinal value of a character.
+	 * @param int $offset The optional offset parameter allows you to specify which
+	 *     character in haystack to start searching. The position returned is still
+	 *     relative to the beginning of haystack.
+	 * @return bool If needle is not found, stripos() will return boolean false.
+	 */
+	public static function stripos($haystack, $needle, $offset = 0)
+	{
+		if (function_exists('stripos'))
+		{
+			return stripos($haystack, $needle, $offset);
+		}
+		else
+		{
+			if (is_string($needle))
+			{
+				$needle = strtolower($needle);
+			}
+			elseif (is_int($needle) || is_bool($needle) || is_double($needle))
+			{
+				$needle = strtolower(chr($needle));
+			}
+			else
+			{
+				trigger_error('needle is not a string or an integer', E_USER_WARNING);
+				return false;
+			}
+
+			return strpos(strtolower($haystack), $needle, $offset);
+		}
+	}
+
+	/**
+	 * Similar to parse_str()
+	 *
+	 * Returns an associative array of name/value pairs, where the value is an
+	 * array of values that have used the same name
+	 *
+	 * @static
+	 * @access string
+	 * @param string $str The input string.
+	 * @return array
+	 */
+	public static function parse_str($str)
+	{
+		$return = array();
+		$str = explode('&', $str);
+
+		foreach ($str as $section)
+		{
+			if (strpos($section, '=') !== false)
+			{
+				list($name, $value) = explode('=', $section, 2);
+				$return[urldecode($name)][] = urldecode($value);
+			}
+			else
+			{
+				$return[urldecode($section)][] = null;
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Detect XML encoding, as per XML 1.0 Appendix F.1
+	 *
+	 * @todo Add support for EBCDIC
+	 * @param string $data XML data
+	 * @return array Possible encodings
+	 */
+	public static function xml_encoding($data)
+	{
+		// UTF-32 Big Endian BOM
+		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
+		{
+			$encoding[] = 'UTF-32BE';
+		}
+		// UTF-32 Little Endian BOM
+		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
+		{
+			$encoding[] = 'UTF-32LE';
+		}
+		// UTF-16 Big Endian BOM
+		elseif (substr($data, 0, 2) === "\xFE\xFF")
+		{
+			$encoding[] = 'UTF-16BE';
+		}
+		// UTF-16 Little Endian BOM
+		elseif (substr($data, 0, 2) === "\xFF\xFE")
+		{
+			$encoding[] = 'UTF-16LE';
+		}
+		// UTF-8 BOM
+		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
+		{
+			$encoding[] = 'UTF-8';
+		}
+		// UTF-32 Big Endian Without BOM
+		elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C")
+		{
+			if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-32BE';
+		}
+		// UTF-32 Little Endian Without BOM
+		elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00")
+		{
+			if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-32LE';
+		}
+		// UTF-16 Big Endian Without BOM
+		elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C")
+		{
+			if ($pos = strpos($data, "\x00\x3F\x00\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-16BE';
+		}
+		// UTF-16 Little Endian Without BOM
+		elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00")
+		{
+			if ($pos = strpos($data, "\x3F\x00\x3E\x00"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-16LE';
+		}
+		// US-ASCII (or superset)
+		elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C")
+		{
+			if ($pos = strpos($data, "\x3F\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-8';
+		}
+		// Fallback to UTF-8
+		else
+		{
+			$encoding[] = 'UTF-8';
+		}
+		return $encoding;
+	}
+
+	public static function output_javascript()
+	{
+		if (function_exists('ob_gzhandler'))
+		{
+			ob_start('ob_gzhandler');
+		}
+		header('Content-type: text/javascript; charset: UTF-8');
+		header('Cache-Control: must-revalidate');
+		header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days
+		?>
+function embed_odeo(link) {
+	document.writeln('<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url='+link+'"></embed>');
+}
+
+function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) {
+	if (placeholder != '') {
+		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
+	}
+	else {
+		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
+	}
+}
+
+function embed_flash(bgcolor, width, height, link, loop, type) {
+	document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>');
+}
+
+function embed_flv(width, height, link, placeholder, loop, player) {
+	document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>');
+}
+
+function embed_wmedia(width, height, link) {
+	document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>');
+}
+		<?php
+	}
+}
+
+/**
+ * Decode HTML Entities
+ *
+ * This implements HTML5 as of revision 967 (2007-06-28)
+ *
+ * @package SimplePie
+ */
+class SimplePie_Decode_HTML_Entities
+{
+	/**
+	 * Data to be parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Currently consumed bytes
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $consumed = '';
+
+	/**
+	 * Position of the current byte being parsed
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $position = 0;
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	public function __construct($data)
+	{
+		$this->data = $data;
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return string Output data
+	 */
+	public function parse()
+	{
+		while (($this->position = strpos($this->data, '&', $this->position)) !== false)
+		{
+			$this->consume();
+			$this->entity();
+			$this->consumed = '';
+		}
+		return $this->data;
+	}
+
+	/**
+	 * Consume the next byte
+	 *
+	 * @access private
+	 * @return mixed The next byte, or false, if there is no more data
+	 */
+	public function consume()
+	{
+		if (isset($this->data[$this->position]))
+		{
+			$this->consumed .= $this->data[$this->position];
+			return $this->data[$this->position++];
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Consume a range of characters
+	 *
+	 * @access private
+	 * @param string $chars Characters to consume
+	 * @return mixed A series of characters that match the range, or false
+	 */
+	public function consume_range($chars)
+	{
+		if ($len = strspn($this->data, $chars, $this->position))
+		{
+			$data = substr($this->data, $this->position, $len);
+			$this->consumed .= $data;
+			$this->position += $len;
+			return $data;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Unconsume one byte
+	 *
+	 * @access private
+	 */
+	public function unconsume()
+	{
+		$this->consumed = substr($this->consumed, 0, -1);
+		$this->position--;
+	}
+
+	/**
+	 * Decode an entity
+	 *
+	 * @access private
+	 */
+	public function entity()
+	{
+		switch ($this->consume())
+		{
+			case "\x09":
+			case "\x0A":
+			case "\x0B":
+			case "\x0B":
+			case "\x0C":
+			case "\x20":
+			case "\x3C":
+			case "\x26":
+			case false:
+				break;
+
+			case "\x23":
+				switch ($this->consume())
+				{
+					case "\x78":
+					case "\x58":
+						$range = '0123456789ABCDEFabcdef';
+						$hex = true;
+						break;
+
+					default:
+						$range = '0123456789';
+						$hex = false;
+						$this->unconsume();
+						break;
+				}
+
+				if ($codepoint = $this->consume_range($range))
+				{
+					static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8");
+
+					if ($hex)
+					{
+						$codepoint = hexdec($codepoint);
+					}
+					else
+					{
+						$codepoint = intval($codepoint);
+					}
+
+					if (isset($windows_1252_specials[$codepoint]))
+					{
+						$replacement = $windows_1252_specials[$codepoint];
+					}
+					else
+					{
+						$replacement = SimplePie_Misc::codepoint_to_utf8($codepoint);
+					}
+
+					if (!in_array($this->consume(), array(';', false), true))
+					{
+						$this->unconsume();
+					}
+
+					$consumed_length = strlen($this->consumed);
+					$this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length);
+					$this->position += strlen($replacement) - $consumed_length;
+				}
+				break;
+
+			default:
+				static $entities = array('Aacute' => "\xC3\x81", 'aacute' => "\xC3\xA1", 'Aacute;' => "\xC3\x81", 'aacute;' => "\xC3\xA1", 'Acirc' => "\xC3\x82", 'acirc' => "\xC3\xA2", 'Acirc;' => "\xC3\x82", 'acirc;' => "\xC3\xA2", 'acute' => "\xC2\xB4", 'acute;' => "\xC2\xB4", 'AElig' => "\xC3\x86", 'aelig' => "\xC3\xA6", 'AElig;' => "\xC3\x86", 'aelig;' => "\xC3\xA6", 'Agrave' => "\xC3\x80", 'agrave' => "\xC3\xA0", 'Agrave;' => "\xC3\x80", 'agrave;' => "\xC3\xA0", 'alefsym;' => "\xE2\x84\xB5", 'Alpha;' => "\xCE\x91", 'alpha;' => "\xCE\xB1", 'AMP' => "\x26", 'amp' => "\x26", 'AMP;' => "\x26", 'amp;' => "\x26", 'and;' => "\xE2\x88\xA7", 'ang;' => "\xE2\x88\xA0", 'apos;' => "\x27", 'Aring' => "\xC3\x85", 'aring' => "\xC3\xA5", 'Aring;' => "\xC3\x85", 'aring;' => "\xC3\xA5", 'asymp;' => "\xE2\x89\x88", 'Atilde' => "\xC3\x83", 'atilde' => "\xC3\xA3", 'Atilde;' => "\xC3\x83", 'atilde;' => "\xC3\xA3", 'Auml' => "\xC3\x84", 'auml' => "\xC3\xA4", 'Auml;' => "\xC3\x84", 'auml;' => "\xC3\xA4", 'bdquo;' => "\xE2\x80\x9E", 'Beta;' => "\xCE\x92", 'beta;' => "\xCE\xB2", 'brvbar' => "\xC2\xA6", 'brvbar;' => "\xC2\xA6", 'bull;' => "\xE2\x80\xA2", 'cap;' => "\xE2\x88\xA9", 'Ccedil' => "\xC3\x87", 'ccedil' => "\xC3\xA7", 'Ccedil;' => "\xC3\x87", 'ccedil;' => "\xC3\xA7", 'cedil' => "\xC2\xB8", 'cedil;' => "\xC2\xB8", 'cent' => "\xC2\xA2", 'cent;' => "\xC2\xA2", 'Chi;' => "\xCE\xA7", 'chi;' => "\xCF\x87", 'circ;' => "\xCB\x86", 'clubs;' => "\xE2\x99\xA3", 'cong;' => "\xE2\x89\x85", 'COPY' => "\xC2\xA9", 'copy' => "\xC2\xA9", 'COPY;' => "\xC2\xA9", 'copy;' => "\xC2\xA9", 'crarr;' => "\xE2\x86\xB5", 'cup;' => "\xE2\x88\xAA", 'curren' => "\xC2\xA4", 'curren;' => "\xC2\xA4", 'Dagger;' => "\xE2\x80\xA1", 'dagger;' => "\xE2\x80\xA0", 'dArr;' => "\xE2\x87\x93", 'darr;' => "\xE2\x86\x93", 'deg' => "\xC2\xB0", 'deg;' => "\xC2\xB0", 'Delta;' => "\xCE\x94", 'delta;' => "\xCE\xB4", 'diams;' => "\xE2\x99\xA6", 'divide' => "\xC3\xB7", 'divide;' => "\xC3\xB7", 'Eacute' => "\xC3\x89", 'eacute' => "\xC3\xA9", 'Eacute;' => "\xC3\x89", 'eacute;' => "\xC3\xA9", 'Ecirc' => "\xC3\x8A", 'ecirc' => "\xC3\xAA", 'Ecirc;' => "\xC3\x8A", 'ecirc;' => "\xC3\xAA", 'Egrave' => "\xC3\x88", 'egrave' => "\xC3\xA8", 'Egrave;' => "\xC3\x88", 'egrave;' => "\xC3\xA8", 'empty;' => "\xE2\x88\x85", 'emsp;' => "\xE2\x80\x83", 'ensp;' => "\xE2\x80\x82", 'Epsilon;' => "\xCE\x95", 'epsilon;' => "\xCE\xB5", 'equiv;' => "\xE2\x89\xA1", 'Eta;' => "\xCE\x97", 'eta;' => "\xCE\xB7", 'ETH' => "\xC3\x90", 'eth' => "\xC3\xB0", 'ETH;' => "\xC3\x90", 'eth;' => "\xC3\xB0", 'Euml' => "\xC3\x8B", 'euml' => "\xC3\xAB", 'Euml;' => "\xC3\x8B", 'euml;' => "\xC3\xAB", 'euro;' => "\xE2\x82\xAC", 'exist;' => "\xE2\x88\x83", 'fnof;' => "\xC6\x92", 'forall;' => "\xE2\x88\x80", 'frac12' => "\xC2\xBD", 'frac12;' => "\xC2\xBD", 'frac14' => "\xC2\xBC", 'frac14;' => "\xC2\xBC", 'frac34' => "\xC2\xBE", 'frac34;' => "\xC2\xBE", 'frasl;' => "\xE2\x81\x84", 'Gamma;' => "\xCE\x93", 'gamma;' => "\xCE\xB3", 'ge;' => "\xE2\x89\xA5", 'GT' => "\x3E", 'gt' => "\x3E", 'GT;' => "\x3E", 'gt;' => "\x3E", 'hArr;' => "\xE2\x87\x94", 'harr;' => "\xE2\x86\x94", 'hearts;' => "\xE2\x99\xA5", 'hellip;' => "\xE2\x80\xA6", 'Iacute' => "\xC3\x8D", 'iacute' => "\xC3\xAD", 'Iacute;' => "\xC3\x8D", 'iacute;' => "\xC3\xAD", 'Icirc' => "\xC3\x8E", 'icirc' => "\xC3\xAE", 'Icirc;' => "\xC3\x8E", 'icirc;' => "\xC3\xAE", 'iexcl' => "\xC2\xA1", 'iexcl;' => "\xC2\xA1", 'Igrave' => "\xC3\x8C", 'igrave' => "\xC3\xAC", 'Igrave;' => "\xC3\x8C", 'igrave;' => "\xC3\xAC", 'image;' => "\xE2\x84\x91", 'infin;' => "\xE2\x88\x9E", 'int;' => "\xE2\x88\xAB", 'Iota;' => "\xCE\x99", 'iota;' => "\xCE\xB9", 'iquest' => "\xC2\xBF", 'iquest;' => "\xC2\xBF", 'isin;' => "\xE2\x88\x88", 'Iuml' => "\xC3\x8F", 'iuml' => "\xC3\xAF", 'Iuml;' => "\xC3\x8F", 'iuml;' => "\xC3\xAF", 'Kappa;' => "\xCE\x9A", 'kappa;' => "\xCE\xBA", 'Lambda;' => "\xCE\x9B", 'lambda;' => "\xCE\xBB", 'lang;' => "\xE3\x80\x88", 'laquo' => "\xC2\xAB", 'laquo;' => "\xC2\xAB", 'lArr;' => "\xE2\x87\x90", 'larr;' => "\xE2\x86\x90", 'lceil;' => "\xE2\x8C\x88", 'ldquo;' => "\xE2\x80\x9C", 'le;' => "\xE2\x89\xA4", 'lfloor;' => "\xE2\x8C\x8A", 'lowast;' => "\xE2\x88\x97", 'loz;' => "\xE2\x97\x8A", 'lrm;' => "\xE2\x80\x8E", 'lsaquo;' => "\xE2\x80\xB9", 'lsquo;' => "\xE2\x80\x98", 'LT' => "\x3C", 'lt' => "\x3C", 'LT;' => "\x3C", 'lt;' => "\x3C", 'macr' => "\xC2\xAF", 'macr;' => "\xC2\xAF", 'mdash;' => "\xE2\x80\x94", 'micro' => "\xC2\xB5", 'micro;' => "\xC2\xB5", 'middot' => "\xC2\xB7", 'middot;' => "\xC2\xB7", 'minus;' => "\xE2\x88\x92", 'Mu;' => "\xCE\x9C", 'mu;' => "\xCE\xBC", 'nabla;' => "\xE2\x88\x87", 'nbsp' => "\xC2\xA0", 'nbsp;' => "\xC2\xA0", 'ndash;' => "\xE2\x80\x93", 'ne;' => "\xE2\x89\xA0", 'ni;' => "\xE2\x88\x8B", 'not' => "\xC2\xAC", 'not;' => "\xC2\xAC", 'notin;' => "\xE2\x88\x89", 'nsub;' => "\xE2\x8A\x84", 'Ntilde' => "\xC3\x91", 'ntilde' => "\xC3\xB1", 'Ntilde;' => "\xC3\x91", 'ntilde;' => "\xC3\xB1", 'Nu;' => "\xCE\x9D", 'nu;' => "\xCE\xBD", 'Oacute' => "\xC3\x93", 'oacute' => "\xC3\xB3", 'Oacute;' => "\xC3\x93", 'oacute;' => "\xC3\xB3", 'Ocirc' => "\xC3\x94", 'ocirc' => "\xC3\xB4", 'Ocirc;' => "\xC3\x94", 'ocirc;' => "\xC3\xB4", 'OElig;' => "\xC5\x92", 'oelig;' => "\xC5\x93", 'Ograve' => "\xC3\x92", 'ograve' => "\xC3\xB2", 'Ograve;' => "\xC3\x92", 'ograve;' => "\xC3\xB2", 'oline;' => "\xE2\x80\xBE", 'Omega;' => "\xCE\xA9", 'omega;' => "\xCF\x89", 'Omicron;' => "\xCE\x9F", 'omicron;' => "\xCE\xBF", 'oplus;' => "\xE2\x8A\x95", 'or;' => "\xE2\x88\xA8", 'ordf' => "\xC2\xAA", 'ordf;' => "\xC2\xAA", 'ordm' => "\xC2\xBA", 'ordm;' => "\xC2\xBA", 'Oslash' => "\xC3\x98", 'oslash' => "\xC3\xB8", 'Oslash;' => "\xC3\x98", 'oslash;' => "\xC3\xB8", 'Otilde' => "\xC3\x95", 'otilde' => "\xC3\xB5", 'Otilde;' => "\xC3\x95", 'otilde;' => "\xC3\xB5", 'otimes;' => "\xE2\x8A\x97", 'Ouml' => "\xC3\x96", 'ouml' => "\xC3\xB6", 'Ouml;' => "\xC3\x96", 'ouml;' => "\xC3\xB6", 'para' => "\xC2\xB6", 'para;' => "\xC2\xB6", 'part;' => "\xE2\x88\x82", 'permil;' => "\xE2\x80\xB0", 'perp;' => "\xE2\x8A\xA5", 'Phi;' => "\xCE\xA6", 'phi;' => "\xCF\x86", 'Pi;' => "\xCE\xA0", 'pi;' => "\xCF\x80", 'piv;' => "\xCF\x96", 'plusmn' => "\xC2\xB1", 'plusmn;' => "\xC2\xB1", 'pound' => "\xC2\xA3", 'pound;' => "\xC2\xA3", 'Prime;' => "\xE2\x80\xB3", 'prime;' => "\xE2\x80\xB2", 'prod;' => "\xE2\x88\x8F", 'prop;' => "\xE2\x88\x9D", 'Psi;' => "\xCE\xA8", 'psi;' => "\xCF\x88", 'QUOT' => "\x22", 'quot' => "\x22", 'QUOT;' => "\x22", 'quot;' => "\x22", 'radic;' => "\xE2\x88\x9A", 'rang;' => "\xE3\x80\x89", 'raquo' => "\xC2\xBB", 'raquo;' => "\xC2\xBB", 'rArr;' => "\xE2\x87\x92", 'rarr;' => "\xE2\x86\x92", 'rceil;' => "\xE2\x8C\x89", 'rdquo;' => "\xE2\x80\x9D", 'real;' => "\xE2\x84\x9C", 'REG' => "\xC2\xAE", 'reg' => "\xC2\xAE", 'REG;' => "\xC2\xAE", 'reg;' => "\xC2\xAE", 'rfloor;' => "\xE2\x8C\x8B", 'Rho;' => "\xCE\xA1", 'rho;' => "\xCF\x81", 'rlm;' => "\xE2\x80\x8F", 'rsaquo;' => "\xE2\x80\xBA", 'rsquo;' => "\xE2\x80\x99", 'sbquo;' => "\xE2\x80\x9A", 'Scaron;' => "\xC5\xA0", 'scaron;' => "\xC5\xA1", 'sdot;' => "\xE2\x8B\x85", 'sect' => "\xC2\xA7", 'sect;' => "\xC2\xA7", 'shy' => "\xC2\xAD", 'shy;' => "\xC2\xAD", 'Sigma;' => "\xCE\xA3", 'sigma;' => "\xCF\x83", 'sigmaf;' => "\xCF\x82", 'sim;' => "\xE2\x88\xBC", 'spades;' => "\xE2\x99\xA0", 'sub;' => "\xE2\x8A\x82", 'sube;' => "\xE2\x8A\x86", 'sum;' => "\xE2\x88\x91", 'sup;' => "\xE2\x8A\x83", 'sup1' => "\xC2\xB9", 'sup1;' => "\xC2\xB9", 'sup2' => "\xC2\xB2", 'sup2;' => "\xC2\xB2", 'sup3' => "\xC2\xB3", 'sup3;' => "\xC2\xB3", 'supe;' => "\xE2\x8A\x87", 'szlig' => "\xC3\x9F", 'szlig;' => "\xC3\x9F", 'Tau;' => "\xCE\xA4", 'tau;' => "\xCF\x84", 'there4;' => "\xE2\x88\xB4", 'Theta;' => "\xCE\x98", 'theta;' => "\xCE\xB8", 'thetasym;' => "\xCF\x91", 'thinsp;' => "\xE2\x80\x89", 'THORN' => "\xC3\x9E", 'thorn' => "\xC3\xBE", 'THORN;' => "\xC3\x9E", 'thorn;' => "\xC3\xBE", 'tilde;' => "\xCB\x9C", 'times' => "\xC3\x97", 'times;' => "\xC3\x97", 'TRADE;' => "\xE2\x84\xA2", 'trade;' => "\xE2\x84\xA2", 'Uacute' => "\xC3\x9A", 'uacute' => "\xC3\xBA", 'Uacute;' => "\xC3\x9A", 'uacute;' => "\xC3\xBA", 'uArr;' => "\xE2\x87\x91", 'uarr;' => "\xE2\x86\x91", 'Ucirc' => "\xC3\x9B", 'ucirc' => "\xC3\xBB", 'Ucirc;' => "\xC3\x9B", 'ucirc;' => "\xC3\xBB", 'Ugrave' => "\xC3\x99", 'ugrave' => "\xC3\xB9", 'Ugrave;' => "\xC3\x99", 'ugrave;' => "\xC3\xB9", 'uml' => "\xC2\xA8", 'uml;' => "\xC2\xA8", 'upsih;' => "\xCF\x92", 'Upsilon;' => "\xCE\xA5", 'upsilon;' => "\xCF\x85", 'Uuml' => "\xC3\x9C", 'uuml' => "\xC3\xBC", 'Uuml;' => "\xC3\x9C", 'uuml;' => "\xC3\xBC", 'weierp;' => "\xE2\x84\x98", 'Xi;' => "\xCE\x9E", 'xi;' => "\xCE\xBE", 'Yacute' => "\xC3\x9D", 'yacute' => "\xC3\xBD", 'Yacute;' => "\xC3\x9D", 'yacute;' => "\xC3\xBD", 'yen' => "\xC2\xA5", 'yen;' => "\xC2\xA5", 'yuml' => "\xC3\xBF", 'Yuml;' => "\xC5\xB8", 'yuml;' => "\xC3\xBF", 'Zeta;' => "\xCE\x96", 'zeta;' => "\xCE\xB6", 'zwj;' => "\xE2\x80\x8D", 'zwnj;' => "\xE2\x80\x8C");
+
+				for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++)
+				{
+					$consumed = substr($this->consumed, 1);
+					if (isset($entities[$consumed]))
+					{
+						$match = $consumed;
+					}
+				}
+
+				if ($match !== null)
+				{
+ 					$this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1);
+					$this->position += strlen($entities[$match]) - strlen($consumed) - 1;
+				}
+				break;
+		}
+	}
+}
+
+/**
+ * IRI parser/serialiser
+ *
+ * @package SimplePie
+ */
+class SimplePie_IRI
+{
+	/**
+	 * Scheme
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $scheme;
+
+	/**
+	 * User Information
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $userinfo;
+
+	/**
+	 * Host
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $host;
+
+	/**
+	 * Port
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $port;
+
+	/**
+	 * Path
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $path;
+
+	/**
+	 * Query
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $query;
+
+	/**
+	 * Fragment
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $fragment;
+
+	/**
+	 * Whether the object represents a valid IRI
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $valid = array();
+
+	/**
+	 * Return the entire IRI when you try and read the object as a string
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->get_iri();
+	}
+
+	/**
+	 * Create a new IRI object, from a specified string
+	 *
+	 * @access public
+	 * @param string $iri
+	 * @return SimplePie_IRI
+	 */
+	public function __construct($iri)
+	{
+		$iri = (string) $iri;
+		if ($iri !== '')
+		{
+			$parsed = $this->parse_iri($iri);
+			$this->set_scheme($parsed['scheme']);
+			$this->set_authority($parsed['authority']);
+			$this->set_path($parsed['path']);
+			$this->set_query($parsed['query']);
+			$this->set_fragment($parsed['fragment']);
+		}
+	}
+
+	/**
+	 * Create a new IRI object by resolving a relative IRI
+	 *
+	 * @static
+	 * @access public
+	 * @param SimplePie_IRI $base Base IRI
+	 * @param string $relative Relative IRI
+	 * @return SimplePie_IRI
+	 */
+	public static function absolutize($base, $relative)
+	{
+		$relative = (string) $relative;
+		if ($relative !== '')
+		{
+			$relative = new SimplePie_IRI($relative);
+			if ($relative->get_scheme() !== null)
+			{
+				$target = $relative;
+			}
+			elseif ($base->get_iri() !== null)
+			{
+				if ($relative->get_authority() !== null)
+				{
+					$target = $relative;
+					$target->set_scheme($base->get_scheme());
+				}
+				else
+				{
+					$target = new SimplePie_IRI('');
+					$target->set_scheme($base->get_scheme());
+					$target->set_userinfo($base->get_userinfo());
+					$target->set_host($base->get_host());
+					$target->set_port($base->get_port());
+					if ($relative->get_path() !== null)
+					{
+						if (strpos($relative->get_path(), '/') === 0)
+						{
+							$target->set_path($relative->get_path());
+						}
+						elseif (($base->get_userinfo() !== null || $base->get_host() !== null || $base->get_port() !== null) && $base->get_path() === null)
+						{
+							$target->set_path('/' . $relative->get_path());
+						}
+						elseif (($last_segment = strrpos($base->get_path(), '/')) !== false)
+						{
+							$target->set_path(substr($base->get_path(), 0, $last_segment + 1) . $relative->get_path());
+						}
+						else
+						{
+							$target->set_path($relative->get_path());
+						}
+						$target->set_query($relative->get_query());
+					}
+					else
+					{
+						$target->set_path($base->get_path());
+						if ($relative->get_query() !== null)
+						{
+							$target->set_query($relative->get_query());
+						}
+						elseif ($base->get_query() !== null)
+						{
+							$target->set_query($base->get_query());
+						}
+					}
+				}
+				$target->set_fragment($relative->get_fragment());
+			}
+			else
+			{
+				// No base URL, just return the relative URL
+				$target = $relative;
+			}
+		}
+		else
+		{
+			$target = $base;
+		}
+		return $target;
+	}
+
+	/**
+	 * Parse an IRI into scheme/authority/path/query/fragment segments
+	 *
+	 * @access private
+	 * @param string $iri
+	 * @return array
+	 */
+	public function parse_iri($iri)
+	{
+		preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/', $iri, $match);
+		for ($i = count($match); $i <= 9; $i++)
+		{
+			$match[$i] = '';
+		}
+		return array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[7], 'fragment' => $match[9]);
+	}
+
+	/**
+	 * Remove dot segments from a path
+	 *
+	 * @access private
+	 * @param string $input
+	 * @return string
+	 */
+	public function remove_dot_segments($input)
+	{
+		$output = '';
+		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
+		{
+			// A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
+			if (strpos($input, '../') === 0)
+			{
+				$input = substr($input, 3);
+			}
+			elseif (strpos($input, './') === 0)
+			{
+				$input = substr($input, 2);
+			}
+			// B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
+			elseif (strpos($input, '/./') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 3);
+			}
+			elseif ($input === '/.')
+			{
+				$input = '/';
+			}
+			// C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
+			elseif (strpos($input, '/../') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 4);
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			elseif ($input === '/..')
+			{
+				$input = '/';
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			// D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
+			elseif ($input === '.' || $input === '..')
+			{
+				$input = '';
+			}
+			// E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer
+			elseif (($pos = strpos($input, '/', 1)) !== false)
+			{
+				$output .= substr($input, 0, $pos);
+				$input = substr_replace($input, '', 0, $pos);
+			}
+			else
+			{
+				$output .= $input;
+				$input = '';
+			}
+		}
+		return $output . $input;
+	}
+
+	/**
+	 * Replace invalid character with percent encoding
+	 *
+	 * @access private
+	 * @param string $string Input string
+	 * @param string $valid_chars Valid characters
+	 * @param int $case Normalise case
+	 * @return string
+	 */
+	public function replace_invalid_with_pct_encoding($string, $valid_chars, $case = SIMPLEPIE_SAME_CASE)
+	{
+		// Normalise case
+		if ($case & SIMPLEPIE_LOWERCASE)
+		{
+			$string = strtolower($string);
+		}
+		elseif ($case & SIMPLEPIE_UPPERCASE)
+		{
+			$string = strtoupper($string);
+		}
+
+		// Store position and string length (to avoid constantly recalculating this)
+		$position = 0;
+		$strlen = strlen($string);
+
+		// Loop as long as we have invalid characters, advancing the position to the next invalid character
+		while (($position += strspn($string, $valid_chars, $position)) < $strlen)
+		{
+			// If we have a % character
+			if ($string[$position] === '%')
+			{
+				// If we have a pct-encoded section
+				if ($position + 2 < $strlen && strspn($string, '0123456789ABCDEFabcdef', $position + 1, 2) === 2)
+				{
+					// Get the the represented character
+					$chr = chr(hexdec(substr($string, $position + 1, 2)));
+
+					// If the character is valid, replace the pct-encoded with the actual character while normalising case
+					if (strpos($valid_chars, $chr) !== false)
+					{
+						if ($case & SIMPLEPIE_LOWERCASE)
+						{
+							$chr = strtolower($chr);
+						}
+						elseif ($case & SIMPLEPIE_UPPERCASE)
+						{
+							$chr = strtoupper($chr);
+						}
+						$string = substr_replace($string, $chr, $position, 3);
+						$strlen -= 2;
+						$position++;
+					}
+
+					// Otherwise just normalise the pct-encoded to uppercase
+					else
+					{
+						$string = substr_replace($string, strtoupper(substr($string, $position + 1, 2)), $position + 1, 2);
+						$position += 3;
+					}
+				}
+				// If we don't have a pct-encoded section, just replace the % with its own esccaped form
+				else
+				{
+					$string = substr_replace($string, '%25', $position, 1);
+					$strlen += 2;
+					$position += 3;
+				}
+			}
+			// If we have an invalid character, change into its pct-encoded form
+			else
+			{
+				$replacement = sprintf("%%%02X", ord($string[$position]));
+				$string = str_replace($string[$position], $replacement, $string);
+				$strlen = strlen($string);
+			}
+		}
+		return $string;
+	}
+
+	/**
+	 * Check if the object represents a valid IRI
+	 *
+	 * @access public
+	 * @return bool
+	 */
+	public function is_valid()
+	{
+		return array_sum($this->valid) === count($this->valid);
+	}
+
+	/**
+	 * Set the scheme. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $scheme
+	 * @return bool
+	 */
+	public function set_scheme($scheme)
+	{
+		if ($scheme === null || $scheme === '')
+		{
+			$this->scheme = null;
+		}
+		else
+		{
+			$len = strlen($scheme);
+			switch (true)
+			{
+				case $len > 1:
+					if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-.', 1))
+					{
+						$this->scheme = null;
+						$this->valid[__FUNCTION__] = false;
+						return false;
+					}
+
+				case $len > 0:
+					if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 0, 1))
+					{
+						$this->scheme = null;
+						$this->valid[__FUNCTION__] = false;
+						return false;
+					}
+			}
+			$this->scheme = strtolower($scheme);
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the authority. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $authority
+	 * @return bool
+	 */
+	public function set_authority($authority)
+	{
+		if (($userinfo_end = strrpos($authority, '@')) !== false)
+		{
+			$userinfo = substr($authority, 0, $userinfo_end);
+			$authority = substr($authority, $userinfo_end + 1);
+		}
+		else
+		{
+			$userinfo = null;
+		}
+
+		if (($port_start = strpos($authority, ':')) !== false)
+		{
+			$port = substr($authority, $port_start + 1);
+			$authority = substr($authority, 0, $port_start);
+		}
+		else
+		{
+			$port = null;
+		}
+
+		return $this->set_userinfo($userinfo) && $this->set_host($authority) && $this->set_port($port);
+	}
+
+	/**
+	 * Set the userinfo.
+	 *
+	 * @access public
+	 * @param string $userinfo
+	 * @return bool
+	 */
+	public function set_userinfo($userinfo)
+	{
+		if ($userinfo === null || $userinfo === '')
+		{
+			$this->userinfo = null;
+		}
+		else
+		{
+			$this->userinfo = $this->replace_invalid_with_pct_encoding($userinfo, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the host. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $host
+	 * @return bool
+	 */
+	public function set_host($host)
+	{
+		if ($host === null || $host === '')
+		{
+			$this->host = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif ($host[0] === '[' && substr($host, -1) === ']')
+		{
+			if (Net_IPv6::checkIPv6(substr($host, 1, -1)))
+			{
+				$this->host = $host;
+				$this->valid[__FUNCTION__] = true;
+				return true;
+			}
+			else
+			{
+				$this->host = null;
+				$this->valid[__FUNCTION__] = false;
+				return false;
+			}
+		}
+		else
+		{
+			$this->host = $this->replace_invalid_with_pct_encoding($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=', SIMPLEPIE_LOWERCASE);
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+	}
+
+	/**
+	 * Set the port. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $port
+	 * @return bool
+	 */
+	public function set_port($port)
+	{
+		if ($port === null || $port === '')
+		{
+			$this->port = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif (strspn($port, '0123456789') === strlen($port))
+		{
+			$this->port = (int) $port;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		else
+		{
+			$this->port = null;
+			$this->valid[__FUNCTION__] = false;
+			return false;
+		}
+	}
+
+	/**
+	 * Set the path.
+	 *
+	 * @access public
+	 * @param string $path
+	 * @return bool
+	 */
+	public function set_path($path)
+	{
+		if ($path === null || $path === '')
+		{
+			$this->path = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif (substr($path, 0, 2) === '//' && $this->userinfo === null && $this->host === null && $this->port === null)
+		{
+			$this->path = null;
+			$this->valid[__FUNCTION__] = false;
+			return false;
+		}
+		else
+		{
+			$this->path = $this->replace_invalid_with_pct_encoding($path, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=@/');
+			if ($this->scheme !== null)
+			{
+				$this->path = $this->remove_dot_segments($this->path);
+			}
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+	}
+
+	/**
+	 * Set the query.
+	 *
+	 * @access public
+	 * @param string $query
+	 * @return bool
+	 */
+	public function set_query($query)
+	{
+		if ($query === null || $query === '')
+		{
+			$this->query = null;
+		}
+		else
+		{
+			$this->query = $this->replace_invalid_with_pct_encoding($query, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$\'()*+,;:@/?');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the fragment.
+	 *
+	 * @access public
+	 * @param string $fragment
+	 * @return bool
+	 */
+	public function set_fragment($fragment)
+	{
+		if ($fragment === null || $fragment === '')
+		{
+			$this->fragment = null;
+		}
+		else
+		{
+			$this->fragment = $this->replace_invalid_with_pct_encoding($fragment, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:@/?');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Get the complete IRI
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_iri()
+	{
+		$iri = '';
+		if ($this->scheme !== null)
+		{
+			$iri .= $this->scheme . ':';
+		}
+		if (($authority = $this->get_authority()) !== null)
+		{
+			$iri .= '//' . $authority;
+		}
+		if ($this->path !== null)
+		{
+			$iri .= $this->path;
+		}
+		if ($this->query !== null)
+		{
+			$iri .= '?' . $this->query;
+		}
+		if ($this->fragment !== null)
+		{
+			$iri .= '#' . $this->fragment;
+		}
+
+		if ($iri !== '')
+		{
+			return $iri;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Get the scheme
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_scheme()
+	{
+		return $this->scheme;
+	}
+
+	/**
+	 * Get the complete authority
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_authority()
+	{
+		$authority = '';
+		if ($this->userinfo !== null)
+		{
+			$authority .= $this->userinfo . '@';
+		}
+		if ($this->host !== null)
+		{
+			$authority .= $this->host;
+		}
+		if ($this->port !== null)
+		{
+			$authority .= ':' . $this->port;
+		}
+
+		if ($authority !== '')
+		{
+			return $authority;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Get the user information
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_userinfo()
+	{
+		return $this->userinfo;
+	}
+
+	/**
+	 * Get the host
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_host()
+	{
+		return $this->host;
+	}
+
+	/**
+	 * Get the port
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_port()
+	{
+		return $this->port;
+	}
+
+	/**
+	 * Get the path
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_path()
+	{
+		return $this->path;
+	}
+
+	/**
+	 * Get the query
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_query()
+	{
+		return $this->query;
+	}
+
+	/**
+	 * Get the fragment
+	 *
+	 * @access public
+	 * @return string
+	 */
+	public function get_fragment()
+	{
+		return $this->fragment;
+	}
+}
+
+/**
+ * Class to validate and to work with IPv6 addresses.
+ *
+ * @package SimplePie
+ * @copyright 2003-2005 The PHP Group
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/package/Net_IPv6
+ * @author Alexander Merz <alexander.merz@web.de>
+ * @author elfrink at introweb dot nl
+ * @author Josh Peck <jmp at joshpeck dot org>
+ * @author Geoffrey Sneddon <geoffers@gmail.com>
+ */
+class SimplePie_Net_IPv6
+{
+	/**
+	 * Removes a possible existing netmask specification of an IP address.
+	 *
+	 * @param string $ip the (compressed) IP as Hex representation
+	 * @return string the IP the without netmask
+	 * @since 1.1.0
+	 * @access public
+	 * @static
+	 */
+	public static function removeNetmaskSpec($ip)
+	{
+		if (strpos($ip, '/') !== false)
+		{
+			list($addr, $nm) = explode('/', $ip);
+		}
+		else
+		{
+			$addr = $ip;
+		}
+		return $addr;
+	}
+
+	/**
+	 * Uncompresses an IPv6 address
+	 *
+	 * RFC 2373 allows you to compress zeros in an address to '::'. This
+	 * function expects an valid IPv6 address and expands the '::' to
+	 * the required zeros.
+	 *
+	 * Example:	 FF01::101	->	FF01:0:0:0:0:0:0:101
+	 *			 ::1		->	0:0:0:0:0:0:0:1
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address (hex format)
+	 * @return string the uncompressed IPv6-address (hex format)
+	 */
+	public static function Uncompress($ip)
+	{
+		$uip = SimplePie_Net_IPv6::removeNetmaskSpec($ip);
+		$c1 = -1;
+		$c2 = -1;
+		if (strpos($ip, '::') !== false)
+		{
+			list($ip1, $ip2) = explode('::', $ip);
+			if ($ip1 === '')
+			{
+				$c1 = -1;
+			}
+			else
+			{
+				$pos = 0;
+				if (($pos = substr_count($ip1, ':')) > 0)
+				{
+					$c1 = $pos;
+				}
+				else
+				{
+					$c1 = 0;
+				}
+			}
+			if ($ip2 === '')
+			{
+				$c2 = -1;
+			}
+			else
+			{
+				$pos = 0;
+				if (($pos = substr_count($ip2, ':')) > 0)
+				{
+					$c2 = $pos;
+				}
+				else
+				{
+					$c2 = 0;
+				}
+			}
+			if (strstr($ip2, '.'))
+			{
+				$c2++;
+			}
+			// ::
+			if ($c1 === -1 && $c2 === -1)
+			{
+				$uip = '0:0:0:0:0:0:0:0';
+			}
+			// ::xxx
+			else if ($c1 === -1)
+			{
+				$fill = str_repeat('0:', 7 - $c2);
+				$uip =	str_replace('::', $fill, $uip);
+			}
+			// xxx::
+			else if ($c2 === -1)
+			{
+				$fill = str_repeat(':0', 7 - $c1);
+				$uip =	str_replace('::', $fill, $uip);
+			}
+			// xxx::xxx
+			else
+			{
+				$fill = str_repeat(':0:', 6 - $c2 - $c1);
+				$uip =	str_replace('::', $fill, $uip);
+				$uip =	str_replace('::', ':', $uip);
+			}
+		}
+		return $uip;
+	}
+
+	/**
+	 * Splits an IPv6 address into the IPv6 and a possible IPv4 part
+	 *
+	 * RFC 2373 allows you to note the last two parts of an IPv6 address as
+	 * an IPv4 compatible address
+	 *
+	 * Example:	 0:0:0:0:0:0:13.1.68.3
+	 *			 0:0:0:0:0:FFFF:129.144.52.38
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address (hex format)
+	 * @return array [0] contains the IPv6 part, [1] the IPv4 part (hex format)
+	 */
+	public static function SplitV64($ip)
+	{
+		$ip = SimplePie_Net_IPv6::Uncompress($ip);
+		if (strstr($ip, '.'))
+		{
+			$pos = strrpos($ip, ':');
+			$ip[$pos] = '_';
+			$ipPart = explode('_', $ip);
+			return $ipPart;
+		}
+		else
+		{
+			return array($ip, '');
+		}
+	}
+
+	/**
+	 * Checks an IPv6 address
+	 *
+	 * Checks if the given IP is IPv6-compatible
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address
+	 * @return bool true if $ip is an IPv6 address
+	 */
+	public static function checkIPv6($ip)
+	{
+		$ipPart = SimplePie_Net_IPv6::SplitV64($ip);
+		$count = 0;
+		if (!empty($ipPart[0]))
+		{
+			$ipv6 = explode(':', $ipPart[0]);
+			for ($i = 0; $i < count($ipv6); $i++)
+			{
+				$dec = hexdec($ipv6[$i]);
+				$hex = strtoupper(preg_replace('/^[0]{1,3}(.*[0-9a-fA-F])$/', '\\1', $ipv6[$i]));
+				if ($ipv6[$i] >= 0 && $dec <= 65535 && $hex === strtoupper(dechex($dec)))
+				{
+					$count++;
+				}
+			}
+			if ($count === 8)
+			{
+				return true;
+			}
+			elseif ($count === 6 && !empty($ipPart[1]))
+			{
+				$ipv4 = explode('.', $ipPart[1]);
+				$count = 0;
+				foreach ($ipv4 as $ipv4_part)
+				{
+					if ($ipv4_part >= 0 && $ipv4_part <= 255 && preg_match('/^\d{1,3}$/', $ipv4_part))
+					{
+						$count++;
+					}
+				}
+				if ($count === 4)
+				{
+					return true;
+				}
+			}
+			else
+			{
+				return false;
+			}
+
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+/**
+ * Date Parser
+ *
+ * @package SimplePie
+ */
+class SimplePie_Parse_Date
+{
+	/**
+	 * Input data
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $date;
+
+	/**
+	 * List of days, calendar day name => ordinal day number in the week
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $day = array(
+		// English
+		'mon' => 1,
+		'monday' => 1,
+		'tue' => 2,
+		'tuesday' => 2,
+		'wed' => 3,
+		'wednesday' => 3,
+		'thu' => 4,
+		'thursday' => 4,
+		'fri' => 5,
+		'friday' => 5,
+		'sat' => 6,
+		'saturday' => 6,
+		'sun' => 7,
+		'sunday' => 7,
+		// Dutch
+		'maandag' => 1,
+		'dinsdag' => 2,
+		'woensdag' => 3,
+		'donderdag' => 4,
+		'vrijdag' => 5,
+		'zaterdag' => 6,
+		'zondag' => 7,
+		// French
+		'lundi' => 1,
+		'mardi' => 2,
+		'mercredi' => 3,
+		'jeudi' => 4,
+		'vendredi' => 5,
+		'samedi' => 6,
+		'dimanche' => 7,
+		// German
+		'montag' => 1,
+		'dienstag' => 2,
+		'mittwoch' => 3,
+		'donnerstag' => 4,
+		'freitag' => 5,
+		'samstag' => 6,
+		'sonnabend' => 6,
+		'sonntag' => 7,
+		// Italian
+		'lunedì' => 1,
+		'martedì' => 2,
+		'mercoledì' => 3,
+		'giovedì' => 4,
+		'venerdì' => 5,
+		'sabato' => 6,
+		'domenica' => 7,
+		// Spanish
+		'lunes' => 1,
+		'martes' => 2,
+		'miércoles' => 3,
+		'jueves' => 4,
+		'viernes' => 5,
+		'sábado' => 6,
+		'domingo' => 7,
+		// Finnish
+		'maanantai' => 1,
+		'tiistai' => 2,
+		'keskiviikko' => 3,
+		'torstai' => 4,
+		'perjantai' => 5,
+		'lauantai' => 6,
+		'sunnuntai' => 7,
+		// Hungarian
+		'hétfő' => 1,
+		'kedd' => 2,
+		'szerda' => 3,
+		'csütörtok' => 4,
+		'péntek' => 5,
+		'szombat' => 6,
+		'vasárnap' => 7,
+		// Greek
+		'Δευ' => 1,
+		'Τρι' => 2,
+		'Τετ' => 3,
+		'Πεμ' => 4,
+		'Παρ' => 5,
+		'Σαβ' => 6,
+		'Κυρ' => 7,
+	);
+
+	/**
+	 * List of months, calendar month name => calendar month number
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $month = array(
+		// English
+		'jan' => 1,
+		'january' => 1,
+		'feb' => 2,
+		'february' => 2,
+		'mar' => 3,
+		'march' => 3,
+		'apr' => 4,
+		'april' => 4,
+		'may' => 5,
+		// No long form of May
+		'jun' => 6,
+		'june' => 6,
+		'jul' => 7,
+		'july' => 7,
+		'aug' => 8,
+		'august' => 8,
+		'sep' => 9,
+		'september' => 8,
+		'oct' => 10,
+		'october' => 10,
+		'nov' => 11,
+		'november' => 11,
+		'dec' => 12,
+		'december' => 12,
+		// Dutch
+		'januari' => 1,
+		'februari' => 2,
+		'maart' => 3,
+		'april' => 4,
+		'mei' => 5,
+		'juni' => 6,
+		'juli' => 7,
+		'augustus' => 8,
+		'september' => 9,
+		'oktober' => 10,
+		'november' => 11,
+		'december' => 12,
+		// French
+		'janvier' => 1,
+		'février' => 2,
+		'mars' => 3,
+		'avril' => 4,
+		'mai' => 5,
+		'juin' => 6,
+		'juillet' => 7,
+		'août' => 8,
+		'septembre' => 9,
+		'octobre' => 10,
+		'novembre' => 11,
+		'décembre' => 12,
+		// German
+		'januar' => 1,
+		'februar' => 2,
+		'märz' => 3,
+		'april' => 4,
+		'mai' => 5,
+		'juni' => 6,
+		'juli' => 7,
+		'august' => 8,
+		'september' => 9,
+		'oktober' => 10,
+		'november' => 11,
+		'dezember' => 12,
+		// Italian
+		'gennaio' => 1,
+		'febbraio' => 2,
+		'marzo' => 3,
+		'aprile' => 4,
+		'maggio' => 5,
+		'giugno' => 6,
+		'luglio' => 7,
+		'agosto' => 8,
+		'settembre' => 9,
+		'ottobre' => 10,
+		'novembre' => 11,
+		'dicembre' => 12,
+		// Spanish
+		'enero' => 1,
+		'febrero' => 2,
+		'marzo' => 3,
+		'abril' => 4,
+		'mayo' => 5,
+		'junio' => 6,
+		'julio' => 7,
+		'agosto' => 8,
+		'septiembre' => 9,
+		'setiembre' => 9,
+		'octubre' => 10,
+		'noviembre' => 11,
+		'diciembre' => 12,
+		// Finnish
+		'tammikuu' => 1,
+		'helmikuu' => 2,
+		'maaliskuu' => 3,
+		'huhtikuu' => 4,
+		'toukokuu' => 5,
+		'kesäkuu' => 6,
+		'heinäkuu' => 7,
+		'elokuu' => 8,
+		'suuskuu' => 9,
+		'lokakuu' => 10,
+		'marras' => 11,
+		'joulukuu' => 12,
+		// Hungarian
+		'január' => 1,
+		'február' => 2,
+		'március' => 3,
+		'április' => 4,
+		'május' => 5,
+		'június' => 6,
+		'július' => 7,
+		'augusztus' => 8,
+		'szeptember' => 9,
+		'október' => 10,
+		'november' => 11,
+		'december' => 12,
+		// Greek
+		'Ιαν' => 1,
+		'Φεβ' => 2,
+		'Μάώ' => 3,
+		'Μαώ' => 3,
+		'Απρ' => 4,
+		'Μάι' => 5,
+		'Μαϊ' => 5,
+		'Μαι' => 5,
+		'Ιούν' => 6,
+		'Ιον' => 6,
+		'Ιούλ' => 7,
+		'Ιολ' => 7,
+		'Αύγ' => 8,
+		'Αυγ' => 8,
+		'Σεπ' => 9,
+		'Οκτ' => 10,
+		'Νοέ' => 11,
+		'Δεκ' => 12,
+	);
+
+	/**
+	 * List of timezones, abbreviation => offset from UTC
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $timezone = array(
+		'ACDT' => 37800,
+		'ACIT' => 28800,
+		'ACST' => 34200,
+		'ACT' => -18000,
+		'ACWDT' => 35100,
+		'ACWST' => 31500,
+		'AEDT' => 39600,
+		'AEST' => 36000,
+		'AFT' => 16200,
+		'AKDT' => -28800,
+		'AKST' => -32400,
+		'AMDT' => 18000,
+		'AMT' => -14400,
+		'ANAST' => 46800,
+		'ANAT' => 43200,
+		'ART' => -10800,
+		'AZOST' => -3600,
+		'AZST' => 18000,
+		'AZT' => 14400,
+		'BIOT' => 21600,
+		'BIT' => -43200,
+		'BOT' => -14400,
+		'BRST' => -7200,
+		'BRT' => -10800,
+		'BST' => 3600,
+		'BTT' => 21600,
+		'CAST' => 18000,
+		'CAT' => 7200,
+		'CCT' => 23400,
+		'CDT' => -18000,
+		'CEDT' => 7200,
+		'CET' => 3600,
+		'CGST' => -7200,
+		'CGT' => -10800,
+		'CHADT' => 49500,
+		'CHAST' => 45900,
+		'CIST' => -28800,
+		'CKT' => -36000,
+		'CLDT' => -10800,
+		'CLST' => -14400,
+		'COT' => -18000,
+		'CST' => -21600,
+		'CVT' => -3600,
+		'CXT' => 25200,
+		'DAVT' => 25200,
+		'DTAT' => 36000,
+		'EADT' => -18000,
+		'EAST' => -21600,
+		'EAT' => 10800,
+		'ECT' => -18000,
+		'EDT' => -14400,
+		'EEST' => 10800,
+		'EET' => 7200,
+		'EGT' => -3600,
+		'EKST' => 21600,
+		'EST' => -18000,
+		'FJT' => 43200,
+		'FKDT' => -10800,
+		'FKST' => -14400,
+		'FNT' => -7200,
+		'GALT' => -21600,
+		'GEDT' => 14400,
+		'GEST' => 10800,
+		'GFT' => -10800,
+		'GILT' => 43200,
+		'GIT' => -32400,
+		'GST' => 14400,
+		'GST' => -7200,
+		'GYT' => -14400,
+		'HAA' => -10800,
+		'HAC' => -18000,
+		'HADT' => -32400,
+		'HAE' => -14400,
+		'HAP' => -25200,
+		'HAR' => -21600,
+		'HAST' => -36000,
+		'HAT' => -9000,
+		'HAY' => -28800,
+		'HKST' => 28800,
+		'HMT' => 18000,
+		'HNA' => -14400,
+		'HNC' => -21600,
+		'HNE' => -18000,
+		'HNP' => -28800,
+		'HNR' => -25200,
+		'HNT' => -12600,
+		'HNY' => -32400,
+		'IRDT' => 16200,
+		'IRKST' => 32400,
+		'IRKT' => 28800,
+		'IRST' => 12600,
+		'JFDT' => -10800,
+		'JFST' => -14400,
+		'JST' => 32400,
+		'KGST' => 21600,
+		'KGT' => 18000,
+		'KOST' => 39600,
+		'KOVST' => 28800,
+		'KOVT' => 25200,
+		'KRAST' => 28800,
+		'KRAT' => 25200,
+		'KST' => 32400,
+		'LHDT' => 39600,
+		'LHST' => 37800,
+		'LINT' => 50400,
+		'LKT' => 21600,
+		'MAGST' => 43200,
+		'MAGT' => 39600,
+		'MAWT' => 21600,
+		'MDT' => -21600,
+		'MESZ' => 7200,
+		'MEZ' => 3600,
+		'MHT' => 43200,
+		'MIT' => -34200,
+		'MNST' => 32400,
+		'MSDT' => 14400,
+		'MSST' => 10800,
+		'MST' => -25200,
+		'MUT' => 14400,
+		'MVT' => 18000,
+		'MYT' => 28800,
+		'NCT' => 39600,
+		'NDT' => -9000,
+		'NFT' => 41400,
+		'NMIT' => 36000,
+		'NOVST' => 25200,
+		'NOVT' => 21600,
+		'NPT' => 20700,
+		'NRT' => 43200,
+		'NST' => -12600,
+		'NUT' => -39600,
+		'NZDT' => 46800,
+		'NZST' => 43200,
+		'OMSST' => 25200,
+		'OMST' => 21600,
+		'PDT' => -25200,
+		'PET' => -18000,
+		'PETST' => 46800,
+		'PETT' => 43200,
+		'PGT' => 36000,
+		'PHOT' => 46800,
+		'PHT' => 28800,
+		'PKT' => 18000,
+		'PMDT' => -7200,
+		'PMST' => -10800,
+		'PONT' => 39600,
+		'PST' => -28800,
+		'PWT' => 32400,
+		'PYST' => -10800,
+		'PYT' => -14400,
+		'RET' => 14400,
+		'ROTT' => -10800,
+		'SAMST' => 18000,
+		'SAMT' => 14400,
+		'SAST' => 7200,
+		'SBT' => 39600,
+		'SCDT' => 46800,
+		'SCST' => 43200,
+		'SCT' => 14400,
+		'SEST' => 3600,
+		'SGT' => 28800,
+		'SIT' => 28800,
+		'SRT' => -10800,
+		'SST' => -39600,
+		'SYST' => 10800,
+		'SYT' => 7200,
+		'TFT' => 18000,
+		'THAT' => -36000,
+		'TJT' => 18000,
+		'TKT' => -36000,
+		'TMT' => 18000,
+		'TOT' => 46800,
+		'TPT' => 32400,
+		'TRUT' => 36000,
+		'TVT' => 43200,
+		'TWT' => 28800,
+		'UYST' => -7200,
+		'UYT' => -10800,
+		'UZT' => 18000,
+		'VET' => -14400,
+		'VLAST' => 39600,
+		'VLAT' => 36000,
+		'VOST' => 21600,
+		'VUT' => 39600,
+		'WAST' => 7200,
+		'WAT' => 3600,
+		'WDT' => 32400,
+		'WEST' => 3600,
+		'WFT' => 43200,
+		'WIB' => 25200,
+		'WIT' => 32400,
+		'WITA' => 28800,
+		'WKST' => 18000,
+		'WST' => 28800,
+		'YAKST' => 36000,
+		'YAKT' => 32400,
+		'YAPT' => 36000,
+		'YEKST' => 21600,
+		'YEKT' => 18000,
+	);
+
+	/**
+	 * Cached PCRE for SimplePie_Parse_Date::$day
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $day_pcre;
+
+	/**
+	 * Cached PCRE for SimplePie_Parse_Date::$month
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $month_pcre;
+
+	/**
+	 * Array of user-added callback methods
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $built_in = array();
+
+	/**
+	 * Array of user-added callback methods
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $user = array();
+
+	/**
+	 * Create new SimplePie_Parse_Date object, and set self::day_pcre,
+	 * self::month_pcre, and self::built_in
+	 *
+	 * @access private
+	 */
+	public function __construct()
+	{
+		$this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')';
+		$this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')';
+
+		static $cache;
+		if (!isset($cache[get_class($this)]))
+		{
+			$all_methods = get_class_methods($this);
+
+			foreach ($all_methods as $method)
+			{
+				if (strtolower(substr($method, 0, 5)) === 'date_')
+				{
+					$cache[get_class($this)][] = $method;
+				}
+			}
+		}
+
+		foreach ($cache[get_class($this)] as $method)
+		{
+			$this->built_in[] = $method;
+		}
+	}
+
+	/**
+	 * Get the object
+	 *
+	 * @access public
+	 */
+	public static function get()
+	{
+		static $object;
+		if (!$object)
+		{
+			$object = new SimplePie_Parse_Date;
+		}
+		return $object;
+	}
+
+	/**
+	 * Parse a date
+	 *
+	 * @final
+	 * @access public
+	 * @param string $date Date to parse
+	 * @return int Timestamp corresponding to date string, or false on failure
+	 */
+	public function parse($date)
+	{
+		foreach ($this->user as $method)
+		{
+			if (($returned = call_user_func($method, $date)) !== false)
+			{
+				return $returned;
+			}
+		}
+
+		foreach ($this->built_in as $method)
+		{
+			if (($returned = call_user_func(array(&$this, $method), $date)) !== false)
+			{
+				return $returned;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Add a callback method to parse a date
+	 *
+	 * @final
+	 * @access public
+	 * @param callback $callback
+	 */
+	public function add_callback($callback)
+	{
+		if (is_callable($callback))
+		{
+			$this->user[] = $callback;
+		}
+		else
+		{
+			trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
+		}
+	}
+
+	/**
+	 * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
+	 * well as allowing any of upper or lower case "T", horizontal tabs, or
+	 * spaces to be used as the time seperator (including more than one))
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	public function date_w3cdtf($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$year = '([0-9]{4})';
+			$month = $day = $hour = $minute = $second = '([0-9]{2})';
+			$decimal = '([0-9]*)';
+			$zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))';
+			$pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Year
+			2: Month
+			3: Day
+			4: Hour
+			5: Minute
+			6: Second
+			7: Decimal fraction of a second
+			8: Zulu
+			9: Timezone ±
+			10: Timezone hours
+			11: Timezone minutes
+			*/
+
+			// Fill in empty matches
+			for ($i = count($match); $i <= 3; $i++)
+			{
+				$match[$i] = '1';
+			}
+
+			for ($i = count($match); $i <= 7; $i++)
+			{
+				$match[$i] = '0';
+			}
+
+			// Numeric timezone
+			if (isset($match[9]) && $match[9] !== '')
+			{
+				$timezone = $match[10] * 3600;
+				$timezone += $match[11] * 60;
+				if ($match[9] === '-')
+				{
+					$timezone = 0 - $timezone;
+				}
+			}
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Convert the number of seconds to an integer, taking decimals into account
+			$second = round($match[6] + $match[7] / pow(10, strlen($match[7])));
+
+			return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Remove RFC822 comments
+	 *
+	 * @access protected
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	public function remove_rfc2822_comments($string)
+	{
+		$string = (string) $string;
+		$position = 0;
+		$length = strlen($string);
+		$depth = 0;
+
+		$output = '';
+
+		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
+		{
+			$output .= substr($string, $position, $pos - $position);
+			$position = $pos + 1;
+			if ($string[$pos - 1] !== '\\')
+			{
+				$depth++;
+				while ($depth && $position < $length)
+				{
+					$position += strcspn($string, '()', $position);
+					if ($string[$position - 1] === '\\')
+					{
+						$position++;
+						continue;
+					}
+					elseif (isset($string[$position]))
+					{
+						switch ($string[$position])
+						{
+							case '(':
+								$depth++;
+								break;
+
+							case ')':
+								$depth--;
+								break;
+						}
+						$position++;
+					}
+					else
+					{
+						break;
+					}
+				}
+			}
+			else
+			{
+				$output .= '(';
+			}
+		}
+		$output .= substr($string, $position);
+
+		return $output;
+	}
+
+	/**
+	 * Parse RFC2822's date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	public function date_rfc2822($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$wsp = '[\x09\x20]';
+			$fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
+			$optional_fws = $fws . '?';
+			$day_name = $this->day_pcre;
+			$month = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$hour = $minute = $second = '([0-9]{2})';
+			$year = '([0-9]{2,4})';
+			$num_zone = '([+\-])([0-9]{2})([0-9]{2})';
+			$character_zone = '([A-Z]{1,5})';
+			$zone = '(?:' . $num_zone . '|' . $character_zone . ')';
+			$pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i';
+		}
+		if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Day
+			3: Month
+			4: Year
+			5: Hour
+			6: Minute
+			7: Second
+			8: Timezone ±
+			9: Timezone hours
+			10: Timezone minutes
+			11: Alphabetic timezone
+			*/
+
+			// Find the month number
+			$month = $this->month[strtolower($match[3])];
+
+			// Numeric timezone
+			if ($match[8] !== '')
+			{
+				$timezone = $match[9] * 3600;
+				$timezone += $match[10] * 60;
+				if ($match[8] === '-')
+				{
+					$timezone = 0 - $timezone;
+				}
+			}
+			// Character timezone
+			elseif (isset($this->timezone[strtoupper($match[11])]))
+			{
+				$timezone = $this->timezone[strtoupper($match[11])];
+			}
+			// Assume everything else to be -0000
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Deal with 2/3 digit years
+			if ($match[4] < 50)
+			{
+				$match[4] += 2000;
+			}
+			elseif ($match[4] < 1000)
+			{
+				$match[4] += 1900;
+			}
+
+			// Second is optional, if it is empty set it to zero
+			if ($match[7] !== '')
+			{
+				$second = $match[7];
+			}
+			else
+			{
+				$second = 0;
+			}
+
+			return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse RFC850's date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	public function date_rfc850($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$space = '[\x09\x20]+';
+			$day_name = $this->day_pcre;
+			$month = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$year = $hour = $minute = $second = '([0-9]{2})';
+			$zone = '([A-Z]{1,5})';
+			$pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Day
+			3: Month
+			4: Year
+			5: Hour
+			6: Minute
+			7: Second
+			8: Timezone
+			*/
+
+			// Month
+			$month = $this->month[strtolower($match[3])];
+
+			// Character timezone
+			if (isset($this->timezone[strtoupper($match[8])]))
+			{
+				$timezone = $this->timezone[strtoupper($match[8])];
+			}
+			// Assume everything else to be -0000
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Deal with 2 digit year
+			if ($match[4] < 50)
+			{
+				$match[4] += 2000;
+			}
+			else
+			{
+				$match[4] += 1900;
+			}
+
+			return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse C99's asctime()'s date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	public function date_asctime($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$space = '[\x09\x20]+';
+			$wday_name = $this->day_pcre;
+			$mon_name = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$hour = $sec = $min = '([0-9]{2})';
+			$year = '([0-9]{4})';
+			$terminator = '\x0A?\x00?';
+			$pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Month
+			3: Day
+			4: Hour
+			5: Minute
+			6: Second
+			7: Year
+			*/
+
+			$month = $this->month[strtolower($match[2])];
+			return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]);
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse dates using strtotime()
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	public function date_strtotime($date)
+	{
+		$strtotime = strtotime($date);
+		if ($strtotime === -1 || $strtotime === false)
+		{
+			return false;
+		}
+		else
+		{
+			return $strtotime;
+		}
+	}
+}
+
+/**
+ * Content-type sniffing
+ *
+ * @package SimplePie
+ */
+class SimplePie_Content_Type_Sniffer
+{
+	/**
+	 * File object
+	 *
+	 * @var SimplePie_File
+	 * @access private
+	 */
+	var $file;
+
+	/**
+	 * Create an instance of the class with the input file
+	 *
+	 * @access public
+	 * @param SimplePie_Content_Type_Sniffer $file Input file
+	 */
+	public function __construct($file)
+	{
+		$this->file = $file;
+	}
+
+	/**
+	 * Get the Content-Type of the specified file
+	 *
+	 * @access public
+	 * @return string Actual Content-Type
+	 */
+	public function get_type()
+	{
+		if (isset($this->file->headers['content-type']))
+		{
+			if (!isset($this->file->headers['content-encoding'])
+				&& ($this->file->headers['content-type'] === 'text/plain'
+					|| $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1'
+					|| $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1'))
+			{
+				return $this->text_or_binary();
+			}
+
+			if (($pos = strpos($this->file->headers['content-type'], ';')) !== false)
+			{
+				$official = substr($this->file->headers['content-type'], 0, $pos);
+			}
+			else
+			{
+				$official = $this->file->headers['content-type'];
+			}
+			$official = strtolower($official);
+
+			if ($official === 'unknown/unknown'
+				|| $official === 'application/unknown')
+			{
+				return $this->unknown();
+			}
+			elseif (substr($official, -4) === '+xml'
+				|| $official === 'text/xml'
+				|| $official === 'application/xml')
+			{
+				return $official;
+			}
+			elseif (substr($official, 0, 6) === 'image/')
+			{
+				if ($return = $this->image())
+				{
+					return $return;
+				}
+				else
+				{
+					return $official;
+				}
+			}
+			elseif ($official === 'text/html')
+			{
+				return $this->feed_or_html();
+			}
+			else
+			{
+				return $official;
+			}
+		}
+		else
+		{
+			return $this->unknown();
+		}
+	}
+
+	/**
+	 * Sniff text or binary
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	public function text_or_binary()
+	{
+		if (substr($this->file->body, 0, 2) === "\xFE\xFF"
+			|| substr($this->file->body, 0, 2) === "\xFF\xFE"
+			|| substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF"
+			|| substr($this->file->body, 0, 3) === "\xEF\xBB\xBF")
+		{
+			return 'text/plain';
+		}
+		elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body))
+		{
+			return 'application/octect-stream';
+		}
+		else
+		{
+			return 'text/plain';
+		}
+	}
+
+	/**
+	 * Sniff unknown
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	public function unknown()
+	{
+		$ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20");
+		if (strtolower(substr($this->file->body, $ws, 14)) === '<!doctype html'
+			|| strtolower(substr($this->file->body, $ws, 5)) === '<html'
+			|| strtolower(substr($this->file->body, $ws, 7)) === '<script')
+		{
+			return 'text/html';
+		}
+		elseif (substr($this->file->body, 0, 5) === '%PDF-')
+		{
+			return 'application/pdf';
+		}
+		elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-')
+		{
+			return 'application/postscript';
+		}
+		elseif (substr($this->file->body, 0, 6) === 'GIF87a'
+			|| substr($this->file->body, 0, 6) === 'GIF89a')
+		{
+			return 'image/gif';
+		}
+		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
+		{
+			return 'image/png';
+		}
+		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
+		{
+			return 'image/jpeg';
+		}
+		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
+		{
+			return 'image/bmp';
+		}
+		else
+		{
+			return $this->text_or_binary();
+		}
+	}
+
+	/**
+	 * Sniff images
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	public function image()
+	{
+		if (substr($this->file->body, 0, 6) === 'GIF87a'
+			|| substr($this->file->body, 0, 6) === 'GIF89a')
+		{
+			return 'image/gif';
+		}
+		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
+		{
+			return 'image/png';
+		}
+		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
+		{
+			return 'image/jpeg';
+		}
+		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
+		{
+			return 'image/bmp';
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Sniff HTML
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	public function feed_or_html()
+	{
+		$len = strlen($this->file->body);
+		$pos = strspn($this->file->body, "\x09\x0A\x0D\x20");
+
+		while ($pos < $len)
+		{
+			switch ($this->file->body[$pos])
+			{
+				case "\x09":
+				case "\x0A":
+				case "\x0D":
+				case "\x20":
+					$pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos);
+					continue 2;
+
+				case '<':
+					$pos++;
+					break;
+
+				default:
+					return 'text/html';
+			}
+
+			if (substr($this->file->body, $pos, 3) === '!--')
+			{
+				$pos += 3;
+				if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false)
+				{
+					$pos += 3;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 1) === '!')
+			{
+				if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false)
+				{
+					$pos++;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 1) === '?')
+			{
+				if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false)
+				{
+					$pos += 2;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 3) === 'rss'
+				|| substr($this->file->body, $pos, 7) === 'rdf:RDF')
+			{
+				return 'application/rss+xml';
+			}
+			elseif (substr($this->file->body, $pos, 4) === 'feed')
+			{
+				return 'application/atom+xml';
+			}
+			else
+			{
+				return 'text/html';
+			}
+		}
+
+		return 'text/html';
+	}
+}
+
+/**
+ * Parses the XML Declaration
+ *
+ * @package SimplePie
+ */
+class SimplePie_XML_Declaration_Parser
+{
+	/**
+	 * XML Version
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $version = '1.0';
+
+	/**
+	 * Encoding
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $encoding = 'UTF-8';
+
+	/**
+	 * Standalone
+	 *
+	 * @access public
+	 * @var bool
+	 */
+	var $standalone = false;
+
+	/**
+	 * Current state of the state machine
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $state = 'before_version_name';
+
+	/**
+	 * Input data
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Input data length (to avoid calling strlen() everytime this is needed)
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $data_length = 0;
+
+	/**
+	 * Current position of the pointer
+	 *
+	 * @var int
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	public function __construct($data)
+	{
+		$this->data = $data;
+		$this->data_length = strlen($this->data);
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return bool true on success, false on failure
+	 */
+	public function parse()
+	{
+		while ($this->state && $this->state !== 'emit' && $this->has_data())
+		{
+			$state = $this->state;
+			$this->$state();
+		}
+		$this->data = '';
+		if ($this->state === 'emit')
+		{
+			return true;
+		}
+		else
+		{
+			$this->version = '';
+			$this->encoding = '';
+			$this->standalone = '';
+			return false;
+		}
+	}
+
+	/**
+	 * Check whether there is data beyond the pointer
+	 *
+	 * @access private
+	 * @return bool true if there is further data, false if not
+	 */
+	public function has_data()
+	{
+		return (bool) ($this->position < $this->data_length);
+	}
+
+	/**
+	 * Advance past any whitespace
+	 *
+	 * @return int Number of whitespace characters passed
+	 */
+	public function skip_whitespace()
+	{
+		$whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position);
+		$this->position += $whitespace;
+		return $whitespace;
+	}
+
+	/**
+	 * Read value
+	 */
+	public function get_value()
+	{
+		$quote = substr($this->data, $this->position, 1);
+		if ($quote === '"' || $quote === "'")
+		{
+			$this->position++;
+			$len = strcspn($this->data, $quote, $this->position);
+			if ($this->has_data())
+			{
+				$value = substr($this->data, $this->position, $len);
+				$this->position += $len + 1;
+				return $value;
+			}
+		}
+		return false;
+	}
+
+	public function before_version_name()
+	{
+		if ($this->skip_whitespace())
+		{
+			$this->state = 'version_name';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function version_name()
+	{
+		if (substr($this->data, $this->position, 7) === 'version')
+		{
+			$this->position += 7;
+			$this->skip_whitespace();
+			$this->state = 'version_equals';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function version_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'version_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function version_value()
+	{
+		if ($this->version = $this->get_value())
+		{
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = 'encoding_name';
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function encoding_name()
+	{
+		if (substr($this->data, $this->position, 8) === 'encoding')
+		{
+			$this->position += 8;
+			$this->skip_whitespace();
+			$this->state = 'encoding_equals';
+		}
+		else
+		{
+			$this->state = 'standalone_name';
+		}
+	}
+
+	public function encoding_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'encoding_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function encoding_value()
+	{
+		if ($this->encoding = $this->get_value())
+		{
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = 'standalone_name';
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function standalone_name()
+	{
+		if (substr($this->data, $this->position, 10) === 'standalone')
+		{
+			$this->position += 10;
+			$this->skip_whitespace();
+			$this->state = 'standalone_equals';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function standalone_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'standalone_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	public function standalone_value()
+	{
+		if ($standalone = $this->get_value())
+		{
+			switch ($standalone)
+			{
+				case 'yes':
+					$this->standalone = true;
+					break;
+
+				case 'no':
+					$this->standalone = false;
+					break;
+
+				default:
+					$this->state = false;
+					return;
+			}
+
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = false;
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+}
+
+class SimplePie_Locator
+{
+	var $useragent;
+	var $timeout;
+	var $file;
+	var $local = array();
+	var $elsewhere = array();
+	var $file_class = 'SimplePie_File';
+	var $cached_entities = array();
+	var $http_base;
+	var $base;
+	var $base_location = 0;
+	var $checked_feeds = 0;
+	var $max_checked_feeds = 10;
+	var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer';
+
+	public function __construct(&$file, $timeout = 10, $useragent = null, $file_class = 'SimplePie_File', $max_checked_feeds = 10, $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer')
+	{
+		$this->file =& $file;
+		$this->file_class = $file_class;
+		$this->useragent = $useragent;
+		$this->timeout = $timeout;
+		$this->max_checked_feeds = $max_checked_feeds;
+		$this->content_type_sniffer_class = $content_type_sniffer_class;
+	}
+
+	public function find($type = SIMPLEPIE_LOCATOR_ALL, &$working)
+	{
+		if ($this->is_feed($this->file))
+		{
+			return $this->file;
+		}
+
+		if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
+		{
+			$sniffer = new $this->content_type_sniffer_class($this->file);
+			if ($sniffer->get_type() !== 'text/html')
+			{
+				return null;
+			}
+		}
+
+		if ($type & ~SIMPLEPIE_LOCATOR_NONE)
+		{
+			$this->get_base();
+		}
+
+		if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery())
+		{
+			return $working[0];
+		}
+
+		if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links())
+		{
+			if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere))
+			{
+				return $working;
+			}
+		}
+		return null;
+	}
+
+	public function is_feed(&$file)
+	{
+		if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
+		{
+			$sniffer = new $this->content_type_sniffer_class($file);
+			$sniffed = $sniffer->get_type();
+			if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml')))
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+		elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	public function get_base()
+	{
+		$this->http_base = $this->file->url;
+		$this->base = $this->http_base;
+		$elements = SimplePie_Misc::get_element('base', $this->file->body);
+		foreach ($elements as $element)
+		{
+			if ($element['attribs']['href']['data'] !== '')
+			{
+				$this->base = SimplePie_Misc::absolutize_url(trim($element['attribs']['href']['data']), $this->http_base);
+				$this->base_location = $element['offset'];
+				break;
+			}
+		}
+	}
+
+	public function autodiscovery()
+	{
+		$links = array_merge(SimplePie_Misc::get_element('link', $this->file->body), SimplePie_Misc::get_element('a', $this->file->body), SimplePie_Misc::get_element('area', $this->file->body));
+		$done = array();
+		$feeds = array();
+		foreach ($links as $link)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (isset($link['attribs']['href']['data']) && isset($link['attribs']['rel']['data']))
+			{
+				$rel = array_unique(SimplePie_Misc::space_seperated_tokens(strtolower($link['attribs']['rel']['data'])));
+
+				if ($this->base_location < $link['offset'])
+				{
+					$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base);
+				}
+				else
+				{
+					$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base);
+				}
+
+				if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !empty($link['attribs']['type']['data']) && in_array(strtolower(SimplePie_Misc::parse_mime($link['attribs']['type']['data'])), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href]))
+				{
+					$this->checked_feeds++;
+					$feed = new $this->file_class($href, $this->timeout, 5, null, $this->useragent);
+					if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+					{
+						$feeds[$href] = $feed;
+					}
+				}
+				$done[] = $href;
+			}
+		}
+
+		if (!empty($feeds))
+		{
+			return array_values($feeds);
+		}
+		else {
+			return null;
+		}
+	}
+
+	public function get_links()
+	{
+		$links = SimplePie_Misc::get_element('a', $this->file->body);
+		foreach ($links as $link)
+		{
+			if (isset($link['attribs']['href']['data']))
+			{
+				$href = trim($link['attribs']['href']['data']);
+				$parsed = SimplePie_Misc::parse_url($href);
+				if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme']))
+				{
+					if ($this->base_location < $link['offset'])
+					{
+						$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base);
+					}
+					else
+					{
+						$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base);
+					}
+
+					$current = SimplePie_Misc::parse_url($this->file->url);
+
+					if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority'])
+					{
+						$this->local[] = $href;
+					}
+					else
+					{
+						$this->elsewhere[] = $href;
+					}
+				}
+			}
+		}
+		$this->local = array_unique($this->local);
+		$this->elsewhere = array_unique($this->elsewhere);
+		if (!empty($this->local) || !empty($this->elsewhere))
+		{
+			return true;
+		}
+		return null;
+	}
+
+	public function extension(&$array)
+	{
+		foreach ($array as $key => $value)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml')))
+			{
+				$this->checked_feeds++;
+				$feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent);
+				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+				{
+					return $feed;
+				}
+				else
+				{
+					unset($array[$key]);
+				}
+			}
+		}
+		return null;
+	}
+
+	public function body(&$array)
+	{
+		foreach ($array as $key => $value)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (preg_match('/(rss|rdf|atom|xml)/i', $value))
+			{
+				$this->checked_feeds++;
+				$feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent);
+				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+				{
+					return $feed;
+				}
+				else
+				{
+					unset($array[$key]);
+				}
+			}
+		}
+		return null;
+	}
+}
+
+class SimplePie_Parser
+{
+	var $error_code;
+	var $error_string;
+	var $current_line;
+	var $current_column;
+	var $current_byte;
+	var $separator = ' ';
+	var $namespace = array('');
+	var $element = array('');
+	var $xml_base = array('');
+	var $xml_base_explicit = array(false);
+	var $xml_lang = array('');
+	var $data = array();
+	var $datas = array(array());
+	var $current_xhtml_construct = -1;
+	var $encoding;
+
+	public function parse(&$data, $encoding)
+	{
+		// Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
+		if (strtoupper($encoding) === 'US-ASCII')
+		{
+			$this->encoding = 'UTF-8';
+		}
+		else
+		{
+			$this->encoding = $encoding;
+		}
+
+		// Strip BOM:
+		// UTF-32 Big Endian BOM
+		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
+		{
+			$data = substr($data, 4);
+		}
+		// UTF-32 Little Endian BOM
+		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
+		{
+			$data = substr($data, 4);
+		}
+		// UTF-16 Big Endian BOM
+		elseif (substr($data, 0, 2) === "\xFE\xFF")
+		{
+			$data = substr($data, 2);
+		}
+		// UTF-16 Little Endian BOM
+		elseif (substr($data, 0, 2) === "\xFF\xFE")
+		{
+			$data = substr($data, 2);
+		}
+		// UTF-8 BOM
+		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
+		{
+			$data = substr($data, 3);
+		}
+
+		if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false)
+		{
+			$declaration = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
+			if ($declaration->parse())
+			{
+				$data = substr($data, $pos + 2);
+				$data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data;
+			}
+			else
+			{
+				$this->error_string = 'SimplePie bug! Please report this!';
+				return false;
+			}
+		}
+
+		$return = true;
+
+		static $xml_is_sane = null;
+		if ($xml_is_sane === null)
+		{
+			$parser_check = xml_parser_create();
+			xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
+			xml_parser_free($parser_check);
+			$xml_is_sane = isset($values[0]['value']);
+		}
+
+		// Create the parser
+		if ($xml_is_sane)
+		{
+			$xml = xml_parser_create_ns($this->encoding, $this->separator);
+			xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
+			xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
+			xml_set_object($xml, $this);
+			xml_set_character_data_handler($xml, 'cdata');
+			xml_set_element_handler($xml, 'tag_open', 'tag_close');
+
+			// Parse!
+			if (!xml_parse($xml, $data, true))
+			{
+				$this->error_code = xml_get_error_code($xml);
+				$this->error_string = xml_error_string($this->error_code);
+				$return = false;
+			}
+			$this->current_line = xml_get_current_line_number($xml);
+			$this->current_column = xml_get_current_column_number($xml);
+			$this->current_byte = xml_get_current_byte_index($xml);
+			xml_parser_free($xml);
+			return $return;
+		}
+		else
+		{
+			libxml_clear_errors();
+			$xml = new XMLReader();
+			$xml->xml($data);
+			while (@$xml->read())
+			{
+				switch ($xml->nodeType)
+				{
+
+					case constant('XMLReader::END_ELEMENT'):
+						if ($xml->namespaceURI !== '')
+						{
+							$tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+						}
+						else
+						{
+							$tagName = $xml->localName;
+						}
+						$this->tag_close(null, $tagName);
+						break;
+					case constant('XMLReader::ELEMENT'):
+						$empty = $xml->isEmptyElement;
+						if ($xml->namespaceURI !== '')
+						{
+							$tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+						}
+						else
+						{
+							$tagName = $xml->localName;
+						}
+						$attributes = array();
+						while ($xml->moveToNextAttribute())
+						{
+							if ($xml->namespaceURI !== '')
+							{
+								$attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+							}
+							else
+							{
+								$attrName = $xml->localName;
+							}
+							$attributes[$attrName] = $xml->value;
+						}
+						$this->tag_open(null, $tagName, $attributes);
+						if ($empty)
+						{
+							$this->tag_close(null, $tagName);
+						}
+						break;
+					case constant('XMLReader::TEXT'):
+
+					case constant('XMLReader::CDATA'):
+						$this->cdata(null, $xml->value);
+						break;
+				}
+			}
+			if ($error = libxml_get_last_error())
+			{
+				$this->error_code = $error->code;
+				$this->error_string = $error->message;
+				$this->current_line = $error->line;
+				$this->current_column = $error->column;
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+		}
+	}
+
+	public function get_error_code()
+	{
+		return $this->error_code;
+	}
+
+	public function get_error_string()
+	{
+		return $this->error_string;
+	}
+
+	public function get_current_line()
+	{
+		return $this->current_line;
+	}
+
+	public function get_current_column()
+	{
+		return $this->current_column;
+	}
+
+	public function get_current_byte()
+	{
+		return $this->current_byte;
+	}
+
+	public function get_data()
+	{
+		return $this->data;
+	}
+
+	public function tag_open($parser, $tag, $attributes)
+	{
+		list($this->namespace[], $this->element[]) = $this->split_ns($tag);
+
+		$attribs = array();
+		foreach ($attributes as $name => $value)
+		{
+			list($attrib_namespace, $attribute) = $this->split_ns($name);
+			$attribs[$attrib_namespace][$attribute] = $value;
+		}
+
+		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base']))
+		{
+			$this->xml_base[] = SimplePie_Misc::absolutize_url($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base));
+			$this->xml_base_explicit[] = true;
+		}
+		else
+		{
+			$this->xml_base[] = end($this->xml_base);
+			$this->xml_base_explicit[] = end($this->xml_base_explicit);
+		}
+
+		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang']))
+		{
+			$this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang'];
+		}
+		else
+		{
+			$this->xml_lang[] = end($this->xml_lang);
+		}
+
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->current_xhtml_construct++;
+			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML)
+			{
+				$this->data['data'] .= '<' . end($this->element);
+				if (isset($attribs['']))
+				{
+					foreach ($attribs[''] as $name => $value)
+					{
+						$this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"';
+					}
+				}
+				$this->data['data'] .= '>';
+			}
+		}
+		else
+		{
+			$this->datas[] =& $this->data;
+			$this->data =& $this->data['child'][end($this->namespace)][end($this->element)][];
+			$this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang));
+			if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml')
+			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml'))
+			{
+				$this->current_xhtml_construct = 0;
+			}
+		}
+	}
+
+	public function cdata($parser, $cdata)
+	{
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding);
+		}
+		else
+		{
+			$this->data['data'] .= $cdata;
+		}
+	}
+
+	public function tag_close($parser, $tag)
+	{
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->current_xhtml_construct--;
+			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param')))
+			{
+				$this->data['data'] .= '</' . end($this->element) . '>';
+			}
+		}
+		if ($this->current_xhtml_construct === -1)
+		{
+			$this->data =& $this->datas[count($this->datas) - 1];
+			array_pop($this->datas);
+		}
+
+		array_pop($this->element);
+		array_pop($this->namespace);
+		array_pop($this->xml_base);
+		array_pop($this->xml_base_explicit);
+		array_pop($this->xml_lang);
+	}
+
+	public function split_ns($string)
+	{
+		static $cache = array();
+		if (!isset($cache[$string]))
+		{
+			if ($pos = strpos($string, $this->separator))
+			{
+				static $separator_length;
+				if (!$separator_length)
+				{
+					$separator_length = strlen($this->separator);
+				}
+				$namespace = substr($string, 0, $pos);
+				$local_name = substr($string, $pos + $separator_length);
+				if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES)
+				{
+					$namespace = SIMPLEPIE_NAMESPACE_ITUNES;
+				}
+
+				// Normalize the Media RSS namespaces
+				if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG ||
+					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2 ||
+					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3 ||
+					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4 ||
+					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5 )
+				{
+					$namespace = SIMPLEPIE_NAMESPACE_MEDIARSS;
+				}
+				$cache[$string] = array($namespace, $local_name);
+			}
+			else
+			{
+				$cache[$string] = array('', $string);
+			}
+		}
+		return $cache[$string];
+	}
+}
+
+/**
+ * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags
+ */
+class SimplePie_Sanitize
+{
+	// Private vars
+	var $base;
+
+	// Options
+	var $remove_div = true;
+	var $image_handler = '';
+	var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');
+	var $encode_instead_of_strip = false;
+	var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
+	var $strip_comments = false;
+	var $output_encoding = 'UTF-8';
+	var $enable_cache = true;
+	var $cache_location = './cache';
+	var $cache_name_function = 'md5';
+	var $cache_class = 'SimplePie_Cache';
+	var $file_class = 'SimplePie_File';
+	var $timeout = 10;
+	var $useragent = '';
+	var $force_fsockopen = false;
+
+	var $replace_url_attributes = array(
+		'a' => 'href',
+		'area' => 'href',
+		'blockquote' => 'cite',
+		'del' => 'cite',
+		'form' => 'action',
+		'img' => array('longdesc', 'src'),
+		'input' => 'src',
+		'ins' => 'cite',
+		'q' => 'cite'
+	);
+
+	public function remove_div($enable = true)
+	{
+		$this->remove_div = (bool) $enable;
+	}
+
+	public function set_image_handler($page = false)
+	{
+		if ($page)
+		{
+			$this->image_handler = (string) $page;
+		}
+		else
+		{
+			$this->image_handler = false;
+		}
+	}
+
+	public function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache')
+	{
+		if (isset($enable_cache))
+		{
+			$this->enable_cache = (bool) $enable_cache;
+		}
+
+		if ($cache_location)
+		{
+			$this->cache_location = (string) $cache_location;
+		}
+
+		if ($cache_name_function)
+		{
+			$this->cache_name_function = (string) $cache_name_function;
+		}
+
+		if ($cache_class)
+		{
+			$this->cache_class = (string) $cache_class;
+		}
+	}
+
+	public function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false)
+	{
+		if ($file_class)
+		{
+			$this->file_class = (string) $file_class;
+		}
+
+		if ($timeout)
+		{
+			$this->timeout = (string) $timeout;
+		}
+
+		if ($useragent)
+		{
+			$this->useragent = (string) $useragent;
+		}
+
+		if ($force_fsockopen)
+		{
+			$this->force_fsockopen = (string) $force_fsockopen;
+		}
+	}
+
+	public function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'))
+	{
+		if ($tags)
+		{
+			if (is_array($tags))
+			{
+				$this->strip_htmltags = $tags;
+			}
+			else
+			{
+				$this->strip_htmltags = explode(',', $tags);
+			}
+		}
+		else
+		{
+			$this->strip_htmltags = false;
+		}
+	}
+
+	public function encode_instead_of_strip($encode = false)
+	{
+		$this->encode_instead_of_strip = (bool) $encode;
+	}
+
+	public function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'))
+	{
+		if ($attribs)
+		{
+			if (is_array($attribs))
+			{
+				$this->strip_attributes = $attribs;
+			}
+			else
+			{
+				$this->strip_attributes = explode(',', $attribs);
+			}
+		}
+		else
+		{
+			$this->strip_attributes = false;
+		}
+	}
+
+	public function strip_comments($strip = false)
+	{
+		$this->strip_comments = (bool) $strip;
+	}
+
+	public function set_output_encoding($encoding = 'UTF-8')
+	{
+		$this->output_encoding = (string) $encoding;
+	}
+
+	/**
+	 * Set element/attribute key/value pairs of HTML attributes
+	 * containing URLs that need to be resolved relative to the feed
+	 *
+	 * @access public
+	 * @since 1.0
+	 * @param array $element_attribute Element/attribute key/value pairs
+	 */
+	public function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'))
+	{
+		$this->replace_url_attributes = (array) $element_attribute;
+	}
+
+	public function sanitize($data, $type, $base = '')
+	{
+		$data = trim($data);
+		if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI)
+		{
+			if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML)
+			{
+				if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data))
+				{
+					$type |= SIMPLEPIE_CONSTRUCT_HTML;
+				}
+				else
+				{
+					$type |= SIMPLEPIE_CONSTRUCT_TEXT;
+				}
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_BASE64)
+			{
+				$data = base64_decode($data);
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_XHTML)
+			{
+				if ($this->remove_div)
+				{
+					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '', $data);
+					$data = preg_replace('/<\/div>$/', '', $data);
+				}
+				else
+				{
+					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '<div>', $data);
+				}
+			}
+
+			if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML))
+			{
+				// Strip comments
+				if ($this->strip_comments)
+				{
+					$data = SimplePie_Misc::strip_comments($data);
+				}
+
+				// Strip out HTML tags and attributes that might cause various security problems.
+				// Based on recommendations by Mark Pilgrim at:
+				// http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely
+				if ($this->strip_htmltags)
+				{
+					foreach ($this->strip_htmltags as $tag)
+					{
+						$pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU';
+						while (preg_match($pcre, $data))
+						{
+							$data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data);
+						}
+					}
+				}
+
+				if ($this->strip_attributes)
+				{
+					foreach ($this->strip_attributes as $attrib)
+					{
+						$data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data);
+					}
+				}
+
+				// Replace relative URLs
+				$this->base = $base;
+				foreach ($this->replace_url_attributes as $element => $attributes)
+				{
+					$data = $this->replace_urls($data, $element, $attributes);
+				}
+
+				// If image handling (caching, etc.) is enabled, cache and rewrite all the image tags.
+				if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache)
+				{
+					$images = SimplePie_Misc::get_element('img', $data);
+					foreach ($images as $img)
+					{
+						if (isset($img['attribs']['src']['data']))
+						{
+							$image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']);
+							$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $image_url, 'spi');
+
+							if ($cache->load())
+							{
+								$img['attribs']['src']['data'] = $this->image_handler . $image_url;
+								$data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+							}
+							else
+							{
+								$file = new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen);
+								$headers = $file->headers;
+
+								if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
+								{
+									if ($cache->save(array('headers' => $file->headers, 'body' => $file->body)))
+									{
+										$img['attribs']['src']['data'] = $this->image_handler . $image_url;
+										$data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+									}
+									else
+									{
+										trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
+									}
+								}
+							}
+						}
+					}
+				}
+
+				// Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data
+				$data = trim($data);
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_IRI)
+			{
+				$data = SimplePie_Misc::absolutize_url($data, $base);
+			}
+
+			if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI))
+			{
+				$data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8');
+			}
+
+			if ($this->output_encoding !== 'UTF-8')
+			{
+				$data = SimplePie_Misc::change_encoding($data, 'UTF-8', $this->output_encoding);
+			}
+		}
+		return $data;
+	}
+
+	public function replace_urls($data, $tag, $attributes)
+	{
+		if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags))
+		{
+			$elements = SimplePie_Misc::get_element($tag, $data);
+			foreach ($elements as $element)
+			{
+				if (is_array($attributes))
+				{
+					foreach ($attributes as $attribute)
+					{
+						if (isset($element['attribs'][$attribute]['data']))
+						{
+							$element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], $this->base);
+							$new_element = SimplePie_Misc::element_implode($element);
+							$data = str_replace($element['full'], $new_element, $data);
+							$element['full'] = $new_element;
+						}
+					}
+				}
+				elseif (isset($element['attribs'][$attributes]['data']))
+				{
+					$element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], $this->base);
+					$data = str_replace($element['full'], SimplePie_Misc::element_implode($element), $data);
+				}
+			}
+		}
+		return $data;
+	}
+
+	public function do_strip_htmltags($match)
+	{
+		if ($this->encode_instead_of_strip)
+		{
+			if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
+			{
+				$match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8');
+				$match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8');
+				return "&lt;$match[1]$match[2]&gt;$match[3]&lt;/$match[1]&gt;";
+			}
+			else
+			{
+				return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8');
+			}
+		}
+		elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
+		{
+			return $match[4];
+		}
+		else
+		{
+			return '';
+		}
+	}
+}
Index: /trunk/libs/extensions/GenericPHPConfig/class.metadata.php
===================================================================
--- /trunk/libs/extensions/GenericPHPConfig/class.metadata.php	(revision 1081)
+++ /trunk/libs/extensions/GenericPHPConfig/class.metadata.php	(revision 1081)
@@ -0,0 +1,440 @@
+<?php
+/**
+ * api: php
+ * type: functions
+ * title: plugin meta data
+ * description: extracts .php plugin meta info, handles dependencies
+ * priority: never
+ * category: library
+ * author: milky
+ * license: Public Domain
+ * version: 1.4
+ * 
+ * 
+ *  Utility code for reading plugin meta data (pmd) and
+ *  dependencies.
+ *
+ *  Upon reading all .php plugin files, it builds up two lists.
+ *  One containing all plugin description data ($this->plugin),
+ *  and the other with configsetting values ($this->config).
+ *  Both are indexed by plugin $id or config/varname.
+ *  Additionaly lists for dependencies are then extracted, too.
+ *
+ *  http://en.php-resource.de/scripte/script,582,Generic-PHP-Config-and-Plugin-System.htm
+ */
+
+
+
+/**
+ * Reads in plugin .php files, and parses meta data header (see top
+ * comment of this one for an example). Provides data normalization
+ * and some grouping functions, resolves some dependencies.
+ *
+ */
+class generic_pmd {
+
+
+   /**
+    * plugin meta data
+    *
+    */
+   var $plugin = array();      // id->row hash, with individual info fields
+   var $config = array();      // extracted config variables
+
+   /**
+    * separated dependency fields
+    * hash id->(id,id,...)
+    *
+    */   
+   var $depends = array();     // enforced dependencies, +virtnames
+   var $replaces = array();    // negative dependencies, +virtnames
+   var $suggests = array();    // relaxed dep
+   var $conflicts = array();   // similar to $replaces, but keeps current plugin off if other enabled
+
+   /**
+    * virtual plugin names are used by depends: and replaces:
+    *
+    */
+   var $provides = array();    // hash alias->(id,id,id,) list
+   
+   
+   /**
+    * allows to read in WordPress plugin comment too,
+    * http://codex.wordpress.org/Writing_a_Plugin
+    *
+    */
+   var $wp_compat = array(
+      "plugin name" => "title",
+      "description" => "description",
+      "plugin uri" => "url",
+      "author uri" => "author_url",
+   );
+
+
+
+   /**
+    * constructor stub
+    *
+    */
+   function generic_pmd() {
+      
+#      $this->pmd = & $this->plugin;   // reference (old name)
+      
+   }
+
+
+
+
+   /**
+    * get meta info data from a single .php plugin
+    * - we only read first 4KiB of file
+    *
+    */
+   function read($fn, $size=4096) {
+   
+      // read from given filename
+      if (file_exists($fn) and ($f = fopen($fn, "r"))) {
+         $src = fread($f, $size);
+         fclose($f);
+         $src .= "\n*/?>";   // add this, else parsing for /*..*/ might fail, because file possible shortened to 4096 byte
+         return $this->parse($src);
+      }
+      // file not found/readable
+      else {
+      	   echo "uh-oh, file '" . $fn . "' not found in class.metadata.php";
+         //return(NULL);
+      }
+   }
+
+
+   
+   /**
+    * Parses out meta informations from a .php script
+    * - does not add [fn] filename itself
+    * - does not parse [config] itself
+    * the data ends up in $this->plugin[] sorted by $id
+    *
+    * individual entries typically contain:
+    * [$id] => array(
+    *    "id" => "$id",
+    *    "title" => "plugin title/name",
+    *    "api" => "php",
+    *    "description" => "...",
+    *    "type" => "class",
+    *    "categoriy" => "system",
+    *    "version" => "1.0",
+    *    "license" => "PHPL",
+    *    "author" => "name <mail@localhost>",
+    *    "homepage" => "http://example.com/author-homepage.htm",
+    *    "url" => "http://example.com/plugins-homepage.htm",
+    *    "priority" => "optional",
+    *    "fn" => "local/path/to/plugin/filename.php",    // later added by ->scan()
+    *    "depends" => "x, y, plugins",
+    *    "update" => "ftp://example.com/bin/downl.asis",
+    *    "sort" => "0",
+    *    "config" => " ... remains here as text <var name=app[set] ... /> ",
+    *    "...",
+    *    "help" => "longer text \n usually spanning \n multiple lines \n ...",
+    * )
+    *
+    */
+   function parse($src) {
+      $info = array();
+      
+      #-- first comment block
+      $src = $this->extract_first_comment($src);
+       
+      #-- find empty line and split cfg:block from help text part
+      if (preg_match("/^(.+?)\n[ \t]*\n(.+)$/s", $src, $uu)) {
+         $src = $uu[1];
+
+         // add second part as help text
+         $info["help"] = trim($uu[2]);
+      }
+
+
+      #-- read lines and name:value pairs
+      preg_match_all("/^(\w+(?: \w+)?):\s*([^\n]*(\n[ ]+[^\n]+)*)/m", $src, $uu);
+      /*                    |                    |
+                       this (?: \w+) is just for compatibility with wordpress plugin comments
+                                                 |
+                       lines with leading spaces (\n[ ]+ hold continuing description values
+      */
+ 
+      #-- add each line after trimming outer whitespace
+      foreach ($uu[1] as $i=>$tmp) {
+         $info[strtolower($uu[1][$i])] = trim($uu[2][$i]);
+      }
+
+      #-- ok - folder used for plugins, name used for themes
+      if (isset($info["folder"]) || isset($info["name"])) {
+         return($info);
+      }
+   }
+
+
+
+   /**
+    * gets first block of asterisk /* comment or # hash or // slash
+    * comment, removes leading whitespace and comment characters
+    *
+    */
+   function extract_first_comment($src) {
+
+      #-- clean out first line
+      $src = preg_replace("/^<\?(php)?[^\n]*/i", "", $src);
+
+      #-- extract /* ... */ comment block
+      #  or lines of #... #... and //... //...
+      if (preg_match("_^\s*/\*+(.+?)\*+/_s", $src, $uu)
+      or (preg_match("_^\s*((^\s*(#+|//+)\s*.+?$\n)+)_ms", $src, $uu))) {
+         $src = $uu[1];
+      }
+      else {
+         return;
+      }
+
+      #-- cut comment/whitespace prefixes like _*__ or  __#_ or _//__ from
+      #   lines - with same length from everyone! - don't care about actual
+      #   pattern, but allow shortened lines (missing spaces after # or *)
+      preg_match("_^([*#/ ]+)\w+( \w+)?:_m", $src, $uu);
+      $n = strlen($uu[1]);
+      $src = preg_replace("_^[*#/ ]{0,$n}_m", "", $src);
+
+      return($src);
+   }
+
+
+
+
+
+
+   /**
+    * Reads in .php plugin meta data from given directory and subdirectories.
+    * (Three levels actually.)
+    * The data gets stored into ->$plugin for later use, augmented by every
+    * plugins [fn] relative to the supplied basedir.
+    *
+    */
+   function scan($basedir) {
+      
+      #-- reading in
+      $basedir = realpath($basedir);
+      
+      #-- each file
+      foreach ($this->scan_subdirs($basedir) as $num=>$fn) {
+      
+         #-- basename == id
+         $id = basename($fn, ".php");
+
+         #-- parse
+         if ($e = $this->read($fn)) {
+         
+            #-- has plugin custom set id: ?  (should not happen, but who knows if this might be useful?)
+            if (!empty($e["id"])) {
+               $id = $e["id"];
+            }
+            else {
+               $e["id"] = $id;
+            }
+	    
+	    #-- WordPress plugin scheme compatibility
+	    if ($e["plugin name"]) {
+	       foreach ($this->wp_compat as $from=>$to) if (!isset($e[$to])) {
+                  $e[$to] = $e[$from];
+                  unset($e[$from]);
+               }
+               $e["api"] = "wordpress";
+	    }
+
+            #-- add localized filename
+            $fn = substr($fn, strlen($basedir)+1);
+            $e["fn"] = $fn;
+            
+            #-- append to list 
+            if (isset($this->plugin[$id])) {
+               $this->error("a plugin with the name '$id' is already registered (second_fn=$fn, registered={$this->plugin[$id][fn]})\n");
+            }
+            else {
+               $this->plugin[$id] = $e;
+            }
+         }
+      }
+
+      // plugin dependencies
+      $this->extract_lists();
+
+      // send it back even if probably unused
+      return $this->plugin;
+   }
+
+
+
+   /**
+    * separates dependency fields and config variables out of all
+    * plugin entries
+    *
+    */
+   function extract_lists() {
+
+      #-- extract list fields
+      $fields = array("depends", "suggests", "replaces", "conflicts");
+      foreach ($this->plugin as $id=>$e) {
+         
+         #-- provides:
+         if ($e["provides"]) foreach (explode(",",$e["provides"]) as $set) if ($set=trim($set)) {
+            $this->provides[$set][] = $id;    // only the first gets used
+         }
+         
+         #-- depends:
+         foreach ($fields as $field) {
+            if (isset($e[$field])) foreach (explode(",",$e["depends"]) as $set) if ($set=trim($set)) {
+               $this->{$field}[$id][] = $set;
+            }
+         }
+         
+         #-- config:
+         if ($e["config"]) {
+            # currently doesn't get unfold into plugin $e entry,
+            # but just into $this->config[] list
+            #
+            $cfg_txt = $e["config"];
+            // $e["config"] = array();
+            foreach ($this->parse_options($cfg_txt, $id) as $opt) {
+               $this->config[$opt["name"]] = $opt;
+               // $e["config"][$opt["name]] = & $this->config[$opt["name"]];
+            }
+         }
+
+      }
+   }
+
+
+   
+   /**
+    * look for .php files in subdirectories
+    *
+    */
+   function scan_subdirs($basedir) {
+      $r = array();
+      if ($dh = opendir($basedir)) {
+         while ($fn = readdir($dh)) {
+            if ($fn[0] == ".") {
+            }
+            elseif (is_dir("$basedir/$fn")) {
+               foreach ($this->scan_subdirs("$basedir/$fn") as $fn) {
+                  $r[] = $fn;
+               }
+            }
+            elseif (strpos($fn, ".php")) {
+               $r[] = "$basedir/$fn";
+            }
+         }
+         closedir($dh);
+      }
+      return $r;
+      #glob("$basedir/*.php") + glob("$basedir/*/*.php") + glob("$basedir/*/*/*.php") + glob("$basedir/*/*/*/*.php");
+   }
+
+
+   /**
+    * extract config: options pseudo XML into array,
+    * returns an array per config option / varname
+    *
+    * <var name="app[setting]" value="default_1" title=".." />
+    *   ->
+    * array(
+    *   "is" => "var",
+    *   "plugin" => "config_option_was_found_in_this_php_plugin_script",
+    *   "name" => "app[setting]",
+    *   "value" => "default_1",
+    *   "title" => "name of first setting",
+    *   "description" => "should better be present",
+    *  # "multi" => array("val1" => "title1", "v2" => "t2", ...),
+    *  # "type" => "text",
+    *  # "..." => "depending on var type, could have other options",
+    * )
+    *
+    */
+   function parse_options($str, $plugin) {
+      $r = array();
+   
+      #-- search for < angle brackets > first
+      preg_match_all("_<(\w+)(.+?)/\s*>_ims", $str, $uu);
+      foreach ($uu[1] as $i=>$optiontype) {
+         $inner = $uu[2][$i];
+
+         #-- prepare new
+         $entry = array(
+            "is" => $optiontype,
+            "plugin" => $plugin,
+         );
+      
+         #-- extract individual fields
+         preg_match_all("_\s+([-\w:]+)=[\"\']([^\"\']*?)[\"\']_msi", $inner, $vv);
+         foreach ($vv[1] as $j=>$field) {
+            $entry[$field] = $vv[2][$j];
+         }
+         
+         #-- clean name=
+         $entry["name"] = preg_replace("/[\$\"\'\s]/", "", $entry["name"]);
+         
+         #-- split up multi= value (our value= field holds the default entry instead)
+         if (strpos($entry["multi"], "|")) {
+            $opt = array();
+            foreach (explode("|", $entry["multi"]) as $o) {
+               if (strpos($o, "=")) {
+                  $opt[strtok($o,"=")] = strtok("\n");
+               }
+               else {
+                  $opt[$o] = $o;
+               }
+            }
+            $entry["multi"] = $opt;
+         }
+         
+         #-- rename, just in case (actually default= is not recommended, and value= should be used)
+         if (isset($row["default"]) && empty($row["value"])) {
+            $row["value"] = $row["default"];
+         }
+         
+         #-- add to list
+         $r[] = $entry;
+      }
+      return($r);
+   }
+
+
+
+   /**
+    * returns plugins grouped by entries' $field: value
+    * - or comparison value
+    *
+    */
+   function by($field, $cmp=NULL) {
+      $r = array();
+      foreach ($this->plugin as $id=>$row) {
+         if ( (empty($cmp) && isset($row[$field]))
+         or ($cmp==strtolower($row[$field])) )
+         {
+            $r[$row[$field]][$id] = $row;
+         }
+      }
+      return($r);
+   }
+
+
+
+   /**
+    * complain
+    *
+    */
+   function error($s) {
+      trigger_error($s, E_USER_WARNING);
+      $this->error = 1;
+   }
+
+}
+
+
+?>
Index: /trunk/libs/extensions/RSSWriterClass/rsswriter.php
===================================================================
--- /trunk/libs/extensions/RSSWriterClass/rsswriter.php	(revision 1081)
+++ /trunk/libs/extensions/RSSWriterClass/rsswriter.php	(revision 1081)
@@ -0,0 +1,174 @@
+<?php
+
+/* Publishes content as an RSS feed 
+   http://snipplr.com/view/23/rss-writer-class/ 
+   
+   E X A M P L E -----------------------------------------------
+		$feed = new RSS();
+		$feed->title       = "RSS Feed Title";
+		$feed->link        = "http://website.com";
+		$feed->description = "Recent articles on your website.";
+
+		$db->query($query);
+		$result = $db->result;
+		while($row = mysql_fetch_array($result, MYSQL_ASSOC))
+		{
+			$item = new RSSItem();
+			$item->title = $title;
+			$item->link  = $link;
+			$item->setPubDate($create_date); 
+			$item->description = "<![CDATA[ $html ]]>";
+			$feed->addItem($item);
+		}
+		echo $feed->serve();
+	---------------------------------------------------------------- */
+
+	class RSS
+	{
+		var $title;
+		var $link;
+		var $description;
+		var $language = "en-us";
+		var $pubDate;
+		var $items;
+		var $tags;
+
+		function RSS()
+		{
+			$this->items = array();
+			$this->tags  = array();
+		}
+
+		function addItem($item)
+		{
+			$this->items[] = $item;
+		}
+
+		function setPubDate($when)
+		{
+			if(strtotime($when) == false)
+				$this->pubDate = date("D, d M Y H:i:s ", $when) . "GMT";
+			else
+				$this->pubDate = date("D, d M Y H:i:s ", strtotime($when)) . "GMT";
+		}
+
+		function getPubDate()
+		{
+  			if(empty($this->pubDate))
+				return date("D, d M Y H:i:s ") . "GMT";
+			else
+				return $this->pubDate;
+		}
+
+		function addTag($tag, $value)
+		{
+			$this->tags[$tag] = $value;
+		}
+
+		function out()
+		{
+			$out  = $this->header();
+			$out .= "<channel>\n";
+			$out .= "<title>" . $this->title . "</title>\n";
+			$out .= "<link>" . $this->link . "</link>\n";
+			$out .= "<description>" . $this->description . "</description>\n";
+			$out .= "<language>" . $this->language . "</language>\n";
+			$out .= "<pubDate>" . $this->getPubDate() . "</pubDate>\n";
+
+			foreach($this->tags as $key => $val) $out .= "<$key>$val</$key>\n";
+			foreach($this->items as $item) $out .= $item->out();
+
+			$out .= "</channel>\n";
+			
+			$out .= $this->footer();
+
+			$out = str_replace("&", "&amp;", $out);
+
+			return $out;
+		}
+		
+		function serve($contentType = "application/xml")
+		{
+			$xml = $this->out();
+			header("Content-type: $contentType");
+			echo $xml;
+		}
+
+		function header()
+		{
+			$out  = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
+			$out .= '<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">' . "\n";
+			return $out;
+		}
+
+		function footer()
+		{
+			return '</rss>';
+		}
+	}
+
+	class RSSItem
+	{
+		var $title;
+		var $link;
+		var $description;
+		var $pubDate;
+		var $guid;
+		var $tags;
+		var $attachment;
+		var $length;
+		var $mimetype;
+
+		function RSSItem()
+		{ 
+			$this->tags = array();
+		}
+
+		function setPubDate($when)
+		{
+			if(strtotime($when) == false)
+				$this->pubDate = date("D, d M Y H:i:s ", $when) . "GMT";
+			else
+				$this->pubDate = date("D, d M Y H:i:s ", strtotime($when)) . "GMT";
+		}
+
+		function getPubDate()
+		{
+			if(empty($this->pubDate))
+				return date("D, d M Y H:i:s ") . "GMT";
+			else
+				return $this->pubDate;
+		}
+
+		function addTag($tag, $value)
+		{
+			$this->tags[$tag] = $value;
+		}
+
+		function out()
+		{
+			$out = "<item>\n";
+			$out .= "<title>" . $this->title . "</title>\n";
+			$out .= "<link>" . $this->link . "</link>\n";
+			$out .= "<description>" . $this->description . "</description>\n";
+			$out .= "<pubDate>" . $this->getPubDate() . "</pubDate>\n";
+
+			if($this->attachment != "")
+				$out .= "<enclosure url='{$this->attachment}' length='{$this->length}' type='{$this->mimetype}' />";
+
+			if(empty($this->guid)) $this->guid = $this->link;
+			$out .= "<guid>" . $this->guid . "</guid>\n";
+
+			foreach($this->tags as $key => $val) $out .= "<$key>$val</$key\n>";
+			$out .= "</item>\n";
+			return $out;
+		}
+
+		function enclosure($url, $mimetype, $length)
+		{
+			$this->attachment = $url;
+			$this->mimetype   = $mimetype;
+			$this->length     = $length;
+		}
+	}
+?>
Index: /trunk/libs/extensions/Paginated/PageLayout.php
===================================================================
--- /trunk/libs/extensions/Paginated/PageLayout.php	(revision 1081)
+++ /trunk/libs/extensions/Paginated/PageLayout.php	(revision 1081)
@@ -0,0 +1,10 @@
+<?php
+/**
+ * The interface which specifies the behaviour all page layout classes must implement
+ * PageLayout is a part of Paginated and can reference programmer defined layouts
+ */
+
+interface PageLayout {
+	public function fetchPagedLinks($parent, $h);
+}
+?>
Index: /trunk/libs/extensions/Paginated/Paginated.php
===================================================================
--- /trunk/libs/extensions/Paginated/Paginated.php	(revision 1081)
+++ /trunk/libs/extensions/Paginated/Paginated.php	(revision 1081)
@@ -0,0 +1,133 @@
+<?php
+/**
+ * The intention of the Paginated class is to manage the iteration of records
+ * based on a specified page number usually addressed by a get parameter in the query string
+ * and to use a layout interface to produce number pages based on the amount of elements
+ */
+
+require_once "PageLayout.php";
+
+class Paginated {
+
+	private $rs;                  		//result set
+	private $pageSize;                      //number of records to display
+	private $pageNumber;                    //the page to be displayed
+	private $rowNumber;                     //the current row of data which must be less than the pageSize in keeping with the specified size
+	private $offSet;
+	private $layout;
+
+	function __construct($obj, $displayRows = 10, $pageNum = 1) {
+		$this->setRs($obj);
+		$this->setPageSize($displayRows);
+		$this->assignPageNumber($pageNum);
+		$this->setRowNumber(0);
+		$this->setOffSet(($this->getPageNumber() - 1) * ($this->getPageSize()));
+	}
+
+	//implement getters and setters
+	public function setOffSet($offSet) {
+		$this->offSet = $offSet;
+	}
+
+	public function getOffSet() {
+		return $this->offSet;
+	}
+
+
+	public function getRs() {
+		return $this->rs;
+	}
+
+	public function setRs($obj) {
+		$this->rs = $obj;
+	}
+
+	public function getPageSize() {
+		return $this->pageSize;
+	}
+
+	public function setPageSize($pages) {
+		$this->pageSize = $pages;
+	}
+
+	//accessor and mutator for page numbers
+	public function getPageNumber() {
+		return $this->pageNumber;
+	}
+
+	public function setPageNumber($number) {
+		$this->pageNumber = $number;
+	}
+
+	//fetches the row number
+	public function getRowNumber() {
+		return $this->rowNumber;
+	}
+
+	public function setRowNumber($number) {
+		$this->rowNumber = $number;
+	}
+
+	public function fetchNumberPages() {
+		if (!$this->getRs()) {
+			return false;
+		}
+		
+		$pages = ceil(count($this->getRs()) / (float)$this->getPageSize());
+		return $pages;
+	}
+
+	//sets the current page being viewed to the value of the parameter
+	public function assignPageNumber($page) {
+		if(($page <= 0) || ($page > $this->fetchNumberPages()) || ($page == "")) {
+			$this->setPageNumber(1);
+		}
+		else {
+			$this->setPageNumber($page);
+		}
+		//upon assigning the current page, move the cursor in the result set to (page number minus one) multiply by the page size
+		//example  (2 - 1) * 10
+	}
+
+	public function fetchPagedRow() {
+		if((!$this->getRs()) || ($this->getRowNumber() >= $this->getPageSize())) {
+			return false;
+		}
+
+		$this->setRowNumber($this->getRowNumber() + 1);
+		$index = $this->getOffSet();
+		$this->setOffSet($this->getOffSet() + 1);
+		// return $this->rs[$index];
+		if(isset($this->rs[$index])) { return $this->rs[$index]; } else { return false; } // edited by Nick to suppress undefined error.
+	}
+
+	public function isFirstPage() {
+		return ($this->getPageNumber() <= 1);
+	}
+
+	public function isLastPage() {
+		return ($this->getPageNumber() >= $this->fetchNumberPages());
+	}
+
+	/**
+	 * <description>
+	 * @return PageLayout <description>
+	 */
+	public function getLayout() {
+		return $this->layout;
+	}
+
+	/**
+	 * <description>
+	 * @param PageLayout <description>
+	 */
+	public function setLayout(PageLayout $layout) {
+		$this->layout = $layout;
+	}
+
+	//returns a string with the base navigation for the page
+	public function fetchPagedNavigation($h = NULL) {
+		return $this->getLayout()->fetchPagedLinks($this, $h);
+	}//end writeNavigation
+}//end Paginated
+?>
Index: /trunk/libs/extensions/Paginated/DoubleBarLayout.php
===================================================================
--- /trunk/libs/extensions/Paginated/DoubleBarLayout.php	(revision 1081)
+++ /trunk/libs/extensions/Paginated/DoubleBarLayout.php	(revision 1081)
@@ -0,0 +1,154 @@
+<?php
+
+require_once "PageLayout.php";
+
+class DoubleBarLayout implements PageLayout
+{
+
+    public function fetchPagedLinks($parent, $h) 
+    {
+        // NOTE: FRIENDLY URLS ARE NOT USED IN PAGINATION (I tried, but there's always *something* that screws up. Nick)
+        if ($h->isAdmin == true) { $head = 'admin_index.php?'; } else { $head = 'index.php?'; }
+        
+        // get full url from address bar
+        $host = $h->cage->server->getMixedString2('HTTP_HOST');
+        $uri = $h->cage->server->getMixedString2('REQUEST_URI');
+        $path = "http://" . $host  . $uri;
+        
+        // if it doesn't contain $head, then it must be a friendly url 
+        if ($path != BASEURL && !strrpos($path, $head)) {
+            $path = $this->friendlyToStandardUrl($path, $head, $h);
+        } 
+        
+        // add the head if we're on the top page (which doesn't have index.php attached) 
+        if ($path == BASEURL) { $path = BASEURL . $head; }
+        
+        // But, for pagination, we can't just add pg=8 etc to the url because there's
+        // quite likely a pg=X query variable already there! We need to strip that out:
+
+        $query_args = parse_url($path, PHP_URL_QUERY);  // get all query vars
+        
+        if ($query_args) {
+            $path = str_replace($query_args, '', $path);  // strip them from original $path
+            parse_str($query_args, $parsed_query_args); // split query vars into key->value pairs
+            unset($parsed_query_args['pg']);   // we'll be replacing pg in the links
+            $path = $path . http_build_query($parsed_query_args); // rebuild url without pg parameter
+        }
+        
+        $currentPage = $parent->getPageNumber();
+        $str = "";
+        
+        $before = 4;
+        $after = 3;
+
+        //write statement that handles the previous and next phases
+           //if it is not the first page then write previous to the screen
+        if (!$parent->isFirstPage()) {
+            $previousPage = $currentPage - 1;
+            $link = $path . '&pg=' . $previousPage;
+            $link = str_replace('?&', '?', $link); // we don't want an ampersand directly after a question mark
+            $str .= "<a class='pagi_previous' href='" . $link . "' title='" . $h->lang['pagination_previous'] . "'>&laquo; " . $h->lang['pagination_previous'] . "</a> \n";
+        }
+        
+        // NOT FIRST PAGE
+        if (!$parent->isFirstPage() && !($currentPage <= ($before + 1))) {
+            if ($currentPage != 1) {
+                $link = $path . '&pg=1';
+                $link = str_replace('?&', '?', $link); // we don't want an ampersand directly after a question mark
+                $str .= "<a class='pagi_first' href='" . $link . "'  title='" . $h->lang['pagination_first'] . "'>1</a> \n";
+                if ($currentPage > ($before+1)) {
+                    $str .= " <span class='dots'>...</span> \n";
+                }
+            }
+        }
+
+        for ($i = $currentPage - $before; $i <= $currentPage + $after; $i++) {
+            //if i is less than one then continue to next iteration        
+            if ($i < 1) {
+                continue;
+            }
+    
+            if ($i > $parent->fetchNumberPages()) {
+                break;
+            }
+    
+            if ($i == $currentPage) {
+                $str .= "<span class='pagi_current'>$i</span>\n";
+            }
+            else {
+                $link = $path . '&pg=' . $i;
+                $link = str_replace('?&', '?', $link); // we don't want an ampersand directly after a question mark
+                $str .= "<a class='pagi_page' href='" . $link . "'>$i</a>\n";
+            }
+            if ($i != $currentPage + $after && $i != $parent->fetchNumberPages()) { $str .= ' '; }
+            // ($i == $currentPage + $after || $i == $parent->fetchNumberPages()) ? $str .= " " : $str .= " | ";    // determine if to print bars or not
+        } //end for
+
+        //$str = rstrtrim($str, '| '); // trim trailing bar
+              
+        if (!$parent->isLastPage() && !($currentPage > ($parent->fetchNumberPages() - $after))) {
+            if ($currentPage != $parent->fetchNumberPages() && $currentPage != $parent->fetchNumberPages() -1 && $currentPage != $parent->fetchNumberPages() - $before)
+            {
+                if ($currentPage < ($parent->fetchNumberPages() - ($after + 1))) { $str .= " <span class='pagi_dots'>...</span> \n"; }
+                $link = $path . '&pg=' . $parent->fetchNumberPages();
+                $link = str_replace('?&', '?', $link); // we don't want an ampersand directly after a question mark
+                $str .= "<a class='pagi_last' href='" . $link . "'  title='" . $h->lang['pagination_last'] . "'>".$parent->fetchNumberPages()."</a> \n";
+            }
+        }
+        
+        // NOT LAST PAGE
+        if (!$parent->isLastPage()) {
+            $nextPage = $currentPage + 1;
+            $link = $path . '&pg=' . $nextPage;
+            $link = str_replace('?&', '?', $link); // we don't want an ampersand directly after a question mark
+            $str .= "<a class='pagi_next' href='" . $link . "' title='" . $h->lang['pagination_next'] . "'>" . $h->lang['pagination_next'] . " &raquo;</a> \n";
+        }
+        
+        // Wrap in a div
+        $pagination = "<div id='pagination'>\n";
+        $pagination .= $str;
+        $pagination .= "</div>\n";
+        
+        return $pagination;
+    }
+    
+    
+    /**
+     * Converts a friendly url into a standard one
+     *
+     * @param string $url
+     * @param string $head - "index.php?" or "admin_index.php?"
+     * @param object $h
+     */
+    public function friendlyToStandardUrl($url, $head, $h) 
+    {
+        // strip off BASEURL and trailing slash
+        $url = str_replace(BASEURL, '', $url);
+        $url = rtrim($url, '/');
+
+        // start the standard url
+        $standard_url = BASEURL . $head;
+        
+        // parts will hold the query vars
+        $parts = array();
+        $parts = explode('/', $url);
+        
+        // if odd number of query vars, the first is the page
+        if (count($parts) % 2 == 1) {
+             $page = array_shift($parts);
+             $standard_url .= 'page=' . $page;
+             if (!empty($parts)) { $standard_url .= '&'; }
+        }
+        
+        // if query vars still in array, add them
+        while (!empty($parts)) {
+            $key = array_shift($parts);
+            $value = array_shift($parts);
+            $standard_url .= $key . '=' . $value;
+            if (!empty($parts)) { $standard_url .= '&'; }
+        }
+        
+        return $standard_url;
+    }
+}
+?>
Index: /trunk/libs/extensions/Paginated/TrailingLayout.php
===================================================================
--- /trunk/libs/extensions/Paginated/TrailingLayout.php	(revision 1081)
+++ /trunk/libs/extensions/Paginated/TrailingLayout.php	(revision 1081)
@@ -0,0 +1,22 @@
+<?php
+class TrailingLayout implements PageLayout {
+
+	public function fetchPagedLinks($parent, $queryVars) {
+	
+		$currentPage = $parent->getPageNumber();
+		$totalPages = $parent->fetchNumberPages();
+		$str = "";
+
+		if($totalPages >= 1) {
+		
+			for($i = 1; $i <= $totalPages; $i++) {
+		
+				$str .= " <a href=\"?page={$i}$queryVars\">Page $i</a>";
+				$str .= $i != $totalPages ? " | " : "";
+			}
+		}
+
+		return $str;
+	}
+}
+?>
Index: /trunk/libs/extensions/Paginated/index.php
===================================================================
--- /trunk/libs/extensions/Paginated/index.php	(revision 1081)
+++ /trunk/libs/extensions/Paginated/index.php	(revision 1081)
@@ -0,0 +1,54 @@
+<?php
+require_once "Paginated.php";
+require_once "DoubleBarLayout.php";
+?>
+<html>
+<head>
+<title>Pagination</title>
+
+<!-- Just a little style formatting. Has no bearing on example -->
+<style type="text/css">
+	body {
+		font-family: Verdana;
+		font-size: 13px;
+	}
+	
+	a {
+		text-decoration: none;
+	}
+	
+	a:hover {
+		text-decoration: underline;
+	}
+</style>
+<!-- End style formatting -->
+</head>
+
+<body>
+
+	<?php
+	//create an array of names in alphabetic order. A database call could have retrieved these items
+	$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", "Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar");
+	
+	$page = $_GET['page'];
+	
+	//constructor takes three parameters
+	//1. array to be paged
+	//2. number of results per page (optional parameter. Default is 10)
+	//3. the current page (optional parameter. Default  is 1)
+	$pagedResults = new Paginated($names, 10, $page);
+	
+	echo "<ul>";
+
+	while($row = $pagedResults->fetchPagedRow()) {	//when $row is false loop terminates
+		echo "<li>{$row}</li>";
+	}
+	
+	echo "</ul>";
+	
+	//important to set the strategy to be used before a call to fetchPagedNavigation
+	$pagedResults->setLayout(new DoubleBarLayout());
+	echo $pagedResults->fetchPagedNavigation();
+	?>
+</body>
+</html>
Index: /trunk/libs/extensions/SWCMS/class.httprequest.php
===================================================================
--- /trunk/libs/extensions/SWCMS/class.httprequest.php	(revision 1081)
+++ /trunk/libs/extensions/SWCMS/class.httprequest.php	(revision 1081)
@@ -0,0 +1,111 @@
+<?php
+
+/* **************************************************************************************************** 
+ *  File: /3rdparty/SWCMS/class.httprequest.php
+ *  Purpose: Fetch content from a given url.
+ *  Notes: ---
+ *  License:
+ *
+ * The source code packaged with this file is Free Software.
+ * Portions are Copyright (C) 2005 by Ricardo Galli <gallir at uib dot es>.
+ * Portions are Copyright (C) 2005 - 2008 by Pligg <www.pligg.com>.
+ * Portions are Copyright (C) 2008 by the Social Web CMS Team <swcms@socialwebcms.com>.
+ * It's licensed under the AFFERO GENERAL PUBLIC LICENSE unless stated otherwise.
+ * You can get copies of the licenses here: http://www.affero.org/oagpl.html
+ * AFFERO GENERAL PUBLIC LICENSE is also included in the file called "COPYING".
+ *
+ **************************************************************************************************** */
+
+class HTTPRequest
+{
+   var $_fp;        // HTTP socket
+   var $_url;        // full URL
+   var $_host;        // HTTP host
+   var $_protocol;    // protocol (HTTP/HTTPS)
+   var $_uri;        // request URI
+   var $_port;        // port
+
+   // scan url
+   function _scan_url()
+   {
+       $req = $this->_url;
+
+       $pos = strpos($req, '://');
+       $this->_protocol = strtolower(substr($req, 0, $pos));
+
+       $req = substr($req, $pos+3);
+       $pos = strpos($req, '/');
+       if($pos === false)
+           $pos = strlen($req);
+       $host = substr($req, 0, $pos);
+
+       if(strpos($host, ':') !== false)
+       {
+           list($this->_host, $this->_port) = explode(':', $host);
+       }
+       else
+       {
+           $this->_host = $host;
+           $this->_port = ($this->_protocol == 'https') ? 443 : 80;
+       }
+
+       $this->_uri = substr($req, $pos);
+       if($this->_uri == '')
+           $this->_uri = '/';
+   }
+
+   // constructor
+   function HTTPRequest($url)
+   {
+		$this->_url = $url;
+		$this->_scan_url();
+   }
+
+   // download URL to string
+   function DownloadToString()
+   {
+       $crlf = "\r\n";
+
+       // generate request
+       $req = 'GET ' . $this->_uri . ' HTTP/1.0' . $crlf
+           .    'Host: ' . $this->_host . $crlf
+           .    $crlf;
+
+	error_reporting(E_ERROR);
+	// fetch
+	$this->_fp = fsockopen(($this->_protocol == 'https' ? 'tls://' : '') . $this->_host, $this->_port, $errno, $errstr, 20);
+	if(!$this->_fp)
+		{return("BADURL");}
+	fwrite($this->_fp, $req);
+       while(is_resource($this->_fp) && $this->_fp && !feof($this->_fp))
+           $response .= fread($this->_fp, 1024);
+       fclose($this->_fp);
+
+       // split header and body
+       $pos = strpos($response, $crlf . $crlf);
+       if($pos === false)
+           return($response);
+       $header = substr($response, 0, $pos);
+       $body = substr($response, $pos + 2 * strlen($crlf));
+
+       // parse headers
+       $headers = array();
+       $lines = explode($crlf, $header);
+       foreach($lines as $line)
+           if(($pos = strpos($line, ':')) !== false)
+               $headers[strtolower(trim(substr($line, 0, $pos)))] = trim(substr($line, $pos+1));
+
+       // redirection?
+       if(isset($headers['location']))
+       {
+           $http = new HTTPRequest($headers['location']);
+           return($http->DownloadToString($http));
+       }
+       else
+       {
+           return($body);
+       }
+   }
+}
+
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt.php	(revision 1081)
@@ -0,0 +1,1294 @@
+<?php
+/**
+ * Inspekt - main source file
+ *
+ *
+ *
+ * @author Chris Shiflett <chris@shiflett.org>
+ * @author Ed Finkler <coj@funkatron.com>
+ *
+ * @package Inspekt
+ */
+
+
+/**
+ * Inspekt_Error
+ */
+require_once('Inspekt/Error.php');
+
+/**
+ * Inspekt_Cage
+ */
+require_once('Inspekt/Cage.php');
+
+/**
+ * Inspekt_Cage_Session
+ */
+//require_once('Inspekt/Cage/Session.php');
+
+/**
+ * Inspekt_Supercage
+ */
+require_once('Inspekt/Supercage.php');
+
+
+/**
+ * Options for isHostname() that specify which types of hostnames
+ * to allow.
+ *
+ * HOST_ALLOW_DNS:   Allows Internet domain names (e.g.,
+ *                   example.com).
+ */
+define ('ISPK_HOST_ALLOW_DNS',   1);
+
+/**
+ * Options for isHostname() that specify which types of hostnames
+ * to allow.
+ *
+ * HOST_ALLOW_IP:    Allows IP addresses.
+ */
+define ('ISPK_HOST_ALLOW_IP',    2);
+
+/**
+ * Options for isHostname() that specify which types of hostnames
+ * to allow.
+ *
+ * HOST_ALLOW_LOCAL: Allows local network names (e.g., localhost,
+ *                   www.localdomain) and Internet domain names.
+ */
+define ('ISPK_HOST_ALLOW_LOCAL', 4);
+
+/**
+ * Options for isHostname() that specify which types of hostnames
+ * to allow.
+ *
+ * HOST_ALLOW_ALL:   Allows all of the above types of hostnames.
+ */
+define ('ISPK_HOST_ALLOW_ALL',   7);
+
+/**
+ * Options for isUri that specify which types of URIs to allow.
+ *
+ * URI_ALLOW_COMMON: Allow only "common" hostnames: http, https, ftp
+ */
+define ('ISPK_URI_ALLOW_COMMON', 1);
+
+/**
+ * regex used to define what we're calling a valid domain name
+ *
+ */
+define ('ISPK_DNS_VALID', '/^(?:[^\W_]((?:[^\W_]|-){0,61}[^\W_])?\.)+[a-zA-Z]{2,6}\.?$/');
+
+/**
+ * regex used to define what we're calling a valid email
+ *
+ * we're taking a "match 99%" approach here, rather than a strict
+ * interpretation of the RFC.
+ *
+ * @see http://www.regular-expressions.info/email.html
+ */
+define ('ISPK_EMAIL_VALID', '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/');
+
+/**
+ * @package    Inspekt
+ */
+class Inspekt
+{
+	
+	
+	protected static $useFilterExtension = true;
+	
+	
+
+	/**
+	 * Returns the $_SERVER data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * 
+	 * @assert()
+	 */
+	static public function makeServerCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_SERVER, $config_file, '_SERVER', $strict);
+		}
+		$GLOBALS['HTTP_SERVER_VARS'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns the $_GET data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 */
+	static public function makeGetCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_GET, $config_file, '_GET', $strict);
+		}
+		$GLOBALS['HTTP_GET_VARS'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns the $_POST data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 */
+	static public function makePostCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_POST, $config_file, '_POST', $strict);
+		}
+		$GLOBALS['HTTP_POST_VARS'] = NULL;
+		return $_instance;
+	}
+
+	/**
+	 * Returns the $_COOKIE data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 */
+	static public function makeCookieCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_COOKIE, $config_file, '_COOKIE', $strict);
+		}
+		$GLOBALS['HTTP_COOKIE_VARS'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns the $_ENV data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 */
+	static public function makeEnvCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_ENV, $config_file, '_ENV', $strict);
+		}
+		$GLOBALS['HTTP_ENV_VARS'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns the $_FILES data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 */
+	static public function makeFilesCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage::Factory($_FILES, $config_file, '_FILES', $strict);
+		}
+		$GLOBALS['HTTP_POST_FILES'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns the $_SESSION data wrapped in an Inspekt_Cage object
+	 *
+	 * This utilizes a singleton pattern to get around scoping issues
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal array
+	 * @return Inspekt_Cage
+	 * @static
+	 * @deprecated
+	 */
+	static public function makeSessionCage($config_file=NULL, $strict=TRUE) {
+		
+		trigger_error('makeSessionCage is disabled in this version', E_USER_ERROR);
+		
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_instance;
+
+		if (!isset($_SESSION)) {
+			return NULL;
+		}
+
+		if (!isset($_instance)) {
+			$_instance = Inspekt_Cage_Session::Factory($_SESSION, $config_file, '_SESSION', $strict);
+		}
+		$GLOBALS['HTTP_SESSION_VARS'] = NULL;
+		return $_instance;
+	}
+
+
+	/**
+	 * Returns a Supercage object, which wraps ALL input superglobals
+	 *
+	 * @param string  $config_file
+	 * @param boolean $strict whether or not to nullify the superglobal
+	 * @return Inspekt_Supercage
+	 * @static
+	 */
+	static public function makeSuperCage($config_file=NULL, $strict=TRUE) {
+		/**
+		 * @staticvar $_instance
+		 */
+		static $_scinstance;
+
+		if (!isset($_scinstance)) {
+			$_scinstance = Inspekt_Supercage::Factory($config_file, $strict);
+		}
+		return $_scinstance;
+
+	}
+
+
+	/**
+	 * Sets and/or retrieves whether we should use the PHP filter extensions where possible
+	 * If a param is passed, it will set the state in addition to returning it
+	 * 
+	 * We use this method of storing in a static class property so that we can access the value outside of class instances
+	 * 
+	 * @param boolean $state optional
+	 * @return boolean
+	 */
+	static public function useFilterExt($state=null) {
+		if (isset($state)) {
+			Inspekt::$useFilterExtension = (bool)$state;
+		}
+		return Inspekt::$useFilterExtension;
+	}
+	
+
+
+	/**
+	 * Recursively walks an array and applies a given filter method to
+	 * every value in the array.
+	 *
+	 * This should be considered a "protected" method, and not be called
+	 * outside of the class
+	 * 
+	 *
+	 * @param array|ArrayObject $input
+	 * @param string $inspektor  The name of a static filtering method, like get* or no*
+	 * @return array
+	 *
+	 */
+	static protected function _walkArray($input, $method, $classname=NULL) {
+				
+		if (!isset($classname)) {
+			$classname = __CLASS__;
+		}
+				
+		if (!self::isArrayObject($input) && !is_array($input) ) {
+			user_error('$input must be an array or ArrayObject', E_USER_ERROR);
+			return FALSE;
+		}
+
+		if ( !is_callable( array($classname, $method) ) ) {
+			user_error('Inspektor '.$classname.'::'.$method.' is invalid', E_USER_ERROR);
+			return FALSE;
+		}
+
+		foreach($input as $key=>$val) {
+			if (is_array($val)) {
+				$input[$key]=self::_walkArray($val, $method, $classname);
+			} else {
+				$val = call_user_func( array($classname, $method), $val);
+				$input[$key]=$val;
+			}
+		}
+		return $input;
+	}
+
+	
+	/**
+	 * Checks to see if this is an ArrayObject
+	 * @param mixed
+	 * @return boolean
+	 * @link http://php.net/arrayobject
+	 */
+	static public function isArrayObject($obj) {
+		$is = false;
+		$is = (is_object($obj) && get_class($obj) === 'ArrayObject');
+		return $is;
+	}
+
+	/**
+	 * Checks to see if this is an array or an ArrayObject
+	 * @param mixed
+	 * @return boolean
+	 * @link http://php.net/arrayobject
+	 * @link http://php.net/array
+	 */
+	static public function isArrayOrArrayObject($arr) {
+		$is = false;
+		$is = Inspekt::isArrayObject($arr) || is_array($arr);
+		return $is;
+	}
+
+	/**
+	 * Converts an array into an ArrayObject. We use ArrayObjects when walking arrays in Inspekt
+	 * @param array
+	 * @return ArrayObject
+	 * 
+	 */
+	static public function convertArrayToArrayObject(&$arr) {
+		foreach ($arr as $key => $value) {
+			if (is_array($value)) {
+				$value = new ArrayObject($value);
+				$arr[$key] = $value;
+				//echo $key." is an array\n";
+				Inspekt::convertArrayToArrayObject($arr[$key]);
+			}
+		}
+
+		return new ArrayObject($arr);
+	}
+	
+
+	/**
+     * Returns only the alphabetic characters in value.
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     */
+	static public function getAlpha($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getAlpha');
+		} else {
+			return preg_replace('/[^[:alpha:]]/', '', $value);
+		}
+	}
+
+	/**
+     * Returns only the alphabetic characters and digits in value.
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     * 
+     * @assert('1)@*(&UR)HQ)W(*(HG))') === '1URHQWHG'
+     */
+	static public function getAlnum($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getAlnum');
+		} else {
+			return preg_replace('/[^[:alnum:]]/', '', $value);
+		}
+	}
+	 
+	/**
+     * Returns only the digits in value.
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     * 
+     * @assert('1)@*(&UR)HQ)56W(*(HG))') === '156'
+     */
+	static public function getDigits($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getDigits');
+		} else {
+			return preg_replace('/[^[:digit:]]/', '', $value);
+		}
+	}
+
+
+
+	/**
+     * Returns dirname(value).
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     * 
+     * @assert('/usr/lib/php/Pear.php') === '/usr/lib/php'
+     */
+	static public function getDir($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getDir');
+		} else {
+			return dirname($value);
+		}
+	}
+
+	/**
+     * Returns (int) value.
+     *
+     * @param mixed $value
+     * @return int
+     *
+     * @tag filter
+     * 
+     * @assert('1)45@*(&UR)HQ)W.0000(*(HG))') === 1
+     * @assert('A1)45@*(&UR)HQ)W.0000(*(HG))') === 0
+     */
+	static public function getInt($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getInt');
+		} else {
+			return (int) $value;
+		}
+	}
+
+	/**
+     * Returns realpath(value).
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     */
+	static public function getPath($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getPath');
+		} else {
+			return realpath($value);
+		}
+	}
+	
+	
+	/**
+	 * Returns the value encoded as ROT13 (or decoded, if already was ROT13)
+	 * 
+	 * @param mixed $value
+	 * @return mixed 
+	 * 
+	 * @link http://php.net/manual/en/function.str-rot13.php
+	 */
+	static public function getROT13($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'getROT13');
+		} else {
+			return str_rot13($value);
+		}		
+	}
+	
+
+	/**
+     * Returns TRUE if every character is alphabetic or a digit,
+     * FALSE otherwise.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * 
+     * @assert('NCOFWIERNVOWIEBHV12047057y0650ytg0314') === true
+     * @assert('NCOFWIERNVOWIEBHV2@12047057y0650ytg0314') === false
+     * @assert('funkatron') === true
+     * @assert('funkatron_user') === false
+     * @assert('funkatron-user') === false
+     * @assert('_funkatronuser') === false
+     */
+	static public function isAlnum($value)
+	{
+		return ctype_alnum($value);
+	}
+
+	/**
+     * Returns TRUE if every character is alphabetic, FALSE
+     * otherwise.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * 
+     * @assert('NCOFWIERNVOWIEBHV12047057y0650ytg0314') === false
+     * @assert('NCOFWIERNVOWIEBHV2@12047057y0650ytg0314') === false
+     * @assert('funkatron') === true
+     * @assert('funkatron_user') === false
+     * @assert('funkatron-user') === false
+     * @assert('_funkatronuser') === false
+     */
+	static public function isAlpha($value)
+	{
+		return ctype_alpha($value);
+	}
+
+	/**
+     * Returns TRUE if value is greater than or equal to $min and less
+     * than or equal to $max, FALSE otherwise. If $inc is set to
+     * FALSE, then the value must be strictly greater than $min and
+     * strictly less than $max.
+     *
+     * @param mixed $value
+     * @param mixed $min
+     * @param mixed $max
+     * @return boolean
+     *
+     * @tag validator
+     * 
+     * @assert(12, 0, 12) === TRUE
+     * @assert(12, 0, 12, FALSE) === FALSE
+     * @assert('f', 'a', 'm', FALSE) === TRUE
+     * @assert('p', 'a', 'm', FALSE) === FALSE
+     * 
+     * 
+     */
+	static public function isBetween($value, $min, $max, $inc = TRUE)
+	{
+		if ($value > $min &&
+		$value < $max) {
+			return TRUE;
+		}
+
+		if ($inc &&
+		$value >= $min &&
+		$value <= $max) {
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	/**
+     * Returns TRUE if it is a valid credit card number format. The
+     * optional second argument allows developers to indicate the
+     * type.
+     *
+     * @param mixed $value
+     * @param mixed $type
+     * @return boolean
+     *
+     * @tag validator
+     */
+	static public function isCcnum($value, $type = NULL)
+	{
+		/**
+         * @todo Type-specific checks
+         */
+		if (isset($type)) {
+			trigger_error('Type-specific cc checks are not yet supported');
+		}
+
+
+		$value = self::getDigits($value);
+		$length = strlen($value);
+
+		if ($length < 13 || $length > 19) {
+			return FALSE;
+		}
+
+		$sum = 0;
+		$weight = 2;
+
+		for ($i = $length - 2; $i >= 0; $i--) {
+			$digit = $weight * $value[$i];
+			$sum += floor($digit / 10) + $digit % 10;
+			$weight = $weight % 2 + 1;
+		}
+
+		$mod = (10 - $sum % 10) % 10;
+
+		return ($mod == $value[$length - 1]);
+	}
+
+	/**
+     * Returns TRUE if value is a valid date, FALSE otherwise. The
+     * date is required to be in ISO 8601 format.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * 
+     * @assert('2009-06-30') === TRUE
+     * @assert('2009-06-31') === FALSE
+     * @assert('2009-6-30') === TRUE
+     * @assert('2-6-30') === TRUE
+     */
+	static public function isDate($value)
+	{
+		list($year, $month, $day) = sscanf($value, '%d-%d-%d');
+		
+		return checkdate($month, $day, $year);
+	}
+
+	/**
+     * Returns TRUE if every character is a digit, FALSE otherwise.
+     * This is just like isInt(), except there is no upper limit.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * 
+     * @assert('1029438750192730t91740987023948') === FALSE
+     * @assert('102943875019273091740987023948') === TRUE
+     * @assert(102943875019273091740987023948) === FALSE
+     */
+	static public function isDigits($value)
+	{
+		return ctype_digit((string) $value);
+	}
+
+	/**
+     * Returns TRUE if value is a valid email format, FALSE otherwise.
+     *
+     * @param string $value
+     * @return boolean
+     * @see http://www.regular-expressions.info/email.html
+     * @see ISPK_EMAIL_VALID
+     *
+     * @tag validator
+     * 
+     * @assert('coj@poop.com') === TRUE
+     * @assert('coj+booboo@poop.com') === TRUE
+     * @assert('coj!booboo@poop.com') === FALSE
+     * @assert('@poop.com') === FALSE
+     * @assert('a@b') === FALSE
+     * @assert('webmaster') === FALSE
+     */
+	static public function isEmail($value)
+	{
+		return (bool) preg_match(ISPK_EMAIL_VALID, $value);
+	}
+
+	/**
+     * Returns TRUE if value is a valid float value, FALSE otherwise.
+     *
+     * @param string $value
+     * @return boolean
+     *
+     * @assert(10244578109.234451) === TRUE
+     * @assert('10244578109.234451') === FALSE
+     * @assert('10,244,578,109.234451') === FALSE
+     * 
+     * @tag validator
+     */
+	static public function isFloat($value)
+	{
+		$locale = localeconv();
+		$value = str_replace($locale['decimal_point'], '.', $value);
+		$value = str_replace($locale['thousands_sep'], '', $value);
+
+		return (strval(floatval($value)) == $value);
+	}
+
+	/**
+     * Returns TRUE if value is greater than $min, FALSE otherwise.
+     *
+     * @param mixed $value
+     * @param mixed $min
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     * 
+     * @assert(5, 0) === TRUE
+     * @assert(2, 10) === FALSE
+     * @assert('b', 'a') === TRUE
+     * @assert('a', 'b') === FALSE
+     */
+	static public function isGreaterThan($value, $min)
+	{
+		return ($value > $min);
+	}
+
+	/**
+     * Returns TRUE if value is a valid hexadecimal format, FALSE
+     * otherwise.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     * 
+     * @assert('6F') === TRUE
+     * @assert('F6') === TRUE
+     * 
+     */
+	static public function isHex($value)
+	{
+		return ctype_xdigit($value);
+	}
+
+	/**
+     * Returns TRUE if value is a valid hostname, FALSE otherwise.
+     * Depending upon the value of $allow, Internet domain names, IP
+     * addresses, and/or local network names are considered valid.
+     * The default is HOST_ALLOW_ALL, which considers all of the
+     * above to be valid.
+     *
+     * @param mixed $value
+     * @param integer $allow bitfield for ISPK_HOST_ALLOW_DNS, ISPK_HOST_ALLOW_IP, ISPK_HOST_ALLOW_LOCAL
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isHostname($value, $allow = ISPK_HOST_ALLOW_ALL)
+	{
+		if (!is_numeric($allow) || !is_int($allow)) {
+			user_error('Illegal value for $allow; expected an integer', E_USER_WARNING);
+		}
+
+		if ($allow < ISPK_HOST_ALLOW_DNS || ISPK_HOST_ALLOW_ALL < $allow) {
+			user_error('Illegal value for $allow; expected integer between ' . ISPK_HOST_ALLOW_DNS . ' and ' . ISPK_HOST_ALLOW_ALL, E_USER_WARNING);
+		}
+
+		// determine whether the input is formed as an IP address
+		$status = self::isIp($value);
+
+		// if the input looks like an IP address
+		if ($status) {
+			// if IP addresses are not allowed, then fail validation
+			if (($allow & ISPK_HOST_ALLOW_IP) == 0) {
+				return FALSE;
+			}
+
+			// IP passed validation
+			return TRUE;
+		}
+
+		// check input against domain name schema
+		$status = @preg_match('/^(?:[^\W_]((?:[^\W_]|-){0,61}[^\W_])?\.)+[a-zA-Z]{2,6}\.?$/', $value);
+		if ($status === false) {
+			user_error('Internal error: DNS validation failed', E_USER_WARNING);
+		}
+
+		// if the input passes as an Internet domain name, and domain names are allowed, then the hostname
+		// passes validation
+		if ($status == 1 && ($allow & ISPK_HOST_ALLOW_DNS) != 0) {
+			return TRUE;
+		}
+
+		// if local network names are not allowed, then fail validation
+		if (($allow & ISPK_HOST_ALLOW_LOCAL) == 0) {
+			return FALSE;
+		}
+
+		// check input against local network name schema; last chance to pass validation
+		$status = @preg_match('/^(?:[^\W_](?:[^\W_]|-){0,61}[^\W_]\.)*(?:[^\W_](?:[^\W_]|-){0,61}[^\W_])\.?$/',
+		$value);
+		if ($status === FALSE) {
+			user_error('Internal error: local network name validation failed', E_USER_WARNING);
+		}
+
+		if ($status == 0) {
+			return FALSE;
+		} else {
+			return TRUE;
+		}
+	}
+
+	/**
+     * Returns TRUE if value is a valid integer value, FALSE otherwise.
+     *
+     * @param string|array $value
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     * 
+     * @todo better handling of diffs b/t 32-bit and 64-bit
+     */
+	static public function isInt($value)
+	{
+		$locale = localeconv();
+
+		$value = str_replace($locale['decimal_point'], '.', $value);
+		$value = str_replace($locale['thousands_sep'], '', $value);
+				
+		$is_valid = (
+			is_numeric($value)  // Must be able to be converted to a number
+			&& preg_replace("/^-?([0-9]+)$/", "", $value) == ""  // Must be an integer (no floats or e-powers)
+			&& bccomp($value, "-9223372036854775807") >= 0  // Must be greater than than min of 64-bit
+			&& bccomp($value, "9223372036854775807") <= 0  // Must be less than max of 64-bit
+		);
+		if (!$is_valid) {
+			return false;
+		} else {
+			return true;
+		}
+		
+
+
+
+		
+		// return (strval(intval($value)) === $value);
+	}
+
+	/**
+     * Returns TRUE if value is a valid IP format, FALSE otherwise.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isIp($value)
+	{
+		return (bool) ip2long($value);
+	}
+
+	/**
+     * Returns TRUE if value is less than $max, FALSE otherwise.
+     *
+     * @param mixed $value
+     * @param mixed $max
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isLessThan($value, $max)
+	{
+		return ($value < $max);
+	}
+
+	/**
+     * Returns TRUE if value is one of $allowed, FALSE otherwise.
+     *
+     * @param mixed $value
+     * @param array|string $allowed
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isOneOf($value, $allowed)
+	{
+		/**
+         * @todo: Consider allowing a string for $allowed, where each
+         * character in the string is an allowed character in the
+         * value.
+         */
+
+		if (is_string($allowed)) {
+			$allowed = str_split($allowed);
+		}
+
+		return in_array($value, $allowed);
+	}
+
+	/**
+     * Returns TRUE if value is a valid phone number format, FALSE
+     * otherwise. The optional second argument indicates the country.
+     * This method requires that the value consist of only digits.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isPhone($value, $country = 'US')
+	{
+		if (!ctype_digit($value)) {
+			return FALSE;
+		}
+
+		switch ($country)
+		{
+			case 'US':
+				if (strlen($value) != 10) {
+					return FALSE;
+				}
+
+				$areaCode = substr($value, 0, 3);
+
+				$areaCodes = array(201, 202, 203, 204, 205, 206, 207, 208,
+				209, 210, 212, 213, 214, 215, 216, 217,
+				218, 219, 224, 225, 226, 228, 229, 231,
+				234, 239, 240, 242, 246, 248, 250, 251,
+				252, 253, 254, 256, 260, 262, 264, 267,
+				268, 269, 270, 276, 281, 284, 289, 301,
+				302, 303, 304, 305, 306, 307, 308, 309,
+				310, 312, 313, 314, 315, 316, 317, 318,
+				319, 320, 321, 323, 325, 330, 334, 336,
+				337, 339, 340, 345, 347, 351, 352, 360,
+				361, 386, 401, 402, 403, 404, 405, 406,
+				407, 408, 409, 410, 412, 413, 414, 415,
+				416, 417, 418, 419, 423, 424, 425, 430,
+				432, 434, 435, 438, 440, 441, 443, 445,
+				450, 469, 470, 473, 475, 478, 479, 480,
+				484, 501, 502, 503, 504, 505, 506, 507,
+				508, 509, 510, 512, 513, 514, 515, 516,
+				517, 518, 519, 520, 530, 540, 541, 555,
+				559, 561, 562, 563, 564, 567, 570, 571,
+				573, 574, 580, 585, 586, 600, 601, 602,
+				603, 604, 605, 606, 607, 608, 609, 610,
+				612, 613, 614, 615, 616, 617, 618, 619,
+				620, 623, 626, 630, 631, 636, 641, 646,
+				647, 649, 650, 651, 660, 661, 662, 664,
+				670, 671, 678, 682, 684, 700, 701, 702,
+				703, 704, 705, 706, 707, 708, 709, 710,
+				712, 713, 714, 715, 716, 717, 718, 719,
+				720, 724, 727, 731, 732, 734, 740, 754,
+				757, 758, 760, 763, 765, 767, 769, 770,
+				772, 773, 774, 775, 778, 780, 781, 784,
+				785, 786, 787, 800, 801, 802, 803, 804,
+				805, 806, 807, 808, 809, 810, 812, 813,
+				814, 815, 816, 817, 818, 819, 822, 828,
+				829, 830, 831, 832, 833, 835, 843, 844,
+				845, 847, 848, 850, 855, 856, 857, 858,
+				859, 860, 863, 864, 865, 866, 867, 868,
+				869, 870, 876, 877, 878, 888, 900, 901,
+				902, 903, 904, 905, 906, 907, 908, 909,
+				910, 912, 913, 914, 915, 916, 917, 918,
+				919, 920, 925, 928, 931, 936, 937, 939,
+				940, 941, 947, 949, 951, 952, 954, 956,
+				959, 970, 971, 972, 973, 978, 979, 980,
+				985, 989);
+
+				return in_array($areaCode, $areaCodes);
+				break;
+			default:
+				user_error('isPhone() does not yet support this country.', E_USER_WARNING);
+				return FALSE;
+				break;
+		}
+	}
+
+	/**
+     * Returns TRUE if value matches $pattern, FALSE otherwise. Uses
+     * preg_match() for the matching.
+     *
+     * @param mixed $value
+     * @param mixed $pattern
+     * @return mixed
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isRegex($value, $pattern)
+	{
+		return (bool) preg_match($pattern, $value);
+	}
+
+
+	/**
+	 * Enter description here...
+	 *
+	 * @param string $value
+	 * @param integer $mode
+	 * @return boolean
+	 *
+	 * @link http://www.ietf.org/rfc/rfc2396.txt
+	 *
+	 * @tag validator
+	 * @static
+	 */
+	static public function isUri($value, $mode=ISPK_URI_ALLOW_COMMON)
+	{
+		/**
+         * @todo
+         */
+		$regex = '';
+		switch ($mode) {
+
+			// a common absolute URI: ftp, http or https
+			case ISPK_URI_ALLOW_COMMON:
+
+				$regex .= '&';
+				$regex .= '^(ftp|http|https):';					// protocol
+				$regex .= '(//)';								// authority-start
+				$regex .= '([-a-z0-9/~;:@=+$,.!*()\']+@)?';		// userinfo
+				$regex .= '(';
+				$regex .= '((?:[^\W_]((?:[^\W_]|-){0,61}[^\W_])?\.)+[a-zA-Z]{2,6}\.?)';		// domain name
+				$regex .= '|';
+				$regex .= '([0-9]{1,3}(\.[0-9]{1,3})?(\.[0-9]{1,3})?(\.[0-9]{1,3})?)';	// OR ipv4
+				$regex .= ')';
+				$regex .= '(:([0-9]*))?';						// port
+				$regex .= '(/((%[0-9a-f]{2}|[-_a-z0-9/~;:@=+$,.!*()\'\&]*)*)/?)?';	// path
+				$regex .= '(\?[^#]*)?';							// query
+				$regex .= '(#([-a-z0-9_]*))?';					// anchor (fragment)
+				$regex .= '$&i';
+				//echo "<pre>"; echo print_r($regex, true); echo "</pre>\n";
+
+				break;
+
+			case ISPK_URI_ALLOW_ABSOLUTE:
+
+				user_error('isUri() for ISPK_URI_ALLOW_ABSOLUTE has not been implemented.', E_USER_WARNING);
+				return FALSE;
+
+//				$regex .= '&';
+//				$regex .= '^(ftp|http|https):';					// protocol
+//				$regex .= '(//)';								// authority-start
+//				$regex .= '([-a-z0-9/~;:@=+$,.!*()\']+@)?';		// userinfo
+//				$regex .= '(';
+//					$regex .= '((?:[^\W_]((?:[^\W_]|-){0,61}[^\W_])?\.)+[a-zA-Z]{2,6}\.?)';		// domain name
+//				$regex .= '|';
+//					$regex .= '([0-9]{1,3}(\.[0-9]{1,3})?(\.[0-9]{1,3})?(\.[0-9]{1,3})?)';	// OR ipv4
+//				$regex .= ')';
+//				$regex .= '(:([0-9]*))?';						// port
+//				$regex .= '(/((%[0-9a-f]{2}|[-a-z0-9/~;:@=+$,.!*()\'\&]*)*)/?)?';	// path
+//				$regex .= '(\?[^#]*)?';							// query
+//				$regex .= '(#([-a-z0-9_]*))?';					// anchor (fragment)
+//				$regex .= '$&i';
+				//echo "<pre>"; echo print_r($regex, true); echo "</pre>\n";
+
+				break;
+
+		}
+		$result = preg_match($regex, $value, $subpatterns);
+
+		if ($result === 1) {
+			return true;
+		} elseif (strstr($value, "http://localhost")) { // allow urls from localhost
+		    return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+     * Returns TRUE if value is a valid US ZIP, FALSE otherwise.
+     *
+     * @param mixed $value
+     * @return boolean
+     *
+     * @tag validator
+     * @static
+     */
+	static public function isZip($value)
+	{
+		return (bool) preg_match('/(^\d{5}$)|(^\d{5}-\d{4}$)/', $value);
+	}
+
+	/**
+     * Returns value with all tags removed.
+     * 
+     * This will utilize the PHP Filter extension if available
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     */
+	static public function noTags($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'noTags');
+		} else {
+			if (Inspekt::useFilterExt()) {
+				return filter_var($value, FILTER_SANITIZE_STRING);
+			} else {
+				return strip_tags($value);
+			}
+		}
+	}
+	
+	/**
+	 * returns value with tags stripped and the chars '"&<> and all ascii chars under 32 encoded as html entities
+	 * 
+	 * This will utilize the PHP Filter extension if available
+	 * 
+	 * @param mixed $value
+	 * @return @mixed
+	 * 
+	 * @tag filter
+	 * 
+	 */
+	static public function noTagsOrSpecial($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'noTagsOrSpecial');
+		} else {
+			if (Inspekt::useFilterExt()) {
+				$newval = filter_var($value, FILTER_SANITIZE_STRING);
+				$newval = filter_var($newval, FILTER_SANITIZE_SPECIAL_CHARS);
+				return $newval;
+			} else {
+				$newval = strip_tags($value);
+				$newval = htmlspecialchars($newval, ENT_QUOTES, 'UTF-8'); // for sake of simplicity and safety we assume UTF-8
+
+				/*
+					convert low ascii chars to entities
+				*/
+				$newval = str_split($newval);
+				for ($i=0; $i < count($newval); $i++) { 
+					$ascii_code = ord($newval[$i]);
+					if ($ascii_code < 32) {
+						$newval[$i] = "&#{$ascii_code};";
+					}
+				}
+				$newval = implode($newval);
+
+				return $newval;
+			}
+		}
+	}
+
+	/**
+     * Returns basename(value).
+     *
+     * @param mixed $value
+     * @return mixed
+     *
+     * @tag filter
+     * @static
+     */
+	static public function noPath($value)
+	{
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'noPath');
+		} else {
+			return basename($value);
+		}
+	}
+	
+	
+	/**
+	 * Escapes the value given with mysql_real_escape_string
+	 * 
+	 * @param mixed $value
+	 * @param resource $conn the mysql connection. If none is given, it will use the last link opened, per behavior of mysql_real_escape_string
+	 * @return mixed
+	 * 
+	 * @link http://php.net/manual/en/function.mysql-real-escape-string.php
+	 * 
+	 * @tag filter
+	 */
+	static public function escMySQL($value, $conn=null) {
+		
+		static $connection;
+		$connection = $conn;
+		
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'escMySQL');
+		} else {
+			if (isset($connection)) {
+				return mysql_real_escape_string($value, $connection);
+			} else {
+				return mysql_real_escape_string($value);
+			}
+			
+		}
+	}
+
+	/**
+	 * Escapes the value given with pg_escape_string
+	 * 
+	 * If the data is for a column of the type bytea, use Inspekt::escPgSQLBytea()
+	 * 
+	 * @param mixed $value
+	 * @param resource $conn the postgresql connection. If none is given, it will use the last link opened, per behavior of pg_escape_string
+	 * @return mixed
+	 * 
+	 * @link http://php.net/manual/en/function.pg-escape-string.php
+	 */
+	static public function escPgSQL($value, $conn=null) {
+		
+		static $connection;
+		$connection = $conn;
+		
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'escPgSQL');
+		} else {
+			if (isset($connection)) {
+				return pg_escape_string($connection, $value);
+			} else {
+				return pg_escape_string($value);
+			}
+		}
+	}
+
+
+	/**
+	 * Escapes the value given with pg_escape_bytea
+	 * 
+	 * @param mixed $value
+	 * @param resource $conn the postgresql connection. If none is given, it will use the last link opened, per behavior of pg_escape_bytea
+	 * @return mixed
+	 * 
+	 * @link http://php.net/manual/en/function.pg-escape-bytea.php
+	 */
+	static public function escPgSQLBytea($value, $conn=null) {
+		static $connection;
+		$connection = $conn;
+		
+		if (Inspekt::isArrayOrArrayObject($value)) {
+			return Inspekt::_walkArray($value, 'escPgSQL');
+		} else {
+			if (isset($connection)) {
+				return pg_escape_bytea($connection, $value); 
+			} else {
+				return pg_escape_bytea($value);
+			}
+		}
+
+	}
+
+}
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt/Cage/Session.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt/Cage/Session.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt/Cage/Session.php	(revision 1081)
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Inspekt Session Cage - main source file
+ *
+ * @author Chris Shiflett <chris@shiflett.org>
+ * @author Ed Finkler <coj@funkatron.com>
+ *
+ * @package Inspekt
+ * 
+ * @deprecated
+ */
+
+require_once dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'Cage.php';
+
+/**
+ * @package Inspekt
+ */
+class Inspekt_Cage_Session extends Inspekt_Cage {
+	
+	static public function Factory(&$source, $conf_file = NULL, $conf_section = NULL, $strict = TRUE) {
+
+		if (!is_array($source)) {
+			user_error('$source '.$source.' is not an array', E_USER_NOTICE);
+		}
+
+		$cage = new Inspekt_Cage_Session();
+		$cage->_setSource($source);
+		$cage->_parseAndApplyAutoFilters($conf_file);
+		
+		if (ini_get('session.use_cookies') || ini_get('session.use_only_cookies') ) {
+			if (isset($_COOKIE) && isset($_COOKIE[session_name()])) {
+				session_id($_COOKIE[session_name()]);
+			} elseif ($cookie = Inspekt::makeSessionCage()) {
+				session_id($cookie->getAlnum(session_name()));
+			}
+		} else { // we're using session ids passed via GET
+			if (isset($_GET) && isset($_GET[session_name()])) {
+				session_id($_GET[session_name()]);
+			} elseif ($cookie = Inspekt::makeSessionCage()) {
+				session_id($cookie->getAlnum(session_name()));
+			}
+		}
+		
+		
+		if ($strict) {
+			$source = NULL;
+		}
+
+		return $cage;
+		
+		register_shutdown_function();
+		
+		register_shutdown_function( array($this, '_repopulateSession') );
+		
+	}
+	
+	
+	
+	protected function _repopulateSession() {
+		$_SESSION = array();
+		$_SESSION = $this->_source;
+	}
+	
+
+	
+}
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt/Error.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt/Error.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt/Error.php	(revision 1081)
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Source file for Inspekt_Error
+ *
+ * @author Ed Finkler <coj@funkatron.com>
+ * @package Inspekt
+ */
+
+/**
+ * Error handling for Inspekt
+ *
+ * @package Inspekt
+ *
+ */
+class Inspekt_Error {
+
+	/**
+	 * Constructor
+	 *
+	 * @return Inspekt_Error
+	 */
+	function Inspekt_Error() {
+
+	}
+
+	/**
+	 * Raises an error.  In >= PHP5, this will throw an exception. In PHP4,
+	 * this will trigger a user error.
+	 *
+	 * @param string $msg
+	 * @param integer $type  One of the PHP Error Constants (E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE)
+	 *
+	 * @link http://www.php.net/manual/en/ref.errorfunc.php#errorfunc.constants
+	 * @todo support PHP5 exceptions without causing a syntax error.  Probably should use factory pattern and instantiate a different class depending on PHP version
+	 *
+	 * @static
+	 */
+	function raiseError($msg, $type=E_USER_WARNING) {
+		/*if (version_compare( PHP_VERSION, '5', '<' )) {
+			Inspekt_Error::raiseErrorPHP4($msg, $type);
+		} else {
+			throw new Exception($msg, $type);
+		}*/
+
+		Inspekt_Error::raiseErrorPHP4($msg, $type);
+	}
+
+	/**
+	 * Triggers a user error for PHP4-compatibility
+	 *
+	 * @param string $msg
+	 * @param integer $type  One of the PHP Error Constants (E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE)
+	 *
+	 * @static
+	 */
+	function raiseErrorPHP4 ($msg, $type=NULL) {
+
+		if (isset($type)) {
+			trigger_error($msg);
+		} else {
+			trigger_error($msg, $type);
+		}
+	}
+}
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt/Inspekt_CageTest.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt/Inspekt_CageTest.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt/Inspekt_CageTest.php	(revision 1081)
@@ -0,0 +1,636 @@
+<?php
+require_once 'PHPUnit/Framework.php';
+
+require_once 'Inspekt/Cage.php';
+
+/**
+ * Test class for Inspekt_Cage.
+ * Generated by PHPUnit on 2009-08-10 at 16:30:49.
+ */
+class Inspekt_CageTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var    Inspekt_Cage
+     * @access protected
+     */
+    protected $cage;
+
+    /**
+     * Sets up the fixture, for example, opens a network connection.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+		$inputarray['html'] = '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">';
+		
+
+		$this->cage = Inspekt_Cage::Factory($array);
+    }
+
+    /**
+     * Tears down the fixture, for example, closes a network connection.
+     * This method is called after a test is executed.
+     *
+     * @access protected
+     */
+    protected function tearDown()
+    {
+    }
+
+    /**
+     * @todo Implement testFactory().
+     */
+    public function testFactory()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetIterator().
+     */
+    public function testGetIterator()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testOffsetSet().
+     */
+    public function testOffsetSet()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testOffsetExists().
+     */
+    public function testOffsetExists()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testOffsetUnset().
+     */
+    public function testOffsetUnset()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testOffsetGet().
+     */
+    public function testOffsetGet()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testCount().
+     */
+    public function testCount()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testLoadHTMLPurifier().
+     */
+    public function testLoadHTMLPurifier()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testSetHTMLPurifier().
+     */
+    public function testSetHTMLPurifier()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetHTMLPurifier().
+     */
+    public function testGetHTMLPurifier()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_parseAndApplyAutoFilters().
+     */
+    public function test_parseAndApplyAutoFilters()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_applyAutoFilters().
+     */
+    public function test_applyAutoFilters()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test__call().
+     */
+    public function test__call()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testAddAccessor().
+     */
+    public function testAddAccessor()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetAlpha().
+     */
+    public function testGetAlpha()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetAlnum().
+     */
+    public function testGetAlnum()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetDigits().
+     */
+    public function testGetDigits()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetDir().
+     */
+    public function testGetDir()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetInt().
+     */
+    public function testGetInt()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetPath().
+     */
+    public function testGetPath()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetROT13().
+     */
+    public function testGetROT13()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetPurifiedHTML().
+     */
+    public function testGetPurifiedHTML()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testGetRaw().
+     */
+    public function testGetRaw()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestAlnum().
+     */
+    public function testTestAlnum()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestAlpha().
+     */
+    public function testTestAlpha()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestBetween().
+     */
+    public function testTestBetween()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestCcnum().
+     */
+    public function testTestCcnum()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestDate().
+     */
+    public function testTestDate()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestDigits().
+     */
+    public function testTestDigits()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestEmail().
+     */
+    public function testTestEmail()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestFloat().
+     */
+    public function testTestFloat()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestGreaterThan().
+     */
+    public function testTestGreaterThan()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestHex().
+     */
+    public function testTestHex()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestHostname().
+     */
+    public function testTestHostname()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestInt().
+     */
+    public function testTestInt()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestIp().
+     */
+    public function testTestIp()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestLessThan().
+     */
+    public function testTestLessThan()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestOneOf().
+     */
+    public function testTestOneOf()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestPhone().
+     */
+    public function testTestPhone()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestRegex().
+     */
+    public function testTestRegex()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestUri().
+     */
+    public function testTestUri()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testTestZip().
+     */
+    public function testTestZip()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testNoTags().
+     */
+    public function testNoTags()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testNoPath().
+     */
+    public function testNoPath()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testNoTagsOrSpecial().
+     */
+    public function testNoTagsOrSpecial()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testEscMySQL().
+     */
+    public function testEscMySQL()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testEscPgSQL().
+     */
+    public function testEscPgSQL()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testEscPgSQLBytea().
+     */
+    public function testEscPgSQLBytea()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement testKeyExists().
+     */
+    public function testKeyExists()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_keyExistsRecursive().
+     */
+    public function test_keyExistsRecursive()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_getValue().
+     */
+    public function test_getValue()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_getValueRecursive().
+     */
+    public function test_getValueRecursive()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_setValue().
+     */
+    public function test_setValue()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+
+    /**
+     * @todo Implement test_setValueRecursive().
+     */
+    public function test_setValueRecursive()
+    {
+        // Remove the following lines when you implement this test.
+        $this->markTestIncomplete(
+          'This test has not been implemented yet.'
+        );
+    }
+}
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt/Supercage.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt/Supercage.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt/Supercage.php	(revision 1081)
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Inspekt Supercage
+ *
+ * @author Ed Finkler <coj@funkatron.com>
+ *
+ * @package Inspekt
+ */
+
+/**
+ * require main Inspekt class
+ */
+require_once EXTENSIONS . 'Inspekt/Inspekt.php';
+
+/**
+ * require the Cage class
+ */
+require_once 'Cage.php';
+
+/**
+ * The Supercage object wraps ALL of the superglobals
+ * 
+ * @package Inspekt
+ *
+ */
+Class Inspekt_Supercage {
+
+	/**
+	 * The get cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $get;
+
+	/**
+	 * The post cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $post;
+
+	/**
+	 * The cookie cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $cookie;
+
+	/**
+	 * The env cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $env;
+
+	/**
+	 * The files cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $files;
+
+	/**
+	 * The session cage
+	 *
+	 * @var Inspekt_Cage
+	 */
+	var $session;
+
+	var $server;
+
+	/**
+	 * Enter description here...
+	 *
+	 * @return Inspekt_Supercage
+	 */
+	public function Inspekt_Supercage() {
+		// placeholder
+	}
+
+	/**
+	 * Enter description here...
+	 * 
+	 * @param string  $config_file
+	 * @param boolean $strict
+	 * @return Inspekt_Supercage
+	 */
+	static public function Factory($config_file = NULL, $strict = TRUE) {
+
+		$sc	= new Inspekt_Supercage();
+		$sc->_makeCages($config_file, $strict);
+
+		// eliminate the $_REQUEST superglobal
+		if ($strict) {
+			$_REQUEST = null;
+		}
+
+		return $sc;
+
+	}
+
+	/**
+	 * Enter description here...
+	 *
+	 * @see Inspekt_Supercage::Factory()
+	 * @param string  $config_file
+	 * @param boolean $strict
+	 */
+	protected function _makeCages($config_file=NULL, $strict=TRUE) {
+		$this->get		= Inspekt::makeGetCage($config_file, $strict);
+		$this->post		= Inspekt::makePostCage($config_file, $strict);
+		$this->cookie	= Inspekt::makeCookieCage($config_file, $strict);
+		$this->env		= Inspekt::makeEnvCage($config_file, $strict);
+		$this->files	= Inspekt::makeFilesCage($config_file, $strict);
+		// $this->session	= Inspekt::makeSessionCage($config_file, $strict);
+		$this->server	= Inspekt::makeServerCage($config_file, $strict);
+	}
+	
+	
+	public function addAccessor($name) {
+		$this->get->addAccessor($name);
+		$this->post->addAccessor($name);
+		$this->cookie->addAccessor($name);
+		$this->env->addAccessor($name);
+		$this->files->addAccessor($name);
+		// $this->session->addAccessor($name);
+		$this->server->addAccessor($name);
+	}
+
+}
+?>
Index: /trunk/libs/extensions/Inspekt/Inspekt/Cage.php
===================================================================
--- /trunk/libs/extensions/Inspekt/Inspekt/Cage.php	(revision 1081)
+++ /trunk/libs/extensions/Inspekt/Inspekt/Cage.php	(revision 1081)
@@ -0,0 +1,1068 @@
+<?php
+/**
+ * Inspekt Cage - main source file
+ *
+ * @author Chris Shiflett <chris@shiflett.org>
+ * @author Ed Finkler <coj@funkatron.com>
+ *
+ * @package Inspekt
+ */
+
+/**
+ * require main Inspekt file
+ */
+require_once EXTENSIONS . 'Inspekt/Inspekt.php';
+
+
+define ('ISPK_ARRAY_PATH_SEPARATOR', '/');
+
+define ('ISPK_RECURSION_MAX', 15);
+
+/**
+ * @package Inspekt
+ */
+class Inspekt_Cage implements IteratorAggregate, ArrayAccess, Countable {
+/**
+ * {@internal The raw source data.  Although tempting, NEVER EVER
+ * EVER access the data directly using this property!}}
+ *
+ * Don't try to access this.  ever.  Now that we're safely on PHP5, we'll
+ * enforce this with the "protected" keyword.
+ *
+ * @var array
+ */
+	protected $_source = NULL;
+
+	/**
+	 * where we store user-defined methods
+	 *
+	 * @var array
+	 */
+	public $_user_accessors = array();
+
+	/**
+	 * the holding property for autofilter config
+	 *
+	 * @var array
+	 */
+	public $_autofilter_conf = NULL;
+
+	/**
+	 *
+	 * @var HTMLPurifer
+	 */
+	public $purifier = NULL;
+
+
+	/**
+	 *
+	 * @return Inspekt_Cage
+	 */
+	public function Inspekt_Cage() {
+		// placeholder -- we're using a factory here
+	}
+
+
+
+	/**
+	 * Takes an array and wraps it inside an object.  If $strict is not set to
+	 * FALSE, the original array will be destroyed, and the data can only be
+	 * accessed via the object's accessor methods
+	 *
+	 * @param array $source
+	 * @param string $conf_file
+	 * @param string $conf_section
+	 * @param boolean $strict
+	 * @return Inspekt_Cage
+	 *
+	 * @static
+	 */
+	static public function Factory(&$source, $conf_file = NULL, $conf_section = NULL, $strict = TRUE) {
+
+		if (!is_array($source)) {
+			user_error('$source '.$source.' is not an array', E_USER_WARNING);
+		}
+
+		$cage = new Inspekt_Cage();
+		$cage->_setSource($source);
+		$cage->_parseAndApplyAutoFilters($conf_file, $conf_section);
+
+		if ($strict) {
+			$source = NULL;
+		}
+
+		return $cage;
+	}
+
+
+	/**
+	 * {@internal we use this to set the data array in Factory()}}
+	 *
+	 * @see Factory()
+	 * @param array $newsource
+	 */
+	private function _setSource(&$newsource) {
+
+		$this->_source = Inspekt::convertArrayToArrayObject($newsource);
+
+	}
+
+
+	/**
+	 * Returns an iterator for looping through an ArrayObject.
+	 *
+	 * @access public
+	 * @return ArrayIterator
+	 */
+	public function getIterator() {
+		return $this->_source->getIterator();
+	}
+
+
+	/**
+	 * Sets the value at the specified $offset to value$
+	 * in $this->_source.
+	 *
+	 * @param mixed $offset
+	 * @param mixed $value
+	 * @access public
+	 * @return void
+	 */
+	public function offsetSet($offset, $value) {
+		$this->_source->offsetSet($offset, $value);
+	}
+
+
+	/**
+	 * Returns whether the $offset exists in $this->_source.
+	 *
+	 * @param mixed $offset
+	 * @access public
+	 * @return bool
+	 */
+	public function offsetExists($offset) {
+		return $this->_source->offsetExists($offset);
+	}
+
+
+	/**
+	 * Unsets the value in $this->_source at $offset.
+	 *
+	 * @param mixed $offset
+	 * @access public
+	 * @return void
+	 */
+	public function offsetUnset($offset) {
+		$this->_source->offsetUnset($offset);
+	}
+
+
+	/**
+	 * Returns the value at $offset from $this->_source.
+	 *
+	 * @param mixed $offset
+	 * @access public
+	 * @return void
+	 */
+	public function offsetGet($offset) {
+		return $this->_source->offsetGet($offset);
+	}
+
+
+	/**
+	 * Returns the number of elements in $this->_source.
+	 *
+	 * @access public
+	 * @return int
+	 */
+	public function count() {
+		return $this->_source->count();
+	}
+
+
+	/**
+	 * Load the HTMLPurifier library and instantiate the object
+	 * @param string $path the full path to the HTMLPurifier.auto.php base file. Optional if HTMLPurifier is already in your include_path
+	 */
+	public function loadHTMLPurifier($path=null, $opts=null) {
+		if (isset($path)) {
+			include_once($path);
+		} else {
+			include_once('HTMLPurifier.auto.php');
+		}
+
+		if (isset($opts) && is_array($opts)) {
+			$config = $this->_buildHTMLPurifierConfig($opts);
+		} else {
+			$config = null;
+		}
+
+		$this->purifier = new HTMLPurifier($config);
+	}
+
+
+	/**
+	 *
+	 * @param HTMLPurifer $pobj an HTMLPurifer Object
+	 */
+	public function setHTMLPurifier($pobj) {
+		$this->purifier = $pobj;
+	}
+
+	/**
+	 * @return HTMLPurifier
+	 */
+	public function getHTMLPurifier() {
+		return $this->purifier;
+	}
+
+
+	protected function _buildHTMLPurifierConfig($opts) {
+		$config = HTMLPurifier_Config::createDefault();
+		foreach ($opts as $key=>$val) {
+			$config->set($key, $val);
+		}
+		return $config;
+	}
+
+
+	function _parseAndApplyAutoFilters($conf_file, $conf_section) {
+		if (isset($conf_file)) {
+			$conf = parse_ini_file($conf_file, true);
+			if ($conf_section) {
+				if (isset($conf[$conf_section])) {
+					$this->_autofilter_conf = $conf[$conf_section];
+				}
+			} else {
+				$this->_autofilter_conf = $conf;
+			}
+
+			$this->_applyAutoFilters();
+		}
+	}
+
+
+	function _applyAutoFilters() {
+
+		if ( isset($this->_autofilter_conf) && is_array($this->_autofilter_conf)) {
+
+			foreach($this->_autofilter_conf as $key=>$filters) {
+
+			// get universal filter key
+				if ($key == '*') {
+
+				// get filters for this key
+					$uni_filters = explode(',', $this->_autofilter_conf[$key]);
+					array_walk($uni_filters, 'trim');
+
+					// apply uni filters
+					foreach($uni_filters as $this_filter) {
+						foreach($this->_source as $key=>$val) {
+							$this->_source[$key] = $this->$this_filter($key);
+						}
+					}
+				//echo "<pre>UNI FILTERS"; echo var_dump($this->_source); echo "</pre>\n";
+
+				} elseif($val == $this->keyExists($key)) {
+
+				// get filters for this key
+					$filters = explode(',', $this->_autofilter_conf[$key]);
+					array_walk($filters, 'trim');
+
+					// apply filters
+					foreach($filters as $this_filter) {
+						$this->_setValue($key, $this->$this_filter($key));
+					}
+				//echo "<pre> Filter $this_filter/$key: "; echo var_dump($this->_source); echo "</pre>\n";
+
+				}
+			}
+		}
+	}
+
+
+
+	function __call($name, $args) {
+		if (in_array($name, $this->_user_accessors) ) {
+
+			$acc = new $name($this, $args);
+			/*
+				this first argument should always be the key we're accessing
+			*/
+			return $acc->run($args[0]);
+
+		} else {
+			trigger_error("The accessor $name does not exist and is not registered", E_USER_ERROR);
+			return false;
+		}
+
+	}
+
+	/**
+	 * This method lets the developer add new accessor methods to a cage object
+	 * Note that calling these will be quite a bit slower, because we have to
+	 * use call_user_func()
+	 *
+	 * The dev needs to define a procedural function like so:
+	 *
+	 * <code>
+	 * function foo_bar($cage_object, $arg2, $arg3, $arg4, $arg5...) {
+	 *    ...
+	 * }
+	 * </code>
+	 *
+	 * @param string $method_name
+	 * @return void
+	 * @author Ed Finkler
+	 */
+	function addAccessor($accessor_name) {
+		$this->_user_accessors[] = $accessor_name;
+	}
+
+
+	/**
+	 * Returns only the alphabetic characters in value.
+	 *
+	 * @param mixed $key
+	 * @return mixed
+	 *
+	 * @tag filter
+	 */
+	function getAlpha($key) {
+		if (!$this->keyExists($key)) {
+			return false;
+		}
+		return Inspekt::getAlpha($this->_getValue($key));
+	}
+
+	/**
+	 * Returns only the alphabetic characters and digits in value.
+	 *
+	 * @param mixed $key
+	 * @return mixed
+	 *
+	 * @tag filter
+	 */
+	function getAlnum($key) {
+		if (!$this->keyExists($key)) {
+			return false;
+		}
+		return Inspekt::getAlnum($this->_getValue($key));
+	}
+
+	/**
+	 * Returns only the digits in value. This differs from getInt().
+	 *
+	 * @param mixed $key
+	 * @return mixed
+	 *
+	 * @tag filter
+	 */
+	function getDigits($key) {
+		if (!$this->keyExists($key)) {
+			return false;
+		}
+		return Inspekt::getDigits($this->_getValue($key));
+	}
+
+	/**
+	 * Returns dirname(value).
+	 *
+	 * @param mixed $key
+	 * @return mixed
+	 *
+	 * @tag filter
+	 */
+	function getDir($key) {
+		if (!$this->keyExists($key)) {
+			return false;
+		}
+		return Inspekt::getDir($this->_getValue($key));
+	}
+
+	/**
+	 * Returns (int) value.
+	 *
+	 * @param mixed $key
+	 * @return int
+	 *
+	 * @tag filter
+	 */
+	function getInt($key) {
+		if (!$this->keyExists($key)) {
+			return false;
+		}
+		return Inspekt::getInt($this->_getValue($key));
+	}
+
+	/**
+	 * Returns realpath(value).
+	 *
+	 * @param mixed $key
+	 * @return mixed
+	 *
+	 * @tag filter
+	 */
+	function getPath($key) {
+		if (!$this->keyExists($