Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla5/plugins/fabrik_element/fileupload/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla5/plugins/fabrik_element/fileupload/fileupload.php

<?php
/**
 * Plug-in to render fileupload element
 *
 * @package     Joomla.Plugin
 * @subpackage  Fabrik.element.fileupload
 * @copyright   Copyright (C) 2005-2020  Media A-Team, Inc. - All rights
reserved.
 * @license     GNU/GPL http://www.gnu.org/copyleft/gpl.html
 */

// No direct access
defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Language\Text;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Filesystem\Folder;
use Joomla\String\StringHelper;
use Fabrik\Helpers\Image;
use Fabrik\Helpers\Uploader;
use Fabrik\Helpers\Html;
use Joomla\CMS\Filesystem\File;
use Joomla\Utilities\ArrayHelper;
use Fabrik\Helpers\Php;

if (!defined('FU_DOWNLOAD_SCRIPT_NONE'))
define("FU_DOWNLOAD_SCRIPT_NONE", '0');
if (!defined('FU_DOWNLOAD_SCRIPT_TABLE'))
define("FU_DOWNLOAD_SCRIPT_TABLE", '1');
if (!defined('FU_DOWNLOAD_SCRIPT_DETAIL'))
define("FU_DOWNLOAD_SCRIPT_DETAIL", '2');
if (!defined('FU_DOWNLOAD_SCRIPT_BOTH'))
define("FU_DOWNLOAD_SCRIPT_BOTH", '3');

$logLvl = Log::ERROR + Log::EMERGENCY + Log::WARNING;
Log::addLogger(array('text_file' =>
'fabrik.element.fileupload.log.php'), $logLvl,
array('com_fabrik.element.fileupload'));

/**
 * Plug-in to render file-upload element
 *
 * @package     Joomla.Plugin
 * @subpackage  Fabrik.element.fileupload
 * @since       3.0
 */
class PlgFabrik_ElementFileupload extends PlgFabrik_Element
{
	/**
	 * Storage method adaptor object (filesystem/amazon s3)
	 * needs to be public as models have to see it
	 *
	 * @var object
	 */
	public $storage = null;

	/**
	 * Is the element an upload element
	 *
	 * @var bool
	 */
	protected $is_upload = true;

	/**
	 * Does the element store its data in a join table (1:n)
	 *
	 * @return  bool
	 */
	public function isJoin()
	{
		$params = $this->getParams();

		if ($this->isAjax() && (int)
$params->get('ajax_max', 4) > 1)
		{
			return true;
		}
		else
		{
			return parent::isJoin();
		}
	}

	/**
	 * Determines if the data in the form element is used when updating a
record
	 *
	 * @param   mixed $val Element form data
	 *
	 * @return  bool  True if ignored on update, default = false
	 */
	public function ignoreOnUpdate($val)
	{
		$input = $this->app->input;

		// Check if its a CSV import if it is allow the val to be inserted
		if ($input->get('task') === 'makeTableFromCSV' ||
$this->getListModel()->importingCSV)
		{
			return false;
		}

		$fullName   = $this->getFullName(true, false);
		$groupModel = $this->getGroupModel();
		$return     = false;

		if ($groupModel->canRepeat())
		{
			/*$$$rob could be the case that we aren't uploading an element by
have removed
			 *a repeat group (no join) with a file upload element, in this case
processUpload has the correct
			 *file path settings.
			 */
			return false;
		}
		else
		{
			if ($groupModel->isJoin())
			{
				$name         = $this->getFullName(true, false);
				$joinId       = $groupModel->getGroup()->join_id;
				$fileJoinData =
FArrayHelper::getValue($_FILES['join']['name'],
$joinId, array());
				$fileData     = FArrayHelper::getValue($fileJoinData, $name);
			}
			else
			{
				$fileData = @$_FILES[$fullName]['name'];
			}

			if ($fileData == '')
			{
				if ($this->canCrop() == false)
				{
					// Was stopping saving of single ajax upload image
					// return true;
				}
				else
				{
					/*if we can crop we need to store the cropped coordinated in the field
data
					 * @see onStoreRow();
					 * above depreciated - not sure what to return here for the moment
					 */
					return false;
				}
			}
			else
			{
				return false;
			}
		}

		return $return;
	}

	/**
	 * Remove the reference to the file from the db table - leaves the file on
the server
	 *
	 * @since  3.0.7
	 *
	 * @return  void
	 */
	public function onAjax_clearFileReference()
	{
		$rowId     = (array) $this->app->input->get('rowid');
		$joinPkVal = $this->app->input->get('joinPkVal');
		$this->loadMeForAjax();
		$col       = $this->getFullName(false, false);
		$listModel = $this->getListModel();

		$listModel->updateRows($rowId, $col, '', '',
$joinPkVal);
	}

	/**
	 * Get the class to manage the form element
	 * to ensure that the file is loaded only once
	 *
	 * @param   array  &$srcs  Scripts previously loaded
	 * @param   string $script Script to load once class has loaded
	 * @param   array  &$shim  Dependant class names to load before
loading the class - put in requirejs.config shim
	 *
	 * @return void
	 */
	public function formJavascriptClass(&$srcs, $script = '',
&$shim = array())
	{
		$key     = Html::isDebug() ? 'element/fileupload/fileupload' :
'element/fileupload/fileupload-min';
		$s       = new stdClass;
		$s->deps = array();
		$params  = $this->getParams();

		if ($this->isAjax())
		{
			$runtimes       = $params->get('ajax_runtime',
'html5');
			$folder         = 'element/fileupload/lib/plupload/js/';
			$plupShim       = new stdClass;
			$plupShim->deps = array($folder . 'plupload');
			$s->deps[]      = $folder . 'plupload';

			// MCL test
			$mcl = Html::mcl();
			//$s->deps = array_merge($s->deps, $mcl);

			if (strstr($runtimes, 'html5'))
			{
				$s->deps[]                        = $folder .
'plupload.html5';
				$shim[$folder . 'plupload.html5'] = $plupShim;
			}

			if (strstr($runtimes, 'html4'))
			{
				$s->deps[]                        = $folder .
'plupload.html4';
				$shim[$folder . 'plupload.html4'] = $plupShim;
			}

			if (strstr($runtimes, 'flash'))
			{
				$s->deps[]                        = $folder .
'plupload.flash';
				$shim[$folder . 'plupload.flash'] = $plupShim;
			}

			if (strstr($runtimes, 'silverlight'))
			{
				$s->deps[]                              = $folder .
'plupload.silverlight';
				$shim[$folder . 'plupload.silverlight'] = $plupShim;
			}

			if (strstr($runtimes, 'browserplus'))
			{
				$s->deps[]                              = $folder .
'plupload.browserplus';
				$shim[$folder . 'plupload.browserplus'] = $plupShim;
			}
		}

		if ($this->requiresSlideshow())
		{
			Html::slideshow();
		}

		if (array_key_exists($key, $shim) && isset($shim[$key]->deps))
		{
			$merged = array_merge($shim[$key]->deps, $s->deps);
			$unique = array_unique($merged);
			$values = array_values($unique);
			$shim[$key]->deps =
array_values(array_unique(array_merge($shim[$key]->deps, $s->deps)));
		}
		else
		{
			$shim[$key] = $s;
		}

		parent::formJavascriptClass($srcs, $script, $shim);

		// $$$ hugh - added this, and some logic in the view, so we will get
called on a per-element basis
		return false;
	}

	/**
	 * Returns javascript which creates an instance of the class defined in
formJavascriptClass()
	 *
	 * @param   int $repeatCounter Repeat group counter
	 *
	 * @return  array
	 */
	public function elementJavascript($repeatCounter)
	{
		$params = $this->getParams();
		$id     = $this->getHTMLId($repeatCounter);
		Html::mcl();

		$element   = $this->getElement();
		$paramsKey = $this->getFullName(true, false);
		$paramsKey = Fabrikstring::rtrimword($paramsKey,
$this->getElement()->name);
		$paramsKey .= 'params';
		$formData  = $this->getFormModel()->data;
		$imgParams = FArrayHelper::getValue($formData, $paramsKey);

		// Above paramsKey stuff looks really wonky - lets test if null and use
something which seems to build the correct key
		if (is_null($imgParams))
		{
			$paramsKey = $this->getFullName(true, false) . '___params';
			$imgParams = FArrayHelper::getValue($formData, $paramsKey);
		}

		$value = $this->getValue(array(), $repeatCounter);
		$value = is_array($value) ? $value : FabrikWorker::JSONtoData($value,
true);
		$value = $this->checkForSingleCropValue($value);

		$singleCrop = false;

		if (is_array($value) && array_key_exists('params',
$value))
		{
			$singleCrop = true;
			$imgParams = (array) FArrayHelper::getValue($value, 'params');
			$value = (array) FArrayHelper::getValue($value, 'file');
		}

		// Repeat_image_repeat_image___params
		$rawValues = count($value) == 0 ? array() : FArrayHelper::array_fill(0,
count($value), 0);
		$fileData  = $this->getFormModel()->data;
		$rawKey    = $this->getFullName(true, false) . '_raw';
		$rawValues = FArrayHelper::getValue($fileData, $rawKey, $rawValues);

		if (!is_array($rawValues))
		{
			$rawValues = FabrikWorker::JSONtoData($rawValues, true);
		}
		else
		{
			/*
			 * $$$ hugh - nasty hack for now, if repeat group with simple
			 * uploads, all raw values are in an array in $rawValues[0]
			 */
			if (array_key_exists(0, $rawValues) &&
is_array(FArrayHelper::getValue($rawValues, 0)))
			{
				$rawValues = $rawValues[0];
			}
		}

		// single ajax
		foreach ($rawValues as &$rawValue)
		{
			if (is_array($rawValue) && array_key_exists('file',
$rawValue))
			{
				$rawValue = FArrayHelper::getValue($rawValue, 'file',
'');
			}
		}

		if (!empty($imgParams) && !is_array($imgParams))
		{
			$imgParams = explode(GROUPSPLITTER, $imgParams);
		}

		$oFiles   = new stdClass;
		$iCounter = 0;

		// Failed validation for ajax upload elements
		if (is_array($value) && array_key_exists('id', $value))
		{
			$imgParams = array_values($value['crop']);
			$value     = array_keys($value['id']);

			/**
			 * Another nasty hack for failed validations, need to massage $rawValues
back into expected shape,
			 * as they will be keyed by filename instead of parent ID after
validation fail.
			 */
			$newRawValues = array();
			foreach ($value as $k => $v)
			{
				$newRawValues[$k] = $rawValues['id'][$v];
			}
			$rawValues = $newRawValues;
		}

		for ($x = 0; $x < count($value); $x++)
		{
			if (is_array($value))
			{
				if (array_key_exists($x, $value) && $value[$x] !==
'')
				{
					if (is_array($value[$x]))
					{
						// From failed validation
						foreach ($value[$x]['id'] as $tKey => $parts)
						{
							$o       = new stdClass;
							$o->id   = 'alreadyuploaded_' . $element->id .
'_' . $iCounter;
							$o->name = array_pop(explode('/', $tKey));
							$o->path = $tKey;

							if ($fileInfo = $this->getStorage()->getFileInfo($o->path))
							{
								$o->size = $fileInfo['filesize'];
							}
							else
							{
								$o->size = 'unknown';
							}

							$o->type           = strstr($fileInfo['mime_type'],
'image/') ? 'image' : 'file';
							$o->url            = $this->getStorage()->pathToURL($tKey);
							$o->url            =
$this->getStorage()->preRenderPath($o->url);
							$o->recordid       = $rawValues[$x];
							$o->params         =
json_decode($value[$x]['crop'][$tKey]);
							$oFiles->$iCounter = $o;
							$iCounter++;
						}
					}
					else
					{
						if ($singleCrop)
						{
							// Single crop image (not sure about the 0 settings in here)
							$parts   = explode('/', $value[$x]);
							$o       = new stdClass;
							$o->id   = 'alreadyuploaded_' . $element->id .
'_0';
							$o->name = array_pop($parts);
							$o->path = $value[$x];

							if ($fileInfo = $this->getStorage()->getFileInfo($o->path))
							{
								$o->size = $fileInfo['filesize'];
							}
							else
							{
								$o->size = 'unknown';
							}

							$o->type           = strstr($fileInfo['mime_type'],
'image/') ? 'image' : 'file';
							$o->url            =
$this->getStorage()->pathToURL($value[$x]);
							$o->url            =
$this->getStorage()->preRenderPath($o->url);
							$o->recordid       = 0;
							$o->params         = json_decode($imgParams[$x]);
							$oFiles->$iCounter = $o;
							$iCounter++;
						}
						else
						{
							$parts   = explode('/', $value[$x]);
							$o       = new stdClass;
							$o->id   = 'alreadyuploaded_' . $element->id .
'_' . $rawValues[$x];
							$o->name = array_pop($parts);
							$o->path = $value[$x];

							if ($fileInfo = $this->getStorage()->getFileInfo($o->path))
							{
								$o->size = $fileInfo['filesize'];
							}
							else
							{
								$o->size = 'unknown';
							}

							$o->type           = strstr($fileInfo['mime_type'],
'image/') ? 'image' : 'file';
							$o->url            =
$this->getStorage()->pathToURL($value[$x]);
							$o->url            =
$this->getStorage()->preRenderPath($o->url);
							$o->recordid       = $rawValues[$x];
							$o->params         =
json_decode(FArrayHelper::getValue($imgParams, $x, '{}'));
							$oFiles->$iCounter = $o;
							$iCounter++;
						}
					}
				}
			}
		}

		$opts     = $this->getElementJSOptions($repeatCounter);
		$opts->id = $this->getId();

		if ($this->isJoin())
		{
			$opts->isJoin = true;
			$opts->joinId = $this->getJoinModel()->getJoin()->id;
		}

		$opts->elid                  = $element->id;
		$opts->defaultImage          =
$params->get('default_image', '');
		$opts->folderSelect          =
$params->get('upload_allow_folderselect', 0);
		$opts->quality               = (float)
$params->get('image_quality') / 100;
		$opts->dir                   = JPATH_SITE . '/' .
$params->get('ul_directory');
		$opts->ajax_upload           = $this->isAjax();
		$opts->ajax_runtime          =
$params->get('ajax_runtime', 'html5');
		$opts->ajax_show_widget      =
$params->get('ajax_show_widget', '1') ===
"1";
		$opts->ajax_silverlight_path = COM_FABRIK_LIVESITE .
'plugins/fabrik_element/fileupload/lib/plupload/js/plupload.flash.swf';
		$opts->ajax_flash_path       = COM_FABRIK_LIVESITE .
'plugins/fabrik_element/fileupload/lib/plupload/js/plupload.flash.swf';
		$opts->max_file_size         = (float)
$params->get('ul_max_file_size');
		$opts->device_capture        = (float)
$params->get('ul_device_capture');
		$opts->ajax_chunk_size       = (int)
$params->get('ajax_chunk_size', 0);
		$opts->filters               = $this->ajaxFileFilters();
		$opts->crop                  = $this->canCrop();
		$opts->canvasSupport         = Html::canvasSupport();
		$opts->modalId               = $this->modalId($repeatCounter);;
		$opts->elementName   = $this->getFullName();
		$opts->cropwidth     = (int)
$params->get('fileupload_crop_width');
		$opts->cropheight    = (int)
$params->get('fileupload_crop_height');
		$opts->ajax_max      = (int) $params->get('ajax_max', 4);
		$opts->dragdrop      = true;
		$icon                = 'picture';
		$resize              = 'expand-2';
		$opts->previewButton = Html::image($icon, 'form',
@$this->tmpl, array('alt' =>
Text::_('PLG_ELEMENT_FILEUPLOAD_VIEW')));
		$opts->resizeButton  = Html::image($resize, 'form',
@$this->tmpl, array('alt' =>
Text::_('PLG_ELEMENT_FILEUPLOAD_RESIZE')));
		$opts->files         = $oFiles;

		$opts->winWidth         = (int) $params->get('win_width',
400);
		$opts->winHeight        = (int)
$params->get('win_height', 400);
		$opts->elementShortName = $element->name;
		$opts->listName         =
$this->getListModel()->getTable()->db_table_name;
		$opts->useWIP           = (bool)
$params->get('upload_use_wip', '0') ==
'1';
		$opts->page_url         = COM_FABRIK_LIVESITE;
		$opts->ajaxToken        = Session::getFormToken();
		$opts->isAdmin          = (bool)
$this->app->isClient('administrator');
		$opts->iconDelete       = Html::icon("icon-delete", 
'', '', true);
		$opts->spanNames        = array();
		$opts->isCarousel       = $params->get('fu_show_image')
=== '3' && !$this->isEditable();
		$opts->isZoom           = $params->get('fu_show_image')
=== '3' && !$this->isEditable();;
		$opts->htmlId           = $id;

		for($i = 1; $i <= 12; $i++)
		{
			$opts->spanNames[$i] = Html::getGridSpan($i);
		}

		Text::script('PLG_ELEMENT_FILEUPLOAD_MAX_UPLOAD_REACHED');
		Text::script('PLG_ELEMENT_FILEUPLOAD_DRAG_FILES_HERE');
		Text::script('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ALL_FILES');
		Text::script('PLG_ELEMENT_FILEUPLOAD_RESIZE');
		Text::script('PLG_ELEMENT_FILEUPLOAD_CROP_AND_SCALE');
		Text::script('PLG_ELEMENT_FILEUPLOAD_PREVIEW');
		Text::script('PLG_ELEMENT_FILEUPLOAD_CONFIRM_SOFT_DELETE');
		Text::script('PLG_ELEMENT_FILEUPLOAD_CONFIRM_HARD_DELETE');
		Text::script('PLG_ELEMENT_FILEUPLOAD_FILE_TOO_LARGE_SHORT');

		return array('FbFileUpload', $id, $opts);
	}

