<?php

function upgrade_joomla25($root_dir, $db_host, $db_user, $db_password, $db_database, $db_prefix) {

	//First delete old joomla files (Plesk bug)
	deleteOldJoomlaFiles($root_dir);

	//Joomla Database Upgrade
	$updater = APSJoomlaUpgrade::getInstance($root_dir, $db_host, $db_user, $db_password, $db_database, $db_prefix);
	$updater->upgrade();
}

class APSJoomlaUpgrade{

	var $rootdir = null;
	var $db = null;
	var $folder = null;
	var $changeItems = array();

	public function __construct($root_dir, $db_host, $db_user, $db_password, $db_database, $db_prefix) {
		$this->rootdir=$root_dir;
		$this->db = APSJoomlaDB::getInstance($db_host, $db_user, $db_password, $db_database, $db_prefix);
	}

	public static function getInstance($root_dir, $db_host, $db_user, $db_password, $db_database, $db_prefix) {
		static $instance;
		if (!is_object($instance)) {
			$instance = new APSJoomlaUpgrade($root_dir, $db_host, $db_user, $db_password, $db_database, $db_prefix);
		}
		return $instance;
	}

	private function fixSchemaVersion($schema_version) {

		// Delete old row
		$this->db->setQuery("DELETE FROM #__schemas WHERE extension_id=700");
		$this->db->executeQuery();

		//Add new row
		$this->db->setQuery("INSERT INTO #__schemas (extension_id, version_id) VALUES (700, '".$schema_version."')");
		$this->db->executeQuery();
	}

	private function getItems() {
		$folder = $this->rootdir . '/components/com_admin/sql/updates/mysql';
		$changeSet = JSchemaChangesetAPS::getInstance($this->rootdir, $this->db, $folder, $this->getJoomlaVersionFromDatabase());
		return $changeSet;
	}

	public function upgrade() {
		$changeSet = $this->getItems();
		$changeSet->fix();
		$schema_version = $changeSet->getSchema();
		$this->fixSchemaVersion($schema_version);
		$this->updateJoomlaManifestCache($schema_version);
		$this->fixTagsMenuLinkBug();
	}

	private function getJoomlaVersionFromDatabase() {

		//Get actual version id
		$this->db->setQuery("SELECT version_id FROM #__schemas WHERE extension_id=700");
		$version_id=$this->db->loadResult();

		//If joomla schema version_id is not set
		if(!$version_id) {
				
			$this->db->setQuery("SELECT manifest_cache FROM #__extensions WHERE extension_id=700");
			$manifest_cache=$this->db->loadResult();
			if($manifest_cache) {
				$manifest_cache = preg_replace('/^"{/', '{', $manifest_cache);
				$manifest_cache = preg_replace('/}"$/', '}', $manifest_cache);
				$manifest_cache_array = json_decode( $manifest_cache, true );
				$version_id = $manifest_cache_array['version'];
			}
		}
		if(!$version_id) {
			$version_id="1.6.0";
		}
		return $version_id;
	}

	private function updateJoomlaManifestCache($schema_version) {

		$this->db->setQuery("SELECT manifest_cache FROM #__extensions WHERE extension_id=700");
		$manifest_cache=$this->db->loadResult();
		// removing trailing ". Found them in some installations for unknown reason
		$manifest_cache = preg_replace('/^"{/', '{', $manifest_cache);
		$manifest_cache = preg_replace('/}"$/', '}', $manifest_cache);
		if(strlen($manifest_cache) > 0) {
			$manifest_cache_array = json_decode( $manifest_cache, true );
			if ($manifest_cache_array['version'] && $manifest_cache_array['version'] != $schema_version) {
				$manifest_cache_array['version'] = $schema_version;
				$manifest_cache = json_encode( $manifest_cache_array );
				$this->db->setQuery("UPDATE #__extensions SET manifest_cache = '$manifest_cache' WHERE extension_id=700");
				$this->db->executeQuery();
			}
		}
	}
	
	private function fixTagsMenuLinkBug() {
	
		// INSERT MENU LINK
		$this->db->setQuery("INSERT IGNORE INTO `#__menu` (`id`, `menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `checked_out`, `checked_out_time`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`) VALUES(23, 'main', 'com_tags', 'Tags', '', 'Tags', 'index.php?option=com_tags', 'component', 0, 1, 1, 29, 0, '0000-00-00 00:00:00', 0, 1, 'class:tags', 0, '', 45, 46, 0, '', 1);");
		$this->db->executeQuery();
	}
}

class JSchemaChangesetAPS {

