<?php
/*
# ------------------------------------------------------------------------
# JA Extenstion Manager Component j16
# ------------------------------------------------------------------------
# Copyright (C) 2004-2009 J.O.O.M Solutions Co., Ltd. All Rights Reserved.
# @license - Copyrighted Commercial Software
# Author: J.O.O.M Solutions Co., Ltd
# Websites:  http://www.joomlart.com -  http://www.joomlancers.com
# This file may not be redistributed in whole or significant part.
# ------------------------------------------------------------------------
*/ 
// No direct access
defined('JPATH_BASE') or die;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.archive');
jimport('joomla.filesystem.path');
jimport('joomla.base.adapter');

class jaExtUploader extends JAdapter
{
		/**
	 * Array of paths needed by the installer
	 * @var array
	 */
	protected $_paths = array();

	/**
	 * True if packakge is an upgrade
	 * @var boolean
	 */
	protected $_upgrade = null;

	/**
	 * The manifest trigger class
	 * @var object
	 */
	public $manifestClass = null;

	/**
	 * True if existing files can be overwritten
	 * @var boolean
	 */
	protected $_overwrite = true;

	/**
	 * Stack of installation steps
	 *	- Used for installation rollback
	 * @var array
	 */
	protected $_stepStack = array();

	/**
	 * Extension Table Entry
	 * @var JTableExtension
	 */
	public $extension = null;

	/**
	 * The output from the install/uninstall scripts
	 * @var string
	 */
	public $message = null;

	/**
	 * The installation manifest XML object
	 * @var object
	 */
	public $manifest = null;

	/**
	 * The extension message that appears
	 * @var string
	 */
	protected $extension_message = null;


	/**
	 * The redirect URL if this extension (can be null if no redirect)
	 * @var string
	 */
	protected $redirect_url = null;

	/**
	 * The output from the install/uninstall scripts
	 * @var string
	 */
	var $errMessage = null;

	/**
	 * The output from the upload scripts
	 * @var string
	 */
	var $results = array();
	
	var $_debug = false;

	/**
	 * Constructor
	 *
	 * @access protected
	 */
	public function __construct()
	{
		parent::__construct(dirname(__FILE__),'jaExtUploader');
	}

	/**
	 * Returns the global Installer object, only creating it
	 * if it doesn't already exist.
	 *
	 * @static
	 * @return	object	An installer object
	 * @since 1.5
	 */
	public static function getInstance()
	{
		static $instance;

		if (!isset ($instance)) {
			$instance = new jaExtUploader();
		}
		return $instance;
	}

	/**
	 * Get the allow overwrite switch
	 *
	 * @access	public
	 * @return	boolean	Allow overwrite switch
	 * @since	1.5
	 */
	public function getOverwrite()
	{
		return $this->_overwrite;
	}

	/**
	 * Set the allow overwrite switch
	 *
	 * @access	public
	 * @param	boolean	$state	Overwrite switch state
	 * @return	boolean	Previous value
	 * @since	1.5
	 */
	public function setOverwrite($state=false)
	{
		$tmp = $this->_overwrite;
		if ($state) {
			$this->_overwrite = true;
		}
		else {
			$this->_overwrite = false;
		}
		return $tmp;
	}

	/**
	 * Get the database connector object
	 *
	 * @access	public
	 * @return	object	Database connector object
	 * @since	1.5
	 */
	function &getDBO()
	{
		return $this->_db;
	}
	
	/**
	 * Get the installation manifest object
	 *
	 * @access	public
	 * @return	object	Manifest object
	 * @since	1.5
	 */
	public function getManifest()
	{
		if (!is_object($this->manifest)) {
			$this->findManifest();
		}
		return $this->manifest;
	}
	
	/**
	 * Get an installer path by name
	 *
	 * @access	public
	 * @param	string	$name		Path name
	 * @param	string	$default	Default value
	 * @return	string	Path
	 * @since	1.5
	 */
	public function getPath($name, $default=null)
	{
		return (!empty($this->_paths[$name])) ? $this->_paths[$name] : $default;
	}