	/**
	 * Get JS code for ini element list js
	 * Overwritten in plugin classes
	 *
	 * @return string
	 */
	public function elementListJavascript()
	{
		$params = $this->getParams();
		$id = $this->getHTMLId();
		$list = $this->getlistModel()->getTable();

		$opts = new stdClass;
		$opts->listid = $list->id;
		$opts->formid = $this->getFormModel()->getId();
		$opts->elid = $this->getElement()->id;
		$opts->renderContext =
$this->getListModel()->getRenderContext();

		if ($this->requiresSlideshow())
		{
			$opts->isCarousel = true;
			Html::slideshow();
		}
		else
		{
			$opts->isCarousel = false;
		}

		$opts = json_encode($opts);

		return "new FbFileuploadList('$id', $opts);\n";
	}


	/**
	 * Create Plupload js options for file extension filters
	 *
	 * @return  array
	 */
	protected function ajaxFileFilters()
	{
		$return     = new stdClass;
		$extensions = $this->_getAllowedExtension();

		$return->title      = 'Allowed files';
		$return->extensions = implode(',', $extensions);

		return array($return);
	}

	/**
	 * Can the plug-in crop. Based on parameters and browser check (IE8 or
less has no canvas support)
	 *
	 * @since   3.0.9
	 *
	 * @return boolean
	 */
	protected function canCrop()
	{
		$params = $this->getParams();

		if (!Html::canvasSupport())
		{
			return false;
		}

		return (bool) $params->get('fileupload_crop', 0);
	}

	/**
	 * Shows the data formatted for the list view
	 *
	 * @param   string   $data     Elements data
	 * @param   stdClass &$thisRow All the data in the lists current row
	 * @param   array    $opts     Rendering options
	 *
	 * @return  string    formatted value
	 */
	public function renderListData($data, stdClass &$thisRow, $opts =
array())
	{
		$profiler = Profiler::getInstance('Application');
		JDEBUG ? $profiler->mark("renderListData:
{$this->element->plugin}: start: {$this->element->name}")
: null;

		$data     = FabrikWorker::JSONtoData($data, true);
		$name     = $this->getFullName(true, false); // used for debugging,
please leave
		$params   = $this->getParams();
		$rendered = '';
		static $id_num = 0;

		// $$$ hugh - have to run through rendering even if data is empty, in
case default image is being used.
		if (FArrayHelper::emptyIsh($data))
		{
			$data[0] = $this->_renderListData('', $thisRow, 0);
		}
		else
		{
			/**
			 * 2 == 'slide-show' ('carousel'), so don't run
individually through _renderListData(), instead
			 * build whatever carousel the data type uses, which will depend on data
type.  Like simple image carousel,
			 * or MP3 player with playlist, etc.
			 */
			if ($params->get('fu_show_image_in_table', '0')
== '2')
			{
				$id = $this->getHTMLId($id_num) . '_' . $id_num;
				$id_num++;
				$rendered = $this->buildCarousel($id, $data, $thisRow, false);
			}
			else
			{
				for ($i = 0; $i < count($data); $i++)
				{
					$data[$i] = $this->_renderListData($data[$i], $thisRow, $i);
				}
			}
		}

		if ($params->get('fu_show_image_in_table', '0') !=
'2')
		{
			$layoutData               = new stdClass;
			$layoutData->data         = $data;
			$layoutData->elementModel = $this;
			$layout                   = $this->getLayout('list');
			$rendered                 = $layout->render($layoutData);

			if (empty($rendered))
			{
				$data = json_encode($data);
				// icons will already have been set in _renderListData
				$opts['icon'] = 0;
				$rendered     = parent::renderListData($data, $thisRow, $opts);
			}
		}

		return $rendered;
	}

	/**
	 * Shows the data formatted for the CSV export view
	 *
	 * @param   string $data     Element data
	 * @param   object &$thisRow All the data in the tables current row
	 *
	 * @return    string    Formatted value
	 */
	public function renderListData_csv($data, &$thisRow)
	{
		$data   = FabrikWorker::JSONtoData($data, true);
		$params = $this->getParams();
		$format = $params->get('ul_export_encode_csv',
'base64');
		$raw    = $this->getFullName(true, false) . '_raw';

		if ($this->isAjax() && $params->get('ajax_max',
4) == 1)
		{
			// Single ajax upload
			if (is_object($data))
			{
				$data = $data->file;
			}
			else
			{
				if ($data !== '')
				{
					$singleCropImg = FArrayHelper::getValue($data, 0);

					if (empty($singleCropImg))
					{
						$data = array();
					}
					else
					{
						$data = (array) $singleCropImg->file;
					}
				}
			}
		}

		foreach ($data as &$d)
		{
			$d = $this->encodeFile($d, $format);
		}

		// Fix \"" in json encoded string - csv clever enough to treat
"" as a quote inside a "string value"
		$data = str_replace('\"', '"', $data);

		if ($this->isJoin())
		{
			// Multiple file uploads - raw data should be the file paths.
			$thisRow->$raw = json_encode($data);
		}
		else
		{
			$thisRow->$raw = str_replace('\"', '"',
$thisRow->$raw);
		}

		return implode(GROUPSPLITTER, $data);
	}

	/**
	 * Shows the data formatted for the JSON export view
	 *
	 * @param   string $data file name
	 * @param   string $rows all the data in the tables current row
	 *
	 * @return    string    formatted value
	 */
	public function renderListData_json($data, $rows)
	{
		$data   = explode(GROUPSPLITTER, $data);
		$params = $this->getParams();
		$format = $params->get('ul_export_encode_json',
'base64');

		foreach ($data as &$d)
		{
			$d = $this->encodeFile($d, $format);
		}

		return implode(GROUPSPLITTER, $data);
	}

	/**
	 * Encodes the file
	 *
	 * @param   string $file   Relative file path
	 * @param   mixed  $format Encode the file full|url|base64|raw|relative
	 *
	 * @return  string    Encoded file for export
	 */
	protected function encodeFile($file, $format = 'relative')
	{
		$path = JPATH_SITE . '/' . $file;

		if (!File::exists($path))
		{
			return $file;
		}

		switch ($format)
		{
			case 'full':
				return $path;
				break;
			case 'url':
				return COM_FABRIK_LIVESITE . str_replace('\\', '/',
$file);
				break;
			case 'base64':
				return base64_encode(file_get_contents($path));
				break;
			case 'raw':
				return file_get_contents($path);
				break;
			case 'relative':
				return $file;
				break;
		}
	}

	/**
	 * Element plugin specific method for setting unencrypted values back into
post data
	 *
	 * @param   array  &$post Data passed by ref
	 * @param   string $key   Key
	 * @param   string $data  Elements unencrypted data
	 *
	 * @return  void
	 */
	public function setValuesFromEncryt(&$post, $key, $data)
	{
		if ($this->isJoin())
		{
			$data = FabrikWorker::JSONtoData($data, true);
		}

		parent::setValuesFromEncryt($post, $key, $data);
	}

	/**
	 * Called by form model to build an array of values to encrypt
	 *
	 * @param   array &$values Previously encrypted values
	 * @param   array $data    Form data
	 * @param   int   $c       Repeat group counter
	 *
	 * @return  void
	 */
	public function getValuesToEncrypt(&$values, $data, $c)
	{
		$name = $this->getFullName(true, false);

		// Needs to be set to raw = false for file-upload
		$opts  = array('raw' => false);
		$group = $this->getGroup();

		if ($group->canRepeat())
		{
			if (!array_key_exists($name, $values))
			{
				$values[$name]['data'] = array();
			}

			$values[$name]['data'][$c] = $this->getValue($data, $c,
$opts);
		}
		else
		{
			$values[$name]['data'] = $this->getValue($data, $c, $opts);
		}
	}

	/**
	 * Examine the file being displayed and load in the corresponding
	 * class that deals with its display
	 *
	 * @param   string $file File
	 *
	 * @return  object  Element renderer
	 */
	protected function loadElement($file)
	{
		// $render loaded in required file.
		$render = null;
		$ext    = StringHelper::strtolower(File::getExt($file));

		if (File::exists(JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/custom/' . $ext .
'.php'))
		{
			require JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/custom/' . $ext .
'.php';
		}
		elseif (File::exists(JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/' . $ext .
'.php'))
		{
			require JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/' . $ext .
'.php';
		}
		else
		{
			// Default down to allvideos content plugin
			if (in_array($ext, array('flv', '3gp',
'divx')))
			{
				require JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/allvideos.php';
			}
			else
			{
				require JPATH_ROOT .
'/plugins/fabrik_element/fileupload/element/default.php';
			}
		}

		return $render;
	}

	/**
	 * Display the file in the list
	 *
	 * @param   string $data     Current cell data
	 * @param   array  &$thisRow Current row data
	 * @param   int    $i        Repeat group count
	 *
	 * @return    string
	 */
	protected function _renderListData($data, &$thisRow, $i = 0)
	{
		$this->_repeatGroupCounter = $i;
		$params                    = $this->getParams();

		// $$$ hugh - added 'skip_check' param, as the exists() check
in s3
		// storage adaptor can add a second or two per file, per row to table
render time.
		$skip_exists_check = (int)
$params->get('fileupload_skip_check', '0');

		if ($this->isAjax() && $params->get('ajax_max',
4) == 1)
		{
			// Not sure but after update from 2.1 to 3 for podion data was an object
			if (is_object($data))
			{
				$data = $data->file;
			}
			else
			{
				if ($data !== '')
				{
					$singleCropImg = json_decode($data);

					if (empty($singleCropImg))
					{
						$data = '';
					}
					else
					{
						$singleCropImg = $singleCropImg[0];
						$data          = $singleCropImg->file;
					}
				}
			}
		}

		$data = FabrikWorker::JSONtoData($data);

		if (is_array($data) && !empty($data))
		{
			// Crop stuff needs to be removed from data to get correct file path
			$data = $data[0];
		}

		$storage             = $this->getStorage();
		$use_download_script =
$params->get('fu_use_download_script', '0');
		$downloadHTML = '';

		if ($use_download_script == FU_DOWNLOAD_SCRIPT_TABLE ||
$use_download_script == FU_DOWNLOAD_SCRIPT_BOTH)
		{
			if (empty($data) || !$storage->exists(COM_FABRIK_BASE . $data))
			{
				$downloadHTML = '';
			}
			else
			{

				$canDownload = true;
				$aclEl       =
$this->getFormModel()->getElement($params->get('fu_download_acl',
''), true);

				if (!empty($aclEl))
				{
					$aclEl       = $aclEl->getFullName();
					$aclElRaw    = $aclEl . '_raw';
					$groups      = $this->user->getAuthorisedViewLevels();
					$canDownload = in_array($thisRow->$aclElRaw, $groups);
				}

				$formModel = $this->getFormModel();
				$formId    = $formModel->getId();
				$rowId     = $thisRow->__pk_val;
				$elementId = $this->getId();
				$title     = '';

				if ($params->get('fu_title_element') == '')
				{
					$title_name = $this->getFullName(true, false) .
'__title';
				}
				else
				{
					$title_name = str_replace('.', '___',
$params->get('fu_title_element'));
				}

				if (property_exists($thisRow, $title_name))
				{
					if (!empty($thisRow->$title_name))
					{
						$title = $thisRow->$title_name;
						$title = FabrikWorker::JSONtoData($title, true);
						//$title = $title[$i];
						$title = array_key_exists($i, $title) ? $title[$i] : '';
					}
				}

				$downloadImg = $params->get('fu_download_access_image');
				$noAccessImage =
$params->get('fu_download_noaccess_image');

				$layout                     =
$this->getLayout('downloadlink');
				$displayData                = new stdClass;
				$displayData->canDownload   = $canDownload;
				$displayData->openInBrowser =
$params->get('fu_open_in_browser', '0') ===
'1';
				$displayData->title         = $title;
				$displayData->file          = $data;
				$displayData->noAccessImage = ($noAccessImage &&
File::exists(JPATH_SITE .'/media/com_fabrik/images/' .
$noAccessImage)) ? COM_FABRIK_LIVESITE .
'media/com_fabrik/images/' . $noAccessImage : '';
				$displayData->noAccessURL   =
$params->get('fu_download_noaccess_url', '');
				$displayData->downloadImg   = ($downloadImg &&
File::exists(JPATH_SITE .'/media/com_fabrik/images/' .
$downloadImg)) ? COM_FABRIK_LIVESITE . 'media/com_fabrik/images/'
. $downloadImg : '';
				$displayData->href          = COM_FABRIK_LIVESITE
					. 'index.php?option=com_' . $this->package .
'&amp;task=plugin.pluginAjax&amp;plugin=fileupload&amp;method=ajax_download&amp;format=raw&amp;element_id='
					. $elementId . '&amp;formid=' . $formId .
'&amp;rowid=' . $rowId .
'&amp;repeatcount=0&ajaxIndex=' . $i;

				$downloadHTML = $layout->render($displayData);
			}

			if ($params->get('fu_download_append', '0') ===
'0')
			{
				return $downloadHTML;
			}
		}

		if ($params->get('fu_show_image_in_table') == '0')
		{
			$render = $this->loadElement('default');
		}
		else
		{
			$render = $this->loadElement($data);
		}

		if (empty($data) || (!$skip_exists_check &&
!$storage->exists($data)))
		{
			$render->output = '';
		}
		else
		{
			$render->renderListData($this, $params, $data, $thisRow);
		}

		if ($render->output == '' &&
$params->get('default_image') != '')
		{
			$defaultURL     = $storage->getFileUrl(str_replace(COM_FABRIK_BASE,
'', $params->get('default_image')));
			$render->output = '<img class="fabrikDefaultImage"
src="' . $defaultURL . '" alt="image"
/>';
		}
		else
		{
			/*
			 * If a static 'icon file' has been specified, we need to call
the main
			 * element model replaceWithIcons() to make it happen.
			 */
			if ($params->get('icon_file', '') !==
'')
			{
				$listModel      = $this->getListModel();
				$render->output = $this->replaceWithIcons($render->output,
'list', $listModel->getTmpl());
			}
		}

		if ($params->get('fu_download_append', '0') ===
'1')
		{
			return $render->output . '<div 
class="fabrik-fu-download-append">' . $downloadHTML .
'</div>';;
		}
		else
		{
			return $render->output;
		}
	}

	/**
	 * Do we need to include the light-box js code
	 *
	 * @return    bool
	 */
	public function requiresLightBox()
	{
		return true;
	}

	/**
	 * Do we need to include the slide-show js code
	 *
	 * @return    bool
	 */
	public function requiresSlideshow()
	{
		/*
		 * $$$ - testing slideshow, @TODO finish this!  Check for view type
		 */
		$params = $this->getParams();

		return $params->get('fu_show_image_in_table', '0')
=== '2' || $params->get('fu_show_image',
'0') === '3';
	}

	/**
	 * Manipulates posted form data for insertion into database
	 *
	 * @param   mixed $val  This elements posted form data
	 * @param   array $data Posted form data
	 *
	 * @return  mixed
	 */
	public function storeDatabaseFormat($val, $data)
	{
		// Val already contains group splitter from processUpload() code
		return $val;
	}

	/**
	 * Checks the posted form data against elements INTERNAL validation rule
	 * e.g. file upload size / type
	 *
	 * @param   array $data          Elements data
	 * @param   int   $repeatCounter Repeat group counter
	 *
	 * @return  bool    True if passes / false if fails validation
	 */
	public function validate($data = array(), $repeatCounter = 0)
	{
		$input                 = $this->app->input;
		$params                = $this->getParams();
		$this->validationError = '';
		$errors                = array();
		$ok                    = true;

		if ($this->isAjax())
		{
			// @TODO figure out validating size properly for multi-chunk AJAX
uploads
			$file = $input->files->get('file');

			if (!empty($file)) {
				$fileName = $_FILES['file']['name'];
				$fileSize = $_FILES['file']['size'];
			}
			else
			{
				// if no 'file', this is part of form submission, we've
already validated during AJAX upload
				return true;
			}
		}
		else
		{
			$name  = $this->getFullName(true, false);
			$files = $input->files->get($name, array(), 'raw');

			// this will happen if AJAX validating another element
			if (empty($files))
			{
				return true;
			}

			if (array_key_exists($repeatCounter, $files)) {
				$file = FArrayHelper::getValue($files, $repeatCounter);
			} else {
				// Single upload
				$file = $files;
			}

			$fileName = $file['name'];
			$fileSize = $file['size'];
		}

		if (empty($fileName))
		{
			return true;
		}

		if (!$this->_fileUploadFileTypeOK($fileName))
		{
			// zap the temp file, just to be safe (might be a malicious PHP file)
			File::delete($file['tmp_name']);
			$errors[] =
Text::_('PLG_ELEMENT_FILEUPLOAD_FILE_TYPE_NOT_ALLOWED');
			$ok       = false;
		}

		if (!$this->_fileUploadSizeOK($fileSize))
		{
			File::delete($file['tmp_name']);
			$ok       = false;
			$size     = $fileSize / 1024;
			$errors[] =
Text::sprintf('PLG_ELEMENT_FILEUPLOAD_FILE_TOO_LARGE',
$params->get('ul_max_file_size'), $size);
		}

		/**
		 * @FIXME - need to check for Amazon S3 storage?
		 */
		$filePath = $this->_getFilePath($repeatCounter);

		if ($this->getStorage()->exists($filePath))
		{
			if ($params->get('ul_file_increment', 0) == 0)
			{
				$errors[] =
Text::_('PLG_ELEMENT_FILEUPLOAD_EXISTING_FILE_NAME');
				$ok       = false;
			}
		}

		$this->validationError = implode('<br />', $errors);

		return $ok;
	}

	/**
	 * Get an array of allowed file extensions
	 *
	 * @param  stripDot  bool  strip the dot prefix
	 *
	 * @return array
	 */
	protected function _getAllowedExtension($stripDot = true)
	{
		$params       = $this->getParams();
		$allowedFiles = $params->get('ul_file_types');

		if ($allowedFiles != '')
		{
			// $$$ hugh - strip spaces and leading ., as folk often do ".bmp,
.jpg"
			// preg_replace('#(\s*|^)\.?#', '',
trim($allowedFiles));
			$allowedFiles = str_replace(' ', '', $allowedFiles);
			if ($stripDot)
			{
				$allowedFiles = str_replace('.', '',
$allowedFiles);
			}
			$aFileTypes   = explode(",", $allowedFiles);
		}
		else
		{
			$mediaParams = ComponentHelper::getParams('com_media');
			$aFileTypes  = explode(',',
$mediaParams->get('restrict_uploads_extensions'));
		}

		if (!$stripDot)
		{
			foreach ($aFileTypes as &$type)
			{
				$type = '.' . ltrim($type, '.');
			}
		}

		return $aFileTypes;
	}