	protected $changeItems = array();
	protected $rootdir = null;
	protected $db = null;
	protected $folder = null;

	public function __construct($rootdir, $db, $folder = null, $fromversion = null) {
		$this->rootdir=$rootdir;
		$this->db = $db;
		$this->folder = $folder;
		$updateFiles = $this->getUpdateFiles();
		$updateQueries = $this->getUpdateQueries($updateFiles);
		foreach ($updateQueries as $obj) {
			$this->changeItems[] = JSchemaChangeItemAPS::getInstance($db, $obj->file, $obj->updateQuery, $fromversion);
		}
	}

	public static function getInstance($rootdir, $db, $folder, $fromversion) {
		static $instance;
		if (!is_object($instance)) {
			$instance = new JSchemaChangeSetAPS($rootdir, $db, $folder, $fromversion);
		}
		return $instance;
	}

	public function check() {
		$errors = array();
		foreach ($this->changeItems as $item) {
			if ($item->check() === -2) { // error found
				$errors[] = $item;
			}
		}
		return $errors;
	}

	public function fix() {
		$this->check();
		foreach ($this->changeItems as $item) {
			$item->fix();
		}
	}

	public function getStatus() {
		$result = array('unchecked' => array(), 'ok' => array(), 'error' => array(), 'skipped' => array());
		foreach ($this->changeItems as $item) {
			switch ($item->checkStatus) {
				case 0:
					$result['unchecked'][] = $item;
					break;
				case 1:
					$result['ok'][] = $item;
					break;
				case -2:
					$result['error'][] = $item;
					break;
				case -1:
					$result['skipped'][] = $item;
					break;
			}
		}
		return $result;
	}

	public function getSchema() {
		foreach ($this->changeItems as $item) {
			$result = $item->file;
		}
		$result = new SplFileInfo($item->file);
		return $result->getBasename('.sql');
	}

	private function getUpdateFiles() {
		// get the folder from the database name
		$sqlFolder = 'mysql';
		// Default folder to core com_admin
		$this->folder = $this->rootdir.'/administrator/components/com_admin/sql/updates/'.$sqlFolder;
		return APSJoomlaFiles::files($this->folder, '\.sql$', 1, true);
	}

	private function getUpdateQueries(array $sqlfiles) {
		$result = array(); // hold results as array of objects
		foreach ($sqlfiles as $file) {
			$buffer = file_get_contents($file);

			// Create an array of queries from the sql file
			$queries = $this->db->splitSql($buffer);
			foreach ($queries as $query) {
				if (trim($query)) {
					$fileQueries = new stdClass();
					$fileQueries->file = $file;
					$fileQueries->updateQuery = $query;
					$result[] = $fileQueries;
				}
			}
		}
		return $result;
	}
}

class JSchemaChangeitemAPS {

	public $file = null;
	public $updateQuery = null;
	public $checkQuery = null;
	public $checkQueryExpected = 1;
	public $db = null;
	public $queryType = null;
	public $msgElements = array();
	public $checkStatus = 0;
	public $rerunStatus = 0;
	protected $fromversion = null;

	public function __construct($db, $file, $updateQuery, $fromversion) {
		$this->updateQuery = $updateQuery;
		$this->file = $file;
		$this->db = $db;
		$this->fromversion = $fromversion;
		$this->buildCheckQuery();
	}

	public static function getInstance($db, $file, $query, $fromversion) {
		$instance = new JSchemaChangeitemAPS($db, $file, $query, $fromversion);
		return $instance;
	}

	public function check() {
		$this->checkStatus = -1;
		if ($this->checkQuery) {
			$this->db->setQuery($this->checkQuery);
			$rows = $this->db->loadObject();
			if ($rows !== false) {
				if (count($rows) === $this->checkQueryExpected) {
					$this->checkStatus = 1;
				} else {
					$this->checkStatus = -2;
				}
			}
			else {
				$this->checkStatus = -2;
			}
		}
		return $this->checkStatus;
	}

	public function fix() {
		//Lanzado de las querys
		if ($this->checkStatus === -2) {
			// at this point we have a failed query
			$this->db->setQuery($this->updateQuery);
			if ($this->db->executeQuery()) {
				if ($this->check()) {
					$this->checkStatus = 1;
					$this->rerunStatus = 1;
				} else {
					$this->rerunStatus = -2;
				}
			} else {
				$this->rerunStatus = -2;
			}
		}
		else if ($this->checkStatus === -1) {
			//Check if I need to insert by version id
			$result = new SplFileInfo($this->file);
			if ( version_compare( $this->fromversion,  $result->getBasename('.sql'), 'le' ) ) {
				$this->db->setQuery($this->updateQuery);
				if ($this->db->executeQuery()) {
					if ($this->check()) {
						$this->checkStatus = 1;
						$this->rerunStatus = 1;
					} else {
						$this->rerunStatus = -2;
					}
				} else {
					$this->rerunStatus = -2;
				}
			}
		}
	}

