<?php
/**
 * @package   	JCE
 * @copyright 	Copyright © 2009-2011 Ryan Demmer. All rights reserved.
 * @license   	GNU/GPL 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * JCE is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 */

defined('_JEXEC') or die('RESTRICTED');

wfimport('editor.libraries.classes.extensions.browser');
wfimport('editor.libraries.classes.extensions.filesystem');

class WFFileBrowser extends WFBrowserExtension 
{
	/*
	 *  @var array
	 */
	private $_buttons = array();
	/*
	 *  @var array
	 */
	private $_actions = array();
	/*
	 *  @var array
	 */
	private $_events = array();
	/*
	 *  @var array
	 */
	private $_result = array('error' => array(), 'files' => array(), 'folders' => array());
	/**
	 * @access  protected
	 */
	public function __construct($config = array())
	{

		$default = array(
			'dir'						=> 'images',
			'filesystem' 				=> 'joomla',
			'filetypes'					=> 'images=jpg,jpeg,png,gif',
			'upload'					=> array(
				'runtimes'			=> 'html5,flash',
				'chunk_size' 		=> null,
				'max_size'			=> 1024,
				'validate_mimetype'	=> 0
			),
			'folder_tree'		=> 1,
			'list_limit'		=> 'all',
			'features'		=> array(
				'upload' 	=> 1,
				'folder'	=> array(
					'create' => 1,
					'delete' => 1,
					'rename' => 1,
					'move'	 => 1	
				),
				'file'		=> array(
					'rename' => 1,
					'delete' => 1,
					'move' 	 => 1
				)
			)
		);
		
		$config = array_merge($default, $config);		

		// Call parent
		parent::__construct($config);

		// Setup XHR callback funtions
		$this->setRequest(array($this, 'getItems'));
        $this->setRequest(array($this, 'getFileDetails'));
		$this->setRequest(array($this, 'getFolderDetails'));
		$this->setRequest(array($this, 'getTree'));
		$this->setRequest(array($this, 'getTreeItem'));

		// Get actions
		$this->getStdActions();
		// Get buttons
		$this->getStdButtons();
	}

	/**
	 * Display the browser
	 * @access public
	 */
	public function display()
	{		
		parent::display();
		
		// Get the Document instance
		$document = WFDocument::getInstance();

		$document->addScript(array(
			'tree',
			'upload'
		), 'libraries');
		
		$document->addScript(array(
			'plupload.full',
		), 'jce.libraries.plupload');
		
		$document->addScript(array(
			'file',
			'sort',
			'search',
			'manager'
		), 'extensions.browser.js');
	
		//$document->addStyleSheet(array('files', 'tree', 'upload'), 'libraries');
		$document->addStyleSheet(array('manager'), 'extensions.browser.css');
		
		// file browser options
		$document->addScriptDeclaration('WFFileBrowser.settings='.json_encode($this->getSettings()).';');
	}
	
	/**
	 * Render the browser view
	 * @access public
	 */
	public function render()
	{
		$session = JFactory::getSession();
		// create file view
		$view = $this->getView('file');
		// assign session data
		$view->assignRef('session', $session);
		// assign form action
		$view->assign('action', $this->getFormAction());		
		// return view output
		$view->display();
	}
	
	/**
	 * Set a WFRequest item
	 * @access 	public
	 * @param 	array $request
	 */
	public function setRequest($request)
	{
		$xhr = WFRequest::getInstance();		
		$xhr->setRequest($request);
	}
	
	/**
	 * Upload form action url
	 *
	 * @access	public
	 * @param	string 	The target action file eg: upload.php
	 * @return	Joomla! component url
	 * @since	1.5
	 */
	protected function getFormAction() 
	{		
		$wf = WFEditorPlugin::getInstance();
		
		$component_id = JRequest::getInt('component_id');
		
		$query = '';
		
		$args = array(
			'plugin' => $wf->getName()
		);
		
		if ($component_id) {
			$args['component_id'] = $component_id;
		}
		
		foreach($args as $k => $v) {
			$query .= '&' . $k . '=' . $v;
		}
				
		return JURI::base(true) .'/index.php?option=com_jce&view=editor&layout=plugin' . $query; 
	}

	public function getFileSystem()
	{
		static $filesystem;

		if (!is_object($filesystem)) {
			$wf = WFEditorPlugin::getInstance();
			
			$config = array(
				'dir' 				=> $this->get('dir'),
				'upload_conflict' 	=> $wf->getParam('editor.upload_conflict', 'overwrite')
			);
			
			$filesystem = WFFileSystem::getInstance($this->get('filesystem'), $config);
		}

		return $filesystem;
	}
	