	/**
	 * Sets an installer path by name
	 *
	 * @access	public
	 * @param	string	$name	Path name
	 * @param	string	$value	Path
	 * @return	void
	 * @since	1.5
	 */
	public function setPath($name, $value)
	{
		$this->_paths[$name] = $value;
	}
	
	/**
	 * !Important: must restarts an upload part before upload next extensions
	 *
	 */
	function resetPath() {
		$this->_paths = array();
	}

	/**
	 * Pushes a step onto the installer stack for rolling back steps
	 *
	 * @access	public
	 * @param	array	$step	Installer step
	 * @return	void
	 * @since	1.5
	 */
	public function pushStep($step)
	{
		$this->_stepStack[] = $step;
	}
	
	/**
	 * get upload adapter for specific extension type
	 *
	 * @param (string) $name - name of extension type
	 * @return (mixed)
	 */
	function getAdapter($name) {
		if(!isset($this->_adapters[$name]) || !is_object($this->_adapters[$name])) {
			//Try to get adapter
			$file = dirname(__FILE__).DS.'adapters'.DS.strtolower($name).'.php';
			if(!is_file($file)) {
				return false;
			}
			// Try to load the adapter object
			require_once($file);
			$class = 'jaExtUploader'.ucfirst($name);
			if (!class_exists($class)) {
				return false;
			}
			$adapter = new $class($this);
			$adapter->parent =& $this;
			
			$this->_adapters[$name] = $adapter;
		}
		return $this->_adapters[$name];
	}

	/**
	 * Upload abort is no need rollback to previous steps like install
	 *
	 * @param unknown_type $msg
	 * @param unknown_type $type
	 */
	function abort($msg=null, $type=null)
	{
		$this->errMessage .= $msg . "<br /><br />";
	}

	
	function upload($packagePath = null) {
		$this->message = "";
		$this->errMessage = "";
		$this->resetResult();
		
		$xmlfiles = JFolder::files($packagePath, '.xml$', true, true);
		if (!empty($xmlfiles)) {
			$result = true;
			foreach ($xmlfiles as $file) {
				$manifest = $this->isManifest($file);
				if(!is_null($manifest)) {
					//upload extension depend on $manifest
					// Set the manifest object and path
					$this->manifest =& $manifest;
					$this->setPath('manifest', $file);

					// Set the installation source path to that of the manifest file
					$this->setPath('source', dirname($file));
					$result &= $this->uploadItems();
				}
			}
			return $this->getResults();
		} else {
			return false;
		}
	}
	
	/**
	 * Upload item (extension) of package
	 * Prepare for upload: this method sets the upload directory, finds
	 * and checks the installation file and verifies the installation type
	 *
	 * @access public
	 * @return boolean True on success
	 * @since 1.0
	 */
	function uploadItems()
	{
		// Load the adapter(s) for the install manifest
		$manifest 	= $this->manifest;
		$type 		= (string)$manifest->attributes()->type;
		$version	= (string)$manifest->attributes()->version;
		$rootName	= (string)$manifest->name;
		$config		= &JFactory::getConfig();

		// Needed for legacy reasons ... to be deprecated in next minor release
		if ($type == 'mambot') {
			$type = 'plugin';
		}
		
		/*
		 * LEGACY CHECK
		 */
		/*if ((version_compare($version, '1.5', '<') || $rootName == 'mosinstall') && !$config->getValue('config.legacy')) {
			$this->abort(JText::_('MUSTENABLELEGACY'));
			return false;
		}*/
		
		// Lazy load the adapter
		$adapter = $this->getAdapter($type);
		if($adapter === false) {
			//$this->abort(JText::_('INVALID_JOOMLA_EXTENSIONS'));
			return false;
		} else {
			return $adapter->upload();
		}
	}
	
