571 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			571 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * @package         Advanced Custom Fields
 | |
|  * @version         2.8.8 Pro
 | |
|  * 
 | |
|  * @author          Tassos Marinos <info@tassos.gr>
 | |
|  * @link            http://www.tassos.gr
 | |
|  * @copyright       Copyright © 2020 Tassos Marinos All Rights Reserved
 | |
|  * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 | |
| */
 | |
| 
 | |
| defined('_JEXEC') or die;
 | |
| 
 | |
| use NRFramework\File;
 | |
| use NRFramework\Mimes;
 | |
| use NRFramework\Image;
 | |
| use NRFramework\Functions;
 | |
| use Joomla\Registry\Registry;
 | |
| use Joomla\CMS\Factory;
 | |
| use Joomla\CMS\MVC\Model\BaseDatabaseModel;
 | |
| use Joomla\CMS\HTML\HTMLHelper;
 | |
| use Joomla\CMS\Form\Form;
 | |
| use Joomla\CMS\Language\Text;
 | |
| use Joomla\CMS\Session\Session;
 | |
| use Joomla\CMS\Utility\Utility;
 | |
| 
 | |
| JLoader::register('ACF_Field', JPATH_PLUGINS . '/system/acf/helper/plugin.php');
 | |
| JLoader::register('ACFUploadHelper', __DIR__ . '/fields/uploadhelper.php');
 | |
| 
 | |
| if (!class_exists('ACF_Field'))
 | |