	private function getViewable()
	{
		return 'jpeg,jpg,gif,png,avi,wmv,wm,asf,asx,wmx,wvx,mov,qt,mpg,mp3,mp4,m4v,mpeg,ogg,ogv,webm,swf,flv,f4v,xml,dcr,rm,ra,ram,divx,html,htm,txt,rtf,pdf,doc,docx,xls,xlsx,ppt,pptx';
	}

	/**
	 * Return a list of allowed file extensions in selected format
	 *
	 * @access public
	 * @return extension list
	 */
	private function getFileTypes($format = 'map')
	{
		$list = $this->get('filetypes');

		// Remove excluded file types (those that have a - prefix character) from the list
		$data 	= array();
		
		foreach(explode(';', $list) as $group) {	
			if (substr(trim($group), 0, 1) === '-') {
				continue;
			}
			// remove excluded file types (those that have a - prefix character) from the list
			$data[] = preg_replace('#(,)?-([\w]+)#', '', $group);
		}
		
		$list = implode(';', $data);

		switch ($format) {
			case 'list':
				return $this->listFileTypes($list);
				break;
			case 'array':
				return explode(',', $this->listFileTypes($list));
				break;
			default:
			case 'map':
				return $list;
				break;
		}
	}
	
	public function setFileTypes($list = 'images=jpg,jpeg,png,gif')
	{		
		$this->set('filetypes', $list);
	}

	/**
	 * Converts the extensions map to a list
	 * @param string $map The extensions map eg: images=jpg,jpeg,gif,png
	 * @return string jpg,jpeg,gif,png
	 */
	private function listFileTypes($map)
	{
		return preg_replace(array('/([\w]+)=([\w]+)/', '/;/'), array('$2', ','), $map);
	}
	
	public function addFileTypes($types)
	{
		$list = explode(';', $this->get('filetypes'));
			
		foreach($types as $group => $extensions) {
			$list[] = $group . '=' . $extensions;
		}	

		$this->set('filetypes', implode(';', $list));
	}
	
	/**
	 * Maps upload file types to an upload dialog list, eg: 'images', 'jpeg,jpg,gif,png'
	 * @return json encoded list
	 */
	private function mapUploadFileTypes()
	{
		$map = array();
		
		// Get the filetype map
		$list = $this->getFileTypes();
		
		if ($list) {
			$items = explode(';', $list);
			
			// [images=jpeg,jpg,gif,png]
			foreach ($items as $item) {
				// ['images', 'jpeg,jpg,gif,png']
				$kv                                               = explode('=', $item);
				$extensions                                       = implode(';', preg_replace('/(\w+)/i', '*.$1', explode(',', $kv[1])));
				$map[WFText::_('WF_FILEGROUP_' . $kv[0], WFText::_($kv[0])) . ' (' . $extensions . ')'] = $kv[1];
			}
		}		

		// All file types
		//$map[WFText::_('WF_FILEGROUP_ALL') . ' (*.*)'] = '*.*';
		
		return $map;
	}
	
	/**
	 * Returns the result variable
	 * @return var $_result
	 */
	public function getResult()
	{
		return $this->_result;
	}

	public function setResult($value, $key = null)
	{
		if ($key) {
			if (is_array($this->_result[$key])) {
				$this->_result[$key][] = $value;
			} else {
				$this->_result[$key] = $value;
			}
		} else {
			$this->_result = $value;
		}
	}
	
	function checkFeature($action, $type = null)
	{
		$features = $this->get('features');		
			
		if ($type) {
			if (isset($features[$type])) {
				
				$type = $features[$type];
				
				if (isset($type[$action])) {
					return (bool)$type[$action];
				}
			}
		} else {
			if (isset($features[$action])) {
				return (bool)$features[$action];
			}
		}	

		return false;
	}

	public function getBaseDir()
	{
		$filesystem = $this->getFileSystem();
		return $filesystem->getBaseDir();
	}