	/**
	 * build product object with JoomlArt Format from $manifest
	 *
	 */
	function buildProduct($pname)
	{
		$manifest 	= $this->manifest;
		
		$legacy 	= ($manifest->getName() == 'mosinstall' || $manifest->getName() == 'install') ? 1 : 0;
		$type 		= (string)$manifest->attributes()->type;
		$group 		= (string)$manifest->attributes()->group;
		$coreVersion = (string)$manifest->attributes()->version;
		//support joomla 1.5 legacy mode
		$coreVersion = (preg_match("/1\.6.*/i", $coreVersion) || !$legacy) ? 'j16' : 'j15';
		$version	= (string)$manifest->version;
		$version 	= JFilterInput::clean($version, 'cmd');
		
		$name		= (string)$manifest->name;
		
		if (!empty ($pname) && !empty($type)) {
			
			$jaProduct = new stdClass();
			$jaProduct->coreVersion = $coreVersion;
			$jaProduct->extKey = $pname;
			$jaProduct->name = $name;
			$jaProduct->group = $group;
			$jaProduct->version = $version;
			$jaProduct->type = $type;
			return $jaProduct;
		} else {
			return false;
		}
	}
	
	/**
	 * Method to parse through a files element of the installation manifest and take appropriate
	 * action.
	 *
	 * @access	public
	 * @param	JXMLElement	$element	The xml node to process
	 * @param	int			$cid		Application ID of application to install to
	 * @param	Array		$oldFiles	List of old files (JXMLElement's)
	 * @param	Array		$oldMD5		List of old MD5 sums (indexed by filename with value as MD5)
	 * @return	boolean	True on success
	 * @since	1.5
	 */
	function parseFiles($element, $cid=0, $oldFiles=null, $oldMD5=null)
	{
		// Get the array of file nodes to process; we checked this had children above
		if ( ! $element || ! count($element->children()))
		{
			// Either the tag does not exist or has no children (hence no files to process) therefore we return zero files processed.
			return 0;
		}

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

		// Get the client info
		jimport('joomla.application.helper');
		$client = JApplicationHelper::getClientInfo($cid);

		/*
		 * Here we set the folder we are going to remove the files from.
		 */
		if ($client)
		{
			$pathname = 'extension_'.$client->name;
			$destination = $this->getPath($pathname);
		}
		else
		{
			$pathname = 'extension_root';
			$destination = $this->getPath($pathname);
		}

		/*
		 * Here we set the folder we are going to copy the files from.
		 *
		 * Does the element have a folder attribute?
		 *
		 * If so this indicates that the files are in a subdirectory of the source
		 * folder and we should append the folder attribute to the source path when
		 * copying files.
		 */
		$folder = (string)$element->attributes()->folder;
		if ($folder && file_exists($this->getPath('source').DS.$folder))
		{
			$source = $this->getPath('source').DS.$folder;
		}
		else {
			$source = $this->getPath('source');
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			$path['src']	= $source.DS.$file;
			$path['dest']	= $destination.DS.$file;

			// Is this path a file or folder?
			$path['type']	= ($file->getName() == 'folder') ? 'folder' : 'file';

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it doesn't,
			 * we need to create it.
			 */
			if (basename($path['dest']) != $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!JFolder::create($newdir))
				{
					JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir));
					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}
	
	/**
	 * Method to parse the parameters of an extension, build the INI
	 * string for it's default parameters, and return the INI string.
	 *
	 * @access	public
	 * @return	string	INI string of parameter values
	 * @since	1.5
	 */
	public function getParams()
	{
		// Validate that we have a fieldset to use
		if(!isset($this->manifest->config->fields->fieldset)) {
			return '{}';
		}
		// Getting the fieldset tags
		$fieldsets = $this->manifest->config->fields->fieldset;

		// Creating the data collection variable:
		$ini = array();

		// Iterating through the fieldsets:
		foreach($fieldsets as $fieldset) {
			if( ! count($fieldset->children())) {
				// Either the tag does not exist or has no children therefore we return zero files processed.
				return null;
			}

			// Iterating through the fields and collecting the name/default values:
			foreach ($fieldset as $field)
			{
				// Modified the below if statements to check against the
				// null value since default values like "0" were casuing
				// entire parameters to be skipped.
				if (($name = $field->attributes()->name) === null) {
					continue;
				}

				if (($value = $field->attributes()->default) === null) {
					continue;
				}

				$ini[(string) $name] = (string) $value;
			}
		}

		return json_encode($ini);
	}
	
