<?php
/**
* File: Upgrader
* A task-based general-purpose Chyrp upgrader.
*
* Performs upgrade functions based on individual tasks, and checks whether or not they need to be done.
*
* Version-agnostic. Completely safe to be run at all times, by anyone.
*/
header("Content-type: text/html; charset=UTF-8");
define('DEBUG', true);
define('UPGRADING', true);
define('XML_RPC', true);
define('MAIN_DIR', dirname(__FILE__));
define('INCLUDES_DIR', dirname(__FILE__)."/includes");
/**
* Function: config_file
* Returns what config file their install is set up for.
*/
function config_file() {
if (file_exists(INCLUDES_DIR."/config.yaml.php"))
return INCLUDES_DIR."/config.yaml.php";
if (file_exists(INCLUDES_DIR."/config.yml.php"))
return INCLUDES_DIR."/config.yml.php";
if (file_exists(INCLUDES_DIR."/config.php"))
return INCLUDES_DIR."/config.php";
exit("Config file not found.");
}
/**
* Function: database_file
* Returns what database config file their install is set up for.
*/
function database_file() {
if (file_exists(INCLUDES_DIR."/database.yaml.php"))
return INCLUDES_DIR."/database.yaml.php";
if (file_exists(INCLUDES_DIR."/database.yml.php"))
return INCLUDES_DIR."/database.yml.php";
if (file_exists(INCLUDES_DIR."/database.php"))
return INCLUDES_DIR."/database.php";
return false;
}
/**
* Function: using_yaml
* Are they using YAML config storage?
*/
function using_yaml() {
return (basename(config_file()) != "config.php" and basename(database_file()) != "database.php") or !database_file();
}
# Evaluate the code in their config files, but with the classes renamed, so we can safely retrieve the values.
if (!using_yaml()) {
eval(str_replace(array("<?php", "?>", "Config"),
array("", "", "OldConfig"),
file_get_contents(config_file())));
if (database_file())
eval(str_replace(array("<?php", "?>", "SQL"),
array("", "", "OldSQL"),
file_get_contents(database_file())));
}
# File: Helpers
# Various functions used throughout Chyrp's code.
require_once INCLUDES_DIR."/helpers.php";
# File: Gettext
# Gettext library.
require_once INCLUDES_DIR."/lib/gettext/gettext.php";
# File: Streams
# Streams library.
require_once INCLUDES_DIR."/lib/gettext/streams.php";
# File: YAML
# Horde YAML parsing library.
require_once INCLUDES_DIR."/lib/YAML.php";
# File: SQL
# See Also:
# <SQL>
require INCLUDES_DIR."/class/SQL.php";
/**
* Class: Config
* Handles writing to whichever config file they're using.
*/
class Config {
# Array: $yaml
# Stores all of the YAML data.
static $yaml = array("config" => array(),
"database" => array());
/**
* Function: get
* Returns a config setting.
*
* Parameters:
* $setting - The setting to return.
*/
static function get($setting) {
return (isset(Config::$yaml["config"][$setting])) ? Config::$yaml["config"][$setting] : false ;
}
/**
* Function: set
* Sets a config setting.
*
* Parameters:
* $setting - The config setting to set.
* $value - The value for the setting.
* $message - The message to display with test().
*/
static function set($setting, $value, $message = null) {
if (self::get($setting) == $value) return;
if (!isset($message))
$message = _f("Setting %s to %s...", array($setting, normalize(print_r($value, true))));
Config::$yaml["config"][$setting] = $value;
$protection = "<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n";
$dump = $protection.YAML::dump(Config::$yaml["config"]);
echo $message.test(@file_put_contents(INCLUDES_DIR."/config.yaml.php", $dump));
}
/**
* Function: check
* Goes a config exist?
*
* Parameters:
* $setting - Name of the config to check.
*/
static function check($setting) {
return (isset(Config::$yaml["config"][$setting]));
}
/**
* Function: fallback
* Sets a config setting to $value if it does not exist.
*
* Parameters:
* $setting - The config setting to set.
* $value - The value for the setting.
* $message - The message to display with test().
*/
static function fallback($setting, $value, $message = null) {
if (!isset($message))
$message = _f("Adding %s setting...", array($setting));
if (!self::check($setting))
echo self::set($setting, $value, $message);
}
/**
* Function: remove
* Removes a setting if it exists.
*
* Parameters:
* $setting - The setting to remove.
*/
static function remove($setting) {
if (!self::check($setting)) return;
unset(Config::$yaml["config"][$setting]);
$protection = "<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>\n";
$dump = $protection.YAML::dump(Config::$yaml["config"]);
echo _f("Removing %s setting...", array($setting)).
test(@file_put_contents(INCLUDES_DIR."/config.yaml.php", $dump));
}
}
if (using_yaml()) {
Config::$yaml["config"] = YAML::load(preg_replace("/<\?php(.+)\?>\n?/s", "", file_get_contents(config_file())));
if (database_file())
Config::$yaml["database"] = YAML::load(preg_replace("/<\?php(.+)\?>\n?/s",
"",
file_get_contents(database_file())));
else
Config::$yaml["database"] = fallback(Config::$yaml["config"]["sql"], array(), true);
} else {
# $config and $sql here are loaded from the eval()'s above.
foreach ($config as $name => $val)
Config::$yaml["config"][$name] = $val;
foreach ($sql as $name => $val)
Config::$yaml["database"][$name] = $val;
}
load_translator("chyrp", INCLUDES_DIR."/locale/".Config::get("locale").".mo");
/**
* Function: test
* Attempts to perform a task, and displays a "success" or "failed" message determined by the outcome.
*
* Parameters:
* $try - The task to attempt. Should return something that evaluates to true or false.
* $message - Message to display for the test.
*/
function test($try, $message = "") {
$sql = SQL::current();
if (!empty($sql->error)) {
$message.= "\n".$sql->error."\n\n";
$sql->error = "";
}
$info = $message;
if ($try)
return " <span class=\"yay\">".__("success!")."</span>\n";
else
return " <span class=\"boo\">".__("failed!")."</span>\n".$info;
}
/**
* Function: xml2arr
* Recursively converts a SimpleXML object (and children) to an array.
*
* Parameters:
* $parse - The SimpleXML object to convert into an array.
*/
function xml2arr($parse) {
if (empty($parse))
return "";
$parse = (array) $parse;
foreach ($parse as &$val)
if (get_class($val) == "SimpleXMLElement")
$val = self::xml2arr($val);
return $parse;
}
/**
* Function: arr2xml
* Recursively adds an array (or object I guess) to a SimpleXML object.
*
* Parameters:
* $object - The SimpleXML object to add to.
* $data - The data to add to the SimpleXML object.
*/
function arr2xml(&$object, $data) {
foreach ($data as $key => $val) {
if (is_int($key) and (empty($val) or (is_string($val) and trim($val) == ""))) {
unset($data[$key]);
continue;
}
if (is_array($val)) {
if (in_array(0, array_keys($val))) { # Numeric-indexed things need to be added as duplicates
foreach ($val as $dup) {
$xml = $object->addChild($key);
arr2xml($xml, $dup);
}
} else {
$xml = $object->addChild($key);
arr2xml($xml, $val);
}
} else
$object->addChild($key, fix($val, false, false));
}
}
#---------------------------------------------
# Upgrading Actions
#---------------------------------------------
function fix_htaccess() {
$url = "http://".$_SERVER['HTTP_HOST'].str_replace("/upgrade.php", "", $_SERVER['REQUEST_URI']);
$index = (parse_url($url, PHP_URL_PATH)) ? "/".trim(parse_url($url, PHP_URL_PATH), "/")."/" : "/" ;
$path = preg_quote($index, "/");
$htaccess_has_chyrp = (file_exists(MAIN_DIR."/.htaccess") and preg_match("/<IfModule mod_rewrite\.c>\n([\s]*)RewriteEngine On\n([\s]*)RewriteBase {$path}\n([\s]*)RewriteCond %\{REQUEST_FILENAME\} !-f\n([\s]*)RewriteCond %\{REQUEST_FILENAME\} !-d\n([\s]*)RewriteRule (\^\.\+\\$|\!\\.\(gif\|jpg\|png\|css\)) index\.php \[L\]\n([\s]*)<\/IfModule>/", file_get_contents(MAIN_DIR."/.htaccess")));
if ($htaccess_has_chyrp)
return;
$htaccess = "<IfModule mod_rewrite.c>\nRewriteEngine On\nRewriteBase {$index}\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule ^.+$ index.php [L]\n</IfModule>";
if (!file_exists(MAIN_DIR."/.htaccess"))
echo __("Generating .htaccess file...").
test(@file_put_contents(MAIN_DIR."/.htaccess", $htaccess), __("Try creating the file and/or CHMODding it to 777 temporarily."));
else
echo __("Appending to .htaccess file...").
test(@file_put_contents(MAIN_DIR."/.htaccess", "\n\n".$htaccess, FILE_APPEND), __("Try creating the file and/or CHMODding it to 777 temporarily."));
}
function tweets_to_posts() {
if (SQL::current()->query("SELECT * FROM __tweets"))
echo __("Renaming tweets table to posts...").
test(SQL::current()->query("RENAME TABLE __tweets TO __posts"));
if (SQL::current()->query("SELECT add_tweet FROM __groups"))
echo __("Renaming add_tweet permission to add_post...").
test(SQL::current()->query("ALTER TABLE __groups CHANGE add_tweet add_post TINYINT(1) NOT NULL DEFAULT '0'"));
if (SQL::current()->query("SELECT edit_tweet FROM __groups"))
echo __("Renaming edit_tweet permission to edit_post...").
test(SQL::current()->query("ALTER TABLE __groups CHANGE edit_tweet edit_post TINYINT(1) NOT NULL DEFAULT '0'"));
if (SQL::current()->query("SELECT delete_tweet FROM __groups"))
echo __("Renaming delete_tweet permission to delete_post...").
test(SQL::current()->query("ALTER TABLE __groups CHANGE delete_tweet delete_post TINYINT(1) NOT NULL DEFAULT '0'"));
if (Config::check("tweets_per_page")) {
Config::fallback("posts_per_page", Config::get("tweets_per_page"));
Config::remove("tweets_per_page");
}
}
function pages_parent_id_column() {
if (SQL::current()->query("SELECT parent_id FROM __pages"))
return;
echo __("Adding parent_id column to pages table...").
test(SQL::current()->query("ALTER TABLE __pages ADD parent_id INT(11) NOT NULL DEFAULT '0' AFTER user_id"));
}
function pages_list_order_column() {
if (SQL::current()->query("SELECT list_order FROM __pages"))
return;
echo __("Adding list_order column to pages table...").
test(SQL::current()->query("ALTER TABLE __pages ADD list_order INT(11) NOT NULL DEFAULT '0' AFTER show_in_list"));
}
function remove_beginning_slash_from_post_url() {
if (substr(Config::get("post_url"), 0, 1) == "/")
Config::set("post_url", ltrim(Config::get("post_url"), "/"));
}
function move_yml_yaml() {
if (file_exists(INCLUDES_DIR."/config.yml.php"))
echo __("Moving /includes/config.yml.php to /includes/config.yaml.php...").
test(@rename(INCLUDES_DIR."/config.yml.php", INCLUDES_DIR."/config.yaml.php"), __("Try CHMODding the file to 777."));
}
function update_protection() {
if (!file_exists(INCLUDES_DIR."/config.yaml.php") or
substr_count(file_get_contents(INCLUDES_DIR."/config.yaml.php"),
"<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>"))
return;
$contents = file_get_contents(INCLUDES_DIR."/config.yaml.php");
$new_error = preg_replace("/<\?php (.+) \?>/",
"<?php header(\"Status: 403\"); exit(\"Access denied.\"); ?>",
$contents);
echo __("Updating protection code in config.yaml.php...").
test(@file_put_contents(INCLUDES_DIR."/config.yaml.php", $new_error), __("Try CHMODding the file to 777."));
}
function theme_default_to_stardust() {
if (Config::get("theme") != "default") return;
Config::set("theme", "stardust");
}
function default_db_adapter_to_mysql() {
$sql = SQL::current();
if (isset($sql->adapter)) return;
$sql->set("adapter", "mysql");
}
function move_upload() {
if (file_exists(MAIN_DIR."/upload") and !file_exists(MAIN_DIR."/uploads"))
echo __("Renaming /upload directory to /uploads...").test(@rename(MAIN_DIR."/upload", MAIN_DIR."/uploads"), __("Try CHMODding the directory to 777."));
}
function make_posts_safe() {
if (!$posts = SQL::current()->query("SELECT * FROM __posts"))
return;
# Replace all the posts' CDATAized XML with well-formed XML.
while ($post = $posts->fetchObject()) {
if (!substr_count($post->xml, "<![CDATA["))
continue;
$xml = simplexml_load_string($post->xml, "SimpleXMLElement", LIBXML_NOCDATA);
$parse = xml2arr($xml);
array_walk_recursive($parse, "fix");
$new_xml = new SimpleXMLElement("<post></post>");
arr2xml($new_xml, $parse);
echo _f("Sanitizing XML data of post #%d...", array($post->id)).
test(SQL::current()->update("posts",
array("id" => $post->id),
&nbs