	/**
	 * Get the list of files in a given folder
	 * @param string $relative The relative path of the folder
	 * @param string $filter A regex filter option
	 * @return File list array
	 */
	private function getFiles($relative, $filter = '.')
	{
		$filesystem = $this->getFileSystem();
		$list = $filesystem->getFiles($relative, $filter);

		return $list;
	}
	/**
	 * Get the list of folder in a given folder
	 * @param string $relative The relative path of the folder
	 * @return Folder list array
	 */
	private function getFolders($relative)
	{
		$filesystem = $this->getFileSystem();
		$list = $filesystem->getFolders($relative);

		return $list;
	}
	/**
	 * Get file and folder lists
	 * @return array Array of file and folder list objects
	 * @param string $relative Relative or absolute path based either on source url or current directory
	 * @param int $limit List limit
	 * @param int $start list start point
	 */
	public function getItems($path, $limit = 25, $start = 0)
	{
		$filesystem = $this->getFileSystem();	
			
		clearstatcache();
		
		// decode path
		$path = rawurldecode($path);

		WFUtility::checkPath($path);
		
		// get source dir from path eg: images/stories/fruit.jpg = images/stories
		$dir = $filesystem->getSourceDir($path);
	
		// get file list by filter
		$files   	= self::getFiles($dir, '\.(?i)(' . str_replace(',', '|', $this->getFileTypes('list')) . ')$');
		// get folder list
		$folders 	= self::getFolders($dir);

		$folderArray = array();
		$fileArray   = array();

		$items = array_merge($folders, $files);

		if ($items) {
			if (is_numeric($limit)) {
				$items = array_slice($items, $start, $limit);
			}

			foreach ($items as $item) {
				$item['classes'] = '';

				if ($item['type'] == 'folders') {
					$folderArray[] 	= $item;
				} else {
					// check for selected item
					$item['selected'] 	= $filesystem->isMatch($item['url'], $path);	
							
					$fileArray[] 		= $item;
				}
			}
		}
		
		$result = array(
            'folders' 	=> $folderArray,
            'files' 	=> $fileArray,
            'total' 	=> array(
                'folders' 	=> count($folders),
                'files' 	=> count($files)
			)
		);
		
		// Fire Event passing result as reference
		$this->fireEvent('onGetItems', array(&$result));

		return $result;
	}
	/**
	 * Get a tree node
	 * @param string $dir The relative path of the folder to search
	 * @return Tree node array
	 */
	public function getTreeItem($path)
	{
		$path = rawurldecode($path);	
			
		WFUtility::checkPath($path);	
			
		$folders = $this->getFolders($path);
		$array   = array();
		if (!empty($folders)) {
			foreach ($folders as $folder) {
				$array[] = array(
                    'id' 	=> $folder['id'],
                    'name' 	=> $folder['name'],
                    'class' => 'folder'
                );
			}
		}
		$result = array(
            'folders' => $array
		);
		return $result;
	}
	/**
	 * Escape a string
	 *
	 * @return string Escaped string
	 * @param string $string
	 */
	private function escape($string)
	{
		return preg_replace(array(
            '/%2F/',
            '/%3F/',
            '/%40/',
            '/%2A/',
            '/%2B/'
            ), array(
            '/',
            '?',
            '@',
            '*',
            '+'
            ), rawurlencode($string));
	}
	/**
	 * Build a tree list
	 * @param string $dir The relative path of the folder to search
	 * @return Tree html string
	 */
	public function getTree($path)
	{
		$filesystem = $this->getFileSystem();

		// decode path
		$path = rawurldecode($path);
		
		WFUtility::checkPath($path);		
		
		// get source dir from path eg: images/stories/fruit.jpg = images/stories
		$dir = $filesystem->getSourceDir($path);	
			
		$result = $this->getTreeItems($dir);
		return $result;
	}
	/**
	 * Get Tree list items as html list
	 *
	 * @return Tree list html string
	 * @param string $dir Current directory
	 * @param boolean $root[optional] Is root directory
	 * @param boolean $init[optional] Is tree initialisation
	 */
	public function getTreeItems($dir, $root = true, $init = true)
	{
		$result = '';
		if ($init) {
			$this->treedir = $dir;
			if ($root) {
				$result = '<ul><li id="/" class="open"><div class="tree-row"><div class="tree-image"></div><span class="root"><a href="javascript:;">' . WFText::_('WF_LABEL_ROOT') . '</a></span></div>';
				$dir    = '/';
			}
		}
		$folders = $this->getFolders($dir);

		if ($folders) {
			$result .= '<ul class="tree-node">';
			foreach ($folders as $folder) {
				$open = strpos($this->treedir, $folder['id']) !== false ? ' open' : '';
				$result .= '<li id="' . $this->escape($folder['id']) . '" class="' . $open . '"><div class="tree-row"><div class="tree-image"></div><span class="folder"><a href="javascript:;">' . $folder['name'] . '</a></span></div>';
				if ($open) {
					if ($h = $this->getTreeItems($folder['id'], false, false)) {
						$result .= $h;
					}
				}
				$result .= '</li>';
			}
			$result .= '</ul>';
		}
		if ($init && $root) {
			$result .= '</li></ul>';
		}
		$init = false;
		return $result;
	}
	/**
	 * Get a folders properties
	 *
	 * @return array Array of properties
	 * @param string $dir Folder relative path
	 */
	public function getFolderDetails($dir)
	{
		WFUtility::checkPath($dir);		
			
		$filesystem = $this->getFileSystem();
		// get array with folder date and content count eg: array('date'=>'00-00-000', 'folders'=>1, 'files'=>2);
		return $filesystem->getFolderDetails($dir);
	}
	/**
	 * Get a files properties
	 *
	 * @return array Array of properties
	 * @param string $file File relative path
	 */
	function getFileDetails($file)
	{
		WFUtility::checkPath($file);	
			
		$filesystem = $this->getFileSystem();
		// get array with folder date and content count eg: array('date'=>'00-00-000', 'folders'=>1, 'files'=>2);
		return $filesystem->getFileDetails($file);
	}
	/**
	 * Create standard actions based on access
	 */
	private function getStdActions()
	{
		$this->addAction('help', '', '', WFText::_('WF_BUTTON_HELP'));
		
		if ($this->checkFeature('upload')) {
			$this->addAction('upload');
			$this->setRequest(array($this, 'upload'));
		}
		
		if ($this->checkFeature('create', 'folder')) {
			$this->addAction('folder_new');
			$this->setRequest(array($this, 'folderNew'));
		}
	}
	/**
	 * Add an action to the list
	 *
	 * @param string $name Action name
	 * @param array  $options Array of options
	 */
	public function addAction($name, $options = array())
	{
		/* TODO */
		// backwards compatability (remove in stable)
		$args = func_get_args();
		
		if (count($args) == 4) {
			$options['icon'] 	= $args[1];
			$options['action'] 	= $args[2];
			$options['title'] 	= $args[3];
		}	
			
		$options = array_merge(array('name' => $name), $options);	
		
		// set some defaults
		if (!array_key_exists('icon', $options)) {
			$options['icon'] = ''; 
		}
		
		if (!array_key_exists('action', $options)) {
			$options['action'] = ''; 
		}
		
		if (!array_key_exists('title', $options)) {
			$options['title'] = WFText::_('WF_BUTTON_' . strtoupper($name));
		}
			
		$this->_actions[$name] = $options;
	}
	/**
	 * Get all actions
	 * @return object
	 */
	private function getActions()
	{
		return array_reverse($this->_actions);
	}
	/**
	 * Remove an action from the list by name
	 * @param string $name Action name to remove
	 */
	public function removeAction($name)
	{
		if (array_key_exists($this->_actions[$name])) {
			unset($this->_actions[$name]);
		}
	}