	/**
	 * Copy files from source directory to the target directory
	 *
	 * @access	public
	 * @param	array $files array with filenames
	 * @param	boolean $overwrite True if existing files can be replaced
	 * @return	boolean True on success
	 * @since	1.5
	 */
	public function copyFiles($files, $overwrite=null)
	{
		/*
		 * To allow for manual override on the overwriting flag, we check to see if
		 * the $overwrite flag was set and is a boolean value.  If not, use the object
		 * allowOverwrite flag.
		 */
		if (is_null($overwrite) || !is_bool($overwrite)) {
			$overwrite = $this->_overwrite;
		}

		/*
		 * $files must be an array of filenames.  Verify that it is an array with
		 * at least one file to copy.
		 */
		if (is_array($files) && count($files) > 0)
		{
			foreach ($files as $file)
			{
				// Get the source and destination paths
				$filesource	= JPath::clean($file['src']);
				$filedest	= JPath::clean($file['dest']);
				$filetype	= array_key_exists('type', $file) ? $file['type'] : 'file';

				if (!file_exists($filesource))
				{
					/*
					 * The source file does not exist.  Nothing to copy so set an error
					 * and return false.
					 */
					JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE', $filesource));
					return false;
				}
				elseif (file_exists($filedest) && !$overwrite)
				{
					/*
					 * It's okay if the manifest already exists
					 */
					if ($this->getPath('manifest') == $filesource) {
						continue;
					}

					/*
					 * The destination file already exists and the overwrite flag is false.
					 * Set an error and return false.
					 */
					JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS', $filedest));
					return false;
				}
				else
				{
					// Copy the folder or file to the new location.
					if ($filetype == 'folder')
					{
						if (!(JFolder::copy($filesource, $filedest, null, $overwrite)))
						{
							JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER', $filesource, $filedest));
							return false;
						}

						$step = array ('type' => 'folder', 'path' => $filedest);
					}
					else
					{
						if (!(JFile::copy($filesource, $filedest,null)))
						{
							JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE', $filesource, $filedest));
							return false;
						}

						$step = array ('type' => 'file', 'path' => $filedest);
					}

					/*
					 * Since we copied a file/folder, we want to add it to the installation step stack so that
					 * in case we have to roll back the installation we can remove the files copied.
					 */
					$this->_stepStack[] = $step;
				}
			}
		}
		else
		{
			/*
			 * The $files variable was either not an array or an empty array
			 */
			return false;
		}
		return count($files);
	}

	/**
	 * Method to parse through a files element of the installation manifest and remove
	 * the files that were installed
	 *
	 * @access	public
	 * @param	object	$element 	The xml node to process
	 * @param	int		$cid		Application ID of application to remove from
	 * @return	boolean	True on success
	 * @since	1.5
	 */
	public function removeFiles($element, $cid=0)
	{
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return zero files processed.
			return true;
		}
		// Initialise variables.
		$removefiles = array ();
		$retval = true;

		$debug = false;
		if(isset($GLOBALS['installerdebug']) && $GLOBALS['installerdebug']) {
			$debug = true;
		}

		// Get the client info if we're using a specific client
		jimport('joomla.application.helper');
		if ($cid > -1) {
			$client = JApplicationHelper::getClientInfo($cid);
		}
		else {
			$client = null;
		}

		// Get the array of file nodes to process
		$files = $element->children();
		if (count($files) == 0) {
			// No files to process
			return true;
		}

		$folder = '';