	/**
	 * This checks the uploaded file type against the csv specified in the
upload
	 * element
	 *
	 * @param   string $myFileName Filename
	 *
	 * @return    bool    True if upload file type ok
	 */
	protected function _fileUploadFileTypeOK($myFileName)
	{
		$aFileTypes = $this->_getAllowedExtension();

		if ($myFileName == '')
		{
			return true;
		}

		$curr_f_ext = StringHelper::strtolower(File::getExt($myFileName));
		array_walk($aFileTypes, function(&$v) {
			$v = StringHelper::strtolower($v);
		});

		return in_array($curr_f_ext, $aFileTypes);
	}

	/**
	 * This checks that the file-upload size is not greater than that
specified in
	 * the upload element
	 *
	 * @param   string $myFileSize File size
	 *
	 * @return    bool    True if upload file type ok
	 */
	protected function _fileUploadSizeOK($myFileSize)
	{
		$params   = $this->getParams();
		$max_size = $params->get('ul_max_file_size') * 1024;

		if ($myFileSize <= $max_size)
		{
			return true;
		}

		return false;
	}

	/**
	 * if we are using plupload but not with crop
	 *
	 * @param   string $name Element
	 *
	 * @return    bool    If processed or not
	 */
	protected function processAjaxUploads($name)
	{
		$input  = $this->app->input;
		$params = $this->getParams();
		$groupModel = $this->getGroup();
		$formModel  = $this->getFormModel();

		if ($params->get('upload_use_wip', '0') ===
'1' && $this->isAjax())
		{
			$key       = 'fabrik.form.fileupload.files.' .
$this->getId();
			$ajaxFiles = $this->session->get($key, []);

			if (!empty($ajaxFiles))
			{
				$formModel  = $this->getFormModel();
				$myFileDirs = $input->get($name, array(), 'array');
				$this->session->clear($key);
				$_FILES[$name] = $ajaxFiles;
				$fileData      = $_FILES[$name]['name'];

				foreach ($fileData as $i => $f)
				{
					$myFileDir = FArrayHelper::getValue($myFileDirs, $i, '');
					$file      = array('name'     =>
$_FILES[$name]['name'][$i],
					                   'type'     =>
$_FILES[$name]['type'][$i],
					                   'tmp_name' =>
$_FILES[$name]['tmp_name'][$i],
					                   'error'    =>
$_FILES[$name]['error'][$i],
					                   'size'     =>
$_FILES[$name]['size'][$i]);

					if ($file['name'] != '')
					{
						$filePath = $this->_processIndUpload($file, $myFileDir, $i);
						$filePath = str_replace('\\', '/', $filePath);

						if ($groupModel->canRepeat())
						{
							foreach ($formModel->formData[$name] as $repeatCounter =>
$files)
							{
								foreach (['id', 'cropdata', 'crop']
as $key)
								{
									foreach ($files[$key] as $k => $v)
									{
										if ($k === $_FILES[$name]['tmp_name'][$i])
										{
											$formModel->formData[$name][$repeatCounter][$key][$filePath]
=
												$formModel->formData[$name][$repeatCounter][$key][$k];
											$_POST[$name][$repeatCounter][$key][$filePath] =
												$formModel->formData[$name][$repeatCounter][$key][$k];
											unset($formModel->formData[$name][$repeatCounter][$key][$k]);
											unset($_POST[$name][$repeatCounter][$key][$k]);
										}
									}
								}
							}
						}
						else
						{
							/*
							foreach ($formModel->formData[$name] as $k => $v)
							{
								if ($v === $_FILES[$name]['tmp_name'][$i])
								{
									$formModel->formData[$name][$k]          = $filePath;
									$formModel->formData[$name . '_raw'][$k] = $filePath;
								}
							}
							*/

							foreach (['id', 'cropdata', 'crop'] as
$key)
							{
								foreach ($formModel->formData[$name][$key] as $k => $v)
								{
									if ($k === $_FILES[$name]['tmp_name'][$i])
									{
										$formModel->formData[$name][$key][$filePath] =
											$formModel->formData[$name][$key][$k];
										$_POST[$name][$key][$filePath] =
											$formModel->formData[$name][$key][$k];
										unset($formModel->formData[$name][$key][$k]);
										unset($_POST[$name][$key][$k]);
									}
								}
							}

						}
					}
				}
			}
		}

		if ($this->canCrop() == false &&
$input->get('task') !== 'pluginAjax' &&
$this->isAjax())
		{
			$filter = InputFilter::getInstance();
			$post   = $filter->clean($_POST, 'array');

			if ($groupModel->canRepeat())
			{
				$names = array();
				$joinsIds = array();
				$joinsParams = array();

				foreach ($post[$name] as $repeatCount => $raw)
				{
					if (empty($raw))
					{
						continue;
					}

					// $$$ hugh - for some reason, we're now getting $raw[] with a
single, uninitialized entry back
					// from getvalue() when no files are uploaded
					if (count($raw) == 1 && array_key_exists(0, $raw) &&
empty($raw[0]))
					{
						return true;
					}

					$crop    = (array) FArrayHelper::getValue($raw, 'crop');
					$id_keys = (array) FArrayHelper::getValue($raw, 'id');
					$ids     = array_values($id_keys);

					$saveParams = array();
					if (!empty($crop))
					{
						$files = array_keys($crop);
					}
					else
					{
						$files = array_keys($id_keys);
					}

					if ($this->isJoin())
					{
						$j          = $this->getJoinModel()->getJoin()->table_join;
						$joinsId    = $j . '___id';
						$joinsParam = $j . '___params';

						$name = $this->getFullName(true, false);

						/*
						$formModel->updateFormData($name, $files, true);
						$formModel->updateFormData($joinsId, $ids, true);
						$formModel->updateFormData($joinsParam, $saveParams, true);
						*/
						$names[$repeatCount] = $files;
						$joinsIds[$repeatCount] = $ids;
						$joinsParams[$repeatCount] = $saveParams;
					}
					else
					{
						// Only one file
						$store = array();

						for ($i = 0; $i < count($files); $i++)
						{
							$o         = new stdClass;
							$o->file   = $files[$i];
							$o->params = $crop[$files[$i]];
							$store[]   = $o;
						}

						$store = json_encode($store);
						/*
						$formModel->updateFormData($name . '_raw', $store);
						$formModel->updateFormData($name, $store);
						*/
						$names[$repeatCount] = $store;
					}
				}

				if ($this->isJoin())
				{
					$formModel->updateFormData($name, $names, true);
					$formModel->updateFormData($joinsId, $joinsIds, true);
					$formModel->updateFormData($joinsParam, $joinsParams, true);
				}
				else
				{
					$formModel->updateFormData($name, $names, true);
				}
			}
			else
			{
				$raw = $this->getValue($post);

				if ($raw == '')
				{
					return true;
				}

				if (empty($raw))
				{
					return true;
				}
				// $$$ hugh - for some reason, we're now getting $raw[] with a
single, uninitialized entry back
				// from getvalue() when no files are uploaded
				if (count($raw) == 1 && array_key_exists(0, $raw) &&
empty($raw[0]))
				{
					return true;
				}

				$crop    = (array) FArrayHelper::getValue($raw, 'crop');
				$id_keys = (array) FArrayHelper::getValue($raw, 'id');
				$ids     = array_values($id_keys);

				$saveParams = array();
				if (!empty($crop))
				{
					$files = array_keys($crop);
				}
				else
				{
					$files = array_keys($id_keys);
				}


				$isJoin     = ($groupModel->isJoin() || $this->isJoin());

				if ($isJoin)
				{
					if (!$groupModel->canRepeat() && !$this->isJoin())
					{
						$files = $files[0];
					}

					$j          = $this->getJoinModel()->getJoin()->table_join;
					$joinsId    = $j . '___id';
					$joinsParam = $j . '___params';

					$name = $this->getFullName(true, false);

					$formModel->updateFormData($name, $files, true);
					$formModel->updateFormData($joinsId, $ids, true);
					$formModel->updateFormData($joinsParam, $saveParams, true);
				}
				else
				{
					// Only one file
					$store = array();

					for ($i = 0; $i < count($files); $i++)
					{
						$o         = new stdClass;
						$o->file   = $files[$i];
						$o->params = $crop[$files[$i]];
						$store[]   = $o;
					}

					$store = count($files) > 0 ? json_encode($store) : '';
					$formModel->updateFormData($name . '_raw', $store);
					$formModel->updateFormData($name, $store);
				}
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * If an image has been uploaded with ajax upload then we may need to crop
it
	 * Since 3.0.7 crop data is posted as base64 encoded info from the actual
canvas element - much simpler and more
	 * accurate cropping
	 *
	 * @param   string $name Element
	 *
	 * @return    bool    If processed or not
	 */
	protected function crop($name)
	{
		$input  = $this->app->input;
		$params = $this->getParams();

		if ($this->canCrop() == true &&
$input->get('task') !== 'pluginAjax')
		{
			$filter = InputFilter::getInstance();
			$post   = $filter->clean($_POST, 'array');
			$raw    = FArrayHelper::getValue($post, $name . '_raw',
array());

			if (!$this->canUse())
			{
				// Ensure readonly elements not overwritten
				return true;
			}

			if ($this->getValue($post) != 'Array,Array')
			{
				$raw = $this->getValue($post);

				// $$$ rob 26/07/2012 inline edit producing a string value for $raw on
save
				if ($raw == '' || empty($raw) || is_string($raw))
				{
					return true;
				}

				if (array_key_exists(0, $raw))
				{
					$crop     = (array) FArrayHelper::getValue($raw[0], 'crop');
					$ids      = (array) FArrayHelper::getValue($raw[0], 'id');
					$cropData = (array) FArrayHelper::getValue($raw[0],
'cropdata');
				}
				else
				{
					// Single uploaded image.
					$crop     = (array) FArrayHelper::getValue($raw, 'crop');
					$ids      = (array) FArrayHelper::getValue($raw, 'id');
					$cropData = (array) FArrayHelper::getValue($raw,
'cropdata');
				}
			}
			else
			{
				// Single image
				$crop     = (array) FArrayHelper::getValue($raw, 'crop');
				$ids      = (array) FArrayHelper::getValue($raw, 'id');
				$cropData = (array) FArrayHelper::getValue($raw, 'cropdata');
			}

			if ($raw == '')
			{
				return true;
			}

			$ids        = array_values($ids);
			$saveParams = array();
			$files      = array_keys($crop);
			$storage    = $this->getStorage();
			$oImage     =
Image::loadLib($params->get('image_library'));
			$oImage->setStorage($storage);
			$fileCounter = 0;

			foreach ($crop as $filePath => $json)
			{
				$imgData = $cropData[$filePath];
				$imgData = substr($imgData, strpos($imgData, ',') + 1);

				// Need to decode before saving since the data we received is already
base64 encoded
				$imgData      = base64_decode($imgData);
				$saveParams[] = $json;

				$destCropFile = $storage->_getCropped($filePath);
				$destCropFile = $storage->getFullPath($destCropFile);
				$w         = new FabrikWorker;
				$destCropFile  = $w->parseMessageForPlaceHolder($destCropFile);
				$cropPath = dirname($destCropFile);

				if ($cropPath != '')
				{
					if (!$storage->folderExists($cropPath))
					{
						if (!$storage->createFolder($cropPath))
						{
							$this->setError(21, "Could not make dir $cropPath ");
							continue;
						}
					}
				}


				/*
				$filePath     = $storage->clean(JPATH_SITE . '/' .
$filePath);
				$fileURL      = $storage->getFileUrl(str_replace(COM_FABRIK_BASE,
'', $filePath));
				$destCropFile = $storage->_getCropped($fileURL);
				$destCropFile = $storage->urlToPath($destCropFile);
				$destCropFile = $storage->clean($destCropFile);

				if (!File::exists($filePath))
				{
					unset($files[$fileCounter]);
					$fileCounter++;
					continue;
				}
				*/

				$fileCounter++;

				if ($imgData != '')
				{
					if (!$storage->write($destCropFile, $imgData))
					{
						throw new RuntimeException('Couldn\'t write image, ' .
$destCropFile, 500);
					}
				}

				$storage->setPermissions($destCropFile);
			}

			$groupModel = $this->getGroup();
			$isJoin     = ($groupModel->isJoin() || $this->isJoin());
			$formModel  = $this->getFormModel();

			if ($isJoin)
			{
				if (!$groupModel->canRepeat() && !$this->isJoin())
				{
					$files = $files[0];
				}

				$name = $this->getFullName(true, false);

				if ($groupModel->isJoin())
				{
					$j = $this->getJoinModel()->getJoin()->table_join;
				}
				else
				{
					$j = $name;
				}

				$joinsId    = $j . '___id';
				$joinsParam = $j . '___params';

				$formModel->updateFormData($name, $files);
				$formModel->updateFormData($name . '_raw', $files);

				$formModel->updateFormData($joinsId, $ids);
				$formModel->updateFormData($joinsId . '_raw', $ids);

				$formModel->updateFormData($joinsParam, $saveParams);
				$formModel->updateFormData($joinsParam . '_raw',
$saveParams);
			}
			else
			{
				// Only one file
				$store = array();

				for ($i = 0; $i < count($files); $i++)
				{
					$o         = new stdClass;
					$o->file   = $files[$i];
					$o->params = $saveParams[$i];
					$store[]   = $o;
				}

				$store = json_encode($store);
				$formModel->updateFormData($name . '_raw', $store);
				$formModel->updateFormData($name, $store);
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Should we process as a standard file upload
	 * Returns false if we cant use the element, or if its an ajax upload
element
	 *
	 * @return bool
	 * @throws Exception
	 */
	protected function shouldDoNonAjaxUpload()
	{
		$input  = $this->app->input;
		$name   = $this->getFullName(true, false);
		$params = $this->getParams();

		if (!$this->canUse())
		{
			// If the user can't use the plugin no point processing an
non-existant upload
			return false;
		}

		if ($this->processAjaxUploads($name))
		{
			// Stops form data being updated with blank data.
			return false;
		}

		/*
		 * remove this if Safari and Edge ever get their FormData act together
		 */
		if ($input->getInt('fabrik_ajax') == 1)
		{
			// Inline edit for example no $_FILE data sent
			return false;
		}


		/* If we've turned on crop but not set ajax upload then the cropping
wont work so we shouldn't return
		 * otherwise no standard image processed
		 */
		if ($this->crop($name) && $this->isAjax())
		{
			// Stops form data being updated with blank data.
			return false;
		}

		/**
		 * If WiP and AJAX, then process
		 */
		if ($params->get('upload_use_wip', '0') ===
'1' && $this->isAjax())
		{
			return false;
		}

		return true;
	}

	/**
	 * OPTIONAL
	 *
	 * @return  void
	 */
	public function processUpload()
	{
		$params     = $this->getParams();
		$input      = $this->app->input;
		$formModel  = $this->getFormModel();
		$name       = $this->getFullName(true, false);
		$myFileDirs = $input->get($name, array(), 'array');

		if (!$this->shouldDoNonAjaxUpload())
		{
			return;
		}

		$files = array();

		// note that this only handles files explicitly deleted with the Delete
button, not repeat groups being deleted
		$deletedImages = $this->filesToDelete();

		// filesToKeep will return array indexed to match the $_FILES data, with
holes where repeat groups have been deleted
		$filesToKeep = $this->filesToKeep($deletedImages);

		$fileData = $_FILES[$name]['name'];

		if (is_array($fileData))
		{
			foreach ($fileData as $i => $f)
			{
				$myFileDir = FArrayHelper::getValue($myFileDirs, $i, '');
				$file      = array('name' =>
$_FILES[$name]['name'][$i],
				                   'type' =>
$_FILES[$name]['type'][$i],
				                   'tmp_name' =>
$_FILES[$name]['tmp_name'][$i],
				                   'error' =>
$_FILES[$name]['error'][$i],
				                   'size' =>
$_FILES[$name]['size'][$i]);

				if ($file['name'] != '')
				{
					$files[$i] = $this->_processIndUpload($file, $myFileDir, $i);
				}
				else
				{
					if (array_key_exists($i, $filesToKeep))
					{
						$files[$i] = $filesToKeep[$i];
					}
				}
			}

			foreach ($filesToKeep as $k => $v)
			{
				if (!array_key_exists($k, $files))
				{
					$files[$k] = $v;
				}
			}

			foreach ($files as &$f)
			{
				$f = str_replace('\\', '/', $f);
			}

			// re-key the array to get rid of any gaps left by deleted groups
			$files = array_values($files);
		}
		else
		{
			$myFileDir = FArrayHelper::getValue($myFileDirs, 0, '');
			$file      = array('name' =>
$_FILES[$name]['name'],
			                   'type' =>
$_FILES[$name]['type'],
			                   'tmp_name' =>
$_FILES[$name]['tmp_name'],
			                   'error' =>
$_FILES[$name]['error'],
			                   'size' =>
$_FILES[$name]['size']);

			if ($file['name'] != '')
			{
				$files = $this->_processIndUpload($file, $myFileDir);
			}
			else
			{
				// No new file uploaded - keep the original one.
				$files = FArrayHelper::getValue($filesToKeep, 0, '');
			}

			// We are in a single upload element - should not re-add in previous
images. So don't use filesToKeep
			// see
http://fabrikar.com/forums/index.php?threads/fileupload-in-repeated-group.41100/page-2

			$files = str_replace('\\', '/', $files);
		}

		$this->deleteFiles($deletedImages);
		$formModel->updateFormData($name . '_raw', $files);
		$formModel->updateFormData($name, $files);
	}

	/**
	 * Delete all files
	 *
	 * @param   array $files Files to delete
	 *
	 * @return  void
	 */
	protected function deleteFiles($files)
	{
		$params = $this->getParams();

		if ($params->get('upload_delete_image', false))
		{
			foreach ($files as $file)
			{
				$this->deleteFile($file);
			}
		}
	}

	/**
	 * Collect an initial list of files to delete, these have only been set in
the form when the ajax fileupload
	 * is being used
	 *
	 * @return mixed
	 * @throws Exception
	 */
	protected function filesToDelete()
	{
		$input        = $this->app->input;
		$groupModel   = $this->getGroup();
		$deletedFiles = $input->get('fabrik_fileupload_deletedfile',
array(), 'array');
		$gid          = $groupModel->getId();

		return FArrayHelper::getValue($deletedFiles, $gid, array());
	}

	/**
	 * Make an array of images to keep during the upload process
	 *
	 * @param array $deletedImages
	 *
	 * @return array Image file paths
	 */
	protected function filesToKeep(array $deletedImages)
	{
		$origData        = $this->getFormModel()->getOrigData();
		$name            = $this->getFullName(true, false);
		$filesToKeep     = array();
		$deletedGroupIds = array();
		$groupModel      = $this->getGroupModel();
		$pkName          = '';

		/**
		 * We have to deal with origData not being "merged", where
we'll have multiple rows for repeated data.
		 * So we need to figure out the PK name for the group, and only process
the data once for that PK.
		 */

		if ($groupModel->isJoin())
		{
			$formModel = $this->getFormModel();
			$groupJoin = $groupModel->getJoinModel();
			$pkName    = $groupJoin->getForeignID('___') .
'_raw';

			$origGroupRowsIds = $groupModel->getOrigGroupRowsIds();
			$formGroupIds     = FArrayHelper::getValue($formModel->formData,
$pkName, array(), 'array');

			foreach ($origGroupRowsIds as $origId)
			{
				if (!in_array($origId, $formGroupIds))
				{
					$deletedGroupIds[] = $origId;
				}
			}
		}
		else
		{
			$table  = $this->getListModel()->getTable();
			$pkName = FabrikString::safeColNameToArrayKey($table->db_primary_key)
. '_raw';
		}

		$pksSeen = array();
		$index   = 0;

		for ($j = 0; $j < count($origData); $j++)
		{
			$pkVal = $origData[$j]->$pkName;

			// if we've already seen it, just unset it if it's being
deleted
			if (in_array($pkVal, $pksSeen))
			{
				if (isset($origData[$j]->$name))
				{
					$val = $origData[$j]->$name;

					if (!empty($val) && in_array($val, $deletedImages))
					{
						unset($origData[$j]->$name);
					}
				}

				continue;
			}

			// if we haven't seen it ...
			$pksSeen[] = $pkVal;

			// if it's in a group which is being deleted, we need to increment
the filesToKeep index, which has to jive with the $_FILES indexing
			if (in_array($pkVal, $deletedGroupIds))
			{
				//$index++;
				continue;
			}

			if (isset($origData[$j]->$name))
			{
				$val = $origData[$j]->$name;

				//
http://fabrikar.com/forums/index.php?threads/fileupload-file-save-in-the-bad-record.44751/#post-230064
				//if (!empty($val))
				//{
				if (in_array($val, $deletedImages))
				{
					unset($origData[$j]->$name);
				}
				else
				{
					$filesToKeep[$index] = $origData[$j]->$name;
				}
				//}
			}

			$index++;
		}

		return $filesToKeep;
	}

	/**
	 * Delete the file
	 *
	 * @param   string $filename Path to file (not including JPATH)
	 *
	 * @return  void
	 */
	protected function deleteFile($filename)
	{
		if (empty($filename))
		{
			return;
		}

		$params = $this->getParams();
		$default = $params->get('default_image', '');
		$storage = $this->getStorage();
		$default = $storage->clean($default);
		$file    = $storage->clean($filename);

		if ($default === $file)
		{
			return;
		}

		$thumb   = $storage->clean($storage->_getThumb($filename));
		$cropped = $storage->clean($storage->_getCropped($filename));

		$logMsg = 'Delete files: ' . $file . ' , ' . $thumb .
', ' . $cropped . '; user = ' .
$this->user->get('id');
		Log::add($logMsg, Log::WARNING,
'com_fabrik.element.fileupload');

		if ($storage->exists($file))
		{
			$storage->delete($file);
		}

		if (!empty($thumb))
		{
			if ($storage->exists($thumb))
			{
				$storage->delete($thumb);
			}
			else
			{
				if ($storage->exists(JPATH_SITE . '/' .
FabrikString::ltrim($thumb, '/')))
				{
					$storage->delete(JPATH_SITE . '/' .
FabrikString::ltrim($thumb, '/'));
				}
			}
		}

		if (!empty($cropped))
		{
			if ($storage->exists($cropped))
			{
				$storage->delete($cropped);
			}
			else
			{
				if ($storage->exists(JPATH_SITE . '/' .
FabrikString::ltrim($cropped, '/')))
				{
					$storage->delete(JPATH_SITE . '/' .
FabrikString::ltrim($cropped, '/'));
				}
			}
		}
	}

	/**
	 * Does the element consider the data to be empty
	 * Used in is-empty validation rule
	 *
	 * @param   array $data          Data to test against
	 * @param   int   $repeatCounter Repeat group #
	 *
	 * @return  bool
	 */
	public function dataConsideredEmpty($data, $repeatCounter)
	{

		$params = $this->getParams();
		$input  = $this->app->input;

		if ($input->get('rowid', '') !== '')
		{
			if ($input->get('task') == '' ||
$input->get('task') == 'view')
			{
				return parent::dataConsideredEmpty($data, $repeatCounter);
			}

			$oldDaata =
FArrayHelper::getValue($this->getFormModel()->_origData,
$repeatCounter);

			if (!is_null($oldDaata))
			{
				$name     = $this->getFullName(true, false);
				$aOldData = ArrayHelper::fromObject($oldDaata);
				$r        = FArrayHelper::getValue($aOldData, $name, '') ===
'' ? true : false;

				if (!$r)
				{
					/* If an original value is found then data not empty - if not found
continue to check the $_FILES array to see if one
					 * has been uploaded
					 */
					return false;
				}
			}
		}
		else
		{
			if ($input->get('task') == '')
			{
				return parent::dataConsideredEmpty($data, $repeatCounter);
			}
		}

		$groupModel = $this->getGroup();

		if ($groupModel->isJoin())
		{
			$name  = $this->getFullName(true, false);
			$files = $input->files->get($name, array(), 'raw');

			if ($groupModel->canRepeat())
			{
				$file = empty($files) ? '' :
$files[$repeatCounter]['name'];
			}
			else
			{
				$file = ArrayHelper::getValue($files, 'name');
			}

			return $file == '' ? true : false;
		}
		else
		{
			$name = $this->getFullName(true, false);

			if ($this->isJoin())
			{
				$d = (array) $input->get($name, array(), 'array');

				return !array_key_exists('id', $d);

			}
			else
			{
				// Single ajax upload
				if ($this->isAjax())
				{
					$d = (array) $input->get($name, array(), 'array');

					if (array_key_exists('id', $d))
					{
						return false;
					}
				}
				else
				{
					$files = $input->files->get($name, array(), 'raw');
					$file  = ArrayHelper::getValue($files, 'name',
'');

					return $file == '' ? true : false;
				}
			}
		}

		if (empty($file))
		{
			$file = $input->get($name);

			// Ajax test - nothing in files
			return $file == '' ? true : false;
		}

		// No files selected?
		return $file['name'] == '' ? true : false;
	}

	/**
	 * Process the upload (can be called via ajax from pluploader)
	 *
	 * @param   array  &$file              File info
	 * @param   string $myFileDir          User selected upload folder
	 * @param   int    $repeatGroupCounter Repeat group counter
	 *
	 * @return    string    Location of uploaded file
	 */
	protected function _processIndUpload(&$file, $myFileDir =
'', $repeatGroupCounter = 0)
	{
		$params  = $this->getParams();
		$storage = $this->getStorage();
		$quality = (int) $params->get('image_quality', 100);

		// $$$ hugh - check if we need to blow away the cached file-path, set in
validation
		$myFileName = $storage->cleanName($file['name'],
$repeatGroupCounter);

		if ($myFileName != $file['name'])
		{
			$file['name'] = $myFileName;
			unset($this->_filePaths[$repeatGroupCounter]);
		}

		$tmpFile  = $file['tmp_name'];
		$uploader = $this->getFormModel()->getUploader();

		if ($params->get('ul_file_types') == '')
		{
			$params->set('ul_file_types', implode(',',
$this->_getAllowedExtension()));
		}

		$err = null;

		// Set FTP credentials, if given
		jimport('joomla.client.helper');
		ClientHelper::setCredentialsFromRequest('ftp');

		if ($myFileName == '')
		{
			return;
		}

		$filePath = $this->_getFilePath($repeatGroupCounter);

		if (!Uploader::canUpload($file, $err, $params))
		{
			$this->setError($file['name'] . ': ' .
Text::_($err));
		}

		if ($storage->exists($filePath))
		{
			switch ($params->get('ul_file_increment', 0))
			{
				case 0:
					break;
				case 1:
					$filePath = Uploader::incrementFileName($filePath, $filePath, 1,
$storage);
					break;
				case 2:
					Log::add('Ind upload Delete file: ' . $filePath . ';
user = ' . $this->user->get('id'), Log::WARNING,
'com_fabrik.element.fileupload');
					$storage->delete($filePath);
					break;
			}
		}

		if (!$storage->upload($tmpFile, $filePath))
		{
			$uploader->moveError = true;

			return;
		}

		$filePath = $storage->getUploadedFilePath();
		jimport('joomla.filesystem.path');
		$storage->setPermissions($filePath);

		if (FabrikWorker::isImageExtension($filePath))
		{
			$oImage = Image::loadLib($params->get('image_library'));
			$oImage->setStorage($storage);

			if ($params->get('upload_use_wip', '0') ==
'1')
			{
				if ($params->get('fileupload_storage_type',
'filesystemstorage') == 'filesystemstorage')
				{
					$mapElementId = $params->get('fu_map_element');

					if (!empty($mapElementId))
					{
						$coordinates = $oImage->getExifCoordinates($filePath);

						if (!empty($coordinates))
						{
							$formModel       = $this->getFormModel();
							$mapElementModel = $formModel->getElement($mapElementId, true);
							$mapParams       = $mapElementModel->getParams();
							$zoom            = $mapParams->get('fb_gm_zoomlevel',
'10');
							$coordinates_str = '(' . $coordinates[0] . ',' .
$coordinates[1] . '):' . $zoom;
							$mapElementName  = $mapElementModel->getFullName(true, false);
							$formModel->updateFormData($mapElementName, $coordinates_str,
true);
						}
					}

					$oImage->rotateImageFromExif($filePath, '');
				}
			}

			// Resize main image
			$mainWidth  = $params->get('fu_main_max_width',
'');
			$mainHeight = $params->get('fu_main_max_height',
'');

			if ($mainWidth != '' || $mainHeight != '')
			{
				// $$$ rob ensure that both values are integers otherwise resize fails
				if ($mainHeight == '')
				{
					$mainHeight = (int) $mainWidth;
				}

				if ($mainWidth == '')
				{
					$mainWidth = (int) $mainHeight;
				}

				$oImage->resize($mainWidth, $mainHeight, $filePath, $filePath,
$quality);
			}
		}

		// $$$ hugh - if it's a PDF, make sure option is set to attempt PDF
thumb
		$make_thumbnail = $params->get('make_thumbnail') ==
'1' ? true : false;

		if (File::getExt($filePath) == 'pdf' &&
$params->get('fu_make_pdf_thumb', '0') ==
'0')
		{
			$make_thumbnail = false;
		}

		// $$$ trob - oImage->rezise is only set if isImageExtension
		if (!FabrikWorker::isImageExtension($filePath))
		{
			$make_thumbnail = false;
		}

		if ($make_thumbnail)
		{
			$thumbPath = $storage->clean(JPATH_SITE . '/' .
$params->get('thumb_dir') . '/' . $myFileDir .
'/', false);
			$w         = new FabrikWorker;
			$formModel = $this->getFormModel();
			$thumbPath = $w->parseMessageForRepeats($thumbPath,
$formModel->formData, $this, $repeatGroupCounter);
			$thumbPath = $w->parseMessageForPlaceHolder($thumbPath);
			$maxWidth  = $params->get('thumb_max_width', 125);
			$maxHeight = $params->get('thumb_max_height', 125);

			if ($thumbPath != '')
			{
				if (!$storage->folderExists($thumbPath))
				{
					if (!$storage->createFolder($thumbPath))
					{
						throw new RuntimeException("Could not make dir
$thumbPath");
					}
				}
			}

			$fileURL       = $storage->getFileUrl(str_replace(COM_FABRIK_BASE,
'', $filePath));
			$destThumbFile = $storage->_getThumb($fileURL);
			$destThumbFile = $storage->urlToPath($destThumbFile);
			$oImage->resize($maxWidth, $maxHeight, $filePath, $destThumbFile,
$quality);
			$storage->setPermissions($destThumbFile);
		}

		$storage->setPermissions($filePath);
		$storage->finalFilePathParse($filePath);

		return $filePath;
	}

	/**
	 * Get the file storage object amazon s3/filesystem
	 *
	 * @return object
	 */
	public function getStorage($storageType = null)
	{
		if (!isset($this->storage))
		{
			/**
			 * Allow overriding the configured storage type, used by AJAX uploading
for temp storage
			 */
			if (!isset($storageType))
			{
				$params      = $this->getParams();
				$storageType =
InputFilter::getInstance()->clean($params->get('fileupload_storage_type',
'filesystemstorage'), 'CMD');
			}
			require_once JPATH_ROOT .
'/plugins/fabrik_element/fileupload/adaptors/' . $storageType .
'.php';
			$storageClass  = StringHelper::ucfirst($storageType);
			$this->storage = new $storageClass($params);
		}

		return $this->storage;
	}

	/**
	 * Get the full server file path for the upload, including the file name
	 *
	 * @param   int $repeatCounter Repeat group counter
	 * @param   bool  $runRename  run the rename code
	 *
	 * @return    string    Path
	 */
	protected function _getFilePath($repeatCounter = 0, $runRename = true)
	{
		$params = $this->getParams();

		if (!isset($this->_filePaths))
		{
			$this->_filePaths = array();
		}

		if (array_key_exists($repeatCounter, $this->_filePaths))
		{
			/*
			 * $$$ hugh - if it uses element placeholders, there's a likelihood
the element
			 * data may have changed since we cached the path during validation, so
we need
			 * to rebuild it.  For instance, if the element data is changed by a
onBeforeProcess
			 * submission plugin, or by a 'replace' validation.
			 */
			$rename = $params->get('fu_rename_file_code',
'');
			if (empty($rename) &&
!FabrikString::usesElementPlaceholders($params->get('ul_directory')))
			{
				return $this->_filePaths[$repeatCounter];
			}
		}

		$filter    = InputFilter::getInstance();
		$aData     = $filter->clean($_POST, 'array');
		$elName    = $this->getFullName(true, false);
		$elNameRaw = $elName . '_raw';
		$myFileName = '';

		if (array_key_exists($elName, $_FILES) &&
is_array($_FILES[$elName]))
		{
			$myFileName = FArrayHelper::getValue($_FILES[$elName], 'name',
'');
		}
		else
		{
			if (array_key_exists('file', $_FILES) &&
is_array($_FILES['file']))
			{
				$myFileName = FArrayHelper::getValue($_FILES['file'],
'name', '');
			}
		}

		if (is_array($myFileName))
		{
			$myFileName = FArrayHelper::getValue($myFileName, $repeatCounter,
'');
		}

		$myFileDir = '';

		if (array_key_exists($elNameRaw, $aData))
		{
			if (is_array($aData[$elNameRaw]))
			{
				$myFileDir = ArrayHelper::getValue($aData[$elNameRaw],
'ul_end_dir', '');
			}
		}
		else
		{
			if (array_key_exists($elName, $aData) &&
is_array($aData[$elName]))
			{
				$myFileDir = ArrayHelper::getValue($aData[$elName],
'ul_end_dir', '');
			}
		}

		if (is_array($myFileDir))
		{
			$myFileDir = FArrayHelper::getValue($myFileDir, $repeatCounter,
'');
		}

		$storage = $this->getStorage();

		// $$$ hugh - check if we need to blow away the cached filepath, set in
validation
		$myFileName = $storage->cleanName($myFileName, $repeatCounter);

		if ($runRename)
		{
			$myFileName = $this->renameFile($myFileName, $repeatCounter);
		}

		$folder = $params->get('ul_directory');
		$folder = $folder . '/' . $myFileDir;

		if ($storage->appendServerPath())
		{
			$folder = JPATH_SITE . '/' . $folder;
		}

		$folder = Path::clean($folder);
		$w      = new FabrikWorker;

		$formModel = $this->getFormModel();
		$folder    = $w->parseMessageForRepeats($folder,
$formModel->formData, $this, $repeatCounter);
		$folder    = $w->parseMessageForPlaceHolder($folder);

		// checkPath will throw an exception if folder is naughty
		$storage->checkPath($folder);
		$storage->makeRecursiveFolders($folder);
		$p                                = $folder . '/' .
$myFileName;
		$this->_filePaths[$repeatCounter] = $storage->clean($p);

		return $this->_filePaths[$repeatCounter];
	}

	/**
	 * Draws the html form element
	 *
	 * @param   array $data          To pre-populate element with
	 * @param   int   $repeatCounter Repeat group counter
	 *
	 * @return  string    Elements html
	 */
	public function render($data, $repeatCounter = 0)
	{
		$this->_repeatGroupCounter = $repeatCounter;
		$id                        = $this->getHTMLId($repeatCounter);
		$name                      = $this->getHTMLName($repeatCounter);
		$groupModel                = $this->getGroup();
		$element                   = $this->getElement();
		$params                    = $this->getParams();
		$isAjax                    = $this->isAjax();

		$use_wip        = $params->get('upload_use_wip',
'0') == '1';
		$device_capture = $params->get('ul_device_capture',
'0');

		if ($element->hidden == '1')
		{
			return $this->getHiddenField($name, $this->getValue($data,
$repeatCounter), $id);
		}

		$str   = array();
		$value = $this->getValue($data, $repeatCounter);
		$value = is_array($value) ? $value : FabrikWorker::JSONtoData($value,
true);
		$value = $this->checkForSingleCropValue($value);

		if ($isAjax)
		{
			if ($params->get('upload_use_wip', '0') ==
'1')
			{
				$key = 'fabrik.form.fileupload.files.' . $this->getId();
				$this->session->clear($key);
			}

			if (is_array($value) && array_key_exists('file',
$value))
			{
				$value = $value['file'];
			}
			else if (is_object($value) && isset($value->file))
			{
				$value = $value->file;
			}
		}

		$storage             = $this->getStorage();
		$use_download_script =
$params->get('fu_use_download_script', '0');

		// $$$ rob - explode as it may be grouped data (if element is a repeating
upload)
		$values = is_array($value) ? $value : FabrikWorker::JSONtoData($value,
true);

		// Failed validations - format different!
		if (array_key_exists('id', $values))
		{
			$values = array_keys($values['id']);
		}

		if (!$this->isEditable() && ($use_download_script ==
FU_DOWNLOAD_SCRIPT_DETAIL || $use_download_script ==
FU_DOWNLOAD_SCRIPT_BOTH))
		{
			$links = array();

			foreach ($values as $k => $v)
			{
				if ($isAjax)
				{
					$links[] = $this->downloadLink($v, $data, $repeatCounter, $k);
				}
				else
				{
					$links[] = $this->downloadLink($v, $data, $repeatCounter,
'');
				}
			}

			return count($links) < 2 ? implode("\n", $links) :
'<ul class="fabrikRepeatData"><li>' .
implode('</li><li>', $links) .
'</li></ul>';
		}

		$render         = new stdClass;
		$render->output = '';
		$allRenders     = array();

		/*
		 * $$$ hugh testing slide-show display
		 */
		if ($params->get('fu_show_image') === '3'
&& !$this->isEditable())
		{
			$rendered = $this->buildCarousel($id, $values, $data, true);

			return $rendered;
		}

		if (($params->get('fu_show_image') !== '0'
&& !$this->isAjax()) || !$this->isEditable())
		{
			foreach ($values as $v)
			{
				if (is_object($v))
				{
					$v = $v->file;
				}

				$render = $this->loadElement($v);

				if (
					($use_wip && $this->isEditable())
					|| (
						$v != ''
						&& (
							$storage->exists($v, true)
							|| StringHelper::substr($v, 0, 4) == 'http')
					)
				)
				{
					$render->render($this, $params, $v);
				}

				if ($render->output != '')
				{
					if ($this->isEditable())
					{
						// $$$ hugh - TESTING - using HTML5 to show a selected image, so if
no file, still need the span, hidden, but not the actual delete button
						if ($use_wip && empty($v))
						{
							$render->output = '<span class="fabrikUploadDelete
fabrikHide" data-role="delete_span">' .
$render->output . '</span>';
						}
						else
						{
							$render->output = '<span
class="fabrikUploadDelete"
data-role="delete_span">' . $this->deleteButton($v,
$repeatCounter) . $render->output . '</span>';
						}

						/*
						if ($use_wip)
						{
							$render->output .= '<video id="' . $id .
'_video_preview" controls></video>';
						}
						*/
					}

					$allRenders[] = $render->output;
				}
			}
		}

		// if show images is off, still want to render a filename when editable,
so they know a file has been uploaded
		if (($params->get('fu_show_image') == '0'
&& !$this->isAjax()) && $this->isEditable())
		{
			foreach ($values as $v)
			{
				if (is_object($v))
				{
					$v = $v->file;
				}

				// use download script to show file rather than direct link to it
				if ($params->get('fu_force_download_script',
'0') !== '0' )
				{
					$render->output = $this->downloadLink($v, $data, $repeatCounter,
'');
				}
				else
				{
					$render = $this->loadElement($v);
					$render->render($this, $params, $v);
				}

				if ($render->output != '')
				{
					// $$$ hugh - TESTING - using HTML5 to show a selected image, so if no
file, still need the span, hidden, but not the actual delete button
					if ($use_wip && empty($v))
					{
						$render->output = '<span class="fabrikUploadDelete
fabrikHide" id="' . $id . '_delete_span">'
. $render->output . '</span>';
					}
					else
					{
						$render->output = '<span
class="fabrikUploadDelete" id="' . $id .
'_delete_span">' . $this->deleteButton($v,
$repeatCounter) . $render->output . '</span>';
					}

					$allRenders[] = $render->output;
				}
			}
		}

		if (!$this->isEditable())
		{
			if ($render->output == '' &&
$params->get('default_image') != '')
			{
				$render->output = '<img src="' .
$params->get('default_image') . '"
alt="image" />';
				$allRenders[]   = $render->output;
			}

			$str[] = '<div
class="fabrikSubElementContainer">';
			$ul    = '<ul
class="fabrikRepeatData"><li>' .
implode('</li><li>', $allRenders) .
'</li></ul>';
			$str[] = count($allRenders) < 2 ? implode("\n",
$allRenders) : $ul;
			$str[] = '</div>';

			return implode("\n", $str);
		}

		$allRenders = implode('<br/>', $allRenders);
		$allRenders .= ($allRenders == '') ? '' :
'<br/>';
		$capture = '';
		$accept = '';
		$fileTypes = implode(',',
$this->_getAllowedExtension(false));

		switch ($device_capture)
		{
			case 1:
				$capture = ' capture="camera"';
			case 2:
				$accept = ' accept="image/*"';
				break;
			case 7:
				$capture = ' capture="user"';
				$accept = ' accept="image/*"';
				break;
			case 8:
				$capture = ' capture="environment"';
				$accept = ' accept="image/*"';
				break;
			case 3:
				$capture = ' capture="microphone"';
			case 4:
				$accept = ' accept="audio/*"';
				break;
			case 5:
				$capture = ' capture="camcorder"';
			case 6:
				$accept = ' accept="video/*"';
				break;
			default:
				$capture = '';
				$accept = ' accept="' . $fileTypes . '"';
				break;
		}

		$str[] = $allRenders . '<input class="fabrikinput"
name="' . $name . '" type="file" ' .
$accept . ' id="' . $id . '" ' . $capture .
' />' . "\n";

		if ($params->get('fileupload_storage_type',
'filesystemstorage') == 'filesystemstorage' &&
$params->get('upload_allow_folderselect') == '1')
		{
			$rDir    = JPATH_SITE . '/' .
$params->get('ul_directory');
			$w        = new FabrikWorker;
			$rDir = $w->parseMessageForPlaceHolder($rDir);
			$folders = Folder::folders($rDir);
			$str[]   = Html::folderAjaxSelect($folders);

			if ($groupModel->canRepeat())
			{
				$uploadName = FabrikString::rtrimword($name,
"[$repeatCounter]") . "[ul_end_dir][$repeatCounter]";
			}
			else
			{
				$uploadName = $name . '[ul_end_dir]';
			}

			$str[] = '<input name="' . $uploadName . '"
type="hidden" class="folderpath"/>';
		}

		if ($this->isAjax())
		{
			$str   = array();
			$str[] = $allRenders;
			$str   = $this->plupload($str, $repeatCounter, $values);
		}

		if (!$this->isAjax()) {
			/*
			 * Store any existing value for non-AJAX in a hidden element, to use
after a failed validation,
			 * otherwise we lose that value, as the data is coming from submitted
data, which won't contain
			 * original file.
			 */
			$nameRepeatSuffix = $groupModel->canRepeat() ? '[' .
$repeatCounter . ']' : '';
			$idRepeatSuffix = $groupModel->canRepeat() ? '_' .
$repeatCounter : '';

			$value = $this->getValue($data, $repeatCounter);
			$value = is_array($value) ? json_encode($value) : $value;

			$str[] = $this->getHiddenField(
				$this->getFullName(true, false) . '_orig' .
$nameRepeatSuffix,
				$value,
				$this->getFullName(true, false) . '_orig' .
$idRepeatSuffix
			);
		}

		array_unshift($str, '<div
class="fabrikSubElementContainer">');
		$str[] = '</div>';

		return implode("\n", $str);
	}

	/**
	 * Build the HTML to create the delete image button
	 *
	 * @param   string $value         File to delete
	 * @param   int    $repeatCounter Repeat group counter
	 *
	 * @return string
	 */
	protected function deleteButton($value, $repeatCounter)
	{
		$joinedGroupPkVal = $this->getJoinedGroupPkVal($repeatCounter);

		return '<button class="btn button"
data-file="' . $value . '"
data-join-pk-val="' . $joinedGroupPkVal . '">'
. Text::_('COM_FABRIK_DELETE') . '</button> ';
	}

	/**
	 * Check if a single crop image has been uploaded and set the value
accordingly
	 *
	 * @param   array $value Uploaded files
	 *
	 * @return mixed
	 */
	protected function checkForSingleCropValue($value)
	{
		$params = $this->getParams();

		// If its a single upload crop element
		if ($this->isAjax() && $params->get('ajax_max',
4) == 1)
		{
			$singleCropImg = $value;

			if (empty($singleCropImg))
			{
				$value = array();
			}
			else
			{
				if (is_array($singleCropImg))
				{
					// failed validation *sigh*
					if (array_key_exists('id', $singleCropImg))
					{
						$singleCropImg = FArrayHelper::getValue($singleCropImg,
'id', '');
						$singleCropImg = array_keys($singleCropImg);
					}

					$value = (array) FArrayHelper::getValue($singleCropImg, 0,
'');
				}
			}
		}

		return $value;
	}

	/**
	 * Make download link
	 *
	 * @param   string $value         File path
	 * @param   array  $data          Row
	 * @param   int    $repeatCounter Repeat counter
	 * @param   int    $ajaxIndex     Index of AJAX
	 *
	 * @return    string    Download link
	 */
	protected function downloadLink($value, $data, $repeatCounter = 0,
$ajaxIndex = '')
	{
		$input     = $this->app->input;
		$params    = $this->getParams();
		$storage   = $this->getStorage();
		$formModel = $this->getFormModel();

		if (empty($value) || !$storage->exists(COM_FABRIK_BASE . $value))
		{
			return '';
		}

		$canDownload = true;
		$aclEl       =
$this->getFormModel()->getElement($params->get('fu_download_acl',
''), true);

		if (!empty($aclEl))
		{
			$aclEl       = $aclEl->getFullName();
			$canDownload = in_array($data[$aclEl],
$this->user->getAuthorisedViewLevels());
		}

		$formId    = $formModel->getId();
		$rowId     = $input->get('rowid', '0');
		$elementId = $this->getId();
		$title     = basename($value);

		if ($params->get('fu_title_element') == '')
		{
			$title_name = $this->getFullName(true, false) . '__title';
		}
		else
		{
			$title_name = str_replace('.', '___',
$params->get('fu_title_element'));
		}

		$fileName = '';

		if (is_array($formModel->data))
		{
			if (array_key_exists($title_name, $formModel->data))
			{
				if (!empty($formModel->data[$title_name]))
				{
					$title  = $formModel->data[$title_name];
					$titles = FabrikWorker::JSONtoData($title, true);
					$title  = FArrayHelper::getValue($titles, $repeatCounter, $title);
				}
			}

			$fileName = FArrayHelper::getValue($formModel->data,
$this->getFullName(true, false), '');
		}

		$downloadImg = $params->get('fu_download_access_image');
		$noAccessImage = $params->get('fu_download_noaccess_image');

		$layout                     =
$this->getLayout('downloadlink');
		$displayData                = new stdClass;
		$displayData->canDownload   = $canDownload;
		$displayData->title         = $title;
		$displayData->file          = $fileName;
		$displayData->ajaxIndex     = $ajaxIndex;
		$displayData->noAccessImage = ($noAccessImage &&
File::exists(JPATH_SITE .'/media/com_fabrik/images/' .
$noAccessImage)) ? COM_FABRIK_LIVESITE .
'media/com_fabrik/images/' . $noAccessImage : '';
		$displayData->noAccessURL   =
$params->get('fu_download_noaccess_url', '');
		$displayData->openInBrowser =
$params->get('fu_open_in_browser', '0') ===
'1';
		$displayData->downloadImg   = ($downloadImg &&
File::exists(JPATH_SITE .'/media/com_fabrik/images/' .
$downloadImg)) ? COM_FABRIK_LIVESITE . 'media/com_fabrik/images/'
. $downloadImg : '';
		$displayData->href          = COM_FABRIK_LIVESITE .
'index.php?option=com_' . $this->package
			.
'&task=plugin.pluginAjax&plugin=fileupload&method=ajax_download&format=raw&element_id='
			. $elementId . '&formid=' . $formId .
'&rowid=' . $rowId . '&repeatcount=' .
$repeatCounter . '&ajaxIndex=' . $ajaxIndex;

		return $layout->render($displayData);
	}

	/**
	 * Create the html for Ajax upload widget
	 *
	 * @param   array $str           Current html output
	 * @param   int   $repeatCounter Repeat group counter
	 * @param   array $values        Existing files
	 *
	 * @return    array    Modified file-upload html
	 */
	protected function plupload($str, $repeatCounter, $values)
	{
		Html::stylesheet(COM_FABRIK_LIVESITE .
'media/com_fabrik/css/slider.css');
		$params       = $this->getParams();
		$w            = (int) $params->get('ajax_dropbox_width', 0);
		$h            = (int) $params->get('ajax_dropbox_height',
0);
		$dropBoxStyle = 'min-height:' . $h . 'px;';

		if ($w !== 0)
		{
			$dropBoxStyle .= 'width:' . $w . 'px;';
		}

		$layout                     = $this->getLayout('widget');
		$displayData                = new stdClass;
		$displayData->id            = $this->getHTMLId($repeatCounter);
		$displayData->winWidth      = $params->get('win_width',
400);
		$displayData->winHeight     = $params->get('win_height',
400);
		$displayData->canCrop       = $this->canCrop();
		$displayData->canvasSupport = Html::canvasSupport();
		$displayData->dropBoxStyle  = $dropBoxStyle;
		$displayData->field         = implode("\n", $str);
		$str                        = (array) $layout->render($displayData);

		Html::jLayoutJs('fabrik-progress-bar',
'fabrik-progress-bar', (object) array('context' =>
'', 'animated' => true));
		Html::jLayoutJs('fabrik-progress-bar-success',
'fabrik-progress-bar', (object) array('context' =>
'success', 'value' => 100));
		Html::jLayoutJs('fabrik-icon-delete', 'fabrik-icon',
(object) array('icon' => 'icon-delete'));

		$this->pluploadModal($repeatCounter);

		return $str;
	}

	/**
	 * Create the upload modal and its content
	 * Needs to be a unique $modalId to ensure that multiple modals can be
created
	 * each with unique content
	 *
	 * @param   int $repeatCounter
	 */
	protected function pluploadModal($repeatCounter)
	{
		$params             = $this->getParams();
		$modalContentLayout = $this->getLayout('modal-content');
		$modalData          = (object) array(
			'id' => $this->getHTMLId($repeatCounter),
			'canCrop' => $this->canCrop(),
			'canvasSupport' => Html::canvasSupport(),
			'width' => $params->get('win_width', 400),
			'height' => $params->get('win_height', 400)
		);

		$modalContent = $modalContentLayout->render($modalData);

		$modalTitle = $this->canCrop() ?
'PLG_ELEMENT_FILEUPLOAD_CROP_AND_SCALE' :
'PLG_ELEMENT_FILEUPLOAD_PREVIEW';

		$modalId   = $this->modalId($repeatCounter);
		$modalOpts = array(
			'content' => $modalContent,
			'id' => $modalId,
			'title' => Text::_($modalTitle),
			'modal' => true
		);
		Html::jLayoutJs($modalId, 'fabrik-modal', (object) $modalOpts);
	}

	/**
	 * Get the modal html id
	 *
	 * @param int $repeatCounter
	 *
	 * @return string
	 */
	private function modalId($repeatCounter)
	{
		return 'fileupload-modal-' .
$this->getHTMLId($repeatCounter) . '-widget-mocha';
	}

	/**
	 * Fabrik 3 - needs to be onAjax_upload not ajax_upload
	 * triggered by plupload widget
	 *
	 * @return  void
	 */
	public function onAjax_upload()
	{
		$input     = $this->app->input;
		$o         = new stdClass;
		$formModel = $this->getFormModel();
		$this->setId($input->getInt('element_id'));
		$this->loadMeForAjax();
		$params    = $this->getParams();

		if (!$this->isAjax())
		{
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		// Check for request forgeries
		if ($formModel->spoofCheck() &&
!Session::checkToken('request'))
		{
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		if (!$this->canUse()) {
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		/*
		 * Turn error reporting off, as some folk get spurious warnings like
this:
		 *
		 * <b>Warning</b>:  utf8_to_unicode: Illegal sequence
identifier in UTF-8 at byte 0 in
		 */
		error_reporting(E_ERROR | E_PARSE);

		if (!$this->validate())
		{
			$o->error = $this->validationError;
			echo json_encode($o);

			return;
		}

		// Get parameters
		$chunk  = $input->getInt('chunk', 0);
		$chunks = $input->getInt('chunks', 0);

		if ($chunk + 1 < $chunks)
		{
			return;
		}

		// @TODO test in join
		if (array_key_exists('file', $_FILES) ||
array_key_exists('join', $_FILES))
		{
			if ($params->get('upload_use_wip', '0') ==
'1')
			{
				$tmpName     = $_FILES['file']['tmp_name'];
				$filePath    = tempnam($this->config->get('tmp_path'),
"fabrik_ajax_");
				$params      = $this->getParams();
				$allowUnsafe = $params->get('allow_unsafe', '0')
=== '1';

				if (!File::upload($tmpName, $filePath, false, $allowUnsafe))
				{
					// zap the temp file, just to be safe (might be a malicious PHP file)
					File::delete($tmpName);

					$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
					echo json_encode($o);

					return;
				}

				$file  = array(
					'name'     =>
[$_FILES['file']['name']],
					'type'     =>
[$_FILES['file']['type']],
					'tmp_name' => [$filePath],
					'error'    =>
[$_FILES['file']['error']],
					'size'     =>
[$_FILES['file']['size']]
				);
				$key   = 'fabrik.form.fileupload.files.' . $this->getId();
				$files = $this->session->get($key, []);
				$files = array_merge_recursive($files, $file);
				$this->session->set($key, $files);

				$uri         =
$this->getStorage('filesystem')->pathToURL($filePath);
				$o->filepath = $filePath;
				$o->uri      =
$this->getStorage('filesystem')->preRenderPath($uri);
			}
			else
			{
				$file     = array(
					'name'     =>
$_FILES['file']['name'],
					'type'     =>
$_FILES['file']['type'],
					'tmp_name' =>
$_FILES['file']['tmp_name'],
					'error'    =>
$_FILES['file']['error'],
					'size'     => $_FILES['file']['size']
				);
				$filePath = $this->_processIndUpload($file, '', 0);

				if (empty($filePath))
				{
					// zap the temp file, just to be safe (might be a malicious PHP file)
					File::delete($file['tmp_name']);

					$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
					echo json_encode($o);

					return;
				}

				$uri         = $this->getStorage()->pathToURL($filePath);
				$o->filepath = $filePath;
				$o->uri      = $this->getStorage()->preRenderPath($uri);
			}
		}
		else
		{
			$o->filepath = null;
			$o->uri      = null;
		}

		echo json_encode($o);

		return;
	}

	/**
	 * Get database field description
	 *
	 * @return  string  db field type
	 */
	public function getFieldDescription()
	{
		if ($this->encryptMe())
		{
			return 'BLOB';
		}

		return "TEXT";
	}

	/**
	 * Attach documents to the email
	 *
	 * @param   string $data Data
	 *
	 * @return  string  Full path to image to attach to email
	 */
	public function addEmailAttachement($data)
	{
		if (is_object($data))
		{
			$data = $data->file;
		}

		// @TODO: check what happens here with open base_dir in effect
		$params = $this->getParams();

		if ($params->get('ul_email_file'))
		{

			if (empty($data) || $params->get('fileupload_storage_type',
'filesystemstorage') == 'filesystemstorage')
			{
				if (empty($data))
				{
					$data = $params->get('default_image');
				}

				if (strstr($data, JPATH_SITE))
				{
					$p = str_replace(COM_FABRIK_LIVESITE, JPATH_SITE, $data);
				}
				else
				{
					$p = JPATH_SITE . '/' . $data;
				}
			}
			else
			{
				$ext = pathinfo($data, PATHINFO_EXTENSION);
				$p   = tempnam($this->config->get('tmp_path'),
'email_');

				if (empty($p))
				{
					return false;
				}

				if (!empty($ext))
				{
					File::delete($p);
					$p .= '.' . $ext;
				}

				$storage     = $this->getStorage();
				$fileContent = $storage->read($data);
				File::write($p, $fileContent);
			}

			return $p;
		}

		return false;
	}

	/**
	 * Should the attachment file we provided in addEmailAttachment() be
removed after use
	 *
	 * @param   string $data Data
	 *
	 * @return  bool
	 */
	public function shouldDeleteEmailAttachment($data)
	{
		$params = $this->getParams();

		if ($params->get('ul_email_file'))
		{
			if (!empty($data) &&
$params->get('fileupload_storage_type',
'filesystemstorage') == 'amazons3storage')
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * If a database join element's value field points to the same db
field as this element
	 * then this element can, within modifyJoinQuery, update the query.
	 * E.g. if the database join element points to a file upload element then
you can replace
	 * the file path that is the standard $val with the html to create the
image
	 *
	 * @param   string $val  Value
	 * @param   string $view Form or list
	 *
	 * @deprecated - doesn't seem to be used
	 *
	 * @return  string    Modified val
	 */
	protected function modifyJoinQuery($val, $view = 'form')
	{
		$params = $this->getParams();

		if (!$params->get('fu_show_image', 0) && $view ==
'form')
		{
			return $val;
		}

		if ($params->get('make_thumbnail'))
		{
			$ulDir    = Path::clean($params->get('ul_directory')) .
'/';
			$ulDir    = str_replace("\\", "\\\\", $ulDir);
			$thumbDir = Path::clean($params->get('thumb_dir')) .
'/';
			$w        = new FabrikWorker;
			$thumbDir = $w->parseMessageForPlaceHolder($thumbDir);
			$thumbDir = str_replace("\\", "\\\\", $thumbDir);

			$w        = new FabrikWorker;
			$thumbDir = $w->parseMessageForPlaceHolder($thumbDir);
			$thumbDir .= $params->get('thumb_prefix');

			// Replace the backslashes with forward slashes
			$str = "CONCAT('<img src=\"" .
COM_FABRIK_LIVESITE . "'," . "REPLACE(" .
"REPLACE($val, '$ulDir', '" . $thumbDir .
"')" . ", '\\\', '/')"
				. ", '\" alt=\"database join image\"
/>')";
		}
		else
		{
			$str = " REPLACE(CONCAT('<img src=\"" .
COM_FABRIK_LIVESITE . "' , $val, '\"
alt=\"database join image\"/>'), '\\\',
'/') ";
		}

		return $str;
	}

	/**
	 * Trigger called when a row is deleted
	 *
	 * @param   array $groups Grouped data of rows to delete
	 *
	 * @return  void
	 */
	public function onDeleteRows($groups)
	{
		// Cant delete files from unpublished elements
		if (!$this->canUse())
		{
			return;
		}

		$db = $this->getListModel()->getDb();
		$params = $this->getParams();

		if ($params->get('upload_delete_image', false))
		{
			jimport('joomla.filesystem.file');
			$elName = $this->getFullName(true, false);
			$name   = $this->getElement()->name;

			foreach ($groups as $rows)
			{
				foreach ($rows as $row)
				{
					if (property_exists($row, $elName . '_raw'))
					{
						if ($this->isJoin())
						{
							$join  = $this->getJoinModel()->getJoin();
							$query = $db->getQuery(true);
							$query->select('*')->from($db->qn($join->table_join))
								->where($db->qn('parent_id') . ' = ' .
$db->q($row->__pk_val));
							$db->setQuery($query);
							$imageRows = $db->loadObjectList('id');

							if (!empty($imageRows))
							{
								foreach ($imageRows as $imageRow)
								{
									$this->deleteFile($imageRow->$name);
								}

								$query->clear();
								$query->delete($db->qn($join->table_join))
									->where($db->qn('id') . ' IN (' .
implode(', ', array_keys($imageRows)) . ')');
								$db->setQuery($query);
								$logMsg = 'onDeleteRows Delete records query: ' .
$db->getQuery() . '; user = ' .
$this->user->get('id');
								Log::add($logMsg, Log::WARNING,
'com_fabrik.element.fileupload');
								$db->execute();
							}
						}
						else
						{
							$files = $row->{$elName . '_raw'};

							if (FabrikWorker::isJSON($files))
							{
								$files = FabrikWorker::JSONtoData($files, true);
							}
							else
							{
								$files = explode(GROUPSPLITTER, $files);
							}

							foreach ($files as $filename)
							{
								if (is_object($filename) ) $filename = $filename->file;
								$this->deleteFile(trim($filename));
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Return the number of bytes
	 *
	 * @param   string $val E.g. 3m
	 *
	 * @return  int  Bytes
	 */
	protected function _return_bytes($val)
	{
		$val  = trim($val);
		$last = StringHelper::strtolower(substr($val, -1));

		if ($last == 'g')
		{
			$val = $val * 1024 * 1024 * 1024;
		}

		if ($last == 'm')
		{
			$val = $val * 1024 * 1024;
		}

		if ($last == 'k')
		{
			$val = $val * 1024;
		}

		return $val;
	}

	/**
	 * Get the max upload size allowed by the server.
	 *
	 * @deprecated  - not used?
	 *
	 * @return  int  kilobyte upload size
	 */
	public function maxUpload()
	{
		$post_value   =
$this->_return_bytes(ini_get('post_max_size'));
		$upload_value =
$this->_return_bytes(ini_get('upload_max_filesize'));
		$value        = min($post_value, $upload_value);
		$value        = $value / 1024;

		return $value;
	}

	/**
	 * Turn form value into email formatted value
	 *
	 * @param   mixed $value         element value
	 * @param   array $data          form data
	 * @param   int   $repeatCounter group repeat counter
	 *
	 * @return  string  email formatted value
	 */
	public function getEmailValue($value, $data = array(), $repeatCounter = 0)
	{
		$params                    = $this->getParams();
		$storage                   = $this->getStorage();
		$this->_repeatGroupCounter = $repeatCounter;
		$output                    = array();

		if ($params->get('fu_show_image_in_email', false))
		{
			$params->set('fu_show_image', true);

			// For ajax repeats
			$value     = (array) $value;

			if (array_key_exists('cropdata', $value))
			{
				$value = array($value);
			}

			$formModel = $this->getFormModel();

			if (!isset($formModel->data))
			{
				$formModel->data = $data;
			}

			if (FArrayHelper::emptyIsh($value))
			{
				return '';
			}

			foreach ($value as $v)
			{
				// From ajax upload
				if (is_array($v))
				{
					if (array_key_exists('cropdata', $v))
					{
						$v 	= array_keys($v['id']);
					}
					else
					{
						$v = array_keys($v);
					}

					/*
					if (empty($v))
					{
						return '';
					}
					*/

					$v = ArrayHelper::getValue($v, 0);
				}

				if ($this->defaultOnCopy())
				{
					$v = $params->get('default_image');
				}

				$render = $this->loadElement($v);

				if ($v != '' && $storage->exists(COM_FABRIK_BASE .
$v))
				{
					$render->render($this, $params, $v);
				}

				if ($render->output == '' &&
$params->get('default_image') != '')
				{
					$render->output = '<img src="' .
$params->get('default_image') . '"
alt="image" />';
				}

				$output[] = $render->output;
			}
		}
		else
		{
			$value     = (array) $value;

			if (array_key_exists('cropdata', $value))
			{
				$value = array($value);
			}

			if (FArrayHelper::emptyIsh($value))
			{
				if ($this->defaultOnCopy())
				{
					$value = $params->get('default_image');
				}

				if (empty($value))
				{
					return '';
				}
			}


			foreach ($value as $v) {
				if (is_array($v))
				{
					if (array_key_exists('cropdata', $v))
					{
						$v 	= array_keys($v['id']);
					}
					else
					{
						$v = array_keys($v);
					}

					$v = ArrayHelper::getValue($v, 0);
				}

				if ($this->defaultOnCopy())
				{
					$v = $params->get('default_image');
				}

				$output[] = $storage->preRenderPath($v);
			}
		}

		// @TODO figure better solution for sepchar
		return implode('<br />', $output);
	}

	/**
	 * Determines the value for the element in the form view
	 *
	 * @param   array $data          Form data
	 * @param   int   $repeatCounter When repeating joined groups we need to
know what part of the array to access
	 *
	 * @return  string    Value
	 */
	public function getROValue($data, $repeatCounter = 0)
	{
		$v       = $this->getValue($data, $repeatCounter);
		$storage = $this->getStorage();

		if (is_array($v))
		{
			$return = array();

			foreach ($v as $tmpV)
			{
				$return[] = $storage->pathToURL($tmpV);
			}

			return $return;
		}

		return $storage->pathToURL($v);
	}

	/**
	 * Not really an AJAX call, we just use the pluginAjax method so we can
run this
	 * method for handling scripted downloads.
	 *
	 * @return  void
	 */
	public function onAjax_download()
	{
		$input = $this->app->input;
		$this->setId($input->getInt('element_id'));
		$this->loadMeForAjax();
		$this->getElement();
		$params = $this->getParams();
		$url    = $input->server->get('HTTP_REFERER',
'', 'string');
		$this->lang->load('com_fabrik.plg.element.fabrikfileupload',
JPATH_ADMINISTRATOR);
		$rowId       = $input->get('rowid', '',
'string');
		$repeatCount = $input->getInt('repeatcount', 0);
		$ajaxIndex   = $input->getStr('ajaxIndex', '');
		$linkOnly    = $input->getStr('linkOnly', '0') ===
'1';
		$listModel   = $this->getListModel();
		$row         = $listModel->getRow($rowId, false, true);

		if (!$this->canView())
		{
			$this->app->enqueueMessage(Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_PERMISSION'));
			$this->app->redirect($url);
			exit;
		}

		if (empty($rowId))
		{
			$errMsg =
Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_SUCH_FILE');
			$errMsg .= Html::isDebug() ? ' (empty rowid)' : '';
			$this->app->enqueueMessage($errMsg);
			$this->app->redirect($url);
			exit;
		}

		if (empty((array)$row))
		{
			$errMsg =
Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_SUCH_FILE');
			$errMsg .= Html::isDebug() ? " (no such row)" : '';
			$this->app->enqueueMessage($errMsg);
			$this->app->redirect($url);
			exit;
		}

		$aclEl =
$this->getFormModel()->getElement($params->get('fu_download_acl',
''), true);

		if (!empty($aclEl))
		{
			$aclEl       = $aclEl->getFullName();
			$aclElRaw    = $aclEl . '_raw';
			$groups      = $this->user->getAuthorisedViewLevels();
			$canDownload = in_array($row->$aclElRaw, $groups);

			if (!$canDownload)
			{
				$this->app->enqueueMessage(Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_PERMISSION'));
				$this->app->redirect($url);
			}
		}

		$storage  = $this->getStorage();
		$elName   = $this->getFullName(true, false);
		$filePath = $row->$elName;
		$filePath = FabrikWorker::JSONtoData($filePath, false);
		$filePath = is_object($filePath) ? FArrayHelper::fromObject($filePath) :
(array) $filePath;

		/*
		 * Special case, usually for S3, which allows custom JS to call this
function with AJAX, specify &linkOnly=1,
		 * and get back the presigned URL to a file in a Private bucket.
		 */
		if ($linkOnly)
		{
			$links = array();

			foreach ($filePath as $path)
			{
				if (is_object($path))
				{
					$path = $path->file;
				}

				$links[] = $storage->preRenderPath($path);
			}

			echo json_encode($links);

			exit;
		}


		$filePath = FArrayHelper::getValue($filePath, $repeatCount);

		if ($ajaxIndex !== '')
		{
			if (is_array($filePath))
			{
				$filePath = FArrayHelper::getValue($filePath, $ajaxIndex);
			}
		}

		$filePath    = $storage->getFullPath($filePath);

		//$fileContent = $storage->read($filePath);

		if ($storage->exists($filePath))
		{
			$thisFileInfo = $storage->getFileInfo($filePath);

			if ($thisFileInfo === false)
			{
				$errMsg =
Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_SUCH_FILE');
				$errMsg .= Html::isDebug(true) ? ' (path: ' . $filePath .
')' : '';
				$this->app->enqueueMessage($errMsg);
				$this->app->redirect($url);
				exit;
			}
			// Some time in the past
			header("Expires: Sat, 26 Jul 1997 05: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");
			header('Accept-Ranges: bytes');
			header('Content-Length: ' .
$thisFileInfo['filesize']);
			header('Content-Type: ' .
$thisFileInfo['mime_type']);
			if ($params->get('fu_open_in_browser', '0') ==
'0' )
			{
				header('Content-Disposition: attachment; filename="' .
$thisFileInfo['filename'] . '"');
			}

			// Serve up the file
			$storage->stream($filePath);

			// $this->downloadEmail($row, $filePath);
			$this->downloadHit($rowId, $repeatCount);
			$this->downloadLog($row, $filePath);

			// And we're done.
			exit();
		}
		else
		{
			$this->app->enqueueMessage(Text::_('PLG_ELEMENT_FILEUPLOAD_DOWNLOAD_NO_SUCH_FILE'));
			$this->app->redirect($url);
			exit;
		}
	}

	/**
	 * Update downloads hits table
	 *
	 * @param   int|string $rowId       Update table's primary key
	 * @param   int        $repeatCount Repeat group counter
	 *
	 * @return  void
	 */
	protected function downloadHit($rowId, $repeatCount = 0)
	{
		// $$$ hugh @TODO - make this work for repeats and/or joins!
		$params = $this->getParams();

		if ($hit_counter = $params->get('fu_download_hit_counter',
''))
		{
			JError::setErrorHandling(E_ALL, 'ignore');
			$listModel = $this->getListModel();
			$pk        = $listModel->getPrimaryKey();
			$fabrikDb  = $listModel->getDb();
			list($table_name, $element_name) = explode('.', $hit_counter);
			$sql = "UPDATE $table_name SET $element_name =
COALESCE($element_name,0) + 1 WHERE $pk = " . $fabrikDb->q($rowId);
			$fabrikDb->setQuery($sql);
			$fabrikDb->execute();
		}
	}

	/**
	 * Log the download
	 *
	 * @param   object $row      Log download row
	 * @param   string $filePath Downloaded file's path
	 *
	 * @since 2.0.5
	 *
	 * @return  void
	 */
	protected function downloadLog($row, $filePath)
	{
		$params = $this->getParams();

		if ((int) $params->get('fu_download_log', 0))
		{
			$input = $this->app->input;
			$log                = FabTable::getInstance('log',
'FabrikTable');
			$log->message_type  = 'fabrik.fileupload.download';
			$msg                = new stdClass;
			$msg->file          = $filePath;
			$msg->userid        = $this->user->get('id');
			$msg->username      = $this->user->get('username');
			$msg->email         = $this->user->get('email');
			$log->referring_url =
$input->server->get('REMOTE_ADDR', '',
'string');
			$log->message       = json_encode($msg);
			$log->store();
		}
	}

	/**
	 * Called when save as copy form button clicked
	 *
	 * @param   mixed $val Value to copy into new record
	 *
	 * @return  mixed  Value to copy into new record
	 */
	public function onSaveAsCopy($val)
	{
		if ($this->defaultOnCopy())
		{
			$val = '';
		}
		else
		{
			if (empty($val))
			{
				$formModel = $this->getFormModel();
				$origData  = $formModel->getOrigData();
				$name      = $this->getFullName(true, false);
				$val       = $origData[$name];
			}
		}

		return $val;
	}

	/**
	 * Is the element a repeating element
	 *
	 * @return  bool
	 */
	public function isRepeatElement()
	{
		$params = $this->getParams();

		return $this->isAjax() &&
($params->get('ajax_max', 4) > 1);
	}

	/**
	 * Fabrik 3: needs to be onAjax_deleteFile
	 * delete a previously uploaded file via ajax
	 *
	 * @return  void
	 */
	public function onAjax_deleteFile()
	{
		$input = $this->app->input;
		$this->loadMeForAjax();
		$o         = new stdClass;
		$o->error  = '';
		$formModel = $this->getFormModel();

		if (!$this->isAjax())
		{
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		// Check for request forgeries
		if ($formModel->spoofCheck() &&
!Session::checkToken('request'))
		{
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		if (!$this->canUse()) {
			$o->error = Text::_('PLG_ELEMENT_FILEUPLOAD_UPLOAD_ERR');
			echo json_encode($o);

			return;
		}

		$filename = $input->get('file', 'string',
'');

		// Filename may be a path - if so just get the name
		if (strstr($filename, '/'))
		{
			$filename = explode('/', $filename);
			$filename = array_pop($filename);
		}
		elseif (strstr($filename, '\\'))
		{
			$filename = explode('\\', $filename);
			$filename = array_pop($filename);
		}

		$repeatCounter = (int) $input->getInt('repeatCounter');
		$join          = FabTable::getInstance('join',
'FabrikTable');
		$join->load(array('element_id' =>
$input->getInt('element_id')));
		$this->setId($input->getInt('element_id'));
		$this->getElement();

		$filePath = $this->_getFilePath($repeatCounter, false);
		$filePath = str_replace(JPATH_SITE, '', $filePath);

		$storage  = $this->getStorage();
		$filename = $storage->cleanName($filename, $repeatCounter);
		$filename = $storage->clean($filePath . '/' . $filename);
		$this->deleteFile($filename);
		$db    = $this->getListModel()->getDb();
		$query = $db->getQuery(true);

		// Could be a single ajax file-upload if so not joined
		if ($join->table_join != '')
		{
			// Use getString as if we have edited a record, added a file and deleted
it the id is alphanumeric and not found in db.
			$query->delete($db->qn($join->table_join))
				->where($db->qn('id') . ' = ' .
$db->q($input->getString('recordid')));
			$db->setQuery($query);

			Log::add('Delete join image entry: ' . $db->getQuery() .
'; user = ' . $this->user->get('id'),
Log::WARNING, 'com_fabrik.element.fileupload');
			$db->execute();
		}

		echo json_encode($o);
	}

	/**
	 * Determines the value for the element in the form view
	 *
	 * @param   array $data          Element value
	 * @param   int   $repeatCounter When repeating joined groups we need to
know what part of the array to access
	 * @param   array $opts          Options
	 *
	 * @return    string    Value
	 */
	public function getValue($data, $repeatCounter = 0, $opts = array())
	{
		$value = parent::getValue($data, $repeatCounter, $opts);

		return $value;
	}

	/**
	 * Build 'slide-show' / carousel.  What gets built will depend
on content type,
	 * using the first file in the data array as the type.  So if the first
file is
	 * an image, a Bootstrap carousel will be built.
	 *
	 * @param   string $id      Widget HTML id
	 * @param   array  $data    Array of file paths
	 * @param   object $thisRow Row data
	 *
	 * @return  string  HTML
	 */
	public function buildCarousel($id = 'carousel', $data = array(),
$thisRow = null, $nav = true)
	{
		$rendered = '';

		if (!FArrayHelper::emptyIsh($data))
		{
			$render   = $this->loadElement($data[0]);
			$params   = $this->getParams();
			$rendered = $render->renderCarousel($id, $data, $this, $params,
$thisRow, $nav);
		}

		return $rendered;
	}

	/**
	 * run on formModel::setFormData()
	 * TESTING - stick the filename (if it's there) in to the formData,
so things like validations
	 * can see it.  Not sure yet if this will mess with the rest of the code. 
And I'm sure it'll get
	 * horribly funky, judging by the code in processUpload!  But hey,
let's have a hack at it
	 *
	 * @param   int $c Repeat group counter
	 *
	 * @return void
	 */
	public function preProcess_off($c)
	{
		$form  = $this->getFormModel();
		$group = $this->getGroup();

		/**
		 * get the key name in dot format for updateFormData method
		 * $$$ hugh - added $rawKey stuff, otherwise when we did "$key .
'_raw'" in the updateFormData
		 * below on repeat data, it ended up in the wrong format, like
join.XX.table___element.0_raw
		 */
		$key    = $this->getFullName(true, false);
		$rawKey = $key . '_raw';

		if (!$group->canRepeat())
		{
			if (!$this->isRepeatElement())
			{
				$fileArray = FArrayHelper::getValue($_FILES, $key, array(),
'array');
				$fileName  = FArrayHelper::getValue($fileArray, 'name');
				$form->updateFormData($key, $fileName);
				$form->updateFormData($rawKey, $fileName);
			}
		}
	}

	/**
	 * Build the sub query which is used when merging in
	 * repeat element records from their joined table into the one field.
	 * Overwritten in database join element to allow for building
	 * the join to the table containing the stored values required ids
	 *
	 * @since   2.1.1
	 *
	 * @return  string    sub query
	 */
	protected function buildQueryElementConcatId()
	{
		//$str        = parent::buildQueryElementConcatId();
		$joinTable  = $this->getJoinModel()->getJoin()->table_join;
		$parentKey  = $this->buildQueryParentKey();
		$fullElName = $this->_db->qn($this->getFullName(true, false) .
'_id');
		$str = "(SELECT GROUP_CONCAT(" . $this->element->name .
" SEPARATOR '" . GROUPSPLITTER . "') FROM
$joinTable WHERE " . $joinTable
			. ".parent_id = " . $parentKey . ") AS $fullElName";

		return $str;
	}

	/**
	 * Get the parent key element name
	 *
	 * @return string
	 */
	protected function buildQueryParentKey()
	{
		$item      = $this->getListModel()->getTable();
		$parentKey = $item->db_primary_key;

		if ($this->isJoin())
		{
			$groupModel = $this->getGroupModel();

			if ($groupModel->isJoin())
			{
				// Need to set the joinTable to be the group's table
				$groupJoin = $groupModel->getJoinModel();
				$parentKey =
$groupJoin->getJoin()->params->get('pk');
			}
		}

		return $parentKey;
	}

	private function isAjax()
	{
		return $this->getParams()->get('ajax_upload',
'0') === '1';
	}

	/**
	 * Give elements a chance to reset data after a failed validation.  For
instance, file upload element
	 * needs to reset the values as they aren't submitted with the form
	 *
	 * @param  $data  array form data
	 */
	public function setValidationFailedData(&$data)
	{
		if (!$this->isAjax()) {
			$origName = $this->getFullName(true, false) . '_orig';
			$thisName = $this->getFullName(true, false);

			if (array_key_exists($origName, $data)) {
				$data[$thisName] = $data[$origName];
				$data[$thisName . '_raw'] = $data[$origName];
			}
		}
	}

	/*
	 * Called during upload, runs optional eval'ed code to rename the
file
	 *
	 * @param  string  $filename  the filename
	 * @param  int     $repeatCounter  repeat counter
	 *
	 * @return  string   $filename   the (optionally) modified filename
	 */
	private function renameFile($filename, $repeatCounter)
	{
		$params = $this->getParams();
		$php = $params->get('fu_rename_file_code', '');

		if (!empty($php))
		{
			FabrikWorker::clearEval();
			$formModel = $this->getFormModel();
			$filename = Php::Eval(['code' => $php,
'vars'=>['formModel'=>$formModel,
'filename'=>$filename]]);
			FabrikWorker::logEval($filename, 'Eval exception : ' .
$this->getElement()->name . '::renameFile() : ' . $filename
. ' : %s');
		}

		return $filename;
	}
}