	/**
	 * Create all standard buttons based on access
	 */
	private function getStdButtons()
	{		
		if ($this->checkFeature('delete', 'folder')) {
			$this->addButton('folder', 'delete', array('multiple' => true));

			$this->setRequest(array($this,'deleteItem'));
		}
		if ($this->checkFeature('rename', 'folder')) {
			$this->addButton('folder', 'rename');

			$this->setRequest(array($this, 'renameItem'));
		}
		if ($this->checkFeature('move', 'folder')) {
			$this->addButton('folder', 'copy', array('multiple' => true));
			$this->addButton('folder', 'cut', array('multiple' => true));

			$this->addButton('folder', 'paste', array('multiple' => true, 'trigger' => true));

			$this->setRequest(array($this,'copyItem'));
            $this->setRequest(array($this,'moveItem'));
		}
		if ($this->checkFeature('rename', 'file')) {
			$this->addButton('file', 'rename');

			$this->setRequest(array($this, 'renameItem'));
		}
		if ($this->checkFeature('delete', 'file')) {
			$this->addButton('file', 'delete', array('multiple' => true));

			$this->setRequest(array($this, 'deleteItem'));
		}
		if ($this->checkFeature('move', 'file')) {
			$this->addButton('file', 'copy', array('multiple' => true));
			$this->addButton('file', 'cut', array('multiple' => true));

			$this->addButton('file', 'paste', array('multiple' => true, 'trigger' => true));

			$this->setRequest(array($this, 'copyItem'));
            $this->setRequest(array($this, 'moveItem'));
		}
		$this->addButton('file', 'view', array('restrict' => $this->getViewable()));
		$this->addButton('file', 'insert');
	}