		/*
		 * Here we set the folder we are going to remove the files from.  There are a few
		 * special cases that need to be considered for certain reserved tags.
		 */
		switch ($element->getName())
		{
			case 'media':
				if ((string)$element->attributes()->destination) {
					$folder = (string)$element->attributes()->destination;
				}
				else {
					$folder = '';
				}
				$source = $client->path.DS.'media'.DS.$folder;
				break;

			case 'languages':
				$lang_client = (string)$element->attributes()->client;
				if($lang_client) {
					$client = JApplicationHelper::getClientInfo($lang_client, true);
					$source = $client->path.DS.'language';
				} else {
					if ($client) {
						$source = $client->path.DS.'language';
					}
					else {
						$source = '';
					}
				}
				break;

			default:
				if ($client)
				{
					$pathname = 'extension_'.$client->name;
					$source = $this->getPath($pathname);
				}
				else
				{
					$pathname = 'extension_root';
					$source = $this->getPath($pathname);
				}
				break;
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($files as $file)
		{
			/*
			 * If the file is a language, we must handle it differently.  Language files
			 * go in a subdirectory based on the language code, ie.
			 *
			 * 		<language tag="en_US">en_US.mycomponent.ini</language>
			 *
			 * would go in the en_US subdirectory of the languages directory.
			 */
			if ($file->getName() == 'language' && (string)$file->attributes()->tag != '')
			{
				if ($source) {
					$path = $source.DS.$file->attributes()->tag.DS.basename((string)$file);
				}
				else
				{
					$target_client = JApplicationHelper::getClientInfo((string)$file->attributes()->client, true);
					$path = $target_client->path.DS.'language'.DS.$file->attributes()->tag.DS.basename((string)$file);
				}

				// If the language folder is not present, then the core pack hasn't been installed... ignore
				if (!JFolder::exists(dirname($path)))
				{
					continue;
				}
			}
			else {
				$path = $source.DS.$file;
			}

			/*
			 * Actually delete the files/folders
			 */
			if (is_dir($path)) {
				$val = JFolder::delete($path);
			}
			else {
				$val = JFile::delete($path);
			}

			if ($val === false)
			{
				JError::raiseWarning(43, 'Failed to delete '. $path);
				$retval = false;
			}
		}

		if (!empty($folder)) {
			$val = JFolder::delete($source);
		}

		return $retval;
	}
	
	/**
	 * Copies the installation manifest file to the extension folder in the given client
	 *
	 * @access	public
	 * @param	int		$cid	Where to copy the installfile [optional: defaults to 1 (admin)]
	 * @return	boolean	True on success, False on error
	 * @since	1.5
	 */
	public function copyManifest($cid=1)
	{
		// Get the client info
		jimport('joomla.application.helper');
		$client = JApplicationHelper::getClientInfo($cid);

		$path['src'] = $this->getPath('manifest');

		if ($client)
		{
			$pathname = 'extension_'.$client->name;
			$path['dest']  = $this->getPath($pathname).DS.basename($this->getPath('manifest'));
		}
		else
		{
			$pathname = 'extension_root';
			$path['dest']  = $this->getPath($pathname).DS.basename($this->getPath('manifest'));
		}
		return $this->copyFiles(array ($path), true);
	}

