<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package clearFusionCMS
 * @copyright Copyright (c) 2012 - 2013 clearFusionCMS. All rights reserved.
 * @link http://clearfusioncms.com
 */

/**
 * flModuleMgr Class
 *
 * Class to manage modules, installation and removal.
 *
 * @package fusionLib
 */
class flModuleMgr {

	/**
	 * The database
	 *
	 * @var flDb
	 */
	protected $db;

	/**
	 * The last error message.
	 *
	 * @var string
	 */
	protected $lastError = '';

	/**
	 * Construct the object.
	 *
	 * @param flDb $db The database object.
	 */
	function __construct($db) {
		$this->db = $db;
	}

	/**
	 * Return the last error.
	 *
	 * @return string The last error string.
	 */
	function getLastError() {
		return $this->lastError;
	}

	/**
	 * Get a list of installed modules and the installed version number.
	 *
	 * @param string $type The installed module time to find.
	 * @return array The module name to installed version mapping.
	 */
	function getInstalled($type = null) {
		$installed = array();
		$result = $this->db->query('SELECT modname,instver FROM fl_modules');
		while($row = $result->fetch()) {
			if($type === null || @is_readable(FLMODPATH . "$row[modname]/$row[modname]$type.php"))
				$installed[$row['modname']] = $row['instver'];
		}
		return $installed;
	}

	/**
	 * Get the list of available modules.
	 *
	 * @return array The names of the available modules.
	 */
	function getAvailable() {
		$r = array();

		$installed = $this->getInstalled();

		$m = glob(FLMODPATH . '*', GLOB_ONLYDIR);
		foreach($m as $mname) {
			$mname = basename($mname);

			if(!isset($installed[$mname])) {
				try {
					class_exists($class = "{$mname}Install");
					$r[] = $mname;
				} catch(flExceptionLoadClass $e) {
					if(is_readable(FLMODPATH . "$mname/{$mname}Info.ini") && !file_exists(FLMODPATH . "$mname/{$mname}Install.php"))
						$r[] = $mname;
				}
			}
		}

		return $r;
	}

	/**
	 * Install a module.
	 *
	 * @param string $module The name of the module to install.
	 * @return bool true if the module was installed; else false.
	 */
	function install($module) {
		$this->lastError = '';
		$instObj = $this->getModuleInstaller($module);
		if($instObj === false || $instObj->install($this->db)) {
			$info = $this->getInfo($module);
			$this->db->insert('fl_modules', array('modname' => $module, 'instver' => $info->version));
			return true;
		}
		else {
			if($instObj)
				$this->lastError = $instObj->getLastError();
			return false;
		}
	}

	/**
	 * Uninstall a module.
	 *
	 * @param string $module The name of the module to uninstall.
	 * @return bool true if the module was uninstalled; else false.
	 * @throws cfException
	 */
	function uninstall($module) {
		$info = $this->getInfo($module);

		if(!$info->get('canUninstall', true))
			throw new cfException("The module can not be uninstalled.");

		$instObj = $this->getModuleInstaller($module);
		if($instObj === false || $instObj->uninstall($this->db)) {
			$this->db->delete('fl_modules', 'modname=?', $module);
			return true;
		}
		else
			return false;
	}

	/**
	 * Upgrade a module, throws an exception if the upgrade fails.
	 *
	 * @param string $module The name of the module to upgrade.
	 * @throws cfException
	 */
	function upgrade($module) {
		$inst = $this->getInstalled();
		if(!isset($inst[$module]))
			throw new cfException("Module $module must be installed before it can be upgraded.");

		if($instObj = $this->getModuleInstaller($module)) {
			$info = $this->getInfo($module);
			if(version_compare($inst[$module], $info->version) < 0) {
				if(!$instObj->upgrade($this->db, $inst[$module])) {
					$this->lastError = $instObj->getLastError();
					throw new cfException("Automatic upgrade failed for module $info->name ($module) when upgrading from version $inst[$module] to version $info->version." . (empty($this->lastError) ? '' : "\n $this->lastError"));
				}
				else
					$this->db->replace('fl_modules', array('modname' => $module, 'instver' => $info->version));
			}
		}
	}

	/**
	 * Get the module information.
	 *
	 * @param string $module The name of the module to get the info for.
	 * @return flConfig The module information of false if failed.
	 */
	function getInfo($module) {
		if(is_readable($file = FLMODPATH . "{$module}/{$module}Info.ini"))
			return new flConfigIni($file, null);

		return false;
	}

	/**
	 * Get a module installer object.
	 *
	 * @param string $module The name of the module to get.
	 * @return flModuleInstallInterface The installer object for the module or false if not loaded.
	 */
	function getModuleInstaller($module) {
		try {
			class_exists($class = "{$module}Install");
			$instObj = new $class();
			return $instObj;
		} catch(flExceptionLoadClass $e) {
		}
		return false;
	}
}