	/**
	 * Add a button
	 *
	 * @param string $type[optional] Button type (file or folder)
	 * @param string $name Button name
	 * @param string $icon[optional] Button icon
	 * @param string $action[optional] Button action / function
	 * @param string $title Button title
	 * @param boolean $multiple[optional] Supports multiple file selection
	 * @param boolean $trigger[optional]
	 */
	public function addButton($type = 'file', $name, $options = array())
	{			
		$options = array_merge(array('name' => $name), $options);	
		
		// set some defaults
		if (!array_key_exists('icon', $options)) {
			$options['icon'] = ''; 
		}
		
		if (!array_key_exists('action', $options)) {
			$options['action'] = ''; 
		}
		
		if (!array_key_exists('title', $options)) {
			$options['title'] = WFText::_('WF_BUTTON_' . strtoupper($name)); 
		}
		
		if (!array_key_exists('multiple', $options)) {
			$options['multiple'] = false; 
		}
		
		if (!array_key_exists('trigger', $options)) {
			$options['trigger'] = false; 
		}

		if (!array_key_exists('restrict', $options)) {
			$options['restrict'] = ''; 
		}
			
		$this->_buttons[$type][$name] = $options;
	}
	/**
	 * Return an object list of all buttons
	 * @return object
	 */
	private function getButtons()
	{
		return $this->_buttons;
	}
	/**
	 * Remove a button
	 * @param string $type Button type
	 * @param string $name Button name
	 */
	public function removeButton($type, $name)
	{
		if (array_key_exists($name, $this->_buttons[$type])) {
			unset($this->_buttons[$type][$name]);
		}
	}
	/**
	 * Change a buttons properties
	 * @param string $type Button type
	 * @param string $name Button name
	 * @param string $keys Button keys
	 */
	public function changeButton($type, $name, $keys)
	{
		foreach ($keys as $key => $value) {
			if (isset($this->_buttons[$type][$name][$key])) {
				$this->_buttons[$type][$name][$key] = $value;
			}
		}
	}
	/**
	 * Add an event
	 * @param string $name Event name
	 * @param string $function Event function name
	 */
	public function addEvent($name, $function)
	{
		$this->_events[$name] = $function;
	}
	
	/**
	 * Execute an event
	 * @return Evenet result
	 * @param object $name Event name
	 * @param array $args[optional] Optional arguments
	 */
	protected function fireEvent($name, $args = null)
	{
		if (array_key_exists($name, $this->_events)) {
			$event = $this->_events[$name];
			
			if (is_array($event)) {
				return call_user_func_array($event, $args);
			} else {
				return call_user_func($event, $args);	
			}

		}
		return $this->_result;
	}
	
	/**
	 * Get a file icon based on extension
	 * @return string Path to file icon
	 * @param string $ext File extension
	 */
	public function getFileIcon($ext)
	{
		if (JFile::exists(WF_EDITOR_LIBRARIES . '/img/icons/' . $ext . '.gif')) {
			return $this->image('libraries.icons/' . $ext . '.gif');
		} elseif (JFile::exists($this->getPluginPath() . '/img/icons/' . $ext . '.gif')) {
			return $this->image('plugins.icons/' . $ext . '.gif');
		} else {
			return $this->image('libraries.icons/def.gif');
		}
	}

	public function getFileSuffix()
	{
		$suffix = WFText::_('WF_MANAGER_FILE_SUFFIX');
		return str_replace('WF_MANAGER_FILE_SUFFIX', '_copy', $suffix);
	}

	private function validateUploadedFile($file, &$result)
	{
		$chunks = JRequest::getInt('chunks', 1);
		
		if (!in_array(strtolower(JFile::getExt($file['name'])), $this->getFileTypes('array'))) {
			$result->state = false;
			$result->message = WFText::_('WF_MANAGER_UPLOAD_INVALID_EXT_ERROR');
			return false;
		}

		if (preg_match('#\.(jpeg|jpg|jpe|png|gif|wbmp|bmp|tiff|tif)$#i', $file['name'])) {
			if (@getimagesize($file['tmp_name']) === false) {
				$result->state = false;
				$result->message = WFText::_('WF_MANAGER_UPLOAD_INVALID_IMAGE_ERROR');
				return false;
			}
		}
		$upload = $this->get('upload');
				
		// validate if set and only if chunking disabled
		if ($upload['validate_mimetype'] && $chunks == 1) {
			wfimport('editor.libraries.classes.mime');	
				
			if (!WFMimeType::check($file['name'], $file['tmp_name'], $file['type'])) {
				$result->state = false;
				$result->message = WFText::_('WF_MANAGER_UPLOAD_INVALID_IMAGE_ERROR');
				return false;
			}
		}

		/** check for XSS
		 * From MediaHelper::canUpload
		 * @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
		 **/

		$xss_check = JFile::read($file['tmp_name'], false, 256);

		// check for hidden php tags
		if (stristr($xss_check, '<?php')) {
			$result->state = false;
			$result->message = WFText::_('WF_MANAGER_UPLOAD_RESTRICTED_ERROR');
			return false;
		}

		$html_tags = array(
            'abbr',
            'acronym',
            'address',
            'applet',
            'area',
            'audioscope',
            'base',
            'basefont',
            'bdo',
            'bgsound',
            'big',
            'blackface',
            'blink',
            'blockquote',
            'body',
            'bq',
            'br',
            'button',
            'caption',
            'center',
            'cite',
            'code',
            'col',
            'colgroup',
            'comment',
            'custom',
            'dd',
            'del',
            'dfn',
            'dir',
            'div',
            'dl',
            'dt',
            'em',
            'embed',
            'fieldset',
            'fn',
            'font',
            'form',
            'frame',
            'frameset',
            'h1',
            'h2',
            'h3',
            'h4',
            'h5',
            'h6',
            'head',
            'hr',
            'html',
            'iframe',
            'ilayer',
            'img',
            'input',
            'ins',
            'isindex',
            'keygen',
            'kbd',
            'label',
            'layer',
            'legend',
            'li',
            'limittext',
            'link',
            'listing',
            'map',
            'marquee',
            'menu',
            'meta',
            'multicol',
            'nobr',
            'noembed',
            'noframes',
            'noscript',
            'nosmartquotes',
            'object',
            'ol',
            'optgroup',
            'option',
            'param',
            'plaintext',
            'pre',
            'rt',
            'ruby',
            's',
            'samp',
            'script',
            'select',
            'server',
            'shadow',
            'sidebar',
            'small',
            'spacer',
            'span',
            'strike',
            'strong',
            'style',
            'sub',
            'sup',
            'table',
            'tbody',
            'td',
            'textarea',
            'tfoot',
            'th',
            'thead',
            'title',
            'tr',
            'tt',
            'ul',
            'var',
            'wbr',
            'xml',
            'xmp',
            '!DOCTYPE',
            '!--'
            );

            foreach ($html_tags as $tag) {
            	// A tag is '<tagname ', so we need to add < and a space or '<tagname>'
            	if (stristr($xss_check, '<' . $tag . ' ') || stristr($xss_check, '<' . $tag . '>')) {
            		$result->state = false;
					$result->message = WFText::_('WF_MANAGER_UPLOAD_RESTRICTED_ERROR');
					return false;
            	}
            }

            return true;
	}