	/**
	 * Tries to find the package manifest file
	 *
	 * @access private
	 * @return boolean True on success, False on error
	 * @since 1.0
	 */
	public function findManifest()
	{
		// Get an array of all the xml files from the installation directory
		$xmlfiles = JFolder::files($this->getPath('source'), '.xml$', 1, true);
		// If at least one xml file exists
		if (!empty($xmlfiles))
		{
			foreach ($xmlfiles as $file)
			{
				// Is it a valid joomla installation manifest file?
				$manifest = $this->isManifest($file);
				if (!is_null($manifest))
				{
					// If the root method attribute is set to upgrade, allow file overwrite
					if ((string)$manifest->attributes()->method == 'upgrade')
					{
						$this->_upgrade = true;
						$this->_overwrite = true;
					}

					// If the overwrite option is set, allow file overwriting
					if ((string)$manifest->attributes()->overwrite == 'true') {
						$this->_overwrite = true;
					}
					
					//thanhnv
					//always overwrite files on repository with newly upload files
					$this->_overwrite = true;

					// Set the manifest object and path
					$this->manifest = $manifest;
					$this->setPath('manifest', $file);

					// Set the installation source path to that of the manifest file
					$this->setPath('source', dirname($file));
					return true;
				}
			}

			// None of the xml files found were valid install files
			JError::raiseWarning(1, JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'));
			return false;
		}
		else
		{
			// No xml files were found in the install folder
			JError::raiseWarning(1, JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'));
			return false;
		}
	}

	/**
	 * Is the xml file a valid Joomla installation manifest file
	 *
	 * @access	private
	 * @param	string	$file	An xmlfile path to check
	 * @return	mixed	A JXMLElement, or null if the file failed to parse
	 * @since	1.5
	 */
	public function isManifest($file)
	{
		// Initialise variables.
		$xml = JFactory::getXML($file);

		// If we cannot load the xml file return null
		if( ! $xml)
		{
			return null;
		}

		/*
		 * Check for a valid XML root tag.
		 * @todo: Remove backwards compatability in a future version
		 * Should be 'extension', but for backward compatability we will accept 'extension' or 'install'.
		 */

		// 1.5 uses 'install'
		// 1.6 uses 'extension'
		if($xml->getName() != 'install' && $xml->getName() != 'extension')
		{
			return null;
		}

		// Valid manifest file return the object
		return $xml;
	}

	/**
	 * Generates a manifest cache
	 * @return string serialised manifest data
	 */
	public function generateManifestCache()
	{
		return serialize(JApplicationHelper::parseXMLInstallFile($this->getPath('manifest')));
	}

	/**
	 * set status of extension uploading progress
	 *
	 * @param (object) $ext - extension information
	 * @param (boolean) $error 
	 * @param (string) $message
	 * @param (string) $location - the location where extension is uploaded to
	 */
	function setResult($ext, $error = false, $message = '', $location = '') {
		$error = (!$error) ? 0 : 1;
		$this->results[] = array(
							'ext' => $ext,
							'error' => $error,
							'message' => $message,
							'location' => $location);
	}
	
	function resetResult() {
		$this->results = array();
	}
	
	function getResults() {
		$result = "";
		if(count($this->results) > 0) {
			$result .= "
			<table class=\"ja-uc-child\">
		      <tr>
		        <th width=\"30\"> </th>
		        <th width=\"150\">".JText::_("EXTENSION_NAME")."</th>
		        <th>".JText::_("TYPE")."</th>
		        <th>".JText::_("VERSION")."</th>
		        <th>".JText::_("RESULT")."</th>
		      </tr>";
			foreach ($this->results as $item) {
				$error = intval($item['error']);
				$ext = $item['ext'];
				if(!$error) {
					$css = "upload-success";
					
					$relLocation = substr($item['location'], strlen(JA_WORKING_DATA_FOLDER));
					$relLocation = FileSystemHelper::clean($relLocation, "/");
					$url = "index.php?option=com_jaextmanager&view=repo&folder={$relLocation}";
					$linkRepo = " <a href=\"{$url}\" onclick=\"opener.location='{$url}'; return false;\" target=\"_parent\" title=\"".addslashes($item['location'])."\">".JText::_("REPOSITORY")."</a>";
					
					$message = JText::sprintf('THE_S_S__VESION_S_IS_SUCCESSFULLY_UPLOADED_TO_LOCAL_REPOSITORY', $ext->type, $ext->name, $ext->version);
					$message .= JText::sprintf('GO_TO_S_TO_SEE_THE_UPLOADED_FILES_OF_THIS_EXTENSIONSSMALL', $linkRepo);
					
				} else {
					$css = "upload-error";
					$message = $item["message"];
				}
				
				$result .= "
			      <tr class=\"".$css."\">
			        <td class=\"icon\"> </td>
			        <td><span title=\"".$ext->extKey."\">".$ext->name."</span></td>
			        <td>".$ext->type."</td>
			        <td>".$ext->version."</td>
			        <td>".$message."</td>
			      </tr>";
			}
			$result .= "</table>";
		}
		return $result;
	}
	
	function printResults() {
		echo $this->getResults();
	}
	
	function debug() {
		return $this->_debug;
	}
}
?>