| {
 | |
| 	Factory::getApplication()->enqueueMessage('Advanced Custom Fields System Plugin is missing', 'error');
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| class PlgFieldsACFUpload extends ACF_Field
 | |
| {
 | |
| 	/**
 | |
| 	 *  The validation rule will be used to validate the field on saving
 | |
| 	 *
 | |
| 	 *  @var  string
 | |
| 	 */
 | |
| 	protected $validate = 'acfrequired';
 | |
| 
 | |
|     public function onUserAfterSave($user, $isnew, $success, $msg)
 | |
|     {
 | |
|         // Load Fields Component Helper class
 | |
| 		JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');
 | |
| 
 | |
| 		$fields = FieldsHelper::getFields('com_users.user', $user, true);
 | |
| 
 | |
| 		if (!$fields)
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 		
 | |
| 		// Get the fields data
 | |
| 		$fieldsData = !empty($user['com_fields']) ? $user['com_fields'] : [];
 | |
| 
 | |
| 		$this->processFiles($fields, $fieldsData, (object) $user);
 | |
| 	}
 | |
| 
 | |
| 	public function onContentAfterSave($context, $item, $isNew, $data = [])
 | |
| 	{
 | |
| 		if (!is_array($data))
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 		
 | |
| 		if (!isset($data['com_fields']))
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 		
 | |
| 		// Create correct context for category
 | |
| 		if ($context == 'com_categories.category')
 | |
| 		{
 | |
| 			$context = $item->get('extension') . '.categories';
 | |
| 		}
 | |
| 
 | |
|         // Load Fields Component Helper class
 | |
| 		JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');
 | |
| 
 | |
| 		// Check the context
 | |
| 		$parts = FieldsHelper::extract($context, $item);
 | |
| 
 | |
| 		if (!$parts)
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		// Compile the right context for the fields
 | |
| 		$context = $parts[0] . '.' . $parts[1];
 | |
| 
 | |
| 		// Loading the fields
 | |
| 		$fields = FieldsHelper::getFields($context, $item);
 | |
| 
 | |
| 		if (!$fields)
 | |
| 		{
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		// Get the fields data
 | |
| 		$fieldsData = !empty($data['com_fields']) ? $data['com_fields'] : [];
 | |
| 
 | |
| 		$this->processFiles($fields, $fieldsData, $item);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Processes the files.
 | |
| 	 * 
 | |
| 	 * Either duplicates the files or uploads them to final directory.
 | |
| 	 * 
 | |
| 	 * @param   array   $fields
 | |
| 	 * @param   array   $fieldsData
 | |
| 	 * @param   object  $item
 | |
| 	 * 
 | |
| 	 * @return  void
 | |
| 	 */
 | |
| 	private function processFiles($fields = [], $fieldsData = [], $item = [])
 | |
| 	{
 | |
| 		if (!$fields || !$fieldsData || !$item)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 		
 | |
| 		// Whether we should clean up the temp folder at the end of this process
 | |
| 		$should_clean = false;
 | |
| 		
 | |
| 		// Get the Fields Model
 | |
|         if (!defined('nrJ4'))
 | |
| 		{
 | |
| 			$model = JModelLegacy::getInstance('Field', 'FieldsModel', ['ignore_request' => true]);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			$model = Factory::getApplication()->bootComponent('com_fields')->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
 | |
| 		}
 | |
| 
 | |
| 		// Cache subform fields
 | |
| 		$subform_fields = [];
 | |
| 
 | |
| 		// Loop over the fields
 | |
| 		foreach ($fields as $field)
 | |
| 		{
 | |
| 			$field_type = $field->type;
 | |
| 			
 | |
| 			/**
 | |
| 			 * Check whether a Gallery field is used within the Subform field.
 | |
| 			 */
 | |
| 			if ($field_type === 'subform')
 | |
| 			{
 | |
| 				$submitted_subform_value = array_key_exists($field->name, $fieldsData) ? $fieldsData[$field->name] : null;
 | |
| 
 | |
| 				// Ensure it has a value
 | |
| 				if (!$submitted_subform_value || !$subform_value = json_decode($field->rawvalue, true))
 | |
| 				{
 | |
| 					// Update subform field
 | |
| 					$model->setFieldValue($field->id, $item->id, json_encode([]));
 | |
| 
 | |
| 					continue;
 | |
| 				}
 | |
| 				
 | |
| 				$update = false;
 | |
| 				$is_subform_non_repeatable = false;
 | |
| 
 | |
| 				// Make non-repeatable subform fields a multi array so we can parse them
 | |
| 				if (Functions::startsWith(array_key_first($subform_value), 'field') && $field->fieldparams->get('repeat', '0') === '0')
 | |
| 				{
 | |
| 					$is_subform_non_repeatable = true;
 | |
| 					$subform_value = [$subform_value];
 | |
| 				}
 | |
| 
 | |
| 				foreach ($subform_value as $key => &$value)
 | |
| 				{
 | |
| 					foreach ($value as $_key => &$_value)
 | |
| 					{
 | |
| 						// Get Field ID
 | |
| 						$field_id = str_replace('field', '', $_key);
 | |
| 						
 | |
| 						// Get Field by ID
 | |
| 						$subform_field = isset($subform_fields[$field_id]) ? $subform_fields[$field_id] : $model->getItem($field_id);
 | |
| 
 | |
| 						// Only proceed for this field type
 | |
| 						if ($subform_field->type !== $this->_name)
 | |
| 						{
 | |
| 							continue;
 | |
| 						}
 | |
| 
 | |
| 						// Cache field
 | |
| 						if (!isset($subform_fields[$field_id]))
 | |
| 						{
 | |
| 							$subformfields[$field_id] = $subform_field;
 | |
| 						}
 | |
| 
 | |
| 
 | |
| 						// Check if value can be json_decoded
 | |
| 						if (is_string($_value))
 | |
| 						{
 | |
| 							if ($decoded = json_decode($_value, true))
 | |
| 							{
 | |
| 								$_value = $decoded;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						if (\ACF\Item::isCopying())
 | |
| 						{
 | |
| 							// Duplicate files
 | |
| 							ACFUploadHelper::duplicateFiles($_value);
 | |
| 						}
 | |
| 						else
 | |
| 						{
 | |
| 							// We should run our cleanup routine at the end
 | |
| 							$should_clean = true;
 | |
| 
 | |
| 							// Move to final folder
 | |
| 							$_value = ACFUploadHelper::moveTempItemsToDestination($_value, $subform_field, $item);
 | |
| 						}
 | |
| 
 | |
| 						$update = true;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if ($update)
 | |
| 				{
 | |
| 					if ($is_subform_non_repeatable)
 | |
| 					{
 | |
| 						$subform_value = reset($subform_value);
 | |
| 					}
 | |
| 
 | |
| 					// Update subform field
 | |
| 					$model->setFieldValue($field->id, $item->id, json_encode($subform_value));
 | |
| 				}
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				// Only proceed for this field type
 | |
| 				if ($field_type !== $this->_name)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 	
 | |
| 				// Determine the value if it is available from the data
 | |
| 				$value = array_key_exists($field->name, $fieldsData) ? $fieldsData[$field->name] : null;
 | |
| 	
 | |
| 				if (!$value)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				// Check if value can be json_decoded
 | |
| 				if (is_string($value))
 | |
| 				{
 | |
| 					if ($decoded = json_decode($value, true))
 | |
| 					{
 | |
| 						$value = $decoded;
 | |
| 					}
 | |
| 				}
 | |
| 	
 | |
| 				if (\ACF\Item::isCopying())
 | |
| 				{
 | |
| 					// Duplicate files
 | |
| 					ACFUploadHelper::duplicateFiles($value);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					// We should run our cleanup routine at the end
 | |
| 					$should_clean = true;
 | |
| 	
 | |
| 					// Move to final folder
 | |
| 					$value = ACFUploadHelper::moveTempItemsToDestination($value, $field, $item);
 | |
| 				}
 | |
| 
 | |
| 				// Setting the value for the field and the item
 | |
| 				$model->setFieldValue($field->id, $item->id, json_encode($value));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ($should_clean)
 | |
| 		{
 | |
| 			// Clean old files from temp folder
 | |
| 			ACFUploadHelper::clean();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
|     /**
 | |
| 	 * Transforms the field into a DOM XML element and appends it as a child on the given parent.
 | |
| 	 *
 | |
| 	 * @param   stdClass    $field   The field.
 | |
| 	 * @param   DOMElement  $parent  The field node parent.
 | |
| 	 * @param   Form       $form    The form.
 | |
| 	 *
 | |
| 	 * @return  DOMElement
 | |
| 	 *
 | |
| 	 * @since   3.7.0
 | |
| 	 */
 | |
| 	public function onCustomFieldsPrepareDom($field, DOMElement $parent, Joomla\CMS\Form\Form $form)
 | |
| 	{
 | |
| 		if (!$fieldNode = parent::onCustomFieldsPrepareDom($field, $parent, $form))
 | |
| 		{
 | |
| 			return $fieldNode;
 | |
|         }
 | |
| 
 | |
|         $this->attachEditModal();
 | |
| 
 | |
|         HTMLHelper::stylesheet('plg_system_acf/acf-backend.css', ['relative' => true, 'version' => 'auto']);
 | |
|         HTMLHelper::script('plg_fields_acfupload/edit-modal.js', ['relative' => true, 'version' => 'auto']);
 | |
|         
 | |
| 		$fieldNode->setAttribute('field_id', $field->id);
 | |
| 
 | |
| 		return $fieldNode;
 | |
| 	}
 | |
| 	
 | |
|     /**
 | |
|      * Attaches the edit modal to the page.
 | |
|      * 
 | |
|      * @return  void
 | |
|      */
 | |
|     private function attachEditModal()
 | |
|     {
 | |
|         $form_source = new SimpleXMLElement('
 | |
|             <form>
 | |
|                 <fieldset name="acfupload_edit_modal">
 | |
|                     <field name="" type="text"
 | |
|                         label="ACF_UPLOAD_TITLE"
 | |
|                         description="ACF_UPLOAD_TITLE_DESC"
 | |
|                         hint="ACF_UPLOAD_TITLE_HINT"
 | |
|                         class="acfupload_custom_title_value w-100"
 | |
|                     />
 | |
|                     <field name="" type="textarea"
 | |
|                         label="ACF_UPLOAD_DESCRIPTION"
 | |
|                         description="ACF_UPLOAD_DESCRIPTION_DESC"
 | |
|                         hint="ACF_UPLOAD_DESCRIPTION_HINT"
 | |
|                         class="acfupload_custom_description_value w-100"
 | |
| 						rows="5"
 | |
| 						filter="safehtml"
 | |
|                     />
 | |
|                 </fieldset>
 | |
|             </form>
 | |
|         ');
 | |
| 
 | |
|         $form = Form::getInstance($this->_name, $form_source->asXML(), ['control' => $this->_name]);
 | |
| 
 | |
|         $content = 
 | |
|         '<div class="acfupload-edit-modal-content">' .
 | |
|             '<div class="acfupload-edit-modal-editing-item">' . Text::_('ACF_UPLOAD_CURRENTLY_EDITING_ITEM') . '</div>' .
 | |
|             $form->renderFieldset('acfupload_edit_modal') .
 | |
|         '</div>';
 | |
| 
 | |
|         echo HTMLHelper::_('bootstrap.renderModal', 'acfUploadItemEditModal', [
 | |
|             'title'  => Text::_('ACF_UPLOAD_EDIT_ITEM'),
 | |
|             'modalWidth' => '40',
 | |
|             'footer' => '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-dismiss="modal" aria-hidden="true">'. Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . '</button><button type="button" class="btn btn-success acf-upload-save-item" data-bs-dismiss="modal" data-dismiss="modal" aria-hidden="true">' . Text::_('JAPPLY') . '</button>'
 | |
|         ], $content);
 | |
|     }
 | |
| 
 | |
| 	/**
 | |
| 	 * The form event. Load additional parameters when available into the field form.
 | |
| 	 * Only when the type of the form is of interest.
 | |
| 	 *
 | |
| 	 * @param   Form      $form  The form
 | |
| 	 * @param   stdClass  $data  The data
 | |
| 	 *
 | |
| 	 * @return  void
 | |
| 	 *
 | |
| 	 * @since   3.7.0
 | |
| 	 */
 | |
| 	public function onContentPrepareForm(Joomla\CMS\Form\Form $form, $data)
 | |
| 	{
 | |
| 		// Make sure we are manipulating the right field.
 | |
| 		if (isset($data->type) && ($data->type != $this->_name))
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		$result = parent::onContentPrepareForm($form, $data);
 | |
| 
 | |
| 		// Display the server's maximum upload size in the field's description
 | |
| 		$max_upload_size_str = HTMLHelper::_('number.bytes', Utility::getMaxUploadSize());
 | |
| 		$field_desc = $form->getFieldAttribute('max_file_size', 'description', null, 'fieldparams');
 | |
| 		$form->setFieldAttribute('max_file_size', 'description', Text::sprintf($field_desc, $max_upload_size_str), 'fieldparams');
 | |
| 
 | |
| 		// If the Fileinfo PHP extension is not installed, display a warning.
 | |
| 		if (!extension_loaded('fileinfo') || !function_exists('mime_content_type'))
 | |
| 		{
 | |
| 			Factory::getApplication()->enqueueMessage(Text::_('ACF_UPLOAD_MIME_CONTENT_TYPE_MISSING'), 'warning');
 | |
| 		}
 | |
| 
 | |
| 		return $result;
 | |
| 	}
 | |
| 
 | |
|     /**
 | |
|      * Handle AJAX endpoint
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function onAjaxACFUpload()
 | |
|     {
 | |
| 		if (!Session::checkToken('request'))
 | |
|         {
 | |
|         	$this->uploadDie(Text::_('JINVALID_TOKEN'));
 | |
|         }
 | |
| 		
 | |
| 		$taskMethod = 'task' . ucfirst(Factory::getApplication()->input->get('task', 'upload'));
 | |
| 
 | |
| 		if (!method_exists($this, $taskMethod))
 | |
| 		{
 | |
| 			$this->uploadDie('Invalid endpoint');
 | |
| 		}
 | |
| 
 | |
| 		$this->$taskMethod();
 | |
| 	}
 | |
| 	
 | |
| 	/**
 | |
| 	 * The Upload task called by the AJAX hanler
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	public function taskUpload()
 | |
| 	{
 | |
| 		$input = Factory::getApplication()->input;
 | |
| 
 | |
| 		// Make sure we have a valid form and a field key
 | |
| 		if (!$field_id = $input->getInt('id'))
 | |
| 		{
 | |
| 			$this->uploadDie('ACF_UPLOAD_ERROR');
 | |
| 		}
 | |
| 
 | |
| 		// Get Upload Settings
 | |
| 		if (!$upload_field_settings = $this->getCustomFieldData($field_id))
 | |
| 		{
 | |
| 			$this->uploadDie('ACF_UPLOAD_ERROR_INVALID_FIELD');
 | |
| 		}
 | |
| 
 | |
| 		$allow_unsafe = $upload_field_settings->get('allow_unsafe', false);
 | |
| 
 | |
| 		// Make sure we have a valid file passed
 | |
| 		if (!$file = $input->files->get('file', null, ($allow_unsafe ? 'raw' : 'cmd')))
 | |
| 		{
 | |
| 			$this->uploadDie('ACF_UPLOAD_ERROR_INVALID_FILE');
 | |
| 		}
 | |
| 
 | |
| 		// In case we allow multiple uploads the file parameter is a 2 levels array.
 | |
| 		$first_property = array_pop($file);
 | |
| 		if (is_array($first_property))
 | |
| 		{
 | |
| 			$file = $first_property;
 | |
| 		}
 | |
| 
 | |
| 		// Upload temporarily to the default upload folder
 | |
| 		$allowed_types = $upload_field_settings->get('upload_types');
 | |
| 
 | |
| 		try {
 | |
| 			$randomize_filename = $upload_field_settings->get('randomize_filename', false);
 | |
| 
 | |
| 			$upload_folder = implode(DIRECTORY_SEPARATOR, [JPATH_ROOT, ACFUploadHelper::getTempFolder()]);
 | |
| 
 | |
| 			$uploaded_filename = File::upload($file, $upload_folder, $allowed_types, $allow_unsafe, $randomize_filename ? '' : null);
 | |
| 			$uploaded_filename = str_replace([JPATH_SITE, JPATH_ROOT], '', $uploaded_filename);
 | |
| 
 | |
| 			// Resize images
 | |
| 			if ($upload_field_settings->get('resize_images', false))
 | |
| 			{
 | |
| 				// Get file type
 | |
| 				$file_type = Mimes::detectFileType(JPATH_ROOT . $uploaded_filename);
 | |
| 
 | |
| 				// Allowed image file types
 | |
| 				$allowed_image_file_types = [
 | |
| 					'image/jpg',
 | |
| 					'image/jpeg',
 | |
| 					'image/pjpeg',
 | |
| 					'image/png',
 | |
| 					'image/x-png',
 | |
| 					'image/webp'
 | |
| 				];
 | |
| 
 | |
| 				// Ensure it is a valid image
 | |
| 				if (Mimes::check($allowed_image_file_types, $file_type))
 | |
| 				{
 | |
| 					// We require at least width or height to not be null
 | |
| 					$resize_width = $upload_field_settings->get('width', null);
 | |
| 					$resize_height = $upload_field_settings->get('height', null);
 | |
| 					if ($resize_width || $resize_height)
 | |
| 					{
 | |
| 						Image::resizeByWidthOrHeight(JPATH_ROOT . $uploaded_filename, $resize_width, $resize_height);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			$response = [
 | |
| 				'file' => $uploaded_filename,
 | |
| 				'file_encode' => base64_encode($uploaded_filename),
 | |
| 				'url' => ACFUploadHelper::absURL($uploaded_filename)
 | |
| 			];
 | |
| 
 | |
| 			header('Content-Type: application/json');
 | |
| 
 | |
| 			echo json_encode($response);
 | |
| 
 | |
| 			jexit();
 | |
| 
 | |
| 		} catch (\Throwable $th)
 | |
| 		{
 | |
| 			$this->uploadDie($th->getMessage());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * The delete task called by the AJAX hanlder
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	private function taskDelete()
 | |
| 	{
 | |
| 		// Make sure we have a valid file passed
 | |
| 		if (!$filename = Factory::getApplication()->input->get('file', '', 'BASE64'))
 | |
| 		{
 | |
| 			$this->uploadDie('ACF_UPLOAD_ERROR_INVALID_FILE');
 | |
| 		}
 | |
| 
 | |
| 		// Delete the uploaded file
 | |
| 		echo json_encode([
 | |
| 			'success' => ACFUploadHelper::deleteFile(base64_decode($filename))
 | |
| 		]);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Pull Custom Field Data
 | |
| 	 *
 | |
| 	 * @param  integer $id The Custom Field primary key
 | |
| 	 *
 | |
| 	 * @return object
 | |
| 	 */
 | |
|     private function getCustomFieldData($id)
 | |
|     {
 | |
|         $db = Factory::getDbo();
 | |
| 
 | |
|         $query = $db->getQuery(true);
 | |
| 
 | |
|         $query
 | |
|             ->select($db->quoteName(['fieldparams']))
 | |
|             ->from($db->quoteName('#__fields'))
 | |
|             ->where($db->quoteName('id') . ' = ' . $id)
 | |
|             ->where($db->quoteName('type') . ' = ' . $db->quote('acfupload'))
 | |
|             ->where($db->quoteName('state') . ' = 1');
 | |
| 
 | |
|         $db->setQuery($query);
 | |
| 
 | |
|         if (!$result = $db->loadResult())
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         return new Joomla\Registry\Registry($result);
 | |
|     }
 | |
| 
 | |
| 	/**
 | |
| 	 * DropzoneJS detects errors based on the response error code.
 | |
| 	 *
 | |
| 	 * @param  string $error_message
 | |
| 	 *
 | |
| 	 * @return void
 | |
| 	 */
 | |
| 	private function uploadDie($error_message)
 | |
| 	{
 | |
| 		http_response_code('500');
 | |
| 		die(Text::_($error_message));
 | |
|     }
 | |
| }
 |