	protected function buildCheckQuery() {
		// Initialize fields in case we can't create a check query
		$this->checkStatus = -1; // change status to skipped
		$result = null;

		// remove any newlines
		$this->updateQuery = str_replace("\n", ' ', $this->updateQuery);

		// fix up extra spaces around () and in general
		$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#');
		$replace = array('($3)', '$1');
		$updateQuery = preg_replace($find, $replace, $this->updateQuery);
		$wordArray = explode(' ', $updateQuery);

		// first, make sure we have an array of at least 6 elements
		// if not, we can't make a check query for this one
		if (count($wordArray) < 6) {
			return; // done with method
		}

		// we can only make check queries for alter table and create table queries
		$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);
		if ($command === 'ALTER TABLE') {
			$alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]);
			if ($alterCommand == 'ADD COLUMN') {
				$result = 'SHOW COLUMNS IN ' . $wordArray[2] .
				' WHERE field = ' . $this->fixQuote($wordArray[5]);
				$this->queryType = 'ADD_COLUMN';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]));
			}
			elseif ($alterCommand == 'ADD INDEX' || $alterCommand == 'ADD UNIQUE') {
				if ($pos = strpos($wordArray[5], '(')) {
					$index = $this->fixQuote(substr($wordArray[5], 0, $pos));
				} else {
					$index = $this->fixQuote($wordArray[5]);
				}
				$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index;
				$this->queryType = 'ADD_INDEX';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif ($alterCommand == 'DROP INDEX') {
				$index = $this->fixQuote($wordArray[5]);
				$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index;
				$this->queryType = 'DROP_INDEX';
				$this->checkQueryExpected = 0;
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif (strtoupper($wordArray[3]) == 'MODIFY') {
				// Kludge to fix problem with "integer unsigned"
				$type = $this->fixQuote($wordArray[5]);
				if (isset($wordArray[6])) {
					$type = $this->fixQuote($this->fixInteger($wordArray[5], $wordArray[6]));
				}
				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = '
				. $this->fixQuote($wordArray[4]) . ' AND type = ' . $type;
				$this->queryType = 'CHANGE_COLUMN_TYPE';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4]), $type);
			}
			elseif (strtoupper($wordArray[3]) == 'CHANGE') {
				// Kludge to fix problem with "integer unsigned"
				$type = $this->fixQuote($this->fixInteger($wordArray[6], $wordArray[7]));
				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' .
						$this->fixQuote($wordArray[4]) . ' AND type = ' . $type;
				$this->queryType = 'CHANGE_COLUMN_TYPE';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4]), $type);
			}
		}

		if ($command == 'CREATE TABLE') {
			if (strtoupper($wordArray[2].$wordArray[3].$wordArray[4]) == 'IFNOTEXISTS') {
				$table = $wordArray[5];
			}
			else {
				$table = $wordArray[2];
			}
			$result = 'SHOW TABLES LIKE ' . $this->fixQuote($table);
			$this->queryType = 'CREATE_TABLE';
			$this->msgElements = array($this->fixQuote($table));
		}

		// set fields based on results
		if ($this->checkQuery = $result) {
			$this->checkStatus = 0; // unchecked status
		}
		else {
			$this->checkStatus = -1; // skipped
		}
	}

	private function fixInteger($type1, $type2) {
		$result = $type1;
		if (strtolower($type1) == "integer" && strtolower(substr($type2, 0, 8)) == 'unsigned') {
			$result = 'int(10) unsigned';
		}
		return $result;
	}

	private function fixQuote($string) {
		$string = str_replace('`', '', $string);
		$string = str_replace(';', '', $string);
		$string = str_replace('#__', $this->db->getPrefix(), $string);
		return $this->db->quote($string);
	}
}

class APSJoomlaFiles{

	public static function files($path, $filter = '.', $recurse = false, $full = false)
	{
		// Check to make sure the path valid and clean
		$path = trim($path);
		$path = preg_replace('#[/\\\\]+#', DIRECTORY_SEPARATOR, $path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			return false;
		}

		// Compute the excludefilter string
		$excludefilter = array('^\..*', '.*~');
		if (count($excludefilter))
		{
			$excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
		}
		else
		{
			$excludefilter_string = '';
		}

		$exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX');
		// Get the files
		$arr = APSJoomlaFiles::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, true);