	/**
	 * Upload a file.
	 * @return array $error on failure or uploaded file name on success
	 */
	public function upload()
	{
		// Check for request forgeries
		WFToken::checkToken() or die();
		
		JError::setErrorHandling(E_ALL, 'callback', array('WFUtility', 'raiseError'));
		
		// check for feature access	
		if (!$this->checkFeature('upload')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}
		
		$wf = WFEditor::getInstance();

		jimport('joomla.filesystem.file');

		// HTTP headers for no cache etc
		//header('Content-type: text/plain; charset=UTF-8');
		header("Expires: Wed, 4 Apr 1984 13:00:00 GMT");
		header("Last-Modified: " . gmdate("D, d M_Y H:i:s") . " GMT");
		header("Cache-Control: no-store, no-cache, must-revalidate");
		header("Cache-Control: post-check=0, pre-check=0", false);
		header("Pragma: no-cache");

		// get uploaded file
		$file = JRequest::getVar('file', '', 'files', 'array');

		// get file name
		$name 	= JRequest::getVar('name', $file['name']);
		$ext 	= WFUtility::getExtension($name);

		// check for extension in file name
		if (preg_match('#\.(php|php(3|4|5)|phtml|pl|py|jsp|asp|htm|shtml|sh|cgi)#i', basename($name, '.' . $ext))) {
			JError::raiseError(403, 'INVALID FILE NAME');
		}

		// get chunks
		$chunk  = JRequest::getInt('chunk', 0);
		$chunks = JRequest::getInt('chunks', 1);

		// create a filesystem result object
		$result = new WFFileSystemResult();

		$filesystem = $this->getFileSystem();
		$complete    = false;
		$contentType = JRequest::getVar('CONTENT_TYPE', '', 'SERVER');

		// Only multipart uploading is supported for now
		if ($contentType && strpos($contentType, "multipart") !== false) {
			if (isset($file['tmp_name']) && is_uploaded_file($file['tmp_name'])) {
				// validate file before continuing (first chunk only)
				if ($chunk == 0) {
					if ($wf->getParam('validate_mimetype', 0) && !preg_match('#(htm|html|txt)#', $ext)) {
						if (!$this->validateUploadedFile($file, $result)) {
							$complete = true;
						}
					}
					
					// check file size
					if (!$complete && $chunks == 1) {
						@clearstatcache();
						$upload 	= $this->get('upload');
						$max_size 	= intval(preg_replace('/[^0-9]/', '', $upload['max_size'])) * 1024;
						$file_size 	= @filesize($file['tmp_name']);
					
						// check size and finish
						if ($file_size > $max_size) {
							$result->message = WFText::sprintf('WF_MANAGER_UPLOAD_SIZE_ERROR', $name, $file_size, ($max_size / 1024) . 'KB');
							$complete = true;
						}
					}
				}	
				// continue upload
				if (!$complete) {
					// make file name 'web safe'
					$name = WFUtility::makeSafe($name);
					
					// get current dir
					$dir  = JRequest::getVar('upload-dir', '');
					
					// check destination path
					WFUtility::checkPath($dir);
					
					// Normal upload
					if ($chunks == 1) {
						$result = $filesystem->upload('multipart', trim($file['tmp_name']), $dir, $name);
					
						if (!$result->state) {
							$result->message 	= WFText::_('WF_MANAGER_UPLOAD_ERROR');
							$result->code 		= 103;
						}
					
						$complete = true;
						// Chunk uploading
					} else {
						$result = $filesystem->upload('multipart-chunking', trim($file['tmp_name']), $dir, $name, $chunks, $chunk);
					
						if (!$result->state) {
							$result->message = WFText::_('WF_MANAGER_UPLOAD_ERROR');
							$result->code = 103;
						}
						$complete = ($chunk == $chunks - 1);
					}	
				}
			}
		} else {
			$result->state   	= false;
			$result->code   	= 103;
			$result->message 	= WFText::_('WF_MANAGER_UPLOAD_ERROR');

			$complete = true;
		}
		// upload finished
		if ($complete) {
			
			if (is_a($result, 'WFFileSystemResult')) {
				if ($result->state === true) {		

					$path = $result->path;
					
					$this->setResult($this->fireEvent('onUpload', array($result->path)));					
					$this->setResult(basename($result->path), 'files');	
				} else {
					$this->setResult($result->message, 'error');
				}
			}

			die(json_encode($this->getResult()));
		}
	}

