<?php
/**
 * Elgg version library.
 * Contains code for handling versioning and upgrades.
 *
 * @package Elgg.Core
 * @subpackage Version
 */

/**
 * Run any php upgrade scripts which are required
 *
 * @param int  $version Version upgrading from.
 * @param bool $quiet   Suppress errors.  Don't use this.
 *
 * @return bool
 */
function upgrade_code($version, $quiet = FALSE) {
	global $CONFIG;

	$version = (int) $version;
	$upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/';
	$processed_upgrades = unserialize(datalist_get('processed_upgrades'));
	// the day we started the new upgrade names
	$upgrade_epoch = 2011021700;

	if (!$processed_upgrades) {
		$processed_upgrades = array();
	}

	$upgrades = array();

	$upgrade_files = elgg_get_upgrade_files($upgrade_path);

	if ($upgrade_files === false) {
		return false;
	}

	// bootstrap into the new upgrade system.
	// can't do this in an upgrade because we need to check for 2010050701,
	// which would already have been run by then.
	if ($version < $upgrade_epoch) {
		foreach ($upgrade_files as $upgrade_file) {
			$upgrade_version = elgg_get_upgrade_file_version($upgrade_file);

			// the upgrade that made life difficult
			// the only way to test if we're upgrading from 1.7 to 1.8 or within 1.8
			// is to test for the the walled_garden config option, which
			// 2010050701 explicitly sets
			if ($upgrade_version == 2010050701) {
				$db_prefix = elgg_get_config('dbprefix');
				$site_guid = elgg_get_config('site_guid');
				$q = "SELECT value FROM {$db_prefix}config
					WHERE name = 'walled_garden' AND site_guid = {$site_guid}";
				$result = get_data_row($q);
				if (!$result) {
					$upgrades[] = $upgrade_file;
				}

				continue;
			} elseif ($version < $upgrade_version) {
				$upgrades[] = $upgrade_file;
			} else {
				// all of the upgrades before the epoch have been run except one...
				$processed_upgrades[] = $upgrade_file;
			}
		}
	} else {
		// add any upgrades that haven't been run to the upgrades list
		$upgrades = elgg_get_unprocessed_upgrades($upgrade_files, $processed_upgrades);
	}

	// Sort and execute
	sort($upgrades);

	foreach ($upgrades as $upgrade) {
		$upgrade_version = elgg_get_upgrade_file_version($upgrade);
		$success = true;

		// hide all errors.
		if ($quiet) {
			// hide include errors as well as any exceptions that might happen
			try {
				if (!@include("$upgrade_path/$upgrade")) {
					$success = false;
					error_log($e->getmessage());
				}
			} catch (Exception $e) {
				$success = false;
				error_log($e->getmessage());
			}
		} else {
			if (!include("$upgrade_path/$upgrade")) {
				$success = false;
			}
		}

		if ($success) {
			// incrementally set upgrade so we know where to start if something fails.
			$processed_upgrades[] = $upgrade;

			// don't set the version to a lower number in instances where an upgrade
			// has been merged from a lower version
			if ($upgrade_version > $version) {
				datalist_set('version', $upgrade_version);
			}

			$processed_upgrades = array_unique($processed_upgrades);
			datalist_set('processed_upgrades', serialize($processed_upgrades));
		} else {
			return false;
		}
	}

	return true;
}

/**
 * Returns the version of the upgrade filename.
 *
 * @param string $filename The upgrade filename. No full path.
 * @return int|false
 * @since 1.8
 */
function elgg_get_upgrade_file_version($filename) {
	preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);

	if (isset($matches[1])) {
		return (int) $matches[1];
	}

	return false;
}

/**
 * Returns a list of upgrade files relative to the $upgrade_path dir.
 *
 * @param string $upgrade_path The up
 * @return array|false
 */
function elgg_get_upgrade_files($upgrade_path = null) {
	if (!$upgrade_path) {
		$upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/';
	}
	$upgrade_path = sanitise_filepath($upgrade_path);
	$handle = opendir($upgrade_path);

	if (!$handle) {
		return false;
	}

	$upgrade_files = array();

	while ($upgrade_file = readdir($handle)) {
		// make sure this is a wellformed upgrade.
		if (is_dir($upgrade_path . '$upgrade_file')) {
			continue;
		}
		$upgrade_version = elgg_get_upgrade_file_version($upgrade_file);
		if (!$upgrade_version) {
			continue;
		}
		$upgrade_files[] = $upgrade_file;
	}

	return $upgrade_files;
}

/**
 * Get the current version information
 *
 * @param bool $humanreadable Whether to return a human readable version (default: false)
 *
 * @return string|false Depending on success
 */
function get_version($humanreadable = false) {
	global $CONFIG;

	if (isset($CONFIG->path)) {
		if (include($CONFIG->path . "version.php")) {
			return (!$humanreadable) ? $version : $release;
		}
	}

	return FALSE;
}

/**
 * Checks if any upgrades need to be run.
 *
 * @param null|array $upgrade_files      Optional upgrade files
 * @param null|array $processed_upgrades Optional processed upgrades
 *
 * @return array()
 */
function elgg_get_unprocessed_upgrades($upgrade_files = null, $processed_upgrades = null) {
	if ($upgrade_files === null) {
		$upgrade_files = elgg_get_upgrade_files();
	}

	if ($processed_upgrades === null) {
		$processed_upgrades = unserialize(datalist_get('processed_upgrades'));
		if (!is_array($processed_upgrades)) {
			$processed_upgrades = array();
		}
	}

	$unprocessed = array_diff($upgrade_files, $processed_upgrades);
	return $unprocessed;
}

/**
 * Determines whether or not the database needs to be upgraded.
 *
 * @return true|false Depending on whether or not the db version matches the code version
 */
function version_upgrade_check() {
	$dbversion = (int) datalist_get('version');
	$version = get_version();

	if ($version > $dbversion) {
		return TRUE;
	}

	return FALSE;
}

/**
 * Upgrades Elgg Database and code
 *
 * @return bool
 *
 */
function version_upgrade() {
	// It's possible large upgrades could exceed the max execution time.
	set_time_limit(0);

	$dbversion = (int) datalist_get('version');

	// No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
	// Run all upgrades without error reporting and hope for the best.
	// See http://trac.elgg.org/elgg/ticket/1432 for more.
	$quiet = !$dbversion;

	// Note: Database upgrades are deprecated as of 1.8.  Use code upgrades.  See #1433
	if (db_upgrade($dbversion, '', $quiet)) {
		system_message(elgg_echo('upgrade:db'));
	}

	if (upgrade_code($dbversion, $quiet)) {
		system_message(elgg_echo('upgrade:core'));

		// Now we trigger an event to give the option for plugins to do something
		$upgrade_details = new stdClass;
		$upgrade_details->from = $dbversion;
		$upgrade_details->to = get_version();

		elgg_trigger_event('upgrade', 'upgrade', $upgrade_details);

		return true;
	}

	return false;
}