		// Sort the files
		natsort($arr);

		return array_values($arr);
	}

	public static function _items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles)
	{
		@set_time_limit(ini_get('max_execution_time'));

		// Initialise variables.
		$arr = array();

		// Read the source directory
		if (!($handle = @opendir($path)))
		{
			return $arr;
		}

		while (($file = readdir($handle)) !== false)
		{
			if ($file != '.' && $file != '..' && !in_array($file, $exclude)
					&& (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
			{
				// Compute the fullpath
				$fullpath = $path . '/' . $file;

				// Compute the isDir flag
				$isDir = is_dir($fullpath);

				if (($isDir xor $findfiles) && preg_match("/$filter/", $file))
				{
					// (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
					if ($full)
					{
						// Full path is requested
						$arr[] = $fullpath;
					}
					else
					{
						// Filename is requested
						$arr[] = $file;
					}
				}
				if ($isDir && $recurse)
				{
					// Search recursively
					if (is_integer($recurse))
					{
						// Until depth 0 is reached
						$arr = array_merge($arr, $this->_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludefilter_string, $findfiles));
					}
					else
					{
						$arr = array_merge($arr, $this->_items($fullpath, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles));
					}
				}
			}
		}
		closedir($handle);
		return $arr;
	}
}

class APSJoomlaDB{

	var $host = null;
	var $user = null;
	var $password = null;
	var $database = null;
	var $prefix = null;

	private $query = null;

	public function __construct($db_host, $db_user, $db_password, $db_database, $db_prefix) {
		$this->host=$db_host;
		$this->user=$db_user;
		$this->password=$db_password;
		$this->database=$db_database;
		$this->prefix=$db_prefix;
	}

	public static function getInstance($db_host, $db_user, $db_password, $db_database, $db_prefix) {
		static $instance;
		if (!is_object($instance)) {
			$instance = new APSJoomlaDB($db_host, $db_user, $db_password, $db_database, $db_prefix);
		}
		return $instance;
	}

	function setQuery($query){
		$this->query=$query;
	}


	public function loadObject()
	{
		// Initialise variables.
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->executeQueryResult()))
		{
			return null;
		}

		// Get the first row from the result set as an object of type $class.
		if ($object = mysql_fetch_object($cursor))
		{
			$ret = $object;
		}

		// Free up system resources and return.
		mysql_free_result($cursor);

		return $ret;
	}

	public function loadResult()
	{
		// Initialise variables.
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->executeQueryResult()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = mysql_fetch_row($cursor))
		{
			$ret = $row[0];
		}

		// Free up system resources and return.
		if ($cursor !== TRUE && $cursor != FALSE) {
			mysql_free_result($cursor);
		}

		return $ret;
	}

	public function executeQueryResult()
	{
		//Replace prefix
		$this->query = str_replace("#__", $this->prefix, $this->query);

		//Connect
		mysql_db_connect($this->host, $this->user,$this->password,$this->database);

		// Execute the query.
		$cursor = mysql_query($this->query);

		return $cursor;
	}

	public function executeQuery()
	{
		//Replace prefix
		$this->query = str_replace("#__", $this->prefix, $this->query);

		//Connect
		mysql_db_connect($this->host, $this->user,$this->password,$this->database);

		// Execute the query.
		$cursor = mysql_query($this->query);

		$resul=true;

		if(!$cursor)
			$resul = false;

		if ($cursor !== TRUE && $cursor != FALSE) {
			mysql_free_result($cursor);
		}

		return $resul;
	}

	public static function splitSql($sql)
	{
		$start = 0;
		$open = false;
		$char = '';
		$end = strlen($sql);
		$queries = array();

		for ($i = 0; $i < $end; $i++)
		{
			$current = substr($sql, $i, 1);
			if (($current == '"' || $current == '\''))
			{
				$n = 2;

				while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
				{
					$n++;
				}

				if ($n % 2 == 0)
				{
					if ($open)
					{
						if ($current == $char)
						{
							$open = false;
							$char = '';
						}
					}
					else
					{
						$open = true;
						$char = $current;
					}
				}
			}

			if (($current == ';' && !$open) || $i == $end - 1)
			{
				$queries[] = substr($sql, $start, ($i - $start + 1));
				$start = $i + 1;
			}
		}

		return $queries;
	}

	public function getPrefix(){
		return $this->prefix;
	}

	public static function quote($text)
	{
		return '\'' . addslashes($text) . '\'';
	}
}