	/**
	 * Delete the relative file(s).
	 * @param $files the relative path to the file name or comma seperated list of multiple paths.
	 * @return string $error on failure.
	 */
	public function deleteItem($items)
	{
		// check for feature access	
		if (!$this->checkFeature('delete', 'folder') && !$this->checkFeature('delete', 'file')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}	
			
		$filesystem = $this->getFileSystem();
		$items 		= explode(",", rawurldecode($items));

		foreach ($items as $item) {
			// check path	
			WFUtility::checkPath($item);	
				
			$result = $filesystem->delete($item);

			if (is_a($result, 'WFFileSystemResult')) {
				if (!$result->state) {
					if ($result->message) {
						$this->setResult($result->message, 'error');
					} else {
						$this->setResult(JText::sprintf('WF_MANAGER_DELETE_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
					}
				} else {
					$this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Delete', array($item)));
					$this->setResult($item, $result->type);
				}
			}
		}

		return $this->getResult();
	}

	/**
	 * Rename a file.
	 * @param string $src The relative path of the source file
	 * @param string $dest The name of the new file
	 * @return string $error
	 */
	public function renameItem()
	{
		// check for feature access	
		if (!$this->checkFeature('rename', 'folder') && !$this->checkFeature('rename', 'file')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}	
			
		$args 			= func_get_args();
		
		$source 		= array_shift($args);
		$destination	= array_shift($args);	
		
		WFUtility::checkPath($source);
		WFUtility::checkPath($destination);
		
		// check for extension in destination name
		if (preg_match('#\.(php|php(3|4|5)|phtml|pl|py|jsp|asp|htm|html|shtml|sh|cgi)\b#i', $destination)) {
			JError::raiseError(403, 'INVALID FILE NAME');
		}
	
		$filesystem 	= $this->getFileSystem();
		$result 		= $filesystem->rename($source, trim($destination), $args);

		if (is_a($result, 'WFFileSystemResult')) {
			if (!$result->state) {
				$this->setResult(WFText::sprintf('WF_MANAGER_RENAME_' . strtoupper($result->type) . '_ERROR', basename($source)), 'error');
				if ($result->message) {
					$this->setResult($result->message, 'error');
				}
			} else {
				$this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Rename', array($destination)));
				$this->setResult($destination, $result->type);
			}
		}

		return $this->getResult();
	}

	/**
	 * Copy a file.
	 * @param string $files The relative file or comma seperated list of files
	 * @param string $dest The relative path of the destination dir
	 * @return string $error on failure
	 */
	public function copyItem($items, $destination)
	{
		// check for feature access	
		if (!$this->checkFeature('move', 'folder') && !$this->checkFeature('move', 'file')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}	
			
		$filesystem = $this->getFileSystem();

		$items = explode(",", rawurldecode($items));

		// check destination path
		WFUtility::checkPath($destination);

		foreach ($items as $item) {
			
			// check source path
			WFUtility::checkPath($item);	
				
			$result = $filesystem->copy($item, $destination);

			if (is_a($result, 'WFFileSystemResult')) {
				if (!$result->state) {
					if ($result->message) {
						$this->setResult($result->message, 'error');
					} else {
						$this->setResult(JText::sprintf('WF_MANAGER_COPY_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
					}
				} else {
					$this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Copy', array($item)));
					$this->setResult($destination, $result->type);
				}
			}
		}
		return $this->getResult();
	}

	/**
	 * Copy a file.
	 * @param string $files The relative file or comma seperated list of files
	 * @param string $dest The relative path of the destination dir
	 * @return string $error on failure
	 */
	public function moveItem($items, $destination)
	{
		// check for feature access	
		if (!$this->checkFeature('move', 'folder') && !$this->checkFeature('move', 'file')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}	
			
		$filesystem = $this->getFileSystem();

		$items = explode(",", rawurldecode($items));
		
		// check destination path
		WFUtility::checkPath($destination);

		foreach ($items as $item) {
			// check source path
			WFUtility::checkPath($item);	

			$result = $filesystem->move($item, $destination);

			if (is_a($result, 'WFFileSystemResult')) {
				if (!$result->state) {
					if ($result->message) {
						$this->setResult($result->message, 'error');
					} else {
						$this->setResult(JText::sprintf('WF_MANAGER_MOVE_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
					}
				} else {
					$this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Move', array($item)));
					$this->setResult($destination, $result->type);
				}
			}
		}
		return $this->getResult();
	}

	/**
	 * New folder
	 * @param string $dir The base dir
	 * @param string $new_dir The folder to be created
	 * @return string $error on failure
	 */
	public function folderNew()
	{
		if (!$this->checkFeature('create', 'folder')) {
			JError::raiseError(403, 'RESTRICTED ACCESS');
		}	
			
		$args 	= func_get_args();
		
		$dir 	= array_shift($args);
		$new	= array_shift($args);
			
		$filesystem = $this->getFileSystem();

		$result = $filesystem->createFolder($dir, trim($new));

		if (is_a($result, 'WFFileSystemResult')) {
			if (!$result->state) {
				if ($result->message) {
					$this->setResult($result->message, 'error');
				} else {
					$this->setResult(JText::sprintf('WF_MANAGER_NEW_FOLDER_ERROR', basename($new)), 'error');
				}
			} else {
				$this->setResult($this->fireEvent('onFolderNew', array($new)));
			}
		}

		return $this->getResult();
	}
	
	private function getUploadValue() {
		$upload = trim(ini_get('upload_max_filesize'));
		$post 	= trim(ini_get('post_max_size'));	
			
		$upload = WFUtility::convertSize($upload);
		$post 	= WFUtility::convertSize($post);
		
		if (intval($upload) <= intval($post)) {
			return $upload;
		}
		
		return $post;
	}

	private function getUploadDefaults()
	{
		$filesystem = $this->getFileSystem();
		$features 	= $filesystem->get('upload');

		$upload_max	= $this->getUploadValue();
		
		$upload 	= $this->get('upload');

		$chunk_size = $upload_max ? $upload_max / 1024 . 'KB' : '1MB';
		$chunk_size = isset($upload['chunk_size']) ? $upload['chunk_size'] : $chunk_size;

		// chunking not yet supported in safe_mode, check base directory is writable and chunking supported by filesystem
		if (!$features['chunking']) {
			$chunk_size = 0;
		}
		
		// get upload size
		$size = intval(preg_replace('/[^0-9]/', '', $upload['max_size'])) . 'kb';
		
		// must not exceed server maximum
		if ((int)$size * 1024 > (int)$upload_max) {
			$size = $upload_max / 1024 . 'kb';
		}
		
		$runtimes = array();
		
		if (is_string($upload['runtimes'])) {
			$runtimes = explode(',', $upload['runtimes']);
		} else {
			foreach($upload['runtimes'] as $k => $v) {
				$runtimes[] = $v;
			}
		}
		
		// add html4
		$runtimes[] = 'html4';

		// remove flash runtime if $chunk_size is 0 (no chunking)
		if (!$chunk_size) {
			unset($runtimes[array_search('flash', $runtimes)]);
		}

		$defaults = array(
            'runtimes' 		=> implode(',', $runtimes),
            'size' 			=> $size,
            'filter' 		=> $this->mapUploadFileTypes(true),
            'chunk_size' 	=> $chunk_size
		);
		
		if (isset($features['dialog'])) {
			$defaults['dialog'] = $features['dialog'];
 		}

		return $defaults;
	}

	public function getDimensions($file)
	{
		$filesystem = $this->getFileSystem();
		return $filesystem->getDimensions($file);
	}

	protected function getSettings($settings = array())
	{
		$filesystem = $this->getFileSystem();

		$default = array(
            'dir'		=> $filesystem->getRootDir(),
			'actions' 	=> $this->getActions(),
            'buttons' 	=> $this->getButtons(),
            'upload' 	=> $this->getUploadDefaults(),
            'tree' 		=> $this->get('folder_tree'),
            'listlimit' => $this->get('list_limit')
		);
		
		$properties = array('base', 'delete', 'rename', 'folder_new', 'copy', 'move');
		
		foreach($properties as $property) {
			if ($filesystem->get($property)) {
				$default[$property] = $filesystem->get($property);
			}
		}

		$settings = array_merge_recursive($default, $settings);

		return $settings;
	}